Merge "Fix logging issue." into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 4f5e519..c92b4ff 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7701,7 +7701,7 @@
method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int);
method public int getWallpaperId(int);
- method public android.app.WallpaperInfo getWallpaperInfo();
+ method @RequiresPermission(value="QUERY_ALL_PACKAGES", conditional=true) public android.app.WallpaperInfo getWallpaperInfo();
method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
method public boolean hasResourceWallpaper(@RawRes int);
method public boolean isSetWallpaperAllowed();
@@ -22196,11 +22196,10 @@
@FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable {
method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
- method public void close();
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") public void close();
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec);
- method @FlaggedApi("android.media.audio.loudness_configurator_api") public void release();
method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec);
}
@@ -41704,10 +41703,10 @@
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+ method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void sendEvent(@NonNull String, @NonNull android.os.Bundle);
method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
- method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void setMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0b17e03..ff2279c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -22,7 +22,7 @@
field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
- field public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
+ field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e288b42..1bdbd4c5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6792,6 +6792,7 @@
}
}
if (killApp) {
+ // Keep in sync with "perhaps it was removed" case below.
mPackages.remove(packages[i]);
mResourcePackages.remove(packages[i]);
}
@@ -6859,6 +6860,12 @@
}
} catch (RemoteException e) {
}
+ } else {
+ // No package, perhaps it was removed?
+ Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+ + " but missing application info. Assuming REMOVED.");
+ mPackages.remove(packages[i]);
+ mResourcePackages.remove(packages[i]);
}
}
}
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
index 1f5f2e4..5dd7ab0 100644
--- a/core/java/android/app/HomeVisibilityListener.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -69,6 +69,11 @@
public HomeVisibilityListener() {
mObserver = new android.app.IProcessObserver.Stub() {
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid,
+ String packageName, String processName) {
+ }
+
+ @Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
refreshHomeVisibility();
}
diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl
index 7be3620..5c5e72c 100644
--- a/core/java/android/app/IProcessObserver.aidl
+++ b/core/java/android/app/IProcessObserver.aidl
@@ -18,6 +18,17 @@
/** {@hide} */
oneway interface IProcessObserver {
+ /**
+ * Invoked when an app process starts up.
+ *
+ * @param pid The pid of the process.
+ * @param processUid The UID associated with the process.
+ * @param packageUid The UID associated with the package.
+ * @param packageName The name of the package.
+ * @param processName The name of the process.
+ */
+ void onProcessStarted(int pid, int processUid, int packageUid,
+ @utf8InCpp String packageName, @utf8InCpp String processName);
void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
void onForegroundServicesChanged(int pid, int uid, int serviceTypes);
void onProcessDied(int pid, int uid);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 36b03c1..0116ca2 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1899,15 +1899,22 @@
/**
* Returns the information about the home screen wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+ * caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
- * In order to use this, apps should declare a {@code <queries>} tag with the action
- * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+ * Before Android U, this method requires the
+ * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
+ * </p>
+ *
+ * <p>
+ * Starting from Android U, in order to use this, apps should declare a {@code <queries>} tag
+ * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
* this method will return {@code null} if the caller doesn't otherwise have
* <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
* </p>
*/
+ @RequiresPermission(value = "QUERY_ALL_PACKAGES", conditional = true)
public WallpaperInfo getWallpaperInfo() {
return getWallpaperInfoForUser(mContext.getUserId());
}
@@ -1924,19 +1931,14 @@
}
/**
- * Returns the information about the home screen wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+ * Returns the information about the designated wallpaper if its current wallpaper is a live
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if
* the caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
- * Before Android U, this method requires the
- * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
- * </p>
- *
- * <p>
- * Starting from Android U, In order to use this, apps should declare a {@code <queries>} tag
- * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
- * this method will return {@code null} if the caller doesn't otherwise have
+ * In order to use this, apps should declare a {@code <queries>} tag with the action
+ * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, this method will return
+ * {@code null} if the caller doesn't otherwise have
* <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
* </p>
*
@@ -1952,7 +1954,7 @@
/**
* Returns the information about the designated wallpaper if its current wallpaper is a live
* wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
- * the caller doesn't have the appropriate permissions, this returns {@code null}.
+ * caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
* In order to use this, apps should declare a {@code <queries>} tag
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 325aa28f..83e18ec 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -133,4 +133,10 @@
* device.
*/
boolean isVirtualDeviceOwnedMirrorDisplay(int displayId);
+
+ /**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ */
+ List<String> getAllPersistentDeviceIds();
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index e4e9fba..903875b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -153,3 +153,11 @@
bug: "291135724"
is_fixed_read_only: true
}
+
+flag {
+ name: "fix_system_apps_first_install_time"
+ namespace: "package_manager_service"
+ description: "Feature flag to fix the first-install timestamps for system apps."
+ bug: "321258605"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index 73ac333..d51e62e 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -33,4 +33,20 @@
* Defines behavior in response to authentication stopping
*/
void onAuthenticationStopped();
+
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationSucceeded(int requestReason, int userId);
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationFailed(int requestReason, int userId);
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index e267e6b..8e234fa 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
*/
package android.hardware.face;
+import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricStateListener;
@@ -181,6 +182,14 @@
// authenticators. The callback is automatically removed after it's invoked.
void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
+ // Registers AuthenticationStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+ // Unregisters AuthenticationStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
// Registers BiometricStateListener.
void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 8644d91..f65b713 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -32,6 +32,7 @@
* @hide
*/
@SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class LogMaker {
private static final String TAG = "LogBuilder";
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 5871717..3977bdf 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -28,6 +28,7 @@
import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.sysprop.DeviceProperties;
import android.sysprop.SocProperties;
import android.sysprop.TelephonyProperties;
@@ -47,6 +48,7 @@
/**
* Information about the current build, extracted from system properties.
*/
+@RavenwoodKeepWholeClass
public class Build {
private static final String TAG = "Build";
@@ -307,7 +309,7 @@
* compatibility.
*/
final String[] abiList;
- if (VMRuntime.getRuntime().is64Bit()) {
+ if (android.os.Process.is64Bit()) {
abiList = SUPPORTED_64_BIT_ABIS;
} else {
abiList = SUPPORTED_32_BIT_ABIS;
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index aa283a2..a818919 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
import android.util.Log;
import android.util.MutableInt;
@@ -36,6 +38,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
/**
* Gives access to the system properties store. The system properties
@@ -51,6 +55,8 @@
* {@hide}
*/
@SystemApi
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host")
public class SystemProperties {
private static final String TAG = "SystemProperties";
private static final boolean TRACK_KEY_ACCESS = false;
@@ -94,6 +100,31 @@
}
}
+ /** @hide */
+ public static void init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+ native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+ SystemProperties::callChangeCallbacks);
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ /** @hide */
+ public static void reset$ravenwood() {
+ native_reset$ravenwood();
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ // These native methods are currently only implemented by Ravenwood, as it's the only
+ // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
+ private static native void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback);
+ private static native void native_reset$ravenwood();
+
// The one-argument version of native_get used to be a regular native function. Nowadays,
// we use the two-argument form of native_get all the time, but we can't just delete the
// one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 5d7e04d..c0b4909 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -36,6 +36,7 @@
* href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
* with Systrace</a>.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class Trace {
/*
* Writes trace events to the kernel trace buffer. These trace events can be
@@ -123,10 +124,26 @@
@UnsupportedAppUsage
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long nativeGetEnabledTags();
+ @android.ravenwood.annotation.RavenwoodReplace
private static native void nativeSetAppTracingAllowed(boolean allowed);
+ @android.ravenwood.annotation.RavenwoodReplace
private static native void nativeSetTracingEnabled(boolean allowed);
+ private static long nativeGetEnabledTags$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) {
+ // Tracing currently completely disabled under Ravenwood
+ }
+
+ private static void nativeSetTracingEnabled$ravenwood(boolean allowed) {
+ // Tracing currently completely disabled under Ravenwood
+ }
+
@FastNative
private static native void nativeTraceCounter(long tag, String name, long value);
@FastNative
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 76e0c25..bbda068 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -271,7 +271,6 @@
boolean mDrawingAllowed;
boolean mOffsetsChanged;
boolean mFixedSizeAllowed;
- boolean mShouldDim;
// Whether the wallpaper should be dimmed by default (when no additional dimming is applied)
// based on its color hints
boolean mShouldDimByDefault;
@@ -348,9 +347,11 @@
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
- private float mWallpaperDimAmount = 0.05f;
+
+ private float mCustomDimAmount = 0f;
+ private float mWallpaperDimAmount = 0f;
private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;
- private float mDefaultDimAmount = mWallpaperDimAmount;
+ private float mDefaultDimAmount = 0.05f;
SurfaceControl mSurfaceControl = new SurfaceControl();
SurfaceControl mBbqSurfaceControl;
@@ -986,11 +987,8 @@
mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
&& (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0);
- // If default dimming value changes and no additional dimming is applied
- if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) {
- mShouldDim = mShouldDimByDefault;
- updateSurfaceDimming();
- }
+ // Recompute dim in case it changed compared to the previous WallpaperService
+ updateWallpaperDimming(mCustomDimAmount);
}
/**
@@ -999,28 +997,21 @@
* @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
*/
private void updateWallpaperDimming(float dimAmount) {
- if (dimAmount == mWallpaperDimAmount) {
- return;
- }
+ mCustomDimAmount = Math.min(1f, dimAmount);
- // Custom dim amount cannot be less than the default dim amount.
- mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
- // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
- // based on its default wallpaper color hints.
- mShouldDim = dimAmount != 0f || mShouldDimByDefault;
- updateSurfaceDimming();
- }
+ // If default dim is enabled, the actual dim amount is at least the default dim amount
+ mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount
+ : Math.max(mDefaultDimAmount, mCustomDimAmount);
- private void updateSurfaceDimming() {
- if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
+ if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null
+ || mWallpaperDimAmount == mPreviousWallpaperDimAmount) {
return;
}
SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
// TODO: apply the dimming to preview as well once surface transparency works in
// preview mode.
- if ((!isPreview() && mShouldDim)
- || mPreviousWallpaperDimAmount != mWallpaperDimAmount) {
+ if (!isPreview()) {
Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
// Animate dimming to gradually change the wallpaper alpha from the previous
@@ -1545,8 +1536,6 @@
.createWindowContext(TYPE_WALLPAPER, null /* options */);
mDefaultDimAmount = mDisplayContext.getResources().getFloat(
com.android.internal.R.dimen.config_wallpaperDimAmount);
- mWallpaperDimAmount = mDefaultDimAmount;
- mPreviousWallpaperDimAmount = mWallpaperDimAmount;
mDisplayState = mDisplay.getCommittedState();
mMergedConfiguration.setOverrideConfiguration(
mDisplayContext.getResources().getConfiguration());
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 17a3a12..7f1e037 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -34,11 +34,11 @@
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsController.LayoutInsetsDuringAnimation;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_FLOATING;
-import static android.view.InsetsState.ISIDE_LEFT;
-import static android.view.InsetsState.ISIDE_RIGHT;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_NONE;
+import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_RIGHT;
+import static android.view.InsetsSource.SIDE_TOP;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -60,7 +60,7 @@
import android.util.SparseIntArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsSource.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
@@ -142,7 +142,7 @@
if (mHasZeroInsetsIme) {
// IME has shownInsets of ZERO, and can't map to a side by default.
// Map zero insets IME to bottom, making it a special case of bottom insets.
- idSideMap.put(ID_IME, ISIDE_BOTTOM);
+ idSideMap.put(ID_IME, SIDE_BOTTOM);
}
buildSideControlsMap(idSideMap, mSideControlsMap, controls);
} else {
@@ -286,10 +286,10 @@
}
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
final ArrayList<SurfaceParams> params = new ArrayList<>();
- updateLeashesForSide(ISIDE_LEFT, offset.left, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_TOP, offset.top, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_LEFT, offset.left, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_TOP, offset.top, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
@@ -499,19 +499,19 @@
final float surfaceOffset = mTranslator != null
? mTranslator.translateLengthInAppWindowToScreen(offset) : offset;
switch (side) {
- case ISIDE_LEFT:
+ case SIDE_LEFT:
m.postTranslate(-surfaceOffset, 0);
frame.offset(-offset, 0);
break;
- case ISIDE_TOP:
+ case SIDE_TOP:
m.postTranslate(0, -surfaceOffset);
frame.offset(0, -offset);
break;
- case ISIDE_RIGHT:
+ case SIDE_RIGHT:
m.postTranslate(surfaceOffset, 0);
frame.offset(offset, 0);
break;
- case ISIDE_BOTTOM:
+ case SIDE_BOTTOM:
m.postTranslate(0, surfaceOffset);
frame.offset(0, offset);
break;
@@ -543,9 +543,10 @@
// control may be null if it got revoked.
continue;
}
- @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
- if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) {
- side = ISIDE_BOTTOM;
+ @InternalInsetsSide int side = InsetsSource.getInsetSide(control.getInsetsHint());
+ if (side == SIDE_NONE && control.getType() == WindowInsets.Type.ime()) {
+ // IME might not provide insets when it is fullscreen or floating.
+ side = SIDE_BOTTOM;
}
sideControlsMap.add(side, control);
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 0927d45..86ab213 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -46,6 +46,24 @@
*/
public class InsetsSource implements Parcelable {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SIDE_", value = {
+ SIDE_NONE,
+ SIDE_LEFT,
+ SIDE_TOP,
+ SIDE_RIGHT,
+ SIDE_BOTTOM,
+ SIDE_UNKNOWN
+ })
+ public @interface InternalInsetsSide {}
+
+ static final int SIDE_NONE = 0;
+ static final int SIDE_LEFT = 1;
+ static final int SIDE_TOP = 2;
+ static final int SIDE_RIGHT = 3;
+ static final int SIDE_BOTTOM = 4;
+ static final int SIDE_UNKNOWN = 5;
+
/** The insets source ID of IME */
public static final int ID_IME = createId(null, 0, ime());
@@ -101,6 +119,12 @@
private boolean mVisible;
+ /**
+ * Used to decide which side of the relative frame should receive insets when the frame fully
+ * covers the relative frame.
+ */
+ private @InternalInsetsSide int mSideHint = SIDE_NONE;
+
private final Rect mTmpFrame = new Rect();
public InsetsSource(int id, @InsetsType int type) {
@@ -119,6 +143,7 @@
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
+ mSideHint = other.mSideHint;
}
public void set(InsetsSource other) {
@@ -128,6 +153,7 @@
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
+ mSideHint = other.mSideHint;
}
public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -160,6 +186,18 @@
return this;
}
+ /**
+ * Updates the side hint which is used to decide which side of the relative frame should receive
+ * insets when the frame fully covers the relative frame.
+ *
+ * @param bounds A rectangle which contains the frame. It will be used to calculate the hint.
+ */
+ public InsetsSource updateSideHint(Rect bounds) {
+ mSideHint = getInsetSide(
+ calculateInsets(bounds, mFrame, true /* ignoreVisibility */));
+ return this;
+ }
+
public int getId() {
return mId;
}
@@ -236,8 +274,21 @@
return Insets.of(0, 0, 0, mTmpFrame.height());
}
- // Intersecting at top/bottom
- if (mTmpFrame.width() == relativeFrame.width()) {
+ if (mTmpFrame.equals(relativeFrame)) {
+ // Covering all sides
+ switch (mSideHint) {
+ default:
+ case SIDE_LEFT:
+ return Insets.of(mTmpFrame.width(), 0, 0, 0);
+ case SIDE_TOP:
+ return Insets.of(0, mTmpFrame.height(), 0, 0);
+ case SIDE_RIGHT:
+ return Insets.of(0, 0, mTmpFrame.width(), 0);
+ case SIDE_BOTTOM:
+ return Insets.of(0, 0, 0, mTmpFrame.height());
+ }
+ } else if (mTmpFrame.width() == relativeFrame.width()) {
+ // Intersecting at top/bottom
if (mTmpFrame.top == relativeFrame.top) {
return Insets.of(0, mTmpFrame.height(), 0, 0);
} else if (mTmpFrame.bottom == relativeFrame.bottom) {
@@ -249,9 +300,8 @@
if (mTmpFrame.top == 0) {
return Insets.of(0, mTmpFrame.height(), 0, 0);
}
- }
- // Intersecting at left/right
- else if (mTmpFrame.height() == relativeFrame.height()) {
+ } else if (mTmpFrame.height() == relativeFrame.height()) {
+ // Intersecting at left/right
if (mTmpFrame.left == relativeFrame.left) {
return Insets.of(mTmpFrame.width(), 0, 0, 0);
} else if (mTmpFrame.right == relativeFrame.right) {
@@ -283,6 +333,46 @@
}
/**
+ * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
+ * is set in order that this method returns a meaningful result.
+ */
+ static @InternalInsetsSide int getInsetSide(Insets insets) {
+ if (Insets.NONE.equals(insets)) {
+ return SIDE_NONE;
+ }
+ if (insets.left != 0) {
+ return SIDE_LEFT;
+ }
+ if (insets.top != 0) {
+ return SIDE_TOP;
+ }
+ if (insets.right != 0) {
+ return SIDE_RIGHT;
+ }
+ if (insets.bottom != 0) {
+ return SIDE_BOTTOM;
+ }
+ return SIDE_UNKNOWN;
+ }
+
+ static String sideToString(@InternalInsetsSide int side) {
+ switch (side) {
+ case SIDE_NONE:
+ return "NONE";
+ case SIDE_LEFT:
+ return "LEFT";
+ case SIDE_TOP:
+ return "TOP";
+ case SIDE_RIGHT:
+ return "RIGHT";
+ case SIDE_BOTTOM:
+ return "BOTTOM";
+ default:
+ return "UNKNOWN:" + side;
+ }
+ }
+
+ /**
* Creates an identifier of an {@link InsetsSource}.
*
* @param owner An object owned by the owner. Only the owner can modify its own sources.
@@ -331,7 +421,7 @@
}
public static String flagsToString(@Flags int flags) {
- final StringJoiner joiner = new StringJoiner(" ");
+ final StringJoiner joiner = new StringJoiner("|");
if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
joiner.add("SUPPRESS_SCRIM");
}
@@ -371,6 +461,7 @@
}
pw.print(" visible="); pw.print(mVisible);
pw.print(" flags="); pw.print(flagsToString(mFlags));
+ pw.print(" sideHint="); pw.print(sideToString(mSideHint));
pw.println();
}
@@ -393,6 +484,7 @@
if (mType != that.mType) return false;
if (mVisible != that.mVisible) return false;
if (mFlags != that.mFlags) return false;
+ if (mSideHint != that.mSideHint) return false;
if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
return mFrame.equals(that.mFrame);
@@ -400,7 +492,7 @@
@Override
public int hashCode() {
- return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
+ return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint);
}
public InsetsSource(Parcel in) {
@@ -414,6 +506,7 @@
}
mVisible = in.readBoolean();
mFlags = in.readInt();
+ mSideHint = in.readInt();
}
@Override
@@ -434,6 +527,7 @@
}
dest.writeBoolean(mVisible);
dest.writeInt(mFlags);
+ dest.writeInt(mSideHint);
}
@Override
@@ -442,7 +536,8 @@
+ " mType=" + WindowInsets.Type.toString(mType)
+ " mFrame=" + mFrame.toShortString()
+ " mVisible=" + mVisible
- + " mFlags=[" + flagsToString(mFlags) + "]"
+ + " mFlags=" + flagsToString(mFlags)
+ + " mSideHint=" + sideToString(mSideHint)
+ "}";
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 59e0932..c88da9e 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -37,7 +37,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration.ActivityType;
@@ -48,6 +47,7 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.InsetsSource.InternalInsetsSide;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -55,8 +55,6 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.StringJoiner;
@@ -66,23 +64,6 @@
*/
public class InsetsState implements Parcelable {
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "ISIDE", value = {
- ISIDE_LEFT,
- ISIDE_TOP,
- ISIDE_RIGHT,
- ISIDE_BOTTOM,
- ISIDE_FLOATING,
- ISIDE_UNKNOWN
- })
- public @interface InternalInsetsSide {}
- static final int ISIDE_LEFT = 0;
- static final int ISIDE_TOP = 1;
- static final int ISIDE_RIGHT = 2;
- static final int ISIDE_BOTTOM = 3;
- static final int ISIDE_FLOATING = 4;
- static final int ISIDE_UNKNOWN = 5;
-
private final SparseArray<InsetsSource> mSources;
/**
@@ -398,37 +379,14 @@
}
if (idSideMap != null) {
- @InternalInsetsSide int insetSide = getInsetSide(insets);
- if (insetSide != ISIDE_UNKNOWN) {
+ @InternalInsetsSide int insetSide = InsetsSource.getInsetSide(insets);
+ if (insetSide != InsetsSource.SIDE_UNKNOWN) {
idSideMap.put(source.getId(), insetSide);
}
}
}
/**
- * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
- * is set in order that this method returns a meaningful result.
- */
- static @InternalInsetsSide int getInsetSide(Insets insets) {
- if (Insets.NONE.equals(insets)) {
- return ISIDE_FLOATING;
- }
- if (insets.left != 0) {
- return ISIDE_LEFT;
- }
- if (insets.top != 0) {
- return ISIDE_TOP;
- }
- if (insets.right != 0) {
- return ISIDE_RIGHT;
- }
- if (insets.bottom != 0) {
- return ISIDE_BOTTOM;
- }
- return ISIDE_UNKNOWN;
- }
-
- /**
* Gets the source mapped from the ID, or creates one if no such mapping has been made.
*/
public InsetsSource getOrCreateSource(int id, int type) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c36227..7bc832e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11991,7 +11991,7 @@
Runnable timeoutRunnable = () -> Log.e(mTag,
"Failed to submit the sync transaction after 4s. Likely to ANR "
+ "soon");
- mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER);
+ mHandler.postDelayed(timeoutRunnable, 4000L * Build.HW_TIMEOUT_MULTIPLIER);
transaction.addTransactionCommittedListener(mSimpleExecutor,
() -> mHandler.removeCallbacks(timeoutRunnable));
surfaceSyncGroup.addTransaction(transaction);
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
index bf1d31c..fbb66d1 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
@@ -149,9 +149,12 @@
*
* Because it is not guaranteed that the events will be enqueued from a single thread, the
* implementation must be thread-safe to prevent unexpected behaviour.
+ *
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
- private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
+ public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
/**
* List of events held to be sent to the {@link ContentCaptureService} as a batch.
@@ -908,7 +911,7 @@
* clear the buffer events then starting sending out current event.
*/
private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
- if (forceFlush) {
+ if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) {
// The buffer events are cleared in the same thread first to prevent new events
// being added during the time of context switch. This would disrupt the sequence
// of events.
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 9f9b7b4..1dd99ba 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -8,6 +8,15 @@
}
flag {
+ name: "enable_surface_native_alloc_registration_ro"
+ namespace: "toolkit"
+ description: "Feature flag for registering surfaces with the VM for faster"
+ " cleanup. Fixed readonly version."
+ bug: "306193257"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_use_measure_cache_during_force_layout"
namespace: "toolkit"
description: "Enables using the measure cache during a view force layout from the second "
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 5dbf328..93297e6 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -94,11 +94,18 @@
@Nullable
private final IBinder mPairedActivityToken;
+ /**
+ * If {@code true}, transitions are allowed even if the TaskFragment is empty. If
+ * {@code false}, transitions will wait until the TaskFragment becomes non-empty or other
+ * conditions are met. Default to {@code false}.
+ */
+ private final boolean mAllowTransitionWhenEmpty;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
- @Nullable IBinder pairedActivityToken) {
+ @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -110,6 +117,7 @@
mWindowingMode = windowingMode;
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
mPairedActivityToken = pairedActivityToken;
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
}
@NonNull
@@ -155,6 +163,11 @@
return mPairedActivityToken;
}
+ /** @hide */
+ public boolean getAllowTransitionWhenEmpty() {
+ return mAllowTransitionWhenEmpty;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -163,6 +176,7 @@
mWindowingMode = in.readInt();
mPairedPrimaryFragmentToken = in.readStrongBinder();
mPairedActivityToken = in.readStrongBinder();
+ mAllowTransitionWhenEmpty = in.readBoolean();
}
/** @hide */
@@ -175,6 +189,7 @@
dest.writeInt(mWindowingMode);
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
dest.writeStrongBinder(mPairedActivityToken);
+ dest.writeBoolean(mAllowTransitionWhenEmpty);
}
@NonNull
@@ -201,6 +216,7 @@
+ " windowingMode=" + mWindowingMode
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ " pairedActivityToken=" + mPairedActivityToken
+ + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ "}";
}
@@ -234,6 +250,8 @@
@Nullable
private IBinder mPairedActivityToken;
+ private boolean mAllowTransitionWhenEmpty;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -298,12 +316,26 @@
return this;
}
+ /**
+ * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+ * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+ * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+ * to {@code false}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken);
+ mPairedActivityToken, mAllowTransitionWhenEmpty);
}
}
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cbf6367..edfbea4 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -50,3 +50,11 @@
description: "Whether we should allow hiding the size compat restart button"
bug: "318840081"
}
+
+flag {
+ name: "configurable_font_scale_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the font_scale is read from a device dependent configuration file"
+ bug: "319808237"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index eeea17b..90ca95a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -71,7 +71,7 @@
"persist.debug.sysui.notification.notif_cooldown_t1", 60000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
+ "persist.debug.sysui.notification.notif_cooldown_t2", 10000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_VOLUME1 = devFlag(
"persist.debug.sysui.notification.notif_volume1", 30);
@@ -81,6 +81,10 @@
public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
"persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
+ /** Value used by polite notif. feature */
+ public static final Flag NOTIF_AVALANCHE_TIMEOUT = devFlag(
+ "persist.debug.sysui.notification.notif_avalanche_timeout", 120_000);
+
/** b/303716154: For debugging only: use short bitmap duration. */
public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
"persist.sysui.notification.debug_short_bitmap_duration");
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index e58f4f0..88aa89a 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class MetricsLogger {
// define metric categories in frameworks/base/proto/src/metrics_constants.proto.
// mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp
diff --git a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
index 6786427..df8bf31 100644
--- a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
+++ b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
@@ -12,6 +12,7 @@
*
* @hide.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class FakeMetricsLogger extends MetricsLogger {
private Queue<LogMaker> logs = new LinkedList<>();
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index e303890..6787ddc 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -27,6 +27,7 @@
*
* @hide.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class UiEventLoggerFake implements UiEventLogger {
/**
* Immutable data class used to record fake log events.
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 0704cb8..ae8f025 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,9 +18,11 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Build;
import android.os.Trace;
import android.text.BoringLayout;
import android.text.Layout;
+import android.text.PrecomputedText;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.method.TransformationMethod;
@@ -48,6 +50,10 @@
private int mLayoutMaxLines = -1;
private int mImageEndMargin;
+ private int mStaticLayoutCreationCountInOnMeasure = 0;
+
+ private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
public ImageFloatingTextView(Context context) {
this(context, null);
}
@@ -71,7 +77,10 @@
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize,
TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
- Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+ mStaticLayoutCreationCountInOnMeasure++;
+ }
TransformationMethod transformationMethod = getTransformationMethod();
CharSequence text = getText();
if (transformationMethod != null) {
@@ -79,7 +88,7 @@
}
text = text == null ? "" : text;
StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
- getPaint(), wantWidth)
+ getPaint(), wantWidth)
.setAlignment(alignment)
.setTextDirection(getTextDirectionHeuristic())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
@@ -115,7 +124,10 @@
}
final StaticLayout result = builder.build();
- Trace.endSection();
+ if (TRACE_ONMEASURE) {
+ trackMaxLines();
+ Trace.endSection();
+ }
return result;
}
@@ -141,7 +153,10 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- Trace.beginSection("ImageFloatingTextView#onMeasure");
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("ImageFloatingTextView#onMeasure");
+ }
+ mStaticLayoutCreationCountInOnMeasure = 0;
int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
if (getLayout() != null && getLayout().getHeight() != availableHeight) {
// We've been measured before and the new size is different than before, lets make sure
@@ -168,7 +183,12 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
- Trace.endSection();
+
+
+ if (TRACE_ONMEASURE) {
+ trackParameters();
+ Trace.endSection();
+ }
}
@Override
@@ -216,4 +236,37 @@
requestLayout();
}
}
+
+ private void trackParameters() {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+ Trace.setCounter("ImageFloatingView#staticLayoutCreationCount",
+ mStaticLayoutCreationCountInOnMeasure);
+ Trace.setCounter("ImageFloatingView#isPrecomputedText",
+ isTextAPrecomputedText());
+ }
+ /**
+ * @return 1 if {@link TextView#getText()} is PrecomputedText, else 0
+ */
+ private int isTextAPrecomputedText() {
+ final CharSequence text = getText();
+ if (text == null || text.isEmpty()) {
+ return 0;
+ }
+
+ if (text instanceof PrecomputedText) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private void trackMaxLines() {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+
+ Trace.setCounter("ImageFloatingView#layoutMaxLines", mLayoutMaxLines);
+ }
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 757978b..b5b3a48 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -333,11 +333,17 @@
@UnsupportedAppUsage
public LockPatternUtils(Context context) {
+ this(context, null);
+ }
+
+ @VisibleForTesting
+ public LockPatternUtils(Context context, ILockSettings lockSettings) {
mContext = context;
mContentResolver = context.getContentResolver();
Looper looper = Looper.myLooper();
mHandler = looper != null ? new Handler(looper) : null;
+ mLockSettingsService = lockSettings;
}
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index c06f5f7..e07acac 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -21,6 +21,8 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -45,6 +47,8 @@
private int mMaxDisplayedLines = Integer.MAX_VALUE;
+ private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -67,6 +71,10 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("MessagingLinearLayout#onMeasure");
+ trackMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
+ }
// This is essentially a bottom-up linear layout that only adds children that fit entirely
// up to a maximum height.
int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
@@ -177,6 +185,9 @@
resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
widthMeasureSpec),
Math.max(getSuggestedMinimumHeight(), totalHeight));
+ if (TRACE_ONMEASURE) {
+ Trace.endSection();
+ }
}
@Override
@@ -240,6 +251,25 @@
}
}
+ private void trackMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+
+ final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecSize",
+ availableWidth);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecMode",
+ widthMode);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecSize",
+ availableHeight);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecMode",
+ heightMode);
+ }
+
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7af69f2..6a640a5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,6 +28,7 @@
#include <meminfo/sysmeminfo.h>
#include <processgroup/processgroup.h>
#include <processgroup/sched_policy.h>
+#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <algorithm>
@@ -232,6 +233,31 @@
}
}
+// Look up the user ID of a process in /proc/${pid}/status. The Uid: line is present in
+// /proc/${pid}/status since at least kernel v2.5.
+static int uid_from_pid(int pid)
+{
+ int uid = -1;
+ std::array<char, 64> path;
+ int res = snprintf(path.data(), path.size(), "/proc/%d/status", pid);
+ if (res < 0 || res >= static_cast<int>(path.size())) {
+ DCHECK(false);
+ return uid;
+ }
+ FILE* f = fopen(path.data(), "r");
+ if (!f) {
+ return uid;
+ }
+ char line[256];
+ while (fgets(line, sizeof(line), f)) {
+ if (sscanf(line, "Uid: %d", &uid) == 1) {
+ break;
+ }
+ }
+ fclose(f);
+ return uid;
+}
+
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
@@ -275,7 +301,12 @@
}
}
- if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
+ const int uid = uid_from_pid(pid);
+ if (uid < 0) {
+ signalExceptionForGroupError(env, ESRCH, pid);
+ return;
+ }
+ if (!SetProcessProfilesCached(uid, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 25b2aaf..98f409a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -343,7 +343,9 @@
const std::vector<SurfaceControlStats>& /*stats*/) {
JNIEnv* env = getenv();
// Adding a strong reference for java SyncFence
- presentFence->incStrong(0);
+ if (presentFence) {
+ presentFence->incStrong(0);
+ }
jobject stats =
env->NewObject(gTransactionStatsClassInfo.clazz, gTransactionStatsClassInfo.ctor,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 100259e..070e2cb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7065,6 +7065,7 @@
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to access the smartspace service as a client.
+ @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE)
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.ACCESS_SMARTSPACE"
android:protectionLevel="signature|privileged|development" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d1a90ae..4406302 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -212,8 +212,8 @@
"src/android/database/CursorWindowTest.java",
"src/android/os/**/*.java",
"src/android/util/**/*.java",
+ "src/com/android/internal/logging/**/*.java",
"src/com/android/internal/os/**/*.java",
- "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
"src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2d3e123..2a718ff 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -71,7 +70,6 @@
*/
@Test
@SmallTest
- @IgnoreUnderRavenwood(blockedBy = Build.class)
public void testBuildFields() throws Exception {
assertNotEmpty("ID", Build.ID);
assertNotEmpty("DISPLAY", Build.DISPLAY);
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index 593833ec..b2c005f 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -34,7 +34,6 @@
* while tracing on the emulator and then run traceview to view the trace.
*/
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Trace.class)
public class TraceTest {
private static final String TAG = "TraceTest";
@@ -46,7 +45,51 @@
private int gMethodCalls = 0;
@Test
+ public void testEnableDisable() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.setTracingEnabled(true, 0);
+ Trace.setTracingEnabled(false, 0);
+
+ Trace.setAppTracingAllowed(true);
+ Trace.setAppTracingAllowed(false);
+ }
+
+ @Test
+ public void testBeginEnd() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG, 42);
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+ Trace.beginSection(TAG);
+ Trace.endSection();
+
+ Trace.beginAsyncSection(TAG, 42);
+ Trace.endAsyncSection(TAG, 42);
+ }
+
+ @Test
+ public void testCounter() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+ Trace.setCounter(TAG, 42);
+ }
+
+ @Test
+ public void testInstant() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG);
+ }
+
+ @Test
public void testNullStrings() {
+ // Currently only verifying that we can invoke without crashing
Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
@@ -62,6 +105,7 @@
@Test
@SmallTest
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void testNativeTracingFromJava()
{
long start = System.currentTimeMillis();
@@ -82,6 +126,7 @@
// This should not run in the automated suite.
@Suppress
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void disableTestNativeTracingFromC()
{
long start = System.currentTimeMillis();
@@ -97,6 +142,7 @@
@Test
@LargeTest
@Suppress // Failing.
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void testMethodTracing()
{
long start = System.currentTimeMillis();
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 906d84e..672875a 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -20,8 +20,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_TOP;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -106,8 +106,8 @@
typeSideMap);
assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
- assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
- assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME));
+ assertEquals(SIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
+ assertEquals(SIDE_BOTTOM, typeSideMap.get(ID_IME));
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
index f0f3a96..0075128 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
@@ -433,6 +433,72 @@
assertThat(session.mEvents).isEmpty();
}
+ @Test
+ public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE - 1; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(0))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isNull();
+ assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1);
+ }
+
+ @Test
+ public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(1))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isEmpty();
+ assertThat(session.mEventProcessQueue).isEmpty();
+ }
+
+ @Test
+ public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(2))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isEmpty();
+ assertThat(session.mEventProcessQueue).hasSize(1);
+ }
+
/** Simulates the regular content capture events sequence. */
private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) {
final ArrayList<Object> events = new ArrayList<>(
diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
new file mode 100644
index 0000000..7054cc0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.metrics.LogMaker;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MetricsLoggerTest {
+ private FakeMetricsLogger mLogger;
+
+ private static final int TEST_ACTION = 42;
+
+ @Before
+ public void setUp() throws Exception {
+ mLogger = new FakeMetricsLogger();
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ assertThat(mLogger.getLogs().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void testAction() throws Exception {
+ mLogger.action(TEST_ACTION);
+ assertThat(mLogger.getLogs().size()).isEqualTo(1);
+ final LogMaker event = mLogger.getLogs().peek();
+ assertThat(event.getType()).isEqualTo(MetricsProto.MetricsEvent.TYPE_ACTION);
+ assertThat(event.getCategory()).isEqualTo(TEST_ACTION);
+ }
+
+ @Test
+ public void testVisible() throws Exception {
+ // Limited testing to confirm we don't crash
+ mLogger.visible(TEST_ACTION);
+ mLogger.hidden(TEST_ACTION);
+ mLogger.visibility(TEST_ACTION, true);
+ mLogger.visibility(TEST_ACTION, false);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
new file mode 100644
index 0000000..7840f71
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.testing.UiEventLoggerFake;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UiEventLoggerTest {
+ private UiEventLoggerFake mLogger;
+
+ private static final int TEST_EVENT_ID = 42;
+ private static final int TEST_INSTANCE_ID = 21;
+
+ private enum MyUiEventEnum implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Example event")
+ TEST_EVENT(TEST_EVENT_ID);
+
+ private final int mId;
+
+ MyUiEventEnum(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+ private InstanceId TEST_INSTANCE = InstanceId.fakeInstanceId(TEST_INSTANCE_ID);
+
+ @Before
+ public void setUp() throws Exception {
+ mLogger = new UiEventLoggerFake();
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ assertThat(mLogger.numLogs()).isEqualTo(0);
+ }
+
+ @Test
+ public void testSimple() throws Exception {
+ mLogger.log(MyUiEventEnum.TEST_EVENT);
+ assertThat(mLogger.numLogs()).isEqualTo(1);
+ assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+ }
+
+ @Test
+ public void testWithInstance() throws Exception {
+ mLogger.log(MyUiEventEnum.TEST_EVENT, TEST_INSTANCE);
+ assertThat(mLogger.numLogs()).isEqualTo(1);
+ assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+ assertThat(mLogger.get(0).instanceId.getId()).isEqualTo(TEST_INSTANCE_ID);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index ec4c563..06d888b 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,10 +18,14 @@
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +37,9 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MonotonicClockTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private final MockClock mClock = new MockClock();
private File mFile;
@@ -70,6 +77,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "b/321832617")
public void corruptedFile() throws IOException {
// Create an invalid binary XML file to cause IOException: "Unexpected magic number"
try (FileWriter w = new FileWriter(mFile)) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 1a668f7..b90480a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -16,20 +16,279 @@
package com.android.internal.widget;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.trust.TrustManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
public class LockPatternUtilsTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ private ILockSettings mLockSettings;
+ private static final int USER_ID = 1;
+ private static final int DEMO_USER_ID = 5;
+
+ private LockPatternUtils mLockPatternUtils;
+
+ private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
+ throws Exception {
+ mLockSettings = mock(ILockSettings.class);
+ final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ final MockContentResolver cr = new MockContentResolver(context);
+ cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(context.getContentResolver()).thenReturn(cr);
+ Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
+
+ when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
+ isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ : LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+ DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
+ when(mLockSettings.hasSecureLockScreen()).thenReturn(true);
+ mLockPatternUtils = new LockPatternUtils(context, mLockSettings);
+
+ final UserInfo userInfo = mock(UserInfo.class);
+ when(userInfo.isDemo()).thenReturn(isDemoUser);
+ final UserManager um = mock(UserManager.class);
+ when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
+ when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
+ }
+
+ @Test
+ public void isUserInLockDown() throws Exception {
+ configureTest(true, false, 2);
+
+ // GIVEN strong auth not required
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+ // THEN user isn't in lockdown
+ assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown and lockout
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isDemoUser_true() throws Exception {
+ configureTest(false, true, 2);
+ assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
+ configureTest(true, true, 2);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
+ configureTest(false, false, 2);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
+ configureTest(false, true, 0);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void testAddWeakEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ int testUserId = 10;
+ IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+ mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+ verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+ }
+
+ @Test
+ public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testRemoveAutoEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+ verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenActive() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+ verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenValid() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+ verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+ }
+
+ @Test
+ public void testSetEnabledTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetEnabledTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
+ List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+ }
+
+ @Test
+ public void testSetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> knownTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
+ List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+ assertTrue(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ assertFalse(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
@Test
public void testUserFrp_isNotRegularUser() throws Exception {
@@ -56,4 +315,49 @@
assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
}
+
+ private TestStrongAuthTracker createStrongAuthTracker() {
+ final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+ return new TestStrongAuthTracker(context, Looper.getMainLooper());
+ }
+
+ private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+ TestStrongAuthTracker(Context context, Looper looper) {
+ super(context, looper);
+ }
+
+ public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+ handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+ }
+ }
+
+ private ILockSettings createTestLockSettings() {
+ final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ final TrustManager trustManager = mock(TrustManager.class);
+ when(context.getSystemService(Context.TRUST_SERVICE)).thenReturn(trustManager);
+
+ final ILockSettings ils = mock(ILockSettings.class);
+ mLockPatternUtils = new LockPatternUtils(context, ils);
+ return ils;
+ }
+
+ private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+ return new IWeakEscrowTokenActivatedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenActivated(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
+
+ private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+ return new IWeakEscrowTokenRemovedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenRemoved(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
}
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 765ca3e..21aa3c44 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -15,6 +15,9 @@
static_libs: [
"android-common",
"frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
],
libs: [
"android.test.runner",
@@ -23,3 +26,22 @@
platform_apis: true,
certificate: "platform",
}
+
+android_ravenwood_test {
+ name: "FrameworksCoreSystemPropertiesTestsRavenwood",
+ static_libs: [
+ "android-common",
+ "frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 67783bf..ea65de0 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,19 +16,36 @@
package android.os;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
import android.test.suitebuilder.annotation.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-public class SystemPropertiesTest extends TestCase {
+public class SystemPropertiesTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setSystemPropertyMutable(KEY, null)
+ .setSystemPropertyMutable(UNSET_KEY, null)
+ .setSystemPropertyMutable(PERSIST_KEY, null)
+ .build();
+
private static final String KEY = "sys.testkey";
private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
private static final String PERSIST_KEY = "persist.sys.testkey";
+ @Test
@SmallTest
public void testStressPersistPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -38,6 +55,7 @@
}
}
+ @Test
@SmallTest
public void testStressMemoryPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -47,6 +65,7 @@
}
}
+ @Test
@SmallTest
public void testProperties() throws Exception {
String value;
@@ -93,6 +112,7 @@
assertEquals(expected, value);
}
+ @Test
@SmallTest
public void testHandle() throws Exception {
String value;
@@ -114,6 +134,7 @@
assertEquals(12345, handle.getInt(12345));
}
+ @Test
@SmallTest
public void testIntegralProperties() throws Exception {
testInt("", 123, 123);
@@ -133,6 +154,7 @@
testLong("-3147483647", 124, -3147483647L);
}
+ @Test
@SmallTest
public void testUnset() throws Exception {
assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc"));
@@ -142,6 +164,7 @@
assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10));
}
+ @Test
@SmallTest
@SuppressWarnings("null")
public void testNullKey() throws Exception {
@@ -176,6 +199,7 @@
}
}
+ @Test
@SmallTest
public void testCallbacks() {
// Latches are not really necessary, but are easy to use.
@@ -220,6 +244,7 @@
}
}
+ @Test
@SmallTest
public void testDigestOf() {
final String empty = SystemProperties.digestOf();
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
deleted file mode 100644
index dcaf676..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.util;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
-import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
-import com.android.internal.widget.LockPatternUtils;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
-public class LockPatternUtilsTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- private ILockSettings mLockSettings;
- private static final int USER_ID = 1;
- private static final int DEMO_USER_ID = 5;
-
- private LockPatternUtils mLockPatternUtils;
-
- private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
- throws Exception {
- mLockSettings = Mockito.mock(ILockSettings.class);
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-
- final MockContentResolver cr = new MockContentResolver(context);
- cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(context.getContentResolver()).thenReturn(cr);
- Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
-
- when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
- isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
- : LockPatternUtils.CREDENTIAL_TYPE_NONE);
- when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
- DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
- // TODO(b/63758238): stop spying the class under test
- mLockPatternUtils = spy(new LockPatternUtils(context));
- when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
- doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
-
- final UserInfo userInfo = Mockito.mock(UserInfo.class);
- when(userInfo.isDemo()).thenReturn(isDemoUser);
- final UserManager um = Mockito.mock(UserManager.class);
- when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
- when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
- }
-
- @Test
- public void isUserInLockDown() throws Exception {
- configureTest(true, false, 2);
-
- // GIVEN strong auth not required
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
-
- // THEN user isn't in lockdown
- assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
-
- // GIVEN lockdown
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-
- // THEN user is in lockdown
- assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
-
- // GIVEN lockdown and lockout
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
- // THEN user is in lockdown
- assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isDemoUser_true() throws Exception {
- configureTest(false, true, 2);
- assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
- configureTest(true, true, 2);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
- configureTest(false, false, 2);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
- configureTest(false, true, 0);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void testAddWeakEscrowToken() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
- int testUserId = 10;
- IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
- mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
- verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
- }
-
- @Test
- public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
- mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
- verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
- }
-
- @Test
- public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
- mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
- verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
- }
-
- @Test
- public void testRemoveAutoEscrowToken() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- long testHandle = 100L;
- mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
- verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
- }
-
- @Test
- public void testIsAutoEscrowTokenActive() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- long testHandle = 100L;
- mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
- verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
- }
-
- @Test
- public void testIsAutoEscrowTokenValid() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
- long testHandle = 100L;
- mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
- verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
- }
-
- @Test
- public void testSetEnabledTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
- doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
- List<ComponentName> enabledTrustAgents = Lists.newArrayList(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
-
- mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
-
- assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
- }
-
- @Test
- public void testGetEnabledTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- when(ils.getString(anyString(), any(), anyInt())).thenReturn(
- "com.android/.TrustAgent,com.test/.TestAgent");
-
- List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
-
- assertThat(trustAgents).containsExactly(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
- }
-
- @Test
- public void testSetKnownTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
- doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
- List<ComponentName> knownTrustAgents = Lists.newArrayList(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
-
- mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
-
- assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
- }
-
- @Test
- public void testGetKnownTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- when(ils.getString(anyString(), any(), anyInt())).thenReturn(
- "com.android/.TrustAgent,com.test/.TestAgent");
-
- List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
-
- assertThat(trustAgents).containsExactly(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
- }
-
- @Test
- public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
- throws RemoteException {
- TestStrongAuthTracker tracker = createStrongAuthTracker();
- tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
-
- assertTrue(tracker.isBiometricAllowedForUser(
- /* isStrongBiometric = */ true,
- DEMO_USER_ID));
- }
-
- @Test
- public void isBiometricAllowedForUser_afterLockout_returnsFalse()
- throws RemoteException {
- TestStrongAuthTracker tracker = createStrongAuthTracker();
- tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
- assertFalse(tracker.isBiometricAllowedForUser(
- /* isStrongBiometric = */ true,
- DEMO_USER_ID));
- }
-
-
- private TestStrongAuthTracker createStrongAuthTracker() {
- final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
- return new TestStrongAuthTracker(context, Looper.getMainLooper());
- }
-
- private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-
- TestStrongAuthTracker(Context context, Looper looper) {
- super(context, looper);
- }
-
- public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
- handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
- }
- }
-
- private ILockSettings createTestLockSettings() {
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mLockPatternUtils = spy(new LockPatternUtils(context));
- final ILockSettings ils = Mockito.mock(ILockSettings.class);
- when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
- return ils;
- }
-
- private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
- return new IWeakEscrowTokenActivatedListener.Stub() {
- @Override
- public void onWeakEscrowTokenActivated(long handle, int userId) {
- // Do nothing.
- }
- };
- }
-
- private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
- return new IWeakEscrowTokenRemovedListener.Stub() {
- @Override
- public void onWeakEscrowTokenRemoved(long handle, int userId) {
- // Do nothing.
- }
- };
- }
-}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c77004d..da91a96 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1867,6 +1867,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
},
+ "-483957611": {
+ "message": "Resuming configuration dispatch for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-481924678": {
"message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
"level": "DEBUG",
@@ -4021,6 +4027,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "1473051122": {
+ "message": "Pausing configuration dispatch for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1494644409": {
"message": " Rejecting as detached: %s",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea..1232baa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1020,7 +1020,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsController.finishInner: no valid PiP leash;"
+ "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
- mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+ mPipTransaction, mPipTask, mPipTaskId);
} else {
t.show(pipLeash);
PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 5a74255..e6faa63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,7 +93,7 @@
// On a smaller screen, don't require as much empty space on screen, as offscreen
// drags will be restricted too much.
- final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600
? R.dimen.freeform_required_visible_empty_space_in_header :
R.dimen.small_screen_required_visible_empty_space_in_header;
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 47bff8d..0d18535 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -78,6 +78,14 @@
uiAutomation.dropShellPermissionIdentity()
}
+ override fun onProcessStarted(
+ pid: Int,
+ processUid: Int,
+ packageUid: Int,
+ packageName: String,
+ processName: String
+ ) {}
+
override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
diff --git a/media/java/android/media/LoudnessCodecController.java b/media/java/android/media/LoudnessCodecController.java
index b3e5c52..61c9131 100644
--- a/media/java/android/media/LoudnessCodecController.java
+++ b/media/java/android/media/LoudnessCodecController.java
@@ -32,12 +32,13 @@
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
/**
* Class for getting recommended loudness parameter updates for audio decoders as they are used
@@ -320,11 +321,6 @@
* Stops any loudness updates and frees up the resources.
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void release() {
- close();
- }
-
- /** @hide */
@Override
public void close() {
synchronized (mControllerLock) {
@@ -339,9 +335,12 @@
}
/** @hide */
- /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
+ /*package*/ void mediaCodecsConsume(
+ Consumer<Entry<LoudnessCodecInfo, Set<MediaCodec>>> consumer) {
synchronized (mControllerLock) {
- return mMediaCodecs;
+ for (Entry<LoudnessCodecInfo, Set<MediaCodec>> entry : mMediaCodecs.entrySet()) {
+ consumer.accept(entry);
+ }
}
}
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index 46be54b..fa08658 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -32,7 +32,6 @@
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
@@ -81,16 +80,15 @@
mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
// send the appropriate bundle for the user to update
if (lcConfig.getSessionId() == sessionId) {
- final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap =
- lcConfig.getRegisteredMediaCodecs();
- for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) {
+ lcConfig.mediaCodecsConsume(mcEntry -> {
+ final LoudnessCodecInfo codecInfo = mcEntry.getKey();
final String infoKey = Integer.toString(codecInfo.hashCode());
Bundle bundle = null;
if (params.containsKey(infoKey)) {
bundle = new Bundle(params.getPersistableBundle(infoKey));
}
- final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo);
+ final Set<MediaCodec> mediaCodecs = mcEntry.getValue();
for (MediaCodec mediaCodec : mediaCodecs) {
final String mediaCodecKey = Integer.toString(
mediaCodec.hashCode());
@@ -121,7 +119,7 @@
break;
}
}
- }
+ });
}
return lcConfig;
});
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 25040a9..e872a58 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -86,7 +86,7 @@
}
// Retrieves the associated object, returns nullValue T if not available.
- T get(JNIEnv *env, jobject thiz) {
+ T get(JNIEnv *env, jobject thiz) const {
std::lock_guard lg(mLock);
// NOLINTNEXTLINE(performance-no-int-to-ptr)
auto ptr = reinterpret_cast<T*>(env->GetLongField(thiz, mFieldId));
@@ -167,8 +167,10 @@
// is possible by checking if the WeakGlobalRef is null equivalent.
auto& getSoundPoolManager() {
- static ObjectManager<std::shared_ptr<SoundPool>> soundPoolManager(fields.mNativeContext);
- return soundPoolManager;
+ // never-delete singleton
+ static auto soundPoolManager =
+ new ObjectManager<std::shared_ptr<SoundPool>>(fields.mNativeContext);
+ return *soundPoolManager;
}
inline auto getSoundPool(JNIEnv *env, jobject thiz) {
@@ -274,8 +276,9 @@
auto& getSoundPoolJavaRefManager() {
// Note this can store shared_ptrs to either jweak and jobject,
// as the underlying type is identical.
- static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap;
- return concurrentHashMap;
+ static auto concurrentHashMap =
+ new ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>>();
+ return *concurrentHashMap;
}
// make_shared_globalref_from_localref() creates a sharable Java global
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
index 4f6ede5..46256ba 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
@@ -126,7 +126,7 @@
try {
mLcc.addMediaCodec(mediaCodec);
- mLcc.release(); // stops updats
+ mLcc.close(); // stops updates
verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId));
} finally {
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 5becc86..f13402c 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -23,7 +23,7 @@
android:shape="rectangle"
android:top="1dp">
<shape>
- <corners android:radius="16dp" />
+ <corners android:radius="4dp" />
<solid android:color="@color/dropdown_container" />
</shape>
</item>
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
new file mode 100644
index 0000000..d7b509e
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+ android:color="@android:color/transparent">
+ <item
+ android:bottom="1dp"
+ android:shape="rectangle"
+ android:top="1dp">
+ <shape>
+ <corners android:bottomLeftRadius="4dp"
+ android:bottomRightRadius="4dp"/>
+ <solid android:color="@color/sign_in_options_container" />
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
new file mode 100644
index 0000000..929756c
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -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.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+ android:elevation="3dp">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:contentDescription="@string/provider_icon_content_description"
+ android:background="@null"/>
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+ style="@style/autofill.TextTitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index cb6c6b4..1fe5e0e 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,22 +17,25 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxWidth="@dimen/autofill_dropdown_layout_width"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
<ImageView
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:contentDescription="@string/provider_icon_content_description"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
- android:layout_width="@dimen/autofill_dropdown_text_width"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextTitle"/>
<TextView
android:id="@android:id/text2"
@@ -40,6 +43,8 @@
android:layout_height="wrap_content"
android:layout_below="@android:id/text1"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextSubtitle"/>
</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index dcb7ef9..7cb1d01 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -20,4 +20,6 @@
<color name="text_primary">#1A1B20</color>
<color name="text_secondary">#44474F</color>
<color name="dropdown_container">#F3F3FA</color>
+ <color name="sign_in_options_container">#DADADA</color>
+ <color name="sign_in_options_icon_color">#1B1B1B</color>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 2a4719d..3a8c78f 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,11 +18,13 @@
<resources>
<dimen name="autofill_view_top_padding">12dp</dimen>
- <dimen name="autofill_view_right_padding">24dp</dimen>
+ <dimen name="autofill_view_right_padding">12dp</dimen>
<dimen name="autofill_view_bottom_padding">12dp</dimen>
<dimen name="autofill_view_left_padding">16dp</dimen>
<dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
<dimen name="autofill_icon_size">24dp</dimen>
- <dimen name="autofill_dropdown_layout_width">296dp</dimen>
- <dimen name="autofill_dropdown_text_width">240dp</dimen>
+ <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
+ <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
+ <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
+ <integer name="autofill_max_visible_datasets">3</integer>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 605e77b..f98164b 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -168,4 +168,9 @@
<string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
<!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] -->
<string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+ <!-- Strings for dropdown presentation. -->
+ <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
+ <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
+ <string name="provider_icon_content_description">Credential provider icon</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 03ac605..985f322 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -30,6 +30,7 @@
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
+import android.provider.Settings
import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
@@ -48,7 +49,9 @@
import android.view.autofill.AutofillId
import android.widget.inline.InlinePresentationSpec
import android.credentials.CredentialManager
+import android.widget.RemoteViews
import androidx.autofill.inline.v1.InlineSuggestionUi
+import androidx.core.content.ContextCompat
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
@@ -115,7 +118,7 @@
}
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId)
+ requestId)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
@@ -307,10 +310,14 @@
val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
- var maxItemCount = totalEntryCount
- if (inlineMaxSuggestedCount > 0) {
- maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
- }
+ val maxDropdownDisplayLimit = this.resources.getInteger(
+ com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
+ var maxInlineItemCount = totalEntryCount
+ maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
+ val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
+ Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+ (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+
var i = 0
var datasetAdded = false
@@ -333,13 +340,8 @@
Log.e(TAG, "PendingIntent was missing from the entry.")
return@usernameLoop
}
- if (inlinePresentationSpecs == null) {
- Log.i(TAG, "Inline presentation spec is null, " +
- "building dropdown presentation only")
- }
- if (i >= maxItemCount) {
- Log.e(TAG, "Skipping because reached the max item count.")
- return@usernameLoop
+ if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+ return@usernameLoop;
}
val icon: Icon = if (primaryEntry.icon == null) {
// The empty entry icon has non-null icon reference but null drawable reference.
@@ -351,38 +353,26 @@
}
// Create inline presentation
var inlinePresentation: InlinePresentation? = null
- var spec: InlinePresentationSpec?
- if (inlinePresentationSpecs != null) {
- if (i < inlinePresentationSpecsCount) {
- spec = inlinePresentationSpecs[i]
+ if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+ val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
+ inlinePresentationSpecs[i]
} else {
- spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+ inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
}
- val displayName: String = if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && primaryEntry.displayName != null) {
- primaryEntry.displayName!!
- } else {
- primaryEntry.userName
- }
- val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(pendingIntent)
- .setTitle(displayName)
- sliceBuilder.setStartIcon(icon)
- if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
- == true) {
- sliceBuilder.setSubtitle(primaryEntry.userName)
- }
- inlinePresentation = InlinePresentation(
- sliceBuilder.build().slice, spec, /* pinned= */ false)
+ inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
+ spec!!, duplicateDisplayNamesForPasskeys)
}
- val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
- this, icon, primaryEntry)
- i++
+ var dropdownPresentation: RemoteViews? = null
+ if (i < lastDropdownDatasetIndex) {
+ dropdownPresentation = RemoteViewsFactory
+ .createDropdownPresentation(this, icon, primaryEntry)
+ }
val dataSetBuilder = Dataset.Builder()
val presentationBuilder = Presentations.Builder()
- .setMenuPresentation(dropdownPresentation)
+ if (dropdownPresentation != null) {
+ presentationBuilder.setMenuPresentation(dropdownPresentation)
+ }
if (inlinePresentation != null) {
presentationBuilder.setInlinePresentation(inlinePresentation)
}
@@ -398,6 +388,12 @@
.setAuthenticationExtras(fillInIntent.extras)
.build())
datasetAdded = true
+ i++
+
+ if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
+ addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+ fillResponseBuilder)
+ }
}
val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
inlinePresentationSpecsCount)
@@ -408,6 +404,49 @@
return datasetAdded
}
+ private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
+ InlinePresentation {
+ val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+ && primaryEntry.displayName != null) {
+ primaryEntry.displayName!!
+ } else {
+ primaryEntry.userName
+ }
+ val sliceBuilder = InlineSuggestionUi
+ .newContentBuilder(pendingIntent)
+ .setTitle(displayName)
+ sliceBuilder.setStartIcon(icon)
+ if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
+ sliceBuilder.setSubtitle(primaryEntry.userName)
+ }
+ return InlinePresentation(
+ sliceBuilder.build().slice, spec, /* pinned= */ false)
+ }
+
+ private fun addDropdownMoreOptionsPresentation(
+ bottomSheetPendingIntent: PendingIntent,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder) {
+ val presentationBuilder = Presentations.Builder()
+ .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+
+ fillResponseBuilder.addDataset(
+ Dataset.Builder()
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentationBuilder.build())
+ .build())
+ .setAuthentication(bottomSheetPendingIntent.intentSender)
+ .build()
+ )
+ }
+
private fun getLastInlinePresentationSpec(
inlinePresentationSpecs: List<InlinePresentationSpec>?,
inlinePresentationSpecsCount: Int
@@ -534,9 +573,9 @@
}
private fun getCredManRequest(
- structure: AssistStructure,
- sessionId: Int,
- requestId: Int
+ structure: AssistStructure,
+ sessionId: Int,
+ requestId: Int
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
traverseStructure(structure, credentialOptions)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index e039dea..68f1c86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -44,7 +44,7 @@
if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
return remoteViews
}
- setRemoteViewsPaddings(remoteViews, context)
+ setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
remoteViews.setTextViewText(android.R.id.text1, displayName)
@@ -81,8 +81,46 @@
return remoteViews
}
+ fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
+ var layoutId: Int = com.android.credentialmanager.R.layout
+ .credman_dropdown_bottom_sheet
+ val remoteViews = RemoteViews(context.packageName, layoutId)
+ setRemoteViewsPaddings(remoteViews, context)
+ remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
+ com.android.credentialmanager
+ .R.string.dropdown_presentation_more_sign_in_options_text))
+
+ val textColorPrimary = ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.text_primary)
+ remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+ val icon = Icon.createWithResource(context, com
+ .android.credentialmanager.R.drawable.more_horiz_24px)
+ icon.setTint(ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.sign_in_options_icon_color))
+ remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+ remoteViews.setBoolean(
+ android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+ remoteViews.setInt(
+ android.R.id.icon1,
+ setMaxHeightMethodName,
+ context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_icon_size));
+ val drawableId =
+ com.android.credentialmanager.R.drawable.more_options_list_item
+ remoteViews.setInt(
+ android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ return remoteViews
+ }
+
private fun setRemoteViewsPaddings(
remoteViews: RemoteViews, context: Context) {
+ val bottomPadding = context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+ setRemoteViewsPaddings(remoteViews, context, bottomPadding)
+ }
+
+ private fun setRemoteViewsPaddings(
+ remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
val leftPadding = context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_view_left_padding)
val iconToTextPadding = context.resources.getDimensionPixelSize(
@@ -104,7 +142,7 @@
iconToTextPadding,
/* top=*/topPadding,
/* right=*/rightPadding,
- /* bottom=*/0)
+ primaryTextBottomPadding)
remoteViews.setViewPadding(
android.R.id.text2,
iconToTextPadding,
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index cd5f597..b015b2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -234,6 +234,6 @@
EditUserPhotoController createEditUserPhotoController(Activity activity,
ActivityStarter activityStarter, ImageView userPhotoView) {
return new EditUserPhotoController(activity, activityStarter, userPhotoView,
- mSavedPhoto, mSavedDrawable, mFileAuthority);
+ mSavedPhoto, mSavedDrawable, mFileAuthority, false);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index e83b9bc..b2de5a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -62,6 +62,7 @@
private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
+ ".FULL_SCREEN_ACTIVITY";
+ static final String EXTRA_IS_USER_NEW = "is_user_new";
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
@@ -72,9 +73,13 @@
private Bitmap mNewUserPhotoBitmap;
private Drawable mNewUserPhotoDrawable;
private String mCachedDrawablePath;
-
public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) {
+ this(activity, activityStarter, view, savedBitmap, savedDrawable, fileAuthority, true);
+ }
+ public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
+ ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority,
+ boolean isUserNew) {
mActivity = activity;
mActivityStarter = activityStarter;
mFileAuthority = fileAuthority;
@@ -82,7 +87,7 @@
mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
mImageView = view;
- mImageView.setOnClickListener(v -> showAvatarPicker());
+ mImageView.setOnClickListener(v -> showAvatarPicker(isUserNew));
mNewUserPhotoBitmap = savedBitmap;
mNewUserPhotoDrawable = savedDrawable;
@@ -117,11 +122,12 @@
return mNewUserPhotoDrawable;
}
- private void showAvatarPicker() {
+ private void showAvatarPicker(boolean isUserNew) {
Intent intent;
if (Flags.avatarSync()) {
intent = new Intent(AVATAR_PICKER_ACTION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.putExtra(EXTRA_IS_USER_NEW, isUserNew);
} else {
intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2c35c77..7eca04a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -364,3 +364,10 @@
description: "Enables styled focus states on pin input field if keyboard is connected"
bug: "316106516"
}
+
+flag {
+ name: "enable_notif_linearlayout_optimized"
+ namespace: "systemui"
+ description: "Enables notification specific LinearLayout optimization"
+ bug: "316110233"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 409f15b..761e74e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -97,11 +97,13 @@
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
+import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -132,6 +134,8 @@
val removeButtonEnabled by remember {
derivedStateOf { selectedIndex.value != null || reorderingWidgets }
}
+ val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) =
+ remember { mutableStateOf(false) }
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -158,6 +162,11 @@
}
viewModel.setSelectedIndex(newIndex)
}
+ }
+ .thenIf(!viewModel.isEditMode) {
+ Modifier.pointerInput(Unit) {
+ detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) }
+ }
},
) {
CommunalHubLazyGrid(
@@ -207,6 +216,16 @@
PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta)
}
+ if (isButtonToEditWidgetsShowing) {
+ ButtonToEditWidgets(
+ onClick = {
+ setIsButtonToEditWidgetsShowing(false)
+ viewModel.onOpenWidgetEditor()
+ },
+ onHide = { setIsButtonToEditWidgetsShowing(false) },
+ )
+ }
+
// This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
// touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
// swipe back to the blank scene.
@@ -414,6 +433,34 @@
}
@Composable
+private fun ButtonToEditWidgets(
+ onClick: () -> Unit,
+ onHide: () -> Unit,
+) {
+ Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+ val colors = LocalAndroidColorScheme.current
+ Button(
+ modifier =
+ Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+ onClick = onClick,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.button_to_configure_widgets_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary,
+ )
+ }
+ }
+}
+
+@Composable
private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) {
Popup(
alignment = Alignment.TopCenter,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index 1407494..bc1e429 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -20,9 +20,13 @@
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastForEach
import kotlinx.coroutines.coroutineScope
/**
@@ -44,6 +48,41 @@
}
}
+/**
+ * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an
+ * Offset representing the position relative to the containing element.
+ */
+suspend fun PointerInputScope.detectLongPressGesture(
+ pass: PointerEventPass = PointerEventPass.Initial,
+ onLongPress: ((Offset) -> Unit),
+) = coroutineScope {
+ awaitEachGesture {
+ val down = awaitFirstDown(pass = pass)
+ val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+ // wait for first tap up or long press
+ try {
+ withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) }
+ } catch (_: PointerEventTimeoutCancellationException) {
+ // withTimeout throws exception if timeout has passed before block completes
+ onLongPress.invoke(down.position)
+ consumeUntilUp(pass)
+ }
+ }
+}
+
+/**
+ * Consumes all pointer events until nothing is pressed and then returns. This method assumes that
+ * something is currently pressed.
+ */
+private suspend fun AwaitPointerEventScope.consumeUntilUp(
+ pass: PointerEventPass = PointerEventPass.Initial
+) {
+ do {
+ val event = awaitPointerEvent(pass = pass)
+ event.changes.fastForEach { it.consume() }
+ } while (event.changes.fastAny { it.pressed })
+}
+
/** Consume all gestures on the initial pass so that child elements do not receive them. */
suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
awaitEachGesture {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 56d6879..bf02d8a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -173,7 +173,8 @@
val belowLockIconPlaceable =
belowLockIconMeasurable.measure(
noMinConstraints.copy(
- maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
)
)
val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index fdf1166..616a7b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -16,19 +16,42 @@
package com.android.systemui.keyguard.ui.composable.blueprint
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
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.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
+import java.util.Optional
import javax.inject.Inject
/**
@@ -39,22 +62,174 @@
@Inject
constructor(
private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val clockSection: ClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+ private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
+ private val clockInteractor: KeyguardClockInteractor,
+ private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
) : LockscreenSceneBlueprint {
override val id: String = "split-shade"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+ val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
+
LockscreenLongPress(
viewModel = viewModel.longPress,
modifier = modifier,
- ) { _ ->
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): split shade blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Column(
+ modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = {
+ viewModel.getSmartSpacePaddingTop(resources)
+ }
+ ),
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ with(clockSection) { LargeClock() }
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ }
+ with(notificationSection) {
+ val splitShadeTopMargin: Dp =
+ if (Flags.centralizedStatusBarDimensRefactor()) {
+ largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
+ } else {
+ dimensionResource(
+ id = R.dimen.large_screen_shade_header_height
+ )
+ }
+ Notifications(
+ modifier =
+ Modifier.fillMaxHeight()
+ .weight(weight = 1f)
+ .padding(top = splitShadeTopMargin)
+ )
+ }
+ }
+
+ if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ with(lockSection) { LockIcon() }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index f40b871..8f21879 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -16,7 +16,8 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.layout.fillMaxWidth
+import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -75,7 +76,13 @@
) {
content {
AndroidView(
- factory = { checkNotNull(currentClock).smallClock.view },
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
modifier =
Modifier.padding(
horizontal =
@@ -83,6 +90,12 @@
)
.padding(top = { viewModel.getSmallClockTopMargin(view.context) })
.onTopPlacementChanged(onTopChanged),
+ update = {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
)
}
}
@@ -116,8 +129,19 @@
) {
content {
AndroidView(
- factory = { checkNotNull(currentClock).largeClock.view },
- modifier = Modifier.fillMaxWidth()
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
+ update = {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 81d5344..bd9ca30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -16,24 +16,28 @@
package com.android.systemui.communal.data.repository
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -44,19 +48,23 @@
class CommunalRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: CommunalRepositoryImpl
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private lateinit var secureSettings: FakeSettings
+ private lateinit var userRepository: FakeUserRepository
- private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
- private lateinit var sceneContainerRepository: SceneContainerRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneContainerRepository = kosmos.sceneContainerRepository
@Before
fun setUp() {
- val kosmos = testKosmos()
- sceneContainerRepository = kosmos.sceneContainerRepository
- featureFlagsClassic = FakeFeatureFlagsClassic()
+ secureSettings = FakeSettings()
+ userRepository = kosmos.fakeUserRepository
- featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ val listOfUserInfo = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(listOfUserInfo)
+
+ kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
underTest = createRepositoryImpl(false)
}
@@ -64,9 +72,13 @@
private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
return CommunalRepositoryImpl(
testScope.backgroundScope,
- featureFlagsClassic,
- FakeSceneContainerFlags(enabled = sceneContainerEnabled),
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ kosmos.fakeFeatureFlagsClassic,
+ kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
sceneContainerRepository,
+ kosmos.fakeUserRepository,
+ secureSettings,
)
}
@@ -147,4 +159,29 @@
assertThat(transitionState)
.isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
}
+
+ @Test
+ fun communalEnabledState_false_whenGlanceableHubSettingFalse() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id)
+
+ val communalEnabled by collectLastValue(underTest.communalEnabledState)
+ assertThat(communalEnabled).isFalse()
+ }
+
+ @Test
+ fun communalEnabledState_true_whenGlanceableHubSettingTrue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id)
+
+ val communalEnabled by collectLastValue(underTest.communalEnabledState)
+ assertThat(communalEnabled).isTrue()
+ }
+
+ companion object {
+ private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+ private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index f23716c..d5e43f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -25,10 +25,13 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
@@ -44,7 +47,6 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -56,7 +58,8 @@
@RunWith(AndroidJUnit4::class)
class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
private val testDispatcher = StandardTestDispatcher()
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
@@ -79,6 +82,8 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
new file mode 100644
index 0000000..51b8342
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+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.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeControllerSceneImplTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+
+ private lateinit var shadeInteractor: ShadeInteractor
+ private lateinit var underTest: ShadeControllerSceneImpl
+
+ @Before
+ fun setup() {
+ kosmos.testCase = this
+ kosmos.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeFeatureFlagsClassic.apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.NSSL_DEBUG_LINES, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ }
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ testScope.runCurrent()
+ shadeInteractor = kosmos.shadeInteractor
+ underTest = kosmos.shadeControllerSceneImpl
+ }
+
+ @Test
+ fun animateCollapseShade_noForceNoExpansion() =
+ testScope.runTest {
+ // GIVEN shade is collapsed and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setDeviceEntered(true)
+ setCollapsed()
+ underTest.addPostCollapseAction(testRunnable)
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade(0, force = false, delayed = false, 1f)
+ runCurrent()
+
+ // THEN the shade remains collapsed and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ verify(testRunnable, times(1)).run()
+ }
+
+ @Test
+ fun animateCollapseShade_expandedExcludeFlagOn() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ underTest.addPostCollapseAction(testRunnable)
+ setDeviceEntered(true)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested with FLAG_EXCLUDE_NOTIFICATION_PANEL
+ underTest.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL)
+ runCurrent()
+
+ // THEN the shade remains expanded and the post-collapse action did not run
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+ verify(testRunnable, never()).run()
+ }
+
+ @Test
+ fun animateCollapseShade_locked() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded on lockscreen
+ setDeviceEntered(false)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade()
+ runCurrent()
+
+ // THEN the shade collapses back to lockscreen and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun animateCollapseShade_unlocked() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded on an unlocked device
+ setDeviceEntered(true)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade()
+ runCurrent()
+
+ // THEN the shade collapses back to lockscreen and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun onCollapseShade_runPostCollapseActionsCalled() =
+ testScope.runTest {
+ // GIVEN shade is expanded and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setShadeFullyExpanded()
+ underTest.addPostCollapseAction(testRunnable)
+
+ // WHEN shade collapses
+ setCollapsed()
+
+ // THEN post-collapse action ran
+ verify(testRunnable, times(1)).run()
+ }
+
+ @Test
+ fun postOnShadeExpanded() =
+ testScope.runTest {
+ // GIVEN shade is collapsed and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setCollapsed()
+ underTest.postOnShadeExpanded(testRunnable)
+
+ // WHEN shade expands
+ setShadeFullyExpanded()
+
+ // THEN post-collapse action ran
+ verify(testRunnable, times(1)).run()
+ }
+
+ private fun setScene(key: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+
+ private fun setDeviceEntered(isEntered: Boolean) {
+ setScene(
+ if (isEntered) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ )
+ assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+ }
+
+ private fun setCollapsed() {
+ setScene(SceneKey.Gone)
+ assertThat(shadeInteractor.isAnyExpanded.value).isFalse()
+ }
+
+ private fun setShadeFullyExpanded() {
+ setScene(SceneKey.Shade)
+ assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 0a10b2c..0c7ce97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -16,11 +16,10 @@
package com.android.systemui.statusbar.notification.collection.render
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -34,18 +33,19 @@
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@SmallTest
+@RunWith(AndroidJUnit4::class)
class GroupExpansionManagerTest : SysuiTestCase() {
- private lateinit var gem: GroupExpansionManagerImpl
+ private lateinit var underTest: GroupExpansionManagerImpl
private val dumpManager: DumpManager = mock()
private val groupMembershipManager: GroupMembershipManager = mock()
- private val featureFlags = FakeFeatureFlagsClassic()
private val pipeline: NotifPipeline = mock()
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@@ -85,79 +85,57 @@
whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
- gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
+ underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager)
}
@Test
- fun testNotifyOnlyOnChange_enabled() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun notifyOnlyOnChange() {
var listenerCalledCount = 0
- gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+ underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- gem.setGroupExpanded(summary1, false)
+ underTest.setGroupExpanded(summary1, false)
assertThat(listenerCalledCount).isEqualTo(0)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
assertThat(listenerCalledCount).isEqualTo(1)
- gem.setGroupExpanded(summary2, true)
+ underTest.setGroupExpanded(summary2, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, false)
+ underTest.setGroupExpanded(summary2, false)
assertThat(listenerCalledCount).isEqualTo(3)
}
@Test
- fun testNotifyOnlyOnChange_disabled() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
- var listenerCalledCount = 0
- gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
-
- gem.setGroupExpanded(summary1, false)
- assertThat(listenerCalledCount).isEqualTo(1)
- gem.setGroupExpanded(summary1, true)
- assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, true)
- assertThat(listenerCalledCount).isEqualTo(3)
- gem.setGroupExpanded(summary1, true)
- assertThat(listenerCalledCount).isEqualTo(4)
- gem.setGroupExpanded(summary2, false)
- assertThat(listenerCalledCount).isEqualTo(5)
- }
-
- @Test
- fun testExpandUnattachedEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun expandUnattachedEntry() {
// First, expand the entry when it is attached.
- gem.setGroupExpanded(summary1, true)
- assertThat(gem.isGroupExpanded(summary1)).isTrue()
+ underTest.setGroupExpanded(summary1, true)
+ assertThat(underTest.isGroupExpanded(summary1)).isTrue()
// Un-attach it, and un-expand it.
NotificationEntryBuilder.setNewParent(summary1, null)
- gem.setGroupExpanded(summary1, false)
+ underTest.setGroupExpanded(summary1, false)
// Expanding again should throw.
- assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) }
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.setGroupExpanded(summary1, true)
+ }
}
@Test
- fun testSyncWithPipeline() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- gem.attach(pipeline)
+ fun syncWithPipeline() {
+ underTest.attach(pipeline)
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
val listener: OnGroupExpansionChangeListener = mock()
- gem.registerGroupExpansionChangeListener(listener)
+ underTest.registerGroupExpansionChangeListener(listener)
beforeRenderListListener.onBeforeRenderList(entries)
verify(listener, never()).onGroupExpansionChange(any(), any())
// Expand one of the groups.
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
verify(listener).onGroupExpansionChange(summary1.row, true)
// Empty the pipeline list and verify that the group is no longer expanded.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
new file mode 100644
index 0000000..2cbcc5a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GroupMembershipManagerTest : SysuiTestCase() {
+ private var underTest = GroupMembershipManagerImpl()
+
+ @Test
+ fun isChildInGroup_topLevel() {
+ val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
+ }
+
+ @Test
+ fun isChildInGroup_noParent() {
+ val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+ assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
+ }
+
+ @Test
+ fun isChildInGroup_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isChildInGroup(summary)).isFalse()
+ }
+
+ @Test
+ fun isGroupSummary_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
+ }
+
+ @Test
+ fun isGroupSummary_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isGroupSummary(summary)).isTrue()
+ }
+
+ @Test
+ fun isGroupSummary_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
+ }
+
+ @Test
+ fun getGroupSummary_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.getGroupSummary(entry)).isNull()
+ }
+
+ @Test
+ fun getGroupSummary_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary)
+ }
+
+ @Test
+ fun getGroupSummary_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 00a86ff..cc4ebd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -1,15 +1,17 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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
@@ -18,9 +20,9 @@
import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
-import android.testing.AndroidTestingRunner
import android.view.View
import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
@@ -66,7 +68,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class ActivityStarterImplTest : SysuiTestCase() {
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var assistManager: AssistManager
@@ -139,7 +141,7 @@
}
@Test
- fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
+ fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
val pendingIntent = mock(PendingIntent::class.java)
val parent = FrameLayout(context)
val view =
@@ -214,7 +216,7 @@
mainExecutor.runAllReady()
verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController).runPostCollapseRunnables()
+ verify(shadeController).collapseShadeForActivityStart()
}
@Test
@@ -226,7 +228,7 @@
mainExecutor.runAllReady()
verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController, never()).runPostCollapseRunnables()
+ verify(shadeController, never()).collapseShadeForActivityStart()
}
@Test
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 2c7467d..fab7840 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -27,7 +27,7 @@
tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip">
>
- <LinearLayout
+ <com.android.systemui.animation.view.LaunchableLinearLayout
android:id="@+id/icons_container"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index dca84b9..b792acc 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -27,10 +27,11 @@
android:fitsSystemWindows="true">
<!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
- <ViewStub
+ <View
android:id="@+id/communal_ui_stub"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ android:visibility="gone" />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_behind"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9bc7681..2b43360 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1089,6 +1089,8 @@
<string name="cta_label_to_open_widget_picker">Add more widgets</string>
<!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
<string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
+ <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
+ <string name="button_to_configure_widgets_text">Customize widgets</string>
<!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
<string name="edit_widget">Edit widget</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
index 259cca8..9e92c93 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
@@ -16,8 +16,11 @@
package com.android.systemui.shared.system;
-import android.graphics.Matrix;
+import static android.os.Trace.TRACE_TAG_INPUT;
+
import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.InputChannel;
@@ -52,23 +55,24 @@
return target.addBatch(src);
}
- /** @see MotionEvent#createRotateMatrix */
- public static Matrix createRotationMatrix(
- /*@Surface.Rotation*/ int rotation, int displayW, int displayH) {
- return MotionEvent.createRotateMatrix(rotation, displayW, displayH);
- }
-
/**
* @see BatchedInputEventReceiver
*/
public static class InputEventReceiver {
+ private final String mName;
private final BatchedInputEventReceiver mReceiver;
+ @Deprecated
public InputEventReceiver(InputChannel inputChannel, Looper looper,
Choreographer choreographer, final InputEventListener listener) {
- mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
+ this("unknown", inputChannel, looper, choreographer, listener);
+ }
+ public InputEventReceiver(String name, InputChannel inputChannel, Looper looper,
+ Choreographer choreographer, final InputEventListener listener) {
+ mName = name;
+ mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
@Override
public void onInputEvent(InputEvent event) {
listener.onInputEvent(event);
@@ -89,6 +93,9 @@
*/
public void dispose() {
mReceiver.dispose();
+ Trace.instant(TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver disposed");
+ Log.d(InputMonitorCompat.TAG, "Input event receiver for monitor (" + mName
+ + ") disposed");
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
index c4aac11..78beaf7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
@@ -17,8 +17,13 @@
import android.hardware.input.InputManagerGlobal;
import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
import android.view.Choreographer;
import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
import com.android.systemui.shared.system.InputChannelCompat.InputEventListener;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -27,14 +32,20 @@
* @see android.view.InputMonitor
*/
public class InputMonitorCompat {
+ static final String TAG = "InputMonitorCompat";
private final InputMonitor mInputMonitor;
+ private final String mName;
/**
* Monitor input on the specified display for gestures.
*/
- public InputMonitorCompat(String name, int displayId) {
+ public InputMonitorCompat(@NonNull String name, int displayId) {
+ mName = name + "-disp" + displayId;
mInputMonitor = InputManagerGlobal.getInstance()
.monitorGestureInput(name, displayId);
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " created");
+ Log.d(TAG, "Input monitor (" + mName + ") created");
+
}
/**
@@ -45,10 +56,19 @@
}
/**
+ * @see InputMonitor#getSurface()
+ */
+ public SurfaceControl getSurface() {
+ return mInputMonitor.getSurface();
+ }
+
+ /**
* @see InputMonitor#dispose()
*/
public void dispose() {
mInputMonitor.dispose();
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " disposed");
+ Log.d(TAG, "Input monitor (" + mName + ") disposed");
}
/**
@@ -56,7 +76,9 @@
*/
public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer,
InputEventListener listener) {
- return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer,
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver created");
+ Log.d(TAG, "Input event receiver for monitor (" + mName + ") created");
+ return new InputEventReceiver(mName, mInputMonitor.getInputChannel(), looper, choreographer,
listener);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3e8c6a7..536f3af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -114,7 +114,6 @@
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
-import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -383,7 +382,6 @@
private List<SubscriptionInfo> mSubscriptionInfo;
@VisibleForTesting
protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- private boolean mFingerprintDetectRunning;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1005,7 +1003,6 @@
final boolean wasCancellingRestarting = mFingerprintRunningState
== BIOMETRIC_STATE_CANCELLING_RESTARTING;
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- mFingerprintDetectRunning = false;
if (wasCancellingRestarting) {
KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else {
@@ -1114,9 +1111,6 @@
boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
- if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
- mFingerprintDetectRunning = false;
- }
mLogger.logFingerprintRunningState(mFingerprintRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -2105,7 +2099,6 @@
@VisibleForTesting
void resetBiometricListeningState() {
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- mFingerprintDetectRunning = false;
}
@VisibleForTesting
@@ -2544,10 +2537,8 @@
return;
}
final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
- final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
- final boolean runningOrRestarting = running
+ final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
- final boolean runDetect = shouldRunFingerprintDetect();
if (runningOrRestarting && !shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_START) {
mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2559,24 +2550,10 @@
mLogger.v("Ignoring startListeningForFingerprint()");
return;
}
- startListeningForFingerprint(runDetect);
- } else if (running && runDetect && !mFingerprintDetectRunning) {
- if (action == BIOMETRIC_ACTION_STOP) {
- mLogger.v("Ignoring startListeningForFingerprint(detect)");
- return;
- }
- // stop running authentication and start running fingerprint detection
- stopListeningForFingerprint();
- startListeningForFingerprint(true);
+ startListeningForFingerprint();
}
}
- private boolean shouldRunFingerprintDetect() {
- return !isUnlockingWithFingerprintAllowed()
- || (Flags.runFingerprintDetectOnDismissibleKeyguard()
- && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
- }
-
/**
* If a user is encrypted or not.
* This is NOT related to the lock screen being visible or not.
@@ -2832,6 +2809,7 @@
&& biometricEnabledForUser
&& !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+ final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
final boolean shouldListenBouncerState =
!strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
@@ -2894,7 +2872,7 @@
}
}
- private void startListeningForFingerprint(boolean runDetect) {
+ private void startListeningForFingerprint() {
final int userId = mSelectedUserInteractor.getSelectedUserId();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
@@ -2924,20 +2902,18 @@
mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
}
- if (runDetect) {
+ if (!isUnlockingWithFingerprintAllowed()) {
mLogger.v("startListeningForFingerprint - detect");
mFpm.detectFingerprint(
mFingerprintCancelSignal,
mFingerprintDetectionCallback,
fingerprintAuthenticateOptions);
- mFingerprintDetectRunning = true;
} else {
mLogger.v("startListeningForFingerprint");
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback,
null /* handler */,
fingerprintAuthenticateOptions);
- mFingerprintDetectRunning = false;
}
setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
}
@@ -3962,7 +3938,6 @@
mSelectedUserInteractor.getSelectedUserId()));
pw.println(" getUserUnlockedWithBiometric()="
+ getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
- pw.println(" mFingerprintDetectRunning=" + mFingerprintDetectRunning);
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
pw.println(" " + data.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 0f5f869..4372826 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -20,11 +20,12 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.UserInfo;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.UserHandle;
import androidx.annotation.NonNull;
-import com.android.systemui.res.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
@@ -32,6 +33,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -61,6 +63,7 @@
private final SecureSettings mSecureSettings;
private final ResetSessionDialogFactory mResetSessionDialogFactory;
private final GuestSessionNotification mGuestSessionNotification;
+ private final HandlerThread mHandlerThread;
@VisibleForTesting
public final UserTracker.Callback mUserChangedCallback =
@@ -111,13 +114,16 @@
mSecureSettings = secureSettings;
mGuestSessionNotification = guestSessionNotification;
mResetSessionDialogFactory = resetSessionDialogFactory;
+ mHandlerThread = new HandlerThread("GuestResumeSessionReceiver");
+ mHandlerThread.start();
}
/**
* Register this receiver with the {@link BroadcastDispatcher}
*/
public void register() {
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
}
private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 86f372a..71d0e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -35,7 +35,6 @@
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -51,7 +50,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.ViewController
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
@@ -64,7 +62,6 @@
*
* The ripple uses the accent color of the current theme.
*/
-@ExperimentalCoroutinesApi
@SysUISingleton
class AuthRippleController @Inject constructor(
private val sysuiContext: Context,
@@ -316,18 +313,6 @@
mView.fadeDwellRipple()
}
}
-
- override fun onBiometricDetected(
- userId: Int,
- biometricSourceType: BiometricSourceType,
- isStrongBiometric: Boolean
- ) {
- // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
- if (!DeviceEntryUdfpsRefactor.isEnabled &&
- keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
- showUnlockRipple(biometricSourceType)
- }
- }
}
private val configurationChangedListener =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ad2136a..d28dbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -94,6 +94,10 @@
override fun onAuthenticationStopped() {
updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
}
+
+ override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
+
+ override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
}
updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 1f4be40..addd880 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -20,13 +20,18 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -34,16 +39,26 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
+ /**
+ * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in
+ * settings).
+ */
+ val communalEnabledState: StateFlow<Boolean>
+
/** Whether the communal hub is showing. */
val isCommunalHubShowing: Flow<Boolean>
@@ -72,13 +87,36 @@
class CommunalRepositoryImpl
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Background backgroundScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
private val featureFlagsClassic: FeatureFlagsClassic,
sceneContainerFlags: SceneContainerFlags,
sceneContainerRepository: SceneContainerRepository,
+ userRepository: UserRepository,
+ private val secureSettings: SecureSettings
) : CommunalRepository {
+
+ private val communalEnabledSettingState: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
+ .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
+
+ override val communalEnabledState: StateFlow<Boolean> =
+ if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) {
+ communalEnabledSettingState
+ .filterNotNull()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = true
+ )
+ } else {
+ MutableStateFlow(false)
+ }
+
override val isCommunalEnabled: Boolean
- get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ get() = communalEnabledState.value
private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
MutableStateFlow(CommunalSceneKey.DEFAULT)
@@ -115,4 +153,26 @@
} else {
desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
}
+
+ private fun observeSettings(userId: Int): Flow<Boolean> =
+ secureSettings
+ .observerFlow(
+ userId = userId,
+ names =
+ arrayOf(
+ GLANCEABLE_HUB_ENABLED,
+ )
+ )
+ // Force an update
+ .onStart { emit(Unit) }
+ .map { readFromSettings(userId) }
+
+ private suspend fun readFromSettings(userId: Int): Boolean =
+ withContext(backgroundDispatcher) {
+ secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1
+ }
+
+ companion object {
+ private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 4c5871d..c36f7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -72,7 +72,12 @@
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
- val isCommunalAvailable =
+ /** A {@link StateFlow} that tracks whether communal features are enabled. */
+ val communalEnabledState: StateFlow<Boolean>
+ get() = communalRepository.communalEnabledState
+
+ /** Whether communal features are enabled and available. */
+ val isCommunalAvailable: StateFlow<Boolean> =
flowOf(isCommunalEnabled)
.flatMapLatest { enabled ->
if (enabled)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3f7c152..d2883cc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -44,11 +44,11 @@
// 100 - notification
// TODO(b/297792660): Tracking Bug
@JvmField val UNCLEARED_TRANSIENT_HUN_FIX =
- unreleasedFlag("uncleared_transient_hun_fix", teamfood = true)
+ releasedFlag("uncleared_transient_hun_fix")
// TODO(b/298308067): Tracking Bug
@JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
- unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true)
+ releasedFlag("swipe_uncleared_transient_view_fix")
// TODO(b/254512751): Tracking Bug
val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
@@ -102,12 +102,6 @@
default = true
)
- /** Only notify group expansion listeners when a change happens. */
- // TODO(b/292213543): Tracking Bug
- @JvmField
- val NOTIFICATION_GROUP_EXPANSION_CHANGE =
- releasedFlag("notification_group_expansion_change")
-
// TODO(b/301955929)
@JvmField
val NOTIF_LS_BACKGROUND_THREAD =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3de9e68..a95ddb5 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2437,6 +2437,7 @@
return true;
}
});
+ mGlobalActionsLayout.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
mGlobalActionsLayout.setRotationListener(this::onRotate);
mGlobalActionsLayout.setAdapter(mAdapter);
mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 23ee00d..a3029b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -146,7 +146,7 @@
null,
UserHandle.ALL
)
- userTracker.addCallback(userTrackerCallback, mainExecutor)
+ userTracker.addCallback(userTrackerCallback, backgroundExecutor)
loadSavedComponents()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 58e0428..91c86df 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -261,7 +262,7 @@
private boolean mIsTrackpadThreeFingerSwipe;
private boolean mIsButtonForcedVisible;
- private InputMonitor mInputMonitor;
+ private InputMonitorCompat mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
private NavigationEdgeBackPlugin mEdgeBackPlugin;
@@ -665,10 +666,8 @@
}
// Register input event receiver
- mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
- "edge-swipe", mDisplayId);
- mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
- mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+ mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
+ mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 958ace35..21de185 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -26,6 +26,8 @@
import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
@@ -95,6 +97,7 @@
private Future mLastShowWarningTask;
private boolean mEnableSkinTemperatureWarning;
private boolean mEnableUsbTemperatureAlarm;
+ private final HandlerThread mHandlerThread;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
@@ -167,6 +170,8 @@
mPowerManager = powerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
mUserTracker = userTracker;
+ mHandlerThread = new HandlerThread("PowerUI");
+ mHandlerThread.start();
}
public void start() {
@@ -185,7 +190,8 @@
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
// Check to see if we need to let the user know that the phone previously shut down due
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 8e1b00d..7a4be3f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -23,11 +23,11 @@
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
import com.android.systemui.res.R
-import com.android.systemui.animation.view.LaunchableFrameLayout
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class OngoingPrivacyChip @JvmOverloads constructor(
@@ -35,7 +35,7 @@
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
defStyleRes: Int = 0
-) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
private var configuration: Configuration
private var iconMargin = 0
@@ -43,6 +43,8 @@
private var iconColor = 0
private val iconsContainer: LinearLayout
+ val launchableContentView
+ get() = iconsContainer
var privacyList = emptyList<PrivacyItem>()
set(value) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index 76ef8a2..f121630 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -26,9 +26,9 @@
import android.os.UserHandle
import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
-import android.view.View
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
+import androidx.core.view.isVisible
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.appops.AppOpsController
@@ -214,7 +214,7 @@
* @param context A context to use to create the dialog.
* @see filterAndSelect
*/
- fun showDialog(context: Context, view: View? = null) {
+ fun showDialog(context: Context, privacyChip: OngoingPrivacyChip? = null) {
dismissDialog()
backgroundExecutor.execute {
val usage = permGroupUsage()
@@ -277,8 +277,8 @@
)
d.setShowForAllUsers(true)
d.addOnDismissListener(onDialogDismissed)
- if (view != null) {
- val controller = getPrivacyDialogController(view)
+ if (privacyChip != null) {
+ val controller = getPrivacyDialogController(privacyChip)
if (controller == null) {
d.show()
} else {
@@ -296,10 +296,13 @@
}
}
- private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? {
- val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null
+ private fun getPrivacyDialogController(
+ source: OngoingPrivacyChip
+ ): DialogLaunchAnimator.Controller? {
+ val delegate =
+ DialogLaunchAnimator.Controller.fromView(source.launchableContentView) ?: return null
return object : DialogLaunchAnimator.Controller by delegate {
- override fun shouldAnimateExit() = false
+ override fun shouldAnimateExit() = source.isVisible
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 592cb3b..211b4594 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -192,6 +192,7 @@
private DialogLaunchAnimator mDialogLaunchAnimator;
private boolean mHasWifiEntries;
private WifiStateWorker mWifiStateWorker;
+ private boolean mHasActiveSubId;
@VisibleForTesting
static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -299,6 +300,7 @@
mExecutor);
// Listen the subscription changes
mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+ refreshHasActiveSubId();
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mOnSubscriptionsChangedListener);
mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -901,18 +903,22 @@
* @return whether there is the carrier item in the slice.
*/
boolean hasActiveSubId() {
- if (mSubscriptionManager == null) {
- if (DEBUG) {
- Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
- }
+ if (isAirplaneModeEnabled() || mTelephonyManager == null) {
return false;
}
- if (isAirplaneModeEnabled() || mTelephonyManager == null
- || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
- return false;
+ return mHasActiveSubId;
+ }
+
+ private void refreshHasActiveSubId() {
+ if (mSubscriptionManager == null) {
+ mHasActiveSubId = false;
+ Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
+ return;
}
- return true;
+
+ mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
+ Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
}
/**
@@ -1204,6 +1210,7 @@
@Override
public void onSubscriptionsChanged() {
+ refreshHasActiveSubId();
updateListener();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index e2959fe..1c37908 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -23,16 +23,22 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -47,6 +53,8 @@
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ sceneContainerFlags: SceneContainerFlags,
+ sceneInteractorProvider: Provider<SceneInteractor>,
) : CoreStartable {
private var notificationPresenter: NotificationPresenter? = null
@@ -58,11 +66,28 @@
/**
* True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
* false if the bouncer is visible.
- *
- * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
*/
val isLockscreenOrShadeVisible: StateFlow<Boolean> =
- windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+ if (!sceneContainerFlags.isEnabled()) {
+ windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+ } else {
+ sceneInteractorProvider
+ .get()
+ .transitionState
+ .map { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen
+ is ObservableTransitionState.Transition ->
+ state.toScene == SceneKey.Shade ||
+ state.toScene == SceneKey.Lockscreen ||
+ state.fromScene == SceneKey.Shade ||
+ state.fromScene == SceneKey.Lockscreen
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
+ }
/**
* True if lockscreen (including AOD) or the shade is visible **and** the user is currently
diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
new file mode 100644
index 0000000..f71a401d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** A base class for non-empty implementations of ShadeController. */
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class BaseShadeControllerImpl(
+ private val touchLog: LogBuffer,
+ protected val commandQueue: CommandQueue,
+ protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ protected val notificationShadeWindowController: NotificationShadeWindowController,
+ protected val assistManagerLazy: Lazy<AssistManager>
+) : ShadeController {
+ protected lateinit var notifPresenter: NotificationPresenter
+ /** Runnables to run after completing a collapse of the shade. */
+ private val postCollapseActions = ArrayList<Runnable>()
+
+ override fun start() {
+ logTouchesTo(touchLog)
+ }
+
+ final override fun animateExpandShade() {
+ if (isShadeEnabled) {
+ expandToNotifications()
+ }
+ }
+
+ /** Expand the shade with notifications visible. */
+ protected abstract fun expandToNotifications()
+
+ final override fun animateExpandQs() {
+ if (isShadeEnabled) {
+ expandToQs()
+ }
+ }
+
+ /** Expand the shade showing only quick settings. */
+ protected abstract fun expandToQs()
+
+ final override fun addPostCollapseAction(action: Runnable) {
+ postCollapseActions.add(action)
+ }
+
+ protected fun runPostCollapseActions() {
+ val clonedList: ArrayList<Runnable> = ArrayList(postCollapseActions)
+ postCollapseActions.clear()
+ for (r in clonedList) {
+ r.run()
+ }
+ statusBarKeyguardViewManager.readyForKeyguardDone()
+ }
+
+ final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {
+ if (!this.notifPresenter.isCollapsing()) {
+ onClosingFinished()
+ }
+ if (launchIsFullScreen) {
+ instantCollapseShade()
+ }
+ }
+ final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {
+ if (
+ notifPresenter.isPresenterFullyCollapsed() &&
+ !notifPresenter.isCollapsing() &&
+ isLaunchForActivity
+ ) {
+ onClosingFinished()
+ } else {
+ collapseShade(true /* animate */)
+ }
+ }
+
+ protected fun onClosingFinished() {
+ runPostCollapseActions()
+ if (!this.notifPresenter.isPresenterFullyCollapsed()) {
+ // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+ // the closing
+ notificationShadeWindowController.setNotificationShadeFocusable(true)
+ }
+ }
+
+ override fun setNotificationPresenter(presenter: NotificationPresenter) {
+ notifPresenter = presenter
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 44cb0d6..97ec3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -22,6 +22,7 @@
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -33,6 +34,7 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -49,7 +51,7 @@
private val powerManager: PowerManager,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
- private lateinit var communalContainerView: View
+ private var communalContainerView: View? = null
/**
* The width of the area in which a right edge swipe can open the hub, in pixels. Read from
@@ -108,6 +110,11 @@
return communalInteractor.isCommunalEnabled && isComposeAvailable()
}
+ /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */
+ fun enabledState(): StateFlow<Boolean> {
+ return communalInteractor.communalEnabledState
+ }
+
/**
* Creates the container view containing the glanceable hub UI.
*
@@ -125,42 +132,44 @@
if (!isEnabled()) {
throw RuntimeException("Glanceable hub is not enabled")
}
- if (::communalContainerView.isInitialized) {
+ if (communalContainerView != null) {
throw RuntimeException("Communal view has already been initialized")
}
communalContainerView = containerView
rightEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_right_edge_swipe_region_width
)
topEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_top_edge_swipe_region_height
)
bottomEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_bottom_edge_swipe_region_height
)
collectFlow(
- communalContainerView,
+ containerView,
keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
{ anyBouncerShowing = it }
)
- collectFlow(
- communalContainerView,
- communalInteractor.isCommunalShowing,
- { hubShowing = it }
- )
- collectFlow(
- communalContainerView,
- shadeInteractor.isAnyFullyExpanded,
- { shadeShowing = it }
- )
+ collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it })
+ collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it })
- return communalContainerView
+ communalContainerView = containerView
+
+ return containerView
+ }
+
+ /** Removes the container view from its parent. */
+ fun disposeView() {
+ communalContainerView?.let {
+ (it.parent as ViewGroup).removeView(it)
+ communalContainerView = null
+ }
}
/**
@@ -173,10 +182,10 @@
* to be fully in control of its own touch handling.
*/
fun onTouchEvent(ev: MotionEvent): Boolean {
- if (!::communalContainerView.isInitialized) {
- return false
- }
+ return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
+ }
+ private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
val isUp = ev.actionMasked == MotionEvent.ACTION_UP
val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
@@ -190,7 +199,7 @@
if (hubShowing && isDown) {
val y = ev.rawY
val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
- val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth
+ val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth
if (topSwipe || bottomSwipe) {
// Don't intercept touches at the top/bottom edge so that swipes can open the
@@ -200,7 +209,7 @@
if (!hubOccluded) {
isTrackingHubTouch = true
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a
// gesture may return false from dispatchTouchEvent.
return true
@@ -209,7 +218,7 @@
if (isUp || isCancel) {
isTrackingHubTouch = false
}
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -223,11 +232,10 @@
if (!isTrackingOpenGesture && isDown) {
val x = ev.rawX
- val inOpeningSwipeRegion: Boolean =
- x >= communalContainerView.width - rightEdgeSwipeRegionWidth
+ val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
if (inOpeningSwipeRegion && !hubOccluded) {
isTrackingOpenGesture = true
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a
// gesture may return false from dispatchTouchEvent.
return true
@@ -236,7 +244,7 @@
if (isUp || isCancel) {
isTrackingOpenGesture = false
}
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -249,8 +257,8 @@
* Dispatches the touch event to the communal container and sends a user activity event to reset
* the screen timeout.
*/
- private fun dispatchTouchEvent(ev: MotionEvent) {
- communalContainerView.dispatchTouchEvent(ev)
+ private fun dispatchTouchEvent(view: View, ev: MotionEvent) {
+ view.dispatchTouchEvent(ev)
powerManager.userActivity(
SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aeccf00..c0ceba3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2883,7 +2883,9 @@
private void onTrackingStarted() {
endClosing();
mShadeRepository.setLegacyShadeTracking(true);
- mTrackingStartedListener.onTrackingStarted();
+ if (mTrackingStartedListener != null) {
+ mTrackingStartedListener.onTrackingStarted();
+ }
notifyExpandingStarted();
updateExpansionAndVisibility();
mScrimController.onTrackingStarted();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 863bb36..5ecc54b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -605,16 +605,21 @@
* The layout lives in {@link R.id.communal_ui_stub}.
*/
public void setupCommunalHubLayout() {
- if (!mGlanceableHubContainerController.isEnabled()) {
- return;
- }
-
- // Replace the placeholder view with the communal UI.
- View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
- int index = mView.indexOfChild(communalPlaceholder);
- mView.removeView(communalPlaceholder);
-
- mView.addView(mGlanceableHubContainerController.initView(mView.getContext()), index);
+ collectFlow(
+ mView,
+ mGlanceableHubContainerController.enabledState(),
+ isEnabled -> {
+ if (isEnabled) {
+ View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+ int index = mView.indexOfChild(communalPlaceholder);
+ mView.addView(
+ mGlanceableHubContainerController.initView(mView.getContext()),
+ index);
+ } else {
+ mGlanceableHubContainerController.disposeView();
+ }
+ }
+ );
}
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 2c4b0b9..ec4b23a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -33,10 +33,20 @@
* {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
*/
public interface ShadeController extends CoreStartable {
- /** True if the shade UI is enabled on this particular Android variant and false otherwise. */
+ /**
+ * True if the shade UI is enabled on this particular Android variant and false otherwise.
+ *
+ * @deprecated use ShadeInteractor instead
+ */
+ @Deprecated
boolean isShadeEnabled();
- /** Make our window larger and the shade expanded */
+ /**
+ * Make our window larger and the shade expanded
+ *
+ * @deprecated will no longer be needed when keyguard is a sibling view to the shade
+ */
+ @Deprecated
void instantExpandShade();
/** Collapse the shade instantly with no animation. */
@@ -74,13 +84,28 @@
/** Expand the shade with quick settings expanded with an animation. */
void animateExpandQs();
- /** Posts a request to collapse the shade. */
+ /**
+ * Posts a request to collapse the shade.
+ *
+ * @deprecated use #animateCollapseShade
+ */
+ @Deprecated
void postAnimateCollapseShade();
- /** Posts a request to force collapse the shade. */
+ /**
+ * Posts a request to force collapse the shade.
+ *
+ * @deprecated use #animateForceCollapseShade
+ */
+ @Deprecated
void postAnimateForceCollapseShade();
- /** Posts a request to expand the shade to quick settings. */
+ /**
+ * Posts a request to expand the shade to quick settings.
+ *
+ * @deprecated use #animateExpandQs
+ */
+ @Deprecated
void postAnimateExpandQs();
/** Cancels any ongoing expansion touch handling and collapses the shade. */
@@ -90,26 +115,29 @@
* If the shade is not fully expanded, collapse it animated.
*
* @return Seems to always return false
+ * @deprecated use {@link #collapseShade()} instead
*/
+ @Deprecated
boolean closeShadeIfOpen();
/**
- * Returns whether the shade state is the keyguard or not.
- */
- boolean isKeyguard();
-
- /**
* Returns whether the shade is currently open.
* Even though in the current implementation shade is in expanded state on keyguard, this
* method makes distinction between shade being truly open and plain keyguard state:
* - if QS and notifications are visible on the screen, return true
* - for any other state, including keyguard, return false
+ *
+ * @deprecated will be replaced by ShadeInteractor once scene container launches
*/
+ @Deprecated
boolean isShadeFullyOpen();
/**
* Returns whether shade or QS are currently opening or collapsing.
+ *
+ * @deprecated will be replaced by ShadeInteractor once scene container launches
*/
+ @Deprecated
boolean isExpandingOrCollapsing();
/**
@@ -127,37 +155,67 @@
*/
void addPostCollapseAction(Runnable action);
- /** Run all of the runnables added by {@link #addPostCollapseAction}. */
- void runPostCollapseRunnables();
-
/**
* Close the shade if it was open
*
* @return true if the shade was open, else false
*/
- boolean collapseShade();
+ void collapseShade();
/**
* If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
* the shade. Post collapse runnables will be executed
*
* @param animate true to animate the collapse, false for instantaneous collapse
+ * @deprecated call either #animateCollapseShade or #instantCollapseShade
*/
+ @Deprecated
void collapseShade(boolean animate);
- /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+ /**
+ * Calls #collapseShade if already on the main thread. If not, posts a call to it.
+ * @deprecated call #collapseShade
+ */
+ @Deprecated
void collapseOnMainThread();
- /** Makes shade expanded but not visible. */
+ /**
+ * If necessary, instantly collapses the shade for an activity start, otherwise runs the
+ * post-collapse runnables. Instant collapse is ok here, because the purpose is to have the
+ * shade collapsed when the user returns to SysUI from the launched activity.
+ */
+ void collapseShadeForActivityStart();
+
+ /**
+ * Makes shade expanded but not visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
void makeExpandedInvisible();
- /** Makes shade expanded and visible. */
+ /**
+ * Makes shade expanded and visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
void makeExpandedVisible(boolean force);
- /** Returns whether the shade is expanded and visible. */
+ /**
+ * Returns whether the shade is expanded and visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
boolean isExpandedVisible();
- /** Handle status bar touch event. */
+ /**
+ * Handle status bar touch event.
+ *
+ * @deprecated only called by CentralSurfaces, which is being deleted
+ */
+ @Deprecated
void onStatusBarTouch(MotionEvent event);
/** Called when a launch animation was cancelled. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 82959ee..08a0c93 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -42,9 +42,6 @@
override fun closeShadeIfOpen(): Boolean {
return false
}
- override fun isKeyguard(): Boolean {
- return false
- }
override fun isShadeFullyOpen(): Boolean {
return false
}
@@ -53,12 +50,10 @@
}
override fun postOnShadeExpanded(action: Runnable?) {}
override fun addPostCollapseAction(action: Runnable?) {}
- override fun runPostCollapseRunnables() {}
- override fun collapseShade(): Boolean {
- return false
- }
+ override fun collapseShade() {}
override fun collapseShade(animate: Boolean) {}
override fun collapseOnMainThread() {}
+ override fun collapseShadeForActivityStart() {}
override fun makeExpandedInvisible() {}
override fun makeExpandedVisible(force: Boolean) {}
override fun isExpandedVisible(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index fdc7eec..e8d9c35 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -33,7 +33,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -44,14 +43,13 @@
import dagger.Lazy;
-import java.util.ArrayList;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/** An implementation of {@link ShadeController}. */
@SysUISingleton
-public final class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl extends BaseShadeControllerImpl {
private static final String TAG = "ShadeControllerImpl";
private static final boolean SPEW = false;
@@ -60,7 +58,6 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
- private final LogBuffer mTouchLog;
private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -73,12 +70,9 @@
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<NotificationGutsManager> mGutsManager;
- private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
-
private boolean mExpandedVisible;
private boolean mLockscreenOrShadeVisible;
- private NotificationPresenter mPresenter;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private ShadeVisibilityListener mShadeVisibilityListener;
@@ -99,9 +93,13 @@
Lazy<AssistManager> assistManagerLazy,
Lazy<NotificationGutsManager> gutsManager
) {
+ super(touchLog,
+ commandQueue,
+ statusBarKeyguardViewManager,
+ notificationShadeWindowController,
+ assistManagerLazy);
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
- mTouchLog = touchLog;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
@@ -117,7 +115,7 @@
@Override
public boolean isShadeEnabled() {
- return true;
+ return mCommandQueue.panelsEnabled() && mDeviceProvisionedController.isCurrentUserSetup();
}
@Override
@@ -125,20 +123,16 @@
// Make our window larger and the panel expanded.
makeExpandedVisible(true /* force */);
getShadeViewController().expand(false /* animate */);
- mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
+ getCommandQueue().recomputeDisableFlags(mDisplayId, false /* animate */);
}
@Override
public void animateCollapseShade(int flags, boolean force, boolean delayed,
float speedUpFactor) {
if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
- runPostCollapseRunnables();
+ runPostCollapseActions();
return;
}
- if (SPEW) {
- Log.d(TAG,
- "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
- }
if (getNotificationShadeWindowView() != null
&& getShadeViewController().canBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
@@ -151,28 +145,19 @@
}
@Override
- public void animateExpandShade() {
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
+ protected void expandToNotifications() {
getShadeViewController().expandToNotifications();
}
@Override
- public void animateExpandQs() {
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
- // Settings are not available in setup
- if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
+ protected void expandToQs() {
getShadeViewController().expandToQs();
}
@Override
public boolean closeShadeIfOpen() {
if (!getShadeViewController().isFullyCollapsed()) {
- mCommandQueue.animateCollapsePanels(
+ getCommandQueue().animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
notifyVisibilityChanged(false);
mAssistManagerLazy.get().hideAssist();
@@ -181,11 +166,6 @@
}
@Override
- public boolean isKeyguard() {
- return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- }
-
- @Override
public boolean isShadeFullyOpen() {
return getShadeViewController().isShadeFullyExpanded();
}
@@ -224,46 +204,34 @@
}
@Override
- public void addPostCollapseAction(Runnable action) {
- mPostCollapseRunnables.add(action);
+ public void collapseShade() {
+ collapseShadeInternal();
}
- @Override
- public void runPostCollapseRunnables() {
- ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
- mPostCollapseRunnables.clear();
- int size = clonedList.size();
- for (int i = 0; i < size; i++) {
- clonedList.get(i).run();
- }
- mStatusBarKeyguardViewManager.readyForKeyguardDone();
- }
-
- @Override
- public boolean collapseShade() {
+ private boolean collapseShadeInternal() {
if (!getShadeViewController().isFullyCollapsed()) {
// close the shade if it was open
animateCollapseShadeForcedDelayed();
notifyVisibilityChanged(false);
-
return true;
} else {
return false;
}
}
+
@Override
public void collapseShade(boolean animate) {
if (animate) {
- boolean willCollapse = collapseShade();
+ boolean willCollapse = collapseShadeInternal();
if (!willCollapse) {
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
- } else if (!mPresenter.isPresenterFullyCollapsed()) {
+ } else if (!getNotifPresenter().isPresenterFullyCollapsed()) {
instantCollapseShade();
notifyVisibilityChanged(false);
} else {
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
}
@@ -296,46 +264,16 @@
}
}
- private void onClosingFinished() {
- runPostCollapseRunnables();
- if (!mPresenter.isPresenterFullyCollapsed()) {
- // if we set it not to be focusable when collapsing, we have to undo it when we aborted
- // the closing
- mNotificationShadeWindowController.setNotificationShadeFocusable(true);
- }
- }
-
- @Override
- public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
- if (mPresenter.isPresenterFullyCollapsed()
- && !mPresenter.isCollapsing()
- && isLaunchForActivity) {
- onClosingFinished();
- } else {
- collapseShade(true /* animate */);
- }
- }
-
- @Override
- public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
- if (!mPresenter.isCollapsing()) {
- onClosingFinished();
- }
- if (launchIsFullScreen) {
- instantCollapseShade();
- }
- }
-
@Override
public void instantCollapseShade() {
getShadeViewController().instantCollapse();
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
@Override
public void makeExpandedVisible(boolean force) {
if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
- if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+ if (!force && (mExpandedVisible || !getCommandQueue().panelsEnabled())) {
return;
}
@@ -346,7 +284,7 @@
mNotificationShadeWindowController.setPanelVisible(true);
notifyVisibilityChanged(true);
- mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+ getCommandQueue().recomputeDisableFlags(mDisplayId, !force /* animate */);
notifyExpandedVisibleChanged(true);
}
@@ -377,9 +315,9 @@
-1 /* y */,
true /* resetMenu */);
- runPostCollapseRunnables();
+ runPostCollapseActions();
notifyExpandedVisibleChanged(false);
- mCommandQueue.recomputeDisableFlags(
+ getCommandQueue().recomputeDisableFlags(
mDisplayId,
getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
@@ -421,11 +359,6 @@
}
@Override
- public void setNotificationPresenter(NotificationPresenter presenter) {
- mPresenter = presenter;
- }
-
- @Override
public void setNotificationShadeWindowViewController(
NotificationShadeWindowViewController controller) {
mNotificationShadeWindowViewController = controller;
@@ -441,8 +374,8 @@
@Override
public void start() {
- TouchLogger.logTouchesTo(mTouchLog);
- getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+ super.start();
+ getShadeViewController().setTrackingStartedListener(this::runPostCollapseActions);
getShadeViewController().setOpenCloseListener(
new OpenCloseListener() {
@Override
@@ -456,4 +389,16 @@
}
});
}
+
+ @Override
+ public void collapseShadeForActivityStart() {
+ if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ animateCollapseShadeForcedDelayed();
+ } else {
+ // Do it after DismissAction has been processed to conserve the
+ // needed ordering.
+ mMainExecutor.execute(this::runPostCollapseActions);
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
new file mode 100644
index 0000000..10b9db0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.ShadeTouchLog
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * Implementation of ShadeController backed by scenes instead of NPVC.
+ *
+ * TODO(b/300258424) rename to ShadeControllerImpl and inline/delete all the deprecated methods
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class ShadeControllerSceneImpl
+@Inject
+constructor(
+ @Background private val scope: CoroutineScope,
+ private val shadeInteractor: ShadeInteractor,
+ private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val notificationStackScrollLayout: NotificationStackScrollLayout,
+ @ShadeTouchLog private val touchLog: LogBuffer,
+ commandQueue: CommandQueue,
+ statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ notificationShadeWindowController: NotificationShadeWindowController,
+ assistManagerLazy: Lazy<AssistManager>,
+) :
+ BaseShadeControllerImpl(
+ touchLog,
+ commandQueue,
+ statusBarKeyguardViewManager,
+ notificationShadeWindowController,
+ assistManagerLazy,
+ ) {
+
+ init {
+ scope.launch {
+ shadeInteractor.isAnyExpanded.collect {
+ if (!it) {
+ runPostCollapseActions()
+ }
+ }
+ }
+ }
+
+ override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value
+
+ override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
+
+ override fun isExpandingOrCollapsing(): Boolean =
+ shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+
+ override fun instantExpandShade() {
+ // Do nothing
+ }
+
+ override fun instantCollapseShade() {
+ // TODO(b/315921512) add support for instant transition
+ sceneInteractor.changeScene(
+ SceneModel(getCollapseDestinationScene(), "instant"),
+ "hide shade"
+ )
+ }
+
+ override fun animateCollapseShade(
+ flags: Int,
+ force: Boolean,
+ delayed: Boolean,
+ speedUpFactor: Float
+ ) {
+ if (!force && !shadeInteractor.isAnyExpanded.value) {
+ runPostCollapseActions()
+ return
+ }
+ if (
+ shadeInteractor.isAnyExpanded.value &&
+ flags and CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL == 0
+ ) {
+ // release focus immediately to kick off focus change transition
+ notificationShadeWindowController.setNotificationShadeFocusable(false)
+ notificationStackScrollLayout.cancelExpandHelper()
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.Shade, null),
+ "ShadeController.animateExpandShade"
+ )
+ if (delayed) {
+ scope.launch {
+ delay(125)
+ animateCollapseShadeInternal()
+ }
+ } else {
+ animateCollapseShadeInternal()
+ }
+ }
+ }
+
+ private fun animateCollapseShadeInternal() {
+ sceneInteractor.changeScene(
+ SceneModel(getCollapseDestinationScene(), "ShadeController.animateCollapseShade"),
+ "ShadeController.animateCollapseShade"
+ )
+ }
+
+ private fun getCollapseDestinationScene(): SceneKey {
+ return if (deviceEntryInteractor.isDeviceEntered.value) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ }
+
+ override fun cancelExpansionAndCollapseShade() {
+ // TODO do we need to actually cancel the touch session?
+ animateCollapseShade()
+ }
+
+ override fun closeShadeIfOpen(): Boolean {
+ if (shadeInteractor.isAnyExpanded.value) {
+ commandQueue.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */
+ )
+ assistManagerLazy.get().hideAssist()
+ }
+ return false
+ }
+
+ override fun collapseShade() {
+ animateCollapseShadeForcedDelayed()
+ }
+
+ override fun collapseShade(animate: Boolean) {
+ if (animate) {
+ animateCollapseShade()
+ } else {
+ instantCollapseShade()
+ }
+ }
+
+ override fun collapseOnMainThread() {
+ // TODO if this works with delegation alone, we can deprecate and delete
+ collapseShade()
+ }
+
+ override fun expandToNotifications() {
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.Shade, null),
+ "ShadeController.animateExpandShade"
+ )
+ }
+
+ override fun expandToQs() {
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.QuickSettings, null),
+ "ShadeController.animateExpandQs"
+ )
+ }
+
+ override fun setVisibilityListener(listener: ShadeVisibilityListener) {
+ scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+ }
+
+ @ExperimentalCoroutinesApi
+ override fun collapseShadeForActivityStart() {
+ if (shadeInteractor.isAnyExpanded.value) {
+ animateCollapseShadeForcedDelayed()
+ } else {
+ runPostCollapseActions()
+ }
+ }
+
+ override fun postAnimateCollapseShade() {
+ animateCollapseShade()
+ }
+
+ override fun postAnimateForceCollapseShade() {
+ animateCollapseShadeForced()
+ }
+
+ override fun postAnimateExpandQs() {
+ expandToQs()
+ }
+
+ override fun postOnShadeExpanded(action: Runnable) {
+ // TODO verify that clicking "reply" in a work profile notification launches the app
+ // TODO verify that there's not a way to replace and deprecate this method
+ scope.launch {
+ shadeInteractor.isAnyFullyExpanded.first { it }
+ action.run()
+ }
+ }
+
+ override fun makeExpandedInvisible() {
+ // Do nothing
+ }
+
+ override fun makeExpandedVisible(force: Boolean) {
+ // Do nothing
+ }
+
+ override fun isExpandedVisible(): Boolean {
+ return sceneInteractor.desiredScene.value.key != SceneKey.Gone
+ }
+
+ override fun onStatusBarTouch(event: MotionEvent) {
+ // The only call to this doesn't happen with KeyguardShadeMigrationNssl enabled
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index c057147..fc2c3ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -53,6 +53,20 @@
@Provides
@SysUISingleton
+ fun provideShadeController(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeControllerSceneImpl>,
+ sceneContainerOff: Provider<ShadeControllerImpl>
+ ): ShadeController {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
fun provideShadeAnimationInteractor(
sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
@@ -79,8 +93,4 @@
abstract fun bindsShadeViewController(
notificationPanelViewController: NotificationPanelViewController
): ShadeViewController
-
- @Binds
- @SysUISingleton
- abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 31a4de4..43ede2a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -29,7 +29,7 @@
val isShadeEnabled: StateFlow<Boolean>
/** Whether either the shade or QS is fully expanded. */
- val isAnyFullyExpanded: Flow<Boolean>
+ val isAnyFullyExpanded: StateFlow<Boolean>
/** Whether the Shade is fully expanded. */
val isShadeFullyExpanded: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6defbcf..55dd674 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -34,7 +34,7 @@
override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean
override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
- override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+ override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 6407b5a..a71cf95 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -55,12 +55,20 @@
private val baseShadeInteractor: BaseShadeInteractor,
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
- disableFlagsRepository.disableFlags
- .map { it.isShadeEnabled() }
+ combine(
+ deviceProvisioningRepository.isFactoryResetProtectionActive,
+ disableFlagsRepository.disableFlags,
+ ) { isFrpActive, isDisabledByFlags ->
+ isDisabledByFlags.isShadeEnabled() && !isFrpActive
+ }
+ .distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
- override val isAnyFullyExpanded: Flow<Boolean> =
- anyExpansion.map { it >= 1f }.distinctUntilChanged()
+ override val isAnyFullyExpanded: StateFlow<Boolean> =
+ anyExpansion
+ .map { it >= 1f }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
override val isShadeFullyExpanded: Flow<Boolean> =
baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
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 fc84973..28d4457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -35,7 +35,6 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Looper;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -61,7 +60,6 @@
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -73,6 +71,7 @@
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -85,6 +84,8 @@
import dalvik.annotation.optimization.NeverCompile;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -99,8 +100,6 @@
import javax.inject.Inject;
-import kotlin.Unit;
-
/** Platform implementation of the network controller. **/
@SysUISingleton
public class NetworkControllerImpl extends BroadcastReceiver
@@ -350,7 +349,7 @@
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
mUserTracker = userTracker;
- mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+ mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 2d5afd5..3cdb2cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -21,8 +21,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -53,14 +51,11 @@
*/
private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
- private final FeatureFlags mFeatureFlags;
-
@Inject
public GroupExpansionManagerImpl(DumpManager dumpManager,
- GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) {
+ GroupMembershipManager groupMembershipManager) {
mDumpManager = dumpManager;
mGroupMembershipManager = groupMembershipManager;
- mFeatureFlags = featureFlags;
}
/**
@@ -86,10 +81,8 @@
};
public void attach(NotifPipeline pipeline) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- mDumpManager.registerDumpable(this);
- pipeline.addOnBeforeRenderListListener(mNotifTracker);
- }
+ mDumpManager.registerDumpable(this);
+ pipeline.addOnBeforeRenderListListener(mNotifTracker);
}
@Override
@@ -105,8 +98,7 @@
@Override
public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)
- && entry.getParent() == null) {
+ if (entry.getParent() == null) {
if (expanded) {
throw new IllegalArgumentException("Cannot expand group that is not attached");
} else {
@@ -124,7 +116,7 @@
}
// Only notify listeners if something changed.
- if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) {
+ if (changed) {
sendOnGroupExpandedChange(entry, expanded);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index cb79353..da12479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,8 +22,6 @@
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,25 +36,17 @@
*/
@SysUISingleton
public class GroupMembershipManagerImpl implements GroupMembershipManager {
- FeatureFlagsClassic mFeatureFlags;
-
@Inject
- public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) {
- mFeatureFlags = featureFlags;
- }
+ public GroupMembershipManagerImpl() {}
@Override
public boolean isGroupSummary(@NonNull NotificationEntry entry) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- if (entry.getParent() == null) {
- // The entry is not attached, so it doesn't count.
- return false;
- }
- // If entry is a summary, its parent is a GroupEntry with summary = entry.
- return entry.getParent().getSummary() == entry;
- } else {
- return getGroupSummary(entry) == entry;
+ if (entry.getParent() == null) {
+ // The entry is not attached, so it doesn't count.
+ return false;
}
+ // If entry is a summary, its parent is a GroupEntry with summary = entry.
+ return entry.getParent().getSummary() == entry;
}
@Nullable
@@ -70,12 +60,8 @@
@Override
public boolean isChildInGroup(@NonNull NotificationEntry entry) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- // An entry is a child if it's not a summary or top level entry, but it is attached.
- return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
- } else {
- return !isTopLevelEntry(entry);
- }
+ // An entry is a child if it's not a summary or top level entry, but it is attached.
+ return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index aca8b64..342828c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -6,6 +6,7 @@
import android.net.Uri
import android.os.Handler
import android.os.HandlerExecutor
+import android.os.HandlerThread
import android.os.UserHandle
import android.provider.Settings
import com.android.keyguard.KeyguardUpdateMonitor
@@ -87,6 +88,7 @@
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
private val onStateChangedListeners = ListenerSet<Consumer<String>>()
private var hideSilentNotificationsOnLockscreen: Boolean = false
+ private val handlerThread: HandlerThread = HandlerThread("KeyguardNotificationVis")
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
@@ -154,7 +156,9 @@
notifyStateChanged("onStatusBarUpcomingStateChanged")
}
})
- userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
+ handlerThread.start()
+ userTracker.addCallback(userTrackerCallback,
+ HandlerExecutor(handlerThread.getThreadHandler()))
}
override fun addOnStateChangedListener(listener: Consumer<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 4349b3b..c6832bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -247,6 +247,9 @@
public void setUpWithContainer(NotificationListContainer listContainer) {
mListContainer = listContainer;
+ if (mLogging) {
+ mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ }
}
@Override
@@ -294,7 +297,9 @@
lockscreen = mLockscreen != null && mLockscreen;
}
mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
- mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ if (mListContainer != null) {
+ mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ }
// Sometimes, the transition from lockscreenOrShadeVisible=false ->
// lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
// events. Hence generate one ourselves to guarantee that we're reporting visible
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
index d10b556..8bc8e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -22,11 +22,9 @@
import android.view.LayoutInflater
import android.view.View
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
-import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import javax.inject.Named
/**
* Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
@@ -37,8 +35,7 @@
constructor(
@Assisted private val row: ExpandableNotificationRow,
@Assisted @InflationFlag val layoutType: Int,
- @Named(NOTIF_REMOTEVIEWS_FACTORIES)
- private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+ private val notifRemoteViewsFactoryContainer: NotifRemoteViewsFactoryContainer
) : LayoutInflater.Factory2 {
override fun onCreateView(
@@ -49,7 +46,7 @@
): View? {
var handledFactory: NotifRemoteViewsFactory? = null
var result: View? = null
- for (layoutFactory in remoteViewsFactories) {
+ for (layoutFactory in notifRemoteViewsFactoryContainer.factories) {
layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run {
check(handledFactory == null) {
"$layoutFactory tries to produce name:$name with type:$layoutType. " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
new file mode 100644
index 0000000..99177c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.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.systemui.statusbar.notification.row
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+interface NotifRemoteViewsFactoryContainer {
+ val factories: Set<NotifRemoteViewsFactory>
+}
+
+class NotifRemoteViewsFactoryContainerImpl
+@Inject
+constructor(
+ featureFlags: FeatureFlags,
+ precomputedTextViewFactory: PrecomputedTextViewFactory,
+ bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
+ callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+) : NotifRemoteViewsFactoryContainer {
+ override val factories: Set<NotifRemoteViewsFactory> = buildSet {
+ add(precomputedTextViewFactory)
+ if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+ add(bigPictureLayoutInflaterFactory)
+ }
+ if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
+ add(callLayoutSetDataAsyncFactory)
+ }
+ }
+}
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 46ddba4..200a08a 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
@@ -17,26 +17,15 @@
package com.android.systemui.statusbar.notification.row;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import dagger.Binds;
import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.inject.Named;
/**
* Dagger Module containing notification row and view inflation implementations.
*/
@Module
public abstract class NotificationRowModule {
- public static final String NOTIF_REMOTEVIEWS_FACTORIES =
- "notif_remoteviews_factories";
/**
* Provides notification row content binder instance.
@@ -54,24 +43,11 @@
public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
NotifRemoteViewCacheImpl cacheImpl);
- /** Provides view factories to be inflated in notification content. */
- @Provides
- @ElementsIntoSet
- @Named(NOTIF_REMOTEVIEWS_FACTORIES)
- static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
- FeatureFlags featureFlags,
- PrecomputedTextViewFactory precomputedTextViewFactory,
- BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory,
- CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
- ) {
- final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
- replacementFactories.add(precomputedTextViewFactory);
- if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
- replacementFactories.add(bigPictureLayoutInflaterFactory);
- }
- if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
- replacementFactories.add(callLayoutSetDataAsyncFactory);
- }
- return replacementFactories;
- }
+ /**
+ * Provides notification remote view factory container
+ */
+ @Binds
+ @SysUISingleton
+ public abstract NotifRemoteViewsFactoryContainer provideNotifRemoteViewsFactoryContainer(
+ NotifRemoteViewsFactoryContainerImpl containerImpl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 63194c3..8a56da3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -797,18 +797,7 @@
}
}
if (dismissShade) {
- if (
- shadeControllerLazy.get().isExpandedVisible &&
- !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
- ) {
- shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
- } else {
- // Do it after DismissAction has been processed to conserve the
- // needed ordering.
- postOnUiThread {
- shadeControllerLazy.get().runPostCollapseRunnables()
- }
- }
+ shadeControllerLazy.get().collapseShadeForActivityStart()
}
return deferred
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 20d1fff..a2d8d15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -28,6 +28,8 @@
import android.icu.text.DateTimePatternGenerator;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -48,11 +50,11 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -106,6 +108,7 @@
private final int mAmPmStyle;
private boolean mShowSeconds;
private Handler mSecondsHandler;
+ private HandlerThread mHandlerThread;
// Fields to cache the width so the clock remains at an approximately constant width
private int mCharsAtCurrentWidth = -1;
@@ -146,6 +149,8 @@
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
mUserTracker = Dependency.get(UserTracker.class);
+ mHandlerThread = new HandlerThread("Clock");
+ mHandlerThread.start();
setIncludeFontPadding(false);
}
@@ -205,7 +210,8 @@
Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_HIDE_LIST);
mCommandQueue.addCallback(this);
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mCurrentUserId = mUserTracker.getUserId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b7d8ee3..a7440d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -21,6 +21,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -51,6 +53,7 @@
private final UserTracker mUserTracker;
private AlarmManager mAlarmManager;
private AlarmManager.AlarmClockInfo mNextAlarm;
+ private HandlerThread mHandlerThread;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -75,7 +78,10 @@
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mHandlerThread = new HandlerThread("NextAlarmControllerImpl");
+ mHandlerThread.start();
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
updateNextAlarm();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 9f4a906..6a6efbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -157,7 +157,7 @@
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
onUserSwitched(mUserTracker.getUserId());
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 2ed9d15..0bc0e88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -36,9 +36,9 @@
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
@@ -66,11 +66,11 @@
/**
*/
@Inject
- public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+ public UserInfoControllerImpl(Context context, @Background Executor bgExecutor,
UserTracker userTracker) {
mContext = context;
mUserTracker = userTracker;
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, bgExecutor);
IntentFilter profileFilter = new IntentFilter();
profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index df210b0..f0b4930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,6 +82,7 @@
private volatile int mZenMode;
private long mZenUpdateTime;
private NotificationManager.Policy mConsolidatedNotificationPolicy;
+ private HandlerThread mHandlerThread;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -133,6 +135,8 @@
}
}
};
+ mHandlerThread = new HandlerThread("ZenModeControllerImpl");
+ mHandlerThread.start();
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
updateZenMode(getModeSettingValueFromProvider());
@@ -143,7 +147,8 @@
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
- mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
// This registers the alarm broadcast receiver for the current user
mUserChangedCallback.onUserChanged(getCurrentUser(), context);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50..77518db 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -480,7 +480,7 @@
return;
}
- mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserTrackerCallback, mBgExecutor);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 550a65c..f5b4d17 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -26,6 +26,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
@@ -38,11 +39,11 @@
import com.android.internal.util.ArrayUtils;
import com.android.systemui.DejankUtils;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -98,6 +99,7 @@
private UserTracker.Callback mCurrentUserTracker;
private UserTracker mUserTracker;
private final ComponentName mTunerComponent;
+ private HandlerThread mHandlerThread;
/**
*/
@@ -117,7 +119,8 @@
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mTunerComponent = new ComponentName(mContext, TunerActivity.class);
-
+ mHandlerThread = new HandlerThread("TunerServiceImpl");
+ mHandlerThread.start();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
mCurrentUser = user.getUserHandle().getIdentifier();
if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
@@ -135,7 +138,7 @@
}
};
mUserTracker.addCallback(mCurrentUserTracker,
- new HandlerExecutor(mainHandler));
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d..74e1339 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -190,7 +190,7 @@
}
}
- tracker.addCallback(callback, mainDispatcher.asExecutor())
+ tracker.addCallback(callback, backgroundDispatcher.asExecutor())
send(currentSelectionStatus)
awaitClose { tracker.removeCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801ae..7c6ad23 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -32,6 +32,8 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.util.Log;
import android.view.Display;
@@ -125,6 +127,7 @@
private final DisplayTracker mDisplayTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
private final Executor mSysUiMainExecutor;
+ private HandlerThread mHandlerThread;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
// avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference
@@ -206,6 +209,8 @@
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
mSysUiMainExecutor = sysUiMainExecutor;
+ mHandlerThread = new HandlerThread("WMShell");
+ mHandlerThread.start();
}
@Override
@@ -219,7 +224,8 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
// Subscribe to user changes
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d06457b..be06cc5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -121,7 +121,6 @@
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -930,7 +929,8 @@
@Test
public void trustAgentHasTrust() {
// WHEN user has trust
- givenSelectedUserCanSkipBouncerFromTrustedState();
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
// THEN user is considered as "having trust" and bouncer can be skipped
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -954,7 +954,8 @@
@Test
public void trustAgentHasTrust_fingerprintLockout() {
// GIVEN user has trust
- givenSelectedUserCanSkipBouncerFromTrustedState();
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
mSelectedUserInteractor.getSelectedUserId()));
@@ -2015,43 +2016,6 @@
}
@Test
- public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
- mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-
- // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
- // will trigger updateBiometricListeningState();
- clearInvocations(mFingerprintManager);
- mKeyguardUpdateMonitor.resetBiometricListeningState();
-
- // GIVEN the user can skip the bouncer
- givenSelectedUserCanSkipBouncerFromTrustedState();
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
- mTestableLooper.processAllMessages();
-
- // WHEN verify authenticate runs
- verifyFingerprintAuthenticateCall();
- }
-
- @Test
- public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
- mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
- // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
- // will trigger updateBiometricListeningState();
- clearInvocations(mFingerprintManager);
- mKeyguardUpdateMonitor.resetBiometricListeningState();
-
- // GIVEN the user can skip the bouncer
- givenSelectedUserCanSkipBouncerFromTrustedState();
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
- mTestableLooper.processAllMessages();
-
- // WHEN verify detect runs
- verifyFingerprintDetectCall();
- }
-
- @Test
public void testFingerprintSensorProperties() throws RemoteException {
mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
new ArrayList<>());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index e0c6bba..0ba9abe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -32,12 +32,16 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
@@ -59,7 +63,6 @@
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Rule
@@ -76,7 +79,8 @@
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class BackActionInteractorTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
private val executor = FakeExecutor(FakeSystemClock())
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -105,6 +109,8 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
index e4432f3..0636831 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -30,13 +30,11 @@
import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
import android.testing.AndroidTestingRunner
-import android.view.View
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.animation.LaunchableView
import com.android.systemui.appops.AppOpsController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.logging.PrivacyLogger
@@ -206,10 +204,7 @@
@Test
fun testShowDialogShowsDialogWithView() {
val parent = LinearLayout(context)
- val view =
- object : View(context), LaunchableView {
- override fun setShouldBlockVisibilityChanges(block: Boolean) {}
- }
+ val view = OngoingPrivacyChip(context)
parent.addView(view)
val usage = createMockPermGroupUsage()
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index fa02e8c..f98b68f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -173,7 +173,7 @@
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
@@ -186,7 +186,7 @@
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
@@ -207,7 +207,7 @@
captor.value.onClick(privacyChip)
verify(privacyDialogController, never()).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index b24b877..c0ef50f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1069,6 +1069,22 @@
assertThat(mInternetDialogController.mCallback).isNull();
}
+ @Test
+ public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+ }
+
+ @Test
+ public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 45f6869..a6e240b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -22,6 +22,7 @@
import android.testing.ViewUtils
import android.view.MotionEvent
import android.view.View
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -61,6 +62,7 @@
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var powerManager: PowerManager
+ private lateinit var parentView: FrameLayout
private lateinit var containerView: View
private lateinit var testableLooper: TestableLooper
@@ -225,15 +227,38 @@
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
+ @Test
+ fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
+ // Communal is open.
+ communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+ initAndAttachContainerView()
+ testableLooper.processAllMessages()
+
+ // Touch events are intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+
+ // Container view disposed.
+ underTest.disposeView()
+
+ // Touch events are not intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ }
+
private fun initAndAttachContainerView() {
containerView = View(context)
+
+ parentView = FrameLayout(context)
+ parentView.addView(containerView)
+
// Make view clickable so that dispatchTouchEvent returns true.
containerView.isClickable = true
underTest.initView(containerView)
// Attach the view so that flows start collecting.
- ViewUtils.attachView(containerView)
+ ViewUtils.attachView(parentView)
// Give the view a size so that determining if a touch starts at the right edge works.
+ parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index a11839c..6681cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -74,10 +74,12 @@
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -480,6 +482,7 @@
}
@Test
+ @Ignore("b/321332798")
fun setsUpCommunalHubLayout_whenFlagEnabled() {
if (!isComposeAvailable()) {
return
@@ -511,6 +514,8 @@
}
whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
+ whenever(mGlanceableHubContainerController.enabledState())
+ .thenReturn(MutableStateFlow(false))
val mockCommunalPlaceholder = mock(View::class.java)
val fakeViewIndex = 20
@@ -520,8 +525,7 @@
underTest.setupCommunalHubLayout()
- // No adding or removing of views occurs.
- verify(view, times(0)).removeView(mockCommunalPlaceholder)
+ // No adding of views occurs.
verify(view, times(0)).addView(any(), eq(fakeViewIndex))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index c4911a4..cc79ca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,15 +22,18 @@
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
-import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.assist.AssistManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -64,6 +67,8 @@
private val executor = FakeExecutor(FakeSystemClock())
private val testDispatcher = StandardTestDispatcher()
private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -84,12 +89,14 @@
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
WindowRootViewVisibilityInteractor(
- TestScopeProvider.getTestScope(),
+ testScope,
WindowRootViewVisibilityRepository(iStatusBarService, executor),
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
- ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 65697b73..36f643a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -24,7 +24,6 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -53,8 +52,8 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -78,28 +77,22 @@
private lateinit var coordinator: ConversationCoordinator
- private val featureFlags = FakeFeatureFlagsClassic()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- coordinator = ConversationCoordinator(
- peopleNotificationIdentifier,
- conversationIconManager,
- HighPriorityProvider(
+ coordinator =
+ ConversationCoordinator(
peopleNotificationIdentifier,
- GroupMembershipManagerImpl(featureFlags)
- ),
- headerController
- )
+ conversationIconManager,
+ HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
+ headerController
+ )
whenever(channel.isImportantConversation).thenReturn(true)
coordinator.attach(pipeline)
// capture arguments:
- promoter = withArgCaptor {
- verify(pipeline).addPromoter(capture())
- }
+ promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) }
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
@@ -111,10 +104,10 @@
entry = NotificationEntryBuilder().setChannel(channel).build()
val section = NotifSection(peopleAlertingSectioner, 0)
- entryA = NotificationEntryBuilder().setChannel(channel)
- .setSection(section).setTag("A").build()
- entryB = NotificationEntryBuilder().setChannel(channel)
- .setSection(section).setTag("B").build()
+ entryA =
+ NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
+ entryB =
+ NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
}
@Test
@@ -129,11 +122,12 @@
val altChildA = NotificationEntryBuilder().setTag("A").build()
val altChildB = NotificationEntryBuilder().setTag("B").build()
val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
- val groupEntry = GroupEntryBuilder()
- .setParent(GroupEntry.ROOT_ENTRY)
- .setSummary(summary)
- .setChildren(listOf(entry, altChildA, altChildB))
- .build()
+ val groupEntry =
+ GroupEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setSummary(summary)
+ .setChildren(listOf(entry, altChildA, altChildB))
+ .build()
assertTrue(promoter.shouldPromoteToTopLevel(entry))
assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
@@ -146,41 +140,42 @@
@Test
fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
// GIVEN
- val alertingEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_DEFAULT).build()
+ val alertingEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// put alerting people notifications in this section
assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
- }
+ }
@Test
fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
// GIVEN
- val silentEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_LOW).build()
+ val silentEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// THEN put silent people notifications in this section
assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
// People Alerting sectioning happens before the silent one.
- // It claims high important conversations and rest of conversations will be considered as silent.
+ // It claims high important conversations and rest of conversations will be considered as
+ // silent.
assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
}
@Test
fun testNotInPeopleSection() {
// GIVEN
- val entry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_LOW).build()
- val importantEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_HIGH).build()
+ val entry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+ val importantEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
- .thenReturn(TYPE_NON_PERSON)
+ .thenReturn(TYPE_NON_PERSON)
whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
- .thenReturn(TYPE_NON_PERSON)
+ .thenReturn(TYPE_NON_PERSON)
// THEN - only put people notification either silent or alerting
assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
@@ -190,19 +185,23 @@
@Test
fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
// GIVEN
- val altChildA = NotificationEntryBuilder().setTag("A")
- .setImportance(IMPORTANCE_DEFAULT).build()
- val altChildB = NotificationEntryBuilder().setTag("B")
- .setImportance(IMPORTANCE_LOW).build()
- val summary = NotificationEntryBuilder().setId(2)
- .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
- val groupEntry = GroupEntryBuilder()
+ val altChildA =
+ NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
+ val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
+ val summary =
+ NotificationEntryBuilder()
+ .setId(2)
+ .setImportance(IMPORTANCE_LOW)
+ .setChannel(channel)
+ .build()
+ val groupEntry =
+ GroupEntryBuilder()
.setParent(GroupEntry.ROOT_ENTRY)
.setSummary(summary)
.setChildren(listOf(altChildA, altChildB))
.build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// THEN
assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
deleted file mode 100644
index c1ffa64..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class GroupMembershipManagerTest : SysuiTestCase() {
- private lateinit var gmm: GroupMembershipManagerImpl
-
- private val featureFlags = FakeFeatureFlagsClassic()
-
- @Before
- fun setUp() {
- gmm = GroupMembershipManagerImpl(featureFlags)
- }
-
- @Test
- fun testIsChildInGroup_topLevel() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse()
- }
-
- @Test
- fun testIsChildInGroup_noParent_old() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(gmm.isChildInGroup(noParentEntry)).isTrue()
- }
-
- @Test
- fun testIsChildInGroup_noParent_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
- }
- @Test
- fun testIsChildInGroup_summary_old() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isChildInGroup(summary)).isTrue()
- }
-
- @Test
- fun testIsChildInGroup_summary_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isChildInGroup(summary)).isFalse()
- }
-
- @Test
- fun testIsChildInGroup_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val childEntry = NotificationEntryBuilder().build()
- assertThat(gmm.isChildInGroup(childEntry)).isTrue()
- }
-
- @Test
- fun testIsGroupSummary_topLevelEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.isGroupSummary(entry)).isFalse()
- }
-
- @Test
- fun testIsGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isGroupSummary(summary)).isTrue()
- }
-
- @Test
- fun testIsGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
- assertThat(gmm.isGroupSummary(entry)).isFalse()
- }
-
- @Test
- fun testGetGroupSummary_topLevelEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.getGroupSummary(entry)).isNull()
- }
-
- @Test
- fun testGetGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
- }
-
- @Test
- fun testGetGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
- assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index ff02ef3..b01281c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -42,9 +42,9 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
@@ -110,7 +110,8 @@
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
new NotificationPanelLoggerFake();
- private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
@@ -133,7 +134,9 @@
mKeyguardRepository,
mHeadsUpManager,
mPowerInteractor,
- mActiveNotificationsInteractor);
+ mActiveNotificationsInteractor,
+ mKosmos.getFakeSceneContainerFlags(),
+ () -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
index 3f7fc97..fd41921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -62,7 +62,7 @@
fun onCreateView_noMatchingViewForName_returnNull() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate an ImageView for the expanded layout
val createdView = inflaterFactory.onCreateView("ImageView", context, attrs)
@@ -78,7 +78,7 @@
fun onCreateView_noMatchingViewForLayoutType_returnNull() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_HEADS_UP
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate a TextView for the heads-up layout
val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -94,7 +94,7 @@
fun onCreateView_matchingViews_returnReplacementView() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate a TextView for the expanded layout
val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -110,7 +110,7 @@
// GIVEN we have two factories that replaces TextViews in expanded layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
inflaterFactory =
- NotifLayoutInflaterFactory(
+ createNotifLayoutInflaterFactory(
row,
layoutType,
setOf(
@@ -147,4 +147,18 @@
null
}
}
+
+ private fun createNotifLayoutInflaterFactory(
+ row: ExpandableNotificationRow,
+ layoutType: Int,
+ notifRemoteViewsFactoryContainer: Set<NotifRemoteViewsFactory>
+ ) =
+ NotifLayoutInflaterFactory(
+ row,
+ layoutType,
+ object : NotifRemoteViewsFactoryContainer {
+ override val factories: Set<NotifRemoteViewsFactory> =
+ notifRemoteViewsFactoryContainer
+ }
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 71613ed..6549193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -69,9 +69,9 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -124,7 +124,8 @@
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
- private TestScope mTestScope = TestScopeProvider.getTestScope();
+ private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private TestScope mTestScope = mKosmos.getTestScope();
private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private TestableLooper mTestableLooper;
@@ -182,7 +183,10 @@
new FakeKeyguardRepository(),
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
- mActiveNotificationsInteractor);
+ mActiveNotificationsInteractor,
+ mKosmos.getFakeSceneContainerFlags(),
+ () -> mKosmos.getSceneInteractor()
+ );
mGutsManager = new NotificationGutsManager(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
new file mode 100644
index 0000000..446b9d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.R
+import android.app.AppOpsManager
+import android.app.INotificationManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
+import android.content.pm.launcherApps
+import android.graphics.Color
+import android.os.Binder
+import android.os.Handler
+import android.os.userManager
+import android.provider.Settings
+import android.service.notification.NotificationListenerService.Ranking
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.util.ArraySet
+import android.view.View
+import android.view.accessibility.accessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.metricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.shade.shadeControllerSceneImpl
+import com.android.systemui.statusbar.NotificationEntryHelper
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import kotlin.test.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.invocation.InvocationOnMock
+
+/** Tests for [NotificationGutsManager] with the scene container enabled. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
+ private val testNotificationChannel =
+ NotificationChannel(
+ TEST_CHANNEL_ID,
+ TEST_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var handler: Handler
+ private lateinit var helper: NotificationTestHelper
+ private lateinit var gutsManager: NotificationGutsManager
+ private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
+
+ private val metricsLogger = kosmos.metricsLogger
+ private val deviceProvisionedController = kosmos.deviceProvisionedController
+ private val accessibilityManager = kosmos.accessibilityManager
+ private val mBarService = kosmos.statusBarService
+ private val launcherApps = kosmos.launcherApps
+ private val shadeController = kosmos.shadeControllerSceneImpl
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+ private val statusBarStateController = kosmos.statusBarStateController
+ private val headsUpManager = kosmos.headsUpManager
+ private val activityStarter = kosmos.activityStarter
+ private val userManager = kosmos.userManager
+ private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+
+ @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback
+ @Mock private lateinit var presenter: NotificationPresenter
+ @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter
+ @Mock private lateinit var notificationListContainer: NotificationListContainer
+ @Mock
+ private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener
+ @Mock private lateinit var highPriorityProvider: HighPriorityProvider
+ @Mock private lateinit var notificationManager: INotificationManager
+ @Mock private lateinit var shortcutManager: ShortcutManager
+ @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+ @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+ @Mock private lateinit var contextTracker: UserContextProvider
+ @Mock private lateinit var bubblesManager: BubblesManager
+ @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+ @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+ sceneContainerFlags.enabled = true
+ testableLooper = TestableLooper.get(this)
+ allowTestableLooperAsMainThread()
+ handler = Handler.createAsync(testableLooper.getLooper())
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+ windowRootViewVisibilityInteractor =
+ WindowRootViewVisibilityInteractor(
+ testScope.backgroundScope,
+ WindowRootViewVisibilityRepository(mBarService, executor),
+ FakeKeyguardRepository(),
+ headsUpManager,
+ create().powerInteractor,
+ activeNotificationsInteractor,
+ sceneContainerFlags,
+ { sceneInteractor },
+ )
+ gutsManager =
+ NotificationGutsManager(
+ mContext,
+ handler,
+ handler,
+ javaAdapter,
+ accessibilityManager,
+ highPriorityProvider,
+ notificationManager,
+ userManager,
+ peopleSpaceWidgetManager,
+ launcherApps,
+ shortcutManager,
+ channelEditorDialogController,
+ contextTracker,
+ assistantFeedbackController,
+ Optional.of(bubblesManager),
+ UiEventLoggerFake(),
+ onUserInteractionCallback,
+ shadeController,
+ windowRootViewVisibilityInteractor,
+ notificationLockscreenUserManager,
+ statusBarStateController,
+ mBarService,
+ deviceProvisionedController,
+ metricsLogger,
+ headsUpManager,
+ activityStarter
+ )
+ gutsManager.setUpWithPresenter(
+ presenter,
+ notificationListContainer,
+ onSettingsClickListener
+ )
+ gutsManager.setNotificationActivityStarter(notificationActivityStarter)
+ gutsManager.start()
+ }
+
+ @Test
+ fun testOpenAndCloseGuts() {
+ val guts = Mockito.spy(NotificationGuts(mContext))
+ Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+ ->
+ handler.post((invocation.arguments[0] as Runnable))
+ null
+ }
+
+ // Test doesn't support animation since the guts view is not attached.
+ Mockito.doNothing()
+ .`when`(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ val realRow = createTestNotificationRow()
+ val menuItem = createTestMenuItem(realRow)
+ val row = Mockito.spy(realRow)
+ Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+ Mockito.`when`(row.guts).thenReturn(guts)
+ Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+ assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
+ testableLooper.processAllMessages()
+ verify(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ verify(headsUpManager).setGutsShown(realRow!!.entry, true)
+ assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
+ gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
+ verify(guts)
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+ verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any())
+ testableLooper.processAllMessages()
+ verify(headsUpManager).setGutsShown(realRow.entry, false)
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_visible_gutsNotClosed() {
+ // First, start out lockscreen or shade as not visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+ val guts = Mockito.mock(NotificationGuts::class.java)
+ gutsManager.exposedGuts = guts
+
+ // WHEN the lockscreen or shade becomes visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the guts are not closed
+ verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any())
+ verify(guts, Mockito.never())
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_notVisible_gutsClosed() {
+ // First, start out lockscreen or shade as visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+ val guts = Mockito.mock(NotificationGuts::class.java)
+ gutsManager.exposedGuts = guts
+
+ // WHEN the lockscreen or shade is no longer visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the guts are closed
+ verify(guts).removeCallbacks(ArgumentMatchers.any())
+ verify(guts)
+ .closeControls(
+ /* leavebehinds= */ ArgumentMatchers.eq(true),
+ /* controls= */ ArgumentMatchers.eq(true),
+ /* x= */ ArgumentMatchers.anyInt(),
+ /* y= */ ArgumentMatchers.anyInt(),
+ /* force= */ ArgumentMatchers.eq(true)
+ )
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_notVisible_listContainerReset() {
+ // First, start out lockscreen or shade as visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+
+ // WHEN the lockscreen or shade is no longer visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the list container is reset
+ verify(notificationListContainer)
+ .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ fun testChangeDensityOrFontScale() {
+ val guts = Mockito.spy(NotificationGuts(mContext))
+ Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+ ->
+ handler.post((invocation.arguments[0] as Runnable))
+ null
+ }
+
+ // Test doesn't support animation since the guts view is not attached.
+ Mockito.doNothing()
+ .`when`(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ val realRow = createTestNotificationRow()
+ val menuItem = createTestMenuItem(realRow)
+ val row = Mockito.spy(realRow)
+ Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+ Mockito.`when`(row.guts).thenReturn(guts)
+ Mockito.doNothing().`when`(row).ensureGutsInflated()
+ val realEntry = realRow!!.entry
+ val entry = Mockito.spy(realEntry)
+ Mockito.`when`(entry.row).thenReturn(row)
+ Mockito.`when`(entry.getGuts()).thenReturn(guts)
+ Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+ testableLooper.processAllMessages()
+ verify(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+
+ // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
+ verify(row).setGutsView(ArgumentMatchers.any())
+ row.onDensityOrFontScaleChanged()
+ gutsManager.onDensityOrFontScaleChanged(entry)
+ testableLooper.processAllMessages()
+ gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
+ verify(guts)
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+
+ // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
+ verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_mic() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_mic() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_mic_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_mic_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_highPriority() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ val entry = row.entry
+ NotificationEntryHelper.modifyRanking(entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .setImportance(NotificationManager.IMPORTANCE_HIGH)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+ val statusBarNotification = entry.sbn
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(true), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ val statusBarNotification = row.entry.sbn
+ val entry = row.entry
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(false), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_withInitialAction() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ val statusBarNotification = row.entry.sbn
+ val entry = row.entry
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(false), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ private fun createTestNotificationRow(): ExpandableNotificationRow? {
+ val nb =
+ Notification.Builder(mContext, testNotificationChannel.id)
+ .setContentTitle("foo")
+ .setColorized(true)
+ .setColor(Color.RED)
+ .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+ .setSmallIcon(R.drawable.sym_def_app_icon)
+ return try {
+ val row = helper.createRow(nb.build())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setChannel(testNotificationChannel)
+ .build()
+ row
+ } catch (e: Exception) {
+ org.junit.Assert.fail()
+ null
+ }
+ }
+
+ private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) {
+ val key =
+ if (isVisible) {
+ SceneKey.Lockscreen
+ } else {
+ SceneKey.Bouncer
+ }
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+
+ private fun createTestMenuItem(
+ row: ExpandableNotificationRow?
+ ): NotificationMenuRowPlugin.MenuItem {
+ val menuRow: NotificationMenuRowPlugin =
+ NotificationMenuRow(mContext, peopleNotificationIdentifier)
+ menuRow.createMenu(row, row!!.entry.sbn)
+ val menuItem = menuRow.getLongpressMenuItem(mContext)
+ Assert.assertNotNull(menuItem)
+ return menuItem
+ }
+
+ companion object {
+ private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b58a41c..457acd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -190,7 +190,7 @@
mWakefulnessLifecycle.dispatchFinishedWakingUp();
mThemeOverlayController.start();
- verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor));
+ verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mBgExecutor));
verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
eq(UserHandle.USER_ALL));
verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 20fa545..cccd908 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -50,4 +50,11 @@
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
+
+ private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState
+
+ fun setCommunalEnabledState(enabled: Boolean) {
+ _communalEnabledState.value = enabled
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 7cc5d6b..e13fa52 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -14,9 +14,66 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade
+import android.view.WindowManager
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.windowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-var Kosmos.shadeController by Kosmos.Fixture { mock<ShadeController>() }
+val Kosmos.shadeControllerSceneImpl by
+ Kosmos.Fixture {
+ ShadeControllerSceneImpl(
+ scope = applicationCoroutineScope,
+ shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
+ notificationStackScrollLayout = mock<NotificationStackScrollLayout>(),
+ deviceEntryInteractor = deviceEntryInteractor,
+ touchLog = mock<LogBuffer>(),
+ commandQueue = mock<CommandQueue>(),
+ statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
+ notificationShadeWindowController = mock<NotificationShadeWindowController>(),
+ assistManagerLazy = { mock<AssistManager>() },
+ )
+ }
+
+val Kosmos.shadeControllerImpl by
+ Kosmos.Fixture {
+ ShadeControllerImpl(
+ mock<CommandQueue>(),
+ fakeExecutor,
+ mock<LogBuffer>(),
+ windowRootViewVisibilityInteractor,
+ mock<KeyguardStateController>(),
+ statusBarStateController,
+ statusBarKeyguardViewManager,
+ mock<StatusBarWindowController>(),
+ deviceProvisionedController,
+ mock<NotificationShadeWindowController>(),
+ mock<WindowManager>(),
+ { mock<ShadeViewController>() },
+ { mock<AssistManager>() },
+ { mock<NotificationGutsManager>() },
+ )
+ }
+var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index dda7fad..4efcada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -52,32 +52,38 @@
return ge;
}
+ /** Sets the group key. */
public GroupEntryBuilder setKey(String key) {
mKey = key;
return this;
}
+ /** Sets the creation time. */
public GroupEntryBuilder setCreationTime(long creationTime) {
mCreationTime = creationTime;
return this;
}
+ /** Sets the parent entry of the group. */
public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
mParent = entry;
return this;
}
+ /** Sets the section the group belongs to. */
public GroupEntryBuilder setSection(@Nullable NotifSection section) {
mNotifSection = section;
return this;
}
+ /** Sets the group summary. */
public GroupEntryBuilder setSummary(
NotificationEntry summary) {
mSummary = summary;
return this;
}
+ /** Sets the group children. */
public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
mChildren.clear();
mChildren.addAll(children);
@@ -90,6 +96,7 @@
return this;
}
+ /** Get the group's internal children list. */
public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
return groupEntry.getRawChildren();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index d80ee75..cf800d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -23,6 +23,8 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
@@ -34,5 +36,7 @@
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
+ sceneInteractorProvider = { sceneInteractor },
+ sceneContainerFlags = sceneContainerFlags,
)
}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index f5e4af5..e33fff1 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -6,6 +6,9 @@
# Keep all feature flag implementations
class :feature_flags stubclass
+# Keep all sysprops generated code implementations
+class :sysprops stubclass
+
# Collections
class android.util.ArrayMap stubclass
class android.util.ArraySet stubclass
@@ -112,6 +115,12 @@
class android.os.PatternMatcher stubclass
class android.os.ParcelUuid stubclass
+# Logging related interfaces from modules-utils
+class com.android.internal.logging.InstanceId stubclass
+class com.android.internal.logging.InstanceIdSequence stubclass
+class com.android.internal.logging.UiEvent stubclass
+class com.android.internal.logging.UiEventLogger stubclass
+
# XML
class com.android.internal.util.XmlPullParserWrapper stubclass
class com.android.internal.util.XmlSerializerWrapper stubclass
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index eacdc2f..91c522e 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -19,8 +19,6 @@
import android.os.HandlerThread;
import android.os.Looper;
-import java.util.Objects;
-
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -31,6 +29,10 @@
public static void init(RavenwoodRule rule) {
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
+ android.os.SystemProperties.init$ravenwood(
+ rule.mSystemProperties.getValues(),
+ rule.mSystemProperties.getKeyReadablePredicate(),
+ rule.mSystemProperties.getKeyWritablePredicate());
com.android.server.LocalServices.removeAllServicesForTest();
@@ -49,7 +51,8 @@
com.android.server.LocalServices.removeAllServicesForTest();
- android.os.Process.reset$ravenwood();
+ android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
+ android.os.Process.reset$ravenwood();
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 53da8ba..dd442f0 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -62,6 +62,8 @@
boolean mProvideMainThread = false;
+ final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
public RavenwoodRule() {
}
@@ -98,6 +100,40 @@
return this;
}
+ /**
+ * Configure the given system property as immutable for the duration of the test.
+ * Read access to the key is allowed, and write access will fail. When {@code value} is
+ * {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadOnly(key);
+ return this;
+ }
+
+ /**
+ * Configure the given system property as mutable for the duration of the test.
+ * Both read and write access to the key is allowed, and its value will be reset between
+ * each test. When {@code value} is {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyMutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadWrite(key);
+ return this;
+ }
+
public RavenwoodRule build() {
return mRule;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 0000000..85ad4e4
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class RavenwoodSystemProperties {
+ private final Map<String, String> mValues = new HashMap<>();
+
+ /** Set of additional keys that should be considered readable */
+ private final Set<String> mKeyReadable = new HashSet<>();
+ private final Predicate<String> mKeyReadablePredicate = (key) -> {
+ final String root = getKeyRoot(key);
+
+ if (root.startsWith("debug.")) return true;
+
+ // This set is carefully curated to help identify situations where a test may
+ // accidentally depend on a default value of an obscure property whose owner hasn't
+ // decided how Ravenwood should behave.
+ if (root.startsWith("boot.")) return true;
+ if (root.startsWith("build.")) return true;
+ if (root.startsWith("product.")) return true;
+ if (root.startsWith("soc.")) return true;
+ if (root.startsWith("system.")) return true;
+
+ switch (key) {
+ case "gsm.version.baseband":
+ case "no.such.thing":
+ case "ro.bootloader":
+ case "ro.debuggable":
+ case "ro.hardware":
+ case "ro.hw_timeout_multiplier":
+ case "ro.odm.build.media_performance_class":
+ case "ro.treble.enabled":
+ case "ro.vndk.version":
+ return true;
+ }
+
+ return mKeyReadable.contains(key);
+ };
+
+ /** Set of additional keys that should be considered writable */
+ private final Set<String> mKeyWritable = new HashSet<>();
+ private final Predicate<String> mKeyWritablePredicate = (key) -> {
+ final String root = getKeyRoot(key);
+
+ if (root.startsWith("debug.")) return true;
+
+ return mKeyWritable.contains(key);
+ };
+
+ public RavenwoodSystemProperties() {
+ // TODO: load these values from build.prop generated files
+ setValueForPartitions("product.brand", "Android");
+ setValueForPartitions("product.device", "Ravenwood");
+ setValueForPartitions("product.manufacturer", "Android");
+ setValueForPartitions("product.model", "Ravenwood");
+ setValueForPartitions("product.name", "Ravenwood");
+
+ setValueForPartitions("product.cpu.abilist", "x86_64");
+ setValueForPartitions("product.cpu.abilist32", "");
+ setValueForPartitions("product.cpu.abilist64", "x86_64");
+
+ setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024");
+ setValueForPartitions("build.date.utc", "1704092400");
+ setValueForPartitions("build.id", "MAIN");
+ setValueForPartitions("build.tags", "dev-keys");
+ setValueForPartitions("build.type", "userdebug");
+ setValueForPartitions("build.version.all_codenames", "REL");
+ setValueForPartitions("build.version.codename", "REL");
+ setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101");
+ setValueForPartitions("build.version.known_codenames", "REL");
+ setValueForPartitions("build.version.release", "14");
+ setValueForPartitions("build.version.release_or_codename", "VanillaIceCream");
+ setValueForPartitions("build.version.sdk", "34");
+
+ setValue("ro.board.first_api_level", "1");
+ setValue("ro.product.first_api_level", "1");
+
+ setValue("ro.soc.manufacturer", "Android");
+ setValue("ro.soc.model", "Ravenwood");
+
+ setValue("ro.debuggable", "1");
+ }
+
+ Map<String, String> getValues() {
+ return new HashMap<>(mValues);
+ }
+
+ Predicate<String> getKeyReadablePredicate() {
+ return mKeyReadablePredicate;
+ }
+
+ Predicate<String> getKeyWritablePredicate() {
+ return mKeyWritablePredicate;
+ }
+
+ private static final String[] PARTITIONS = {
+ "bootimage",
+ "odm",
+ "product",
+ "system",
+ "system_ext",
+ "vendor",
+ "vendor_dlkm",
+ };
+
+ /**
+ * Set the given property for all possible partitions where it could be defined. For
+ * example, the value of {@code ro.build.type} is typically also mirrored under
+ * {@code ro.system.build.type}, etc.
+ */
+ private void setValueForPartitions(String key, String value) {
+ setValue("ro." + key, value);
+ for (String partition : PARTITIONS) {
+ setValue("ro." + partition + "." + key, value);
+ }
+ }
+
+ public void setValue(String key, Object value) {
+ final String valueString = (value == null) ? null : String.valueOf(value);
+ if ((valueString == null) || valueString.isEmpty()) {
+ mValues.remove(key);
+ } else {
+ mValues.put(key, valueString);
+ }
+ }
+
+ public void setAccessNone(String key) {
+ mKeyReadable.remove(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadOnly(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadWrite(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.add(key);
+ }
+
+ /**
+ * Return the "root" of the given property key, stripping away any modifier prefix such as
+ * {@code ro.} or {@code persist.}.
+ */
+ private static String getKeyRoot(String key) {
+ if (key.startsWith("ro.")) {
+ return key.substring(3);
+ } else if (key.startsWith("persist.")) {
+ return key.substring(8);
+ } else {
+ return key;
+ }
+ }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index ab2546b..5700f00 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,9 @@
# Only classes listed here can use the Ravenwood annotations.
com.android.internal.util.ArrayUtils
+com.android.internal.logging.MetricsLogger
+com.android.internal.logging.testing.FakeMetricsLogger
+com.android.internal.logging.testing.UiEventLoggerFake
com.android.internal.os.BatteryStatsHistory
com.android.internal.os.BatteryStatsHistory$TraceDelegate
com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -47,6 +50,7 @@
android.os.Binder
android.os.Binder$IdentitySupplier
android.os.Broadcaster
+android.os.Build
android.os.BundleMerger
android.os.ConditionVariable
android.os.FileUtils
@@ -65,8 +69,10 @@
android.os.Process
android.os.ServiceSpecificException
android.os.SystemClock
+android.os.SystemProperties
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
+android.os.Trace
android.os.UidBatteryConsumer
android.os.UidBatteryConsumer$Builder
android.os.UserHandle
@@ -126,6 +132,8 @@
android.content.ContentProvider
+android.metrics.LogMaker
+
com.android.server.LocalServices
com.android.server.power.stats.BatteryStatsImpl
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 993b254..44682e2 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -45,6 +45,13 @@
}
flag {
+ name: "fix_drag_pointer_when_ending_drag"
+ namespace: "accessibility"
+ description: "Send the correct pointer id when transitioning from dragging to delegating states."
+ bug: "300002193"
+}
+
+flag {
name: "pinch_zoom_zero_min_span"
namespace: "accessibility"
description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c418485..3086ce1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1466,8 +1466,11 @@
int policyFlags = mState.getLastReceivedPolicyFlags();
if (mState.isDragging()) {
// Send an event to the end of the drag gesture.
- mDispatcher.sendMotionEvent(
- event, ACTION_UP, rawEvent, ALL_POINTER_ID_BITS, policyFlags);
+ int pointerIdBits = ALL_POINTER_ID_BITS;
+ if (Flags.fixDragPointerWhenEndingDrag()) {
+ pointerIdBits = 1 << mDraggingPointerId;
+ }
+ mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
}
mState.startDelegating();
// Deliver all pointers to the view hierarchy.
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 b6e1140..2168cb2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -583,6 +583,11 @@
return associationInfo == null ? null : associationInfo.getDisplayName();
}
+ @Override // Binder call
+ public @NonNull List<String> getAllPersistentDeviceIds() {
+ return new ArrayList<>(mLocalService.getAllPersistentDeviceIds());
+ }
+
// Binder call
@Override
public boolean isValidVirtualDeviceId(int deviceId) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9375fb1..a54a48a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -214,6 +214,7 @@
"backup_flags_lib",
"policy_flags_lib",
"net_flags_lib",
+ "stats_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 52a5408..ca04e41 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -472,6 +472,8 @@
import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.stats.pull.StatsPullAtomService;
+import com.android.server.stats.pull.StatsPullAtomServiceInternal;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -1308,6 +1310,8 @@
*/
final BatteryStatsService mBatteryStatsService;
+ StatsPullAtomServiceInternal mStatsPullAtomServiceInternal;
+
/**
* Information about component usage
*/
@@ -16554,6 +16558,21 @@
final @ProcessCapability int capability) {
mBatteryStatsService.noteUidProcessState(uid, state);
mAppOpsService.updateUidProcState(uid, state, capability);
+ if (StatsPullAtomService.ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ try {
+ if (mStatsPullAtomServiceInternal == null) {
+ mStatsPullAtomServiceInternal = LocalServices.getService(
+ StatsPullAtomServiceInternal.class);
+ }
+ if (mStatsPullAtomServiceInternal != null) {
+ mStatsPullAtomServiceInternal.noteUidProcessState(uid, state);
+ } else {
+ Slog.d(TAG, "StatsPullAtomService not ready yet");
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception during logging uid proc state change event", e);
+ }
+ }
if (mTrackingAssociations) {
for (int i1=0, N1=mAssociations.size(); i1<N1; i1++) {
ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>> targetComponents
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 57c52c2..45f657d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3754,6 +3754,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
}
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 1f98aba..fb89b8e 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -102,6 +102,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onProcessDied(int pid, int uid) {
}
};
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fa5dbd2..f5c34a5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2852,6 +2852,7 @@
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
}
+ dispatchProcessStarted(app, pid);
checkSlow(app.getStartTime(), "startProcess: done updating pids map");
return true;
}
@@ -4977,6 +4978,22 @@
}
}
+ void dispatchProcessStarted(ProcessRecord app, int pid) {
+ int i = mProcessObservers.beginBroadcast();
+ while (i > 0) {
+ i--;
+ final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+ if (observer != null) {
+ try {
+ observer.onProcessStarted(pid, app.uid, app.info.uid,
+ app.info.packageName, app.processName);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mProcessObservers.finishBroadcast();
+ }
+
void dispatchProcessDied(int pid, int uid) {
int i = mProcessObservers.beginBroadcast();
while (i > 0) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index dc14c7a..7aafda5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -153,6 +153,7 @@
"machine_learning",
"mainline_modularization",
"mainline_sdk",
+ "make_pixel_haptics",
"media_audio",
"media_drm",
"media_reliability",
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 684d6a0..cdd147a 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -177,6 +177,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
}
};
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 99b45ec..cd295b5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1047,11 +1047,9 @@
private void initAudioHalBluetoothState() {
synchronized (mBluetoothAudioStateLock) {
mBluetoothScoOnApplied = false;
- AudioSystem.setParameters("BT_SCO=off");
mBluetoothA2dpSuspendedApplied = false;
- AudioSystem.setParameters("A2dpSuspended=false");
mBluetoothLeSuspendedApplied = false;
- AudioSystem.setParameters("LeAudioSuspended=false");
+ reapplyAudioHalBluetoothState();
}
}
@@ -1114,6 +1112,34 @@
}
}
+ @GuardedBy("mBluetoothAudioStateLock")
+ private void reapplyAudioHalBluetoothState() {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: "
+ + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: "
+ + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: "
+ + mBluetoothLeSuspendedApplied);
+ }
+ // Note: the order of parameters is important.
+ if (mBluetoothScoOnApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ AudioSystem.setParameters("BT_SCO=on");
+ } else {
+ AudioSystem.setParameters("BT_SCO=off");
+ if (mBluetoothA2dpSuspendedApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ } else {
+ AudioSystem.setParameters("A2dpSuspended=false");
+ }
+ if (mBluetoothLeSuspendedApplied) {
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ } else {
+ AudioSystem.setParameters("LeAudioSuspended=false");
+ }
+ }
+ }
+
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
@@ -1775,6 +1801,9 @@
initRoutingStrategyIds();
updateActiveCommunicationDevice();
mDeviceInventory.onRestoreDevices();
+ synchronized (mBluetoothAudioStateLock) {
+ reapplyAudioHalBluetoothState();
+ }
mBtHelper.onAudioServerDiedRestoreA2dp();
updateCommunicationRoute("MSG_RESTORE_DEVICES");
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 8fd2ee2..21e6bac 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -439,6 +439,10 @@
if (fingerprintService != null) {
fingerprintService.registerAuthenticationStateListener(listener);
}
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ faceService.registerAuthenticationStateListener(listener);
+ }
}
@Override
@@ -449,6 +453,10 @@
if (fingerprintService != null) {
fingerprintService.unregisterAuthenticationStateListener(listener);
}
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ faceService.unregisterAuthenticationStateListener(listener);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 5863535..1ae4d64 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -91,6 +91,40 @@
}
}
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ public void onAuthenticationSucceeded(int requestReason, int userId) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationSucceeded(requestReason, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "succeeded", e);
+ }
+ }
+ }
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ public void onAuthenticationFailed(int requestReason, int userId) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationFailed(requestReason, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "failed", e);
+ }
+ }
+ }
+
@Override
public void binderDied() {
// Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 73f3999..321e951 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
+import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
@@ -63,6 +64,7 @@
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -99,6 +101,8 @@
private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
mBiometricStateCallback;
@NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
+ @NonNull
private final FaceProviderFunction mFaceProviderFunction;
@NonNull private final Function<String, FaceProvider> mFaceProvider;
@NonNull
@@ -695,7 +699,8 @@
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
providers.add(
Face10.newInstance(getContext(), mBiometricStateCallback,
- hidlSensor, mLockoutResetDispatcher));
+ mAuthenticationStateListeners, hidlSensor,
+ mLockoutResetDispatcher));
}
return providers;
@@ -830,6 +835,24 @@
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
mBiometricStateCallback.registerBiometricStateListener(listener);
}
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void registerAuthenticationStateListener(
+ @NonNull AuthenticationStateListener listener) {
+ super.registerAuthenticationStateListener_enforcePermission();
+
+ mAuthenticationStateListeners.registerAuthenticationStateListener(listener);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void unregisterAuthenticationStateListener(
+ @NonNull AuthenticationStateListener listener) {
+ super.unregisterAuthenticationStateListener_enforcePermission();
+
+ mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener);
+ }
}
public FaceService(Context context) {
@@ -848,6 +871,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+ mAuthenticationStateListeners = new AuthenticationStateListeners();
mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@Override
@@ -868,8 +892,8 @@
try {
final SensorProps[] props = face.getSensorProps();
return new FaceProvider(getContext(),
- mBiometricStateCallback, props, name, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
+ mBiometricStateCallback, mAuthenticationStateListeners, props, name,
+ mLockoutResetDispatcher, BiometricContext.getInstance(getContext()),
false /* resetLockoutRequiresChallenge */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -881,7 +905,7 @@
if (Flags.deHidl()) {
mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
- getContext(), mBiometricStateCallback,
+ getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
filteredSensorProps.second,
filteredSensorProps.first, mLockoutResetDispatcher,
BiometricContext.getInstance(getContext()),
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 22e399c..f35de93 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.NotificationManager;
@@ -44,6 +46,7 @@
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -77,6 +80,8 @@
private ICancellationSignal mCancellationSignal;
@Nullable
private final SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
@FaceManager.FaceAcquired
private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
@@ -89,11 +94,13 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength) {
+ @Authenticators.Types int sensorStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
this(context, lazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
- context.getSystemService(SensorPrivacyManager.class), sensorStrength);
+ context.getSystemService(SensorPrivacyManager.class), sensorStrength,
+ authenticationStateListeners);
}
@VisibleForTesting
@@ -107,7 +114,8 @@
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
SensorPrivacyManager sensorPrivacyManager,
- @Authenticators.Types int biometricStrength) {
+ @Authenticators.Types int biometricStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */, lockoutTracker,
@@ -118,6 +126,7 @@
mNotificationManager = context.getSystemService(NotificationManager.class);
mSensorPrivacyManager = sensorPrivacyManager;
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
+ mAuthenticationStateListeners = authenticationStateListeners;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -262,6 +271,16 @@
0 /* error */,
0 /* vendorError */,
getTargetUserId()));
+
+ if (reportBiometricAuthAttempts()) {
+ if (authenticated) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ } else {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index e4ecf1a..d01c268 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -59,6 +59,7 @@
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -103,6 +104,8 @@
@NonNull
private final BiometricStateCallback mBiometricStateCallback;
@NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
+ @NonNull
private final String mHalInstanceName;
@NonNull
private final Handler mHandler;
@@ -156,18 +159,20 @@
public FaceProvider(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
boolean resetLockoutRequiresChallenge) {
- this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
- biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge,
- false /* testHalEnabled */);
+ this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
+ lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
+ resetLockoutRequiresChallenge, false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -178,6 +183,7 @@
boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
+ mAuthenticationStateListeners = authenticationStateListeners;
mHalInstanceName = halInstanceName;
mFaceSensors = new SensorList<>(ActivityManager.getService());
if (Flags.deHidl()) {
@@ -610,7 +616,8 @@
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mUsageStats, lockoutTracker,
- allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId),
+ mAuthenticationStateListeners);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
public void onClientStarted(
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 5337666..48a676c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -64,6 +64,7 @@
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -119,6 +120,8 @@
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
@NonNull private final BiometricStateCallback mBiometricStateCallback;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
@NonNull private final Context mContext;
@NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
@NonNull private final Handler mHandler;
@@ -350,6 +353,7 @@
@VisibleForTesting
Face10(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
@@ -358,6 +362,7 @@
mSensorProperties = sensorProps;
mContext = context;
mBiometricStateCallback = biometricStateCallback;
+ mAuthenticationStateListeners = authenticationStateListeners;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
mHandler = handler;
@@ -392,11 +397,12 @@
public static Face10 newInstance(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
- return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
- handler, new BiometricScheduler<>(
+ return new Face10(context, biometricStateCallback, authenticationStateListeners,
+ sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */),
BiometricContext.getInstance(context));
@@ -846,7 +852,8 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector), mBiometricContext,
isStrongBiometric, mUsageStats, mLockoutTracker,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+ mAuthenticationStateListeners);
mScheduler.scheduleClientMonitor(client);
}
@@ -860,7 +867,8 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector), mBiometricContext,
isStrongBiometric, mLockoutTracker, mUsageStats,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+ mAuthenticationStateListeners);
mScheduler.scheduleClientMonitor(client);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 8ab8892..e44b263 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.hidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
@@ -36,6 +38,7 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -65,6 +68,8 @@
private int mLastAcquire;
private SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon,
@@ -75,7 +80,8 @@
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
@NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength) {
+ @Authenticators.Types int sensorStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */,
@@ -84,6 +90,7 @@
setRequestId(requestId);
mUsageStats = usageStats;
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mAuthenticationStateListeners = authenticationStateListeners;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -186,6 +193,16 @@
0 /* error */,
0 /* vendorError */,
getTargetUserId()));
+
+ if (reportBiometricAuthAttempts()) {
+ if (authenticated) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ } else {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index f7e8123..6912961 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
import android.annotation.NonNull;
@@ -232,8 +234,16 @@
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ }
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 4c1d4d6..7a329e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.hidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
import android.annotation.NonNull;
@@ -142,6 +144,10 @@
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ }
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
final @LockoutTracker.LockoutMode int lockoutMode =
@@ -161,6 +167,10 @@
onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
cancel();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
}
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 6ec6a12..77cb08b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -204,6 +204,10 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {}
+
+ @Override
public void onProcessDied(int pid, int uid) {}
@Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 403b421..71a9f54 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -248,6 +248,7 @@
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
if (subManager != null && telManager != null) {
+ subManager = subManager.createForAllUserProfiles();
List<SubscriptionInfo> subscriptionInfoList =
subManager.getActiveSubscriptionInfoList();
HashSet<Integer> activeSubIds = new HashSet<Integer>();
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 550aed5..978f468 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -214,6 +214,11 @@
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid,
+ String packageName, String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
serviceTypes);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 85c4ffe..f852b81 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -57,6 +57,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -71,7 +72,6 @@
import com.android.server.EventLogTags;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
-import com.android.server.notification.Flags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -81,6 +81,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* NotificationManagerService helper for handling notification attention effects:
@@ -100,6 +101,20 @@
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
+ @VisibleForTesting
+ static final Set<String> NOTIFICATION_AVALANCHE_TRIGGER_INTENTS = Set.of(
+ Intent.ACTION_AIRPLANE_MODE_CHANGED,
+ Intent.ACTION_BOOT_COMPLETED,
+ Intent.ACTION_USER_SWITCHED,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE
+ );
+
+ @VisibleForTesting
+ static final Map<String, Pair<String, Boolean>> NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS = Map.of(
+ Intent.ACTION_AIRPLANE_MODE_CHANGED, new Pair<>("state", false),
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
+ );
+
private final Context mContext;
private final PackageManager mPackageManager;
private final TelephonyManager mTelephonyManager;
@@ -191,7 +206,7 @@
mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
if (Flags.politeNotifications()) {
- mStrategy = getPolitenessStrategy();
+ mStrategy = createPolitenessStrategy();
} else {
mStrategy = null;
}
@@ -200,7 +215,7 @@
loadUserSettings();
}
- private PolitenessStrategy getPolitenessStrategy() {
+ private PolitenessStrategy createPolitenessStrategy() {
if (Flags.crossAppPoliteNotifications()) {
PolitenessStrategy appStrategy = new StrategyPerApp(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -209,11 +224,12 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
- return new StrategyGlobal(
+ return new StrategyAvalanche(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
appStrategy);
} else {
return new StrategyPerApp(
@@ -225,6 +241,11 @@
}
}
+ @VisibleForTesting
+ PolitenessStrategy getPolitenessStrategy() {
+ return mStrategy;
+ }
+
public void onSystemReady() {
mSystemReady = true;
@@ -259,6 +280,11 @@
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ if (Flags.crossAppPoliteNotifications()) {
+ for (String avalancheIntent : NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ filter.addAction(avalancheIntent);
+ }
+ }
mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
mContext.getContentResolver().registerContentObserver(
@@ -1052,7 +1078,8 @@
}
}
- abstract private static class PolitenessStrategy {
+ @VisibleForTesting
+ abstract static class PolitenessStrategy {
static final int POLITE_STATE_DEFAULT = 0;
static final int POLITE_STATE_POLITE = 1;
static final int POLITE_STATE_MUTED = 2;
@@ -1079,6 +1106,8 @@
protected boolean mApplyPerPackage;
protected final Map<String, Long> mLastUpdatedTimestampByPackage;
+ protected boolean mIsActive = true;
+
public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
int volumeMuted) {
mVolumeStates = new HashMap<>();
@@ -1218,6 +1247,10 @@
}
return nextState;
}
+
+ boolean isActive() {
+ return mIsActive;
+ }
}
// TODO b/270456865: Only one of the two strategies will be released.
@@ -1289,55 +1322,60 @@
}
/**
- * Global (cross-app) strategy.
+ * Avalanche (cross-app) strategy.
*/
- private static class StrategyGlobal extends PolitenessStrategy {
+ private static class StrategyAvalanche extends PolitenessStrategy {
private static final String COMMON_KEY = "cross_app_common_key";
private final PolitenessStrategy mAppStrategy;
private long mLastNotificationTimestamp = 0;
- public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted, PolitenessStrategy appStrategy) {
+ private final int mTimeoutAvalanche;
+ private long mLastAvalancheTriggerTimestamp = 0;
+
+ StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
+ int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ mTimeoutAvalanche = timeoutAvalanche;
mAppStrategy = appStrategy;
if (DEBUG) {
- Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
+ Log.i(TAG, "StrategyAvalanche: " + timeoutPolite + " " + timeoutMuted + " "
+ + timeoutAvalanche);
}
}
@Override
void onNotificationPosted(NotificationRecord record) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
+ if (isAvalancheActive()) {
+ if (shouldIgnoreNotification(record)) {
+ return;
+ }
- long timeSinceLastNotif =
+ long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
- final String key = getChannelKey(record);
- @PolitenessState final int currState = getPolitenessState(record);
- @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ final String key = getChannelKey(record);
+ @PolitenessState final int currState = getPolitenessState(record);
+ @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
- if (DEBUG) {
- Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
- + " vol state: " + nextState + " key: " + key);
+ if (DEBUG) {
+ Log.i(TAG,
+ "StrategyAvalanche onNotificationPosted time delta: "
+ + timeSinceLastNotif
+ + " vol state: " + nextState + " key: " + key);
+ }
+
+ mVolumeStates.put(key, nextState);
}
- mVolumeStates.put(key, nextState);
-
mAppStrategy.onNotificationPosted(record);
}
@Override
public float getSoundVolume(final NotificationRecord record) {
- final @PolitenessState int globalVolState = getPolitenessState(record);
- final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
-
- // Prioritize the most polite outcome
- if (globalVolState > appVolState) {
+ if (isAvalancheActive()) {
return super.getSoundVolume(record);
} else {
return mAppStrategy.getSoundVolume(record);
@@ -1382,6 +1420,24 @@
super.setApplyCooldownPerPackage(applyPerPackage);
mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
}
+
+ boolean isAvalancheActive() {
+ mIsActive = (System.currentTimeMillis() - mLastAvalancheTriggerTimestamp
+ < mTimeoutAvalanche);
+ if (DEBUG) {
+ Log.i(TAG, "StrategyAvalanche: active " + mIsActive);
+ }
+ return mIsActive;
+ }
+
+ @Override
+ boolean isActive() {
+ return isAvalancheActive();
+ }
+
+ void setTriggerTimeMs(long timestamp) {
+ mLastAvalancheTriggerTimestamp = timestamp;
+ }
}
//====================== Observers =============================
@@ -1415,6 +1471,30 @@
|| action.equals(Intent.ACTION_USER_UNLOCKED)) {
loadUserSettings();
}
+
+ if (Flags.crossAppPoliteNotifications()) {
+ if (NOTIFICATION_AVALANCHE_TRIGGER_INTENTS.contains(action)) {
+ boolean enableAvalancheStrategy = true;
+ // Some actions must also match extras, ie. airplane mode => disabled
+ Pair<String, Boolean> expectedExtras =
+ NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS.get(action);
+ if (expectedExtras != null) {
+ enableAvalancheStrategy =
+ intent.getBooleanExtra(expectedExtras.first, false)
+ == expectedExtras.second;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "Avalanche trigger intent received: " + action
+ + ". Enabling avalanche strategy: " + enableAvalancheStrategy);
+ }
+
+ if (enableAvalancheStrategy && mStrategy instanceof StrategyAvalanche) {
+ ((StrategyAvalanche) mStrategy)
+ .setTriggerTimeMs(System.currentTimeMillis());
+ }
+ }
+ }
}
};
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f311034..ada79ae 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4217,8 +4217,10 @@
}
}
+ final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
+ ? System.currentTimeMillis() : 0;
final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
- scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
+ scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
}
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index c8c16db..f5dfb5c 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -4,7 +4,7 @@
flag {
name: "enable_early_screen_timeout_detector"
- namespace: "power_manager"
+ namespace: "power"
description: "Feature flag for Early Screen Timeout detector"
bug: "309861917"
is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
new file mode 100644
index 0000000..e597c3a
--- /dev/null
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+ name: "stats_flags",
+ package: "com.android.server.stats",
+ srcs: [
+ "stats_flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "stats_flags_lib",
+ aconfig_declarations: "stats_flags",
+}
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
new file mode 100644
index 0000000..0de73a5
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+import android.app.ActivityManager;
+import android.app.StatsManager;
+import android.app.usage.NetworkStatsManager;
+import android.net.NetworkStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Aggregates Mobile Data Usage by process state per uid
+ */
+class AggregatedMobileDataStatsPuller {
+ private static final String TAG = "AggregatedMobileDataStatsPuller";
+
+ private static final boolean DEBUG = false;
+
+ private static class UidProcState {
+
+ private final int mUid;
+ private final int mState;
+
+ UidProcState(int uid, int state) {
+ mUid = uid;
+ mState = state;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UidProcState key)) return false;
+ return mUid == key.mUid && mState == key.mState;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUid;
+ result = 31 * result + mState;
+ return result;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ }
+
+ private static class MobileDataStats {
+ private long mRxPackets = 0;
+ private long mTxPackets = 0;
+ private long mRxBytes = 0;
+ private long mTxBytes = 0;
+
+ public long getRxPackets() {
+ return mRxPackets;
+ }
+
+ public long getTxPackets() {
+ return mTxPackets;
+ }
+
+ public long getRxBytes() {
+ return mRxBytes;
+ }
+
+ public long getTxBytes() {
+ return mTxBytes;
+ }
+
+ public void addRxPackets(long rxPackets) {
+ mRxPackets += rxPackets;
+ }
+
+ public void addTxPackets(long txPackets) {
+ mTxPackets += txPackets;
+ }
+
+ public void addRxBytes(long rxBytes) {
+ mRxBytes += rxBytes;
+ }
+
+ public void addTxBytes(long txBytes) {
+ mTxBytes += txBytes;
+ }
+
+ public boolean isEmpty() {
+ return mRxPackets == 0 && mTxPackets == 0 && mRxBytes == 0 && mTxBytes == 0;
+ }
+ }
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<UidProcState, MobileDataStats> mUidStats;
+
+ private final SparseIntArray mUidPreviousState;
+
+ private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1);
+
+ private final NetworkStatsManager mNetworkStatsManager;
+
+ private final Handler mMobileDataStatsHandler;
+
+ AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) {
+ mUidStats = new ArrayMap<>();
+ mUidPreviousState = new SparseIntArray();
+
+ mNetworkStatsManager = networkStatsManager;
+
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ }
+
+ HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler");
+ mMobileDataStatsHandlerThread.start();
+ mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper());
+ }
+
+ public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
+ long unusedUptime) {
+ mMobileDataStatsHandler.post(
+ () -> {
+ noteUidProcessStateImpl(uid, state);
+ });
+ }
+
+ public int pullDataBytesTransfer(List<StatsEvent> data) {
+ synchronized (mLock) {
+ return pullDataBytesTransferLocked(data);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private MobileDataStats getUidStatsForPreviousStateLocked(int uid) {
+ final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN);
+ if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) {
+ Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid "
+ + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN");
+ }
+
+ final UidProcState statsKey = new UidProcState(uid, previousState);
+ MobileDataStats stats;
+ if (mUidStats.containsKey(statsKey)) {
+ stats = mUidStats.get(statsKey);
+ } else {
+ stats = new MobileDataStats();
+ mUidStats.put(statsKey, stats);
+ }
+ return stats;
+ }
+
+ private void noteUidProcessStateImpl(int uid, int state) {
+ // noteUidProcessStateLocked can be called back to back several times while
+ // the updateNetworkStatsLocked loops over several stats for multiple uids
+ // and during the first call in a batch of proc state change event it can
+ // contain info for uid with unknown previous state yet which can happen due to a few
+ // reasons:
+ // - app was just started
+ // - app was started before the ActivityManagerService
+ // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ } else {
+ Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+ }
+ mUidPreviousState.put(uid, state);
+ }
+
+ private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
+ if (DEBUG) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
+ }
+ }
+
+ final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
+ if (isEmpty(latestStats)) {
+ if (DEBUG) {
+ Slog.w(TAG, "getMobileUidStats() failed");
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ return;
+ }
+ NetworkStats delta = latestStats.subtract(mLastMobileUidStats);
+ mLastMobileUidStats = latestStats;
+
+ if (!isEmpty(delta)) {
+ updateNetworkStatsDelta(delta);
+ } else if (DEBUG) {
+ Slog.w(TAG, "updateNetworkStats() no delta");
+ }
+ if (DEBUG) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ }
+
+ private void updateNetworkStatsDelta(NetworkStats delta) {
+ synchronized (mLock) {
+ for (NetworkStats.Entry entry : delta) {
+ if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+ continue;
+ }
+ MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+ stats.addTxBytes(entry.getTxBytes());
+ stats.addRxBytes(entry.getRxBytes());
+ stats.addTxPackets(entry.getTxPackets());
+ stats.addRxPackets(entry.getRxPackets());
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) {
+ if (DEBUG) {
+ Slog.d(TAG, "pullDataBytesTransferLocked() start");
+ }
+ for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) {
+ if (!uidStats.getValue().isEmpty()) {
+ MobileDataStats stats = uidStats.getValue();
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE,
+ uidStats.getKey().getUid(),
+ ActivityManager.processStateAmToProto(uidStats.getKey().getState()),
+ stats.getRxBytes(),
+ stats.getRxPackets(),
+ stats.getTxBytes(),
+ stats.getTxPackets()));
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "pullDataBytesTransferLocked() done. results count " + pulledData.size());
+ }
+ if (!pulledData.isEmpty()) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ return StatsManager.PULL_SKIP;
+ }
+
+ private static boolean isEmpty(NetworkStats stats) {
+ long totalRxPackets = 0;
+ long totalTxPackets = 0;
+ for (NetworkStats.Entry entry : stats) {
+ if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+ continue;
+ }
+ totalRxPackets += entry.getRxPackets();
+ totalTxPackets += entry.getTxPackets();
+ // at least one non empty entry located
+ break;
+ }
+ final long totalPackets = totalRxPackets + totalTxPackets;
+ return totalPackets == 0;
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e876241..285bcc3 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -59,6 +59,7 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -409,6 +410,15 @@
@GuardedBy("mKeystoreLock")
private IKeystoreMetrics mIKeystoreMetrics;
+ private AggregatedMobileDataStatsPuller mAggregatedMobileDataStatsPuller = null;
+
+ /**
+ * Whether or not to enable the new puller with aggregation by process state per uid on a
+ * system server side.
+ */
+ public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
+ addMobileBytesTransferByProcStatePuller();
+
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -469,6 +479,20 @@
mContext = context;
}
+ private final class StatsPullAtomServiceInternalImpl extends StatsPullAtomServiceInternal {
+
+ @Override
+ public void noteUidProcessState(int uid, int state) {
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+ && mAggregatedMobileDataStatsPuller != null) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ mAggregatedMobileDataStatsPuller.noteUidProcessState(uid, state, elapsedRealtime,
+ uptime);
+ }
+ }
+ }
+
private native void initializeNativePullers();
/**
@@ -486,6 +510,11 @@
}
try {
switch (atomTag) {
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE:
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+ && mAggregatedMobileDataStatsPuller != null) {
+ return mAggregatedMobileDataStatsPuller.pullDataBytesTransfer(data);
+ }
case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
@@ -776,7 +805,10 @@
@Override
public void onStart() {
- // no op
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ LocalServices.addService(StatsPullAtomServiceInternal.class,
+ new StatsPullAtomServiceInternalImpl());
+ }
}
@Override
@@ -811,6 +843,9 @@
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+
+ initMobileDataStatsPuller();
+
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -972,6 +1007,18 @@
registerCachedAppsHighWatermarkPuller();
}
+ private void initMobileDataStatsPuller() {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = "
+ + ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER);
+ }
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ mAggregatedMobileDataStatsPuller =
+ new AggregatedMobileDataStatsPuller(mNetworkStatsManager);
+ }
+ }
+
private void initAndRegisterNetworkStatsPullers() {
if (DEBUG) {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1013,6 +1060,9 @@
registerWifiBytesTransferBackground();
registerMobileBytesTransfer();
registerMobileBytesTransferBackground();
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ registerMobileBytesTransferByProcState();
+ }
registerBytesTransferByTagAndMetered();
registerDataUsageBytesTransfer();
registerOemManagedBytesTransfer();
@@ -1021,6 +1071,13 @@
}
}
+ private void registerMobileBytesTransferByProcState() {
+ final int tagId = FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE;
+ PullAtomMetadata metadata =
+ new PullAtomMetadata.Builder().setAdditiveFields(new int[] {3, 4, 5, 6}).build();
+ mStatsManager.setPullAtomCallback(tagId, metadata, DIRECT_EXECUTOR, mStatsCallbackImpl);
+ }
+
private void initAndRegisterDeferredPullers() {
mUwbManager = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
? mContext.getSystemService(UwbManager.class) : null;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
new file mode 100644
index 0000000..06adbfc
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+/**
+ * System-server internal interface to the {@link StatsPullAtomService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class StatsPullAtomServiceInternal {
+
+ /**
+ * @param state Process state from ActivityManager.java.
+ */
+ public abstract void noteUidProcessState(int uid, int state);
+
+}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
new file mode 100644
index 0000000..5101a69
--- /dev/null
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.stats"
+
+flag {
+ name: "add_mobile_bytes_transfer_by_proc_state_puller"
+ namespace: "statsd"
+ description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
+ bug: "309512867"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 106be5f..4cc2c02 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -48,6 +48,7 @@
import java.io.FileDescriptor;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
/**
* System service for managing sensing {@link AmbientContextEvent}s on Wearables.
@@ -191,9 +192,23 @@
}
}
+ private void callPerUserServiceIfExist(
+ Consumer<WearableSensingManagerPerUserService> serviceConsumer,
+ RemoteCallback statusCallback) {
+ int userId = UserHandle.getCallingUserId();
+ synchronized (mLock) {
+ WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "Service not available for userId " + userId);
+ WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback,
+ WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ serviceConsumer.accept(service);
+ }
+ }
+
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
- final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
- UserHandle.getCallingUserId());
@Override
public void provideDataStream(
@@ -210,7 +225,9 @@
WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
- mService.onProvideDataStream(parcelFileDescriptor, callback);
+ callPerUserServiceIfExist(
+ service -> service.onProvideDataStream(parcelFileDescriptor, callback),
+ callback);
}
@Override
@@ -229,7 +246,9 @@
WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
- mService.onProvidedData(data, sharedMemory, callback);
+ callPerUserServiceIfExist(
+ service -> service.onProvidedData(data, sharedMemory, callback),
+ callback);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 036f7b6..3d492bb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -141,6 +141,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -991,6 +992,9 @@
private CustomAppTransition mCustomOpenTransition;
private CustomAppTransition mCustomCloseTransition;
+ /** Non-zero to pause dispatching configuration changes to the client. */
+ int mPauseConfigurationDispatchCount = 0;
+
private final Runnable mPauseTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -9276,6 +9280,59 @@
}
}
+ @Override
+ void dispatchConfigurationToChild(WindowState child, Configuration config) {
+ if (isConfigurationDispatchPaused()) {
+ return;
+ }
+ super.dispatchConfigurationToChild(child, config);
+ }
+
+ /**
+ * Pauses dispatch of configuration changes to the client. This includes any
+ * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If
+ * a lifecycle change comes from another source (eg. stop), it will still run but will use the
+ * paused configuration.
+ *
+ * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}.
+ * That method is responsible for evaluating whether the activity needs to be relaunched and
+ * sending configurations.
+ */
+ void pauseConfigurationDispatch() {
+ ++mPauseConfigurationDispatchCount;
+ if (mPauseConfigurationDispatchCount == 1) {
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for "
+ + " %s", this);
+ }
+ }
+
+ /** @return `true` if configuration actually changed. */
+ boolean resumeConfigurationDispatch() {
+ --mPauseConfigurationDispatchCount;
+ if (mPauseConfigurationDispatchCount > 0) {
+ return false;
+ }
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this);
+ if (mPauseConfigurationDispatchCount < 0) {
+ Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch");
+ mPauseConfigurationDispatchCount = 0;
+ return false;
+ }
+ if (mLastReportedDisplayId == getDisplayId()
+ && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) {
+ return false;
+ }
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ dispatchConfigurationToChild(getChildAt(i), getConfiguration());
+ }
+ updateReportedConfigurationAndSend();
+ return true;
+ }
+
+ boolean isConfigurationDispatchPaused() {
+ return mPauseConfigurationDispatchCount > 0;
+ }
+
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds) {
return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -9525,6 +9582,17 @@
return true;
}
+ if (isConfigurationDispatchPaused()) {
+ return true;
+ }
+
+ return updateReportedConfigurationAndSend();
+ }
+
+ boolean updateReportedConfigurationAndSend() {
+ if (isConfigurationDispatchPaused()) {
+ Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
+ }
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
+ "configuration: %s", this);
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 7f785af..a1799b4 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -92,35 +92,39 @@
mRotation = rotation;
mWidth = w;
mHeight = h;
- final Rect unrestricted = mUnrestricted;
- unrestricted.set(0, 0, w, h);
- state.setDisplayFrame(unrestricted);
+ final Rect u = mUnrestricted;
+ u.set(0, 0, w, h);
+ state.setDisplayFrame(u);
state.setDisplayCutout(displayCutout);
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
state.setDisplayShape(displayShape);
state.getDisplayCutoutSafe(safe);
- if (safe.left > unrestricted.left) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame(
- unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
+ if (safe.left > u.left) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout())
+ .setFrame(u.left, u.top, safe.left, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_LEFT);
}
- if (safe.top > unrestricted.top) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame(
- unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
+ if (safe.top > u.top) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout())
+ .setFrame(u.left, u.top, u.right, safe.top)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_TOP);
}
- if (safe.right < unrestricted.right) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame(
- safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
+ if (safe.right < u.right) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout())
+ .setFrame(safe.right, u.top, u.right, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_RIGHT);
}
- if (safe.bottom < unrestricted.bottom) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame(
- unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
+ if (safe.bottom < u.bottom) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout())
+ .setFrame(u.left, safe.bottom, u.right, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 9d5ddf3..d9dda4a 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -62,6 +62,8 @@
*/
class InsetsSourceProvider {
+ private static final Rect EMPTY_RECT = new Rect();
+
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
protected WindowContainer mWindowContainer;
@@ -286,12 +288,15 @@
private void updateSourceFrameForServerVisibility() {
// Make sure we set the valid source frame only when server visible is true, because the
- // frame may not yet determined that server side doesn't think the window is ready to
+ // frame may not yet be determined that server side doesn't think the window is ready to
// visible. (i.e. No surface, pending insets that were given during layout, etc..)
- if (mServerVisible) {
- mSource.setFrame(mSourceFrame);
- } else {
- mSource.setFrame(0, 0, 0, 0);
+ final Rect frame = mServerVisible ? mSourceFrame : EMPTY_RECT;
+ if (mSource.getFrame().equals(frame)) {
+ return;
+ }
+ mSource.setFrame(frame);
+ if (mWindowContainer != null) {
+ mSource.updateSideHint(mWindowContainer.getBounds());
}
}
@@ -631,7 +636,7 @@
}
pw.print(prefix);
pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
- pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
+ pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition);
pw.println();
if (mWindowContainer != null) {
pw.print(prefix + "mWindowContainer=");
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6371bb4..0c6b174 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -369,6 +369,15 @@
*/
private boolean mMoveToBottomIfClearWhenLaunch;
+ /**
+ * If {@code true}, transitions are allowed even if this TaskFragment is empty. If
+ * {@code false}, transitions will wait until this TaskFragment becomes non-empty or other
+ * conditions are met. Default to {@code false}.
+ *
+ * @see #isReadyToTransit
+ */
+ private boolean mAllowTransitionWhenEmpty;
+
/** When set, will force the task to report as invisible. */
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -509,6 +518,19 @@
mIsolatedNav = isolatedNav;
}
+ /**
+ * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+ * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+ * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+ * to {@code false}.
+ */
+ void setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+ if (!isEmbedded()) {
+ return;
+ }
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ }
+
/** @see #mIsolatedNav */
boolean isIsolatedNav() {
return isEmbedded() && mIsolatedNav;
@@ -2827,8 +2849,9 @@
return true;
}
// We don't want to start the transition if the organized TaskFragment is empty, unless
- // it is requested to be removed.
- if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+ // it is requested to be removed or the mAllowTransitionWhenEmpty flag is true.
+ if (getTopNonFinishingActivity() != null || mIsRemovalRequested
+ || mAllowTransitionWhenEmpty) {
return true;
}
// Organizer shouldn't change embedded TaskFragment in PiP.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bdea1bc..286182e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -465,7 +465,7 @@
}
}
final InsetsSource source = new InsetsSource(id, provider.getType());
- source.setFrame(provider.getArbitraryRectangle());
+ source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds());
mLocalInsetsSources.put(id, source);
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 205ed97..4ba52e4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2202,6 +2202,7 @@
}
final TaskFragment taskFragment = new TaskFragment(mService,
creationParams.getFragmentToken(), true /* createdByOrganizer */);
+ taskFragment.setAllowTransitionWhenEmpty(creationParams.getAllowTransitionWhenEmpty());
// Set task fragment organizer immediately, since it might have to be notified about further
// actions.
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56f2bc3..7ad87ed 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5187,6 +5187,11 @@
if (mSurfaceControl == null) {
return;
}
+ if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) {
+ // Don't update surface-position while dispatch paused. This is calculated from
+ // the server-side activity configuration so return early.
+ return;
+ }
if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
&& !mSurfacePlacementNeeded) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5048cef..13e1ba78 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -639,9 +639,12 @@
@Override
void updateSurfacePosition(SurfaceControl.Transaction t) {
+ final ActivityRecord r = asActivityRecord();
+ if (r != null && r.isConfigurationDispatchPaused()) {
+ return;
+ }
super.updateSurfacePosition(t);
if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
- final ActivityRecord r = asActivityRecord();
final Task rootTask = r != null ? r.getRootTask() : null;
// Don't transform the activity in PiP because the PiP task organizer will handle it.
if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index a0fb013..24d4952 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -94,7 +94,9 @@
isSystemUpdated: Boolean
) {
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ // The package may still be removed even if it was once notified as installed.
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
trimPermissionStates(packageState.appId)
}
}
@@ -245,6 +247,13 @@
flagMask: Int,
flagValues: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+ // sometimes get race conditions between that check and the actual mutateState() call.
+ // This should rarely happen but at least we should not crash.
+ Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+ return false
+ }
val oldFlags =
newState.userStates[userId]!!
.appIdDevicePermissionFlags[appId]
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
new file mode 100644
index 0000000..fcf761f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.
+ */
+
+package com.android.server.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.IProcessObserver;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify that process events are dispatched to process observers.
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class ProcessObserverTest {
+ private static final String TAG = "ProcessObserverTest";
+
+ private static final String PACKAGE = "com.foo";
+
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInt;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInt;
+ @Mock
+ private BatteryStatsService mBatteryStatsService;
+
+ private ActivityManagerService mRealAms;
+ private ActivityManagerService mAms;
+
+ private ProcessList mRealProcessList = new ProcessList();
+ private ProcessList mProcessList;
+
+ final IProcessObserver mProcessObserver = mock(IProcessObserver.Stub.class);
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+ doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+ mRealAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ mRealAms.mConstants.loadDeviceConfigConstants();
+ mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mRealAms.mAtmInternal = mActivityTaskManagerInt;
+ mRealAms.mPackageManagerInt = mPackageManagerInt;
+ mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+ mRealAms.mProcessesReady = true;
+ mAms = spy(mRealAms);
+ mRealProcessList.mService = mAms;
+ mProcessList = spy(mRealProcessList);
+
+ doReturn(mProcessObserver).when(mProcessObserver).asBinder();
+ mProcessList.registerProcessObserver(mProcessObserver);
+
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+ + Arrays.toString(invocation.getArguments()));
+ return null;
+ }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mRealProcessList;
+ }
+
+ @Override
+ public BatteryStatsService getBatteryStatsService() {
+ return mBatteryStatsService;
+ }
+ }
+
+ private ProcessRecord makeActiveProcessRecord(String packageName)
+ throws Exception {
+ final ApplicationInfo ai = makeApplicationInfo(packageName);
+ return makeActiveProcessRecord(ai);
+ }
+
+ private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
+ throws Exception {
+ final IApplicationThread thread = mock(IApplicationThread.class);
+ final IBinder threadBinder = new Binder();
+ doReturn(threadBinder).when(thread).asBinder();
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting bindApplication() for "
+ + Arrays.toString(invocation.getArguments()));
+ if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+ mRealAms.finishAttachApplication(0);
+ }
+ return null;
+ }).when(thread).bindApplication(
+ any(), any(),
+ any(), any(), anyBoolean(),
+ any(), any(),
+ any(), any(),
+ any(),
+ any(), anyInt(),
+ anyBoolean(), anyBoolean(),
+ anyBoolean(), anyBoolean(), any(),
+ any(), any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ anyLong(), anyLong());
+ final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+ r.setPid(myPid());
+ r.setStartUid(myUid());
+ r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+ r.makeActive(thread, mAms.mProcessStats);
+ doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+ anyBoolean());
+ return r;
+ }
+
+ static ApplicationInfo makeApplicationInfo(String packageName) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.processName = packageName;
+ ai.uid = myUid();
+ return ai;
+ }
+
+ /**
+ * Verify that a process start event is dispatched to process observers.
+ */
+ @Test
+ public void testNormal() throws Exception {
+ ProcessRecord app = startProcess();
+ verify(mProcessObserver).onProcessStarted(
+ app.getPid(), app.uid, app.info.uid, PACKAGE, PACKAGE);
+ }
+
+ private ProcessRecord startProcess() throws Exception {
+ final ProcessRecord app = makeActiveProcessRecord(PACKAGE);
+ final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+ mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+ /* expectedStartSeq */ 0, /* procAttached */ false);
+ app.getThread().bindApplication(PACKAGE, appInfo,
+ null, null, false,
+ null,
+ null,
+ null, null,
+ null,
+ null, 0,
+ false, false,
+ true, false,
+ null,
+ null, null,
+ null,
+ null, null, null,
+ null, null,
+ 0, 0);
+ return app;
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 654d7a8d..f49f638 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -44,6 +44,7 @@
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "ravenwood-junit",
],
libs: [
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index ca162e0..ba2b538 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -32,6 +32,7 @@
import android.os.HandlerThread;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -57,7 +58,8 @@
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
- private final MockBatteryStatsImpl mBatteryStats;
+ private final File mHistoryDir;
+ private MockBatteryStatsImpl mBatteryStats;
private Handler mHandler;
private BatteryUsageStats mBatteryUsageStats;
@@ -66,6 +68,10 @@
private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
+ private int mDisplayCount = -1;
+ private int mPerUidModemModel = -1;
+ private NetworkStats mNetworkStats;
+
public BatteryUsageStatsRule() {
this(0, null);
}
@@ -78,16 +84,38 @@
mHandler = mock(Handler.class);
mPowerProfile = spy(new PowerProfile());
mMockClock.currentTime = currentTime;
- mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
- mBatteryStats.setPowerProfile(mPowerProfile);
+ mHistoryDir = historyDir;
+
+ if (!RavenwoodRule.isUnderRavenwood()) {
+ lateInitBatteryStats();
+ }
mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+ }
+
+ private void lateInitBatteryStats() {
+ if (mBatteryStats != null) return;
+
+ mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+ mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
mBatteryStats.onSystemReady();
+
+ if (mDisplayCount != -1) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
+ if (mPerUidModemModel != -1) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
+ }
+ if (mNetworkStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
public MockClock getMockClock() {
@@ -112,7 +140,10 @@
}
mCpusByPolicy.put(policy, relatedCpus);
mFreqsByPolicy.put(policy, frequencies);
- mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ if (mBatteryStats != null) {
+ mBatteryStats.setCpuScalingPolicies(
+ new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ }
return this;
}
@@ -174,13 +205,19 @@
public BatteryUsageStatsRule setNumDisplays(int value) {
when(mPowerProfile.getNumDisplays()).thenReturn(value);
- mBatteryStats.setDisplayCountLocked(value);
+ mDisplayCount = value;
+ if (mBatteryStats != null) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
return this;
}
public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
- synchronized (mBatteryStats) {
- mBatteryStats.setPerUidModemModel(perUidModemModel);
+ mPerUidModemModel = perUidModemModel;
+ if (mBatteryStats != null) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
}
return this;
}
@@ -210,7 +247,10 @@
}
public void setNetworkStats(NetworkStats networkStats) {
- mBatteryStats.setNetworkStats(networkStats);
+ mNetworkStats = networkStats;
+ if (mBatteryStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
@Override
@@ -225,6 +265,7 @@
}
private void before() {
+ lateInitBatteryStats();
HandlerThread bgThread = new HandlerThread("bg thread");
bgThread.start();
mHandler = new Handler(bgThread.getLooper());
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index be68e9c..8958fac 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -31,6 +31,10 @@
"test-apps/SuspendTestApp/src/**/*.java",
],
+
+ kotlincflags: [
+ "-Werror",
+ ],
static_libs: [
"frameworks-base-testutils",
"services.accessibility",
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 88b2ed4..071db68 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -491,6 +492,22 @@
}
@Test
+ public void testRegisterAuthenticationStateListener_callsFaceService() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ final AuthenticationStateListener listener = mock(AuthenticationStateListener.class);
+
+ mAuthService.mImpl.registerAuthenticationStateListener(listener);
+
+ waitForIdle();
+ verify(mFaceService).registerAuthenticationStateListener(eq(listener));
+ }
+
+ @Test
public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
throws Exception {
setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3a3dd6e..f8b5b04 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
@@ -49,6 +50,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
@@ -58,6 +60,7 @@
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -81,6 +84,8 @@
@SmallTest
public class FaceAuthenticationClientTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int USER_ID = 12;
private static final long OP_ID = 32;
private static final int WAKE_REASON = WakeReason.LIFT;
@@ -105,6 +110,8 @@
@Mock
private ClientMonitorCallback mCallback;
@Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
+ @Mock
private AidlResponseHandler mAidlResponseHandler;
@Mock
private ActivityTaskManager mActivityTaskManager;
@@ -264,6 +271,29 @@
verify(mHal, never()).authenticate(anyInt());
}
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+ false /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+ }
+
private FaceAuthenticationClient createClient() throws RemoteException {
return createClient(2 /* version */, mClientMonitorCallbackConverter,
false /* allowBackgroundAuthentication */,
@@ -311,7 +341,8 @@
false /* requireConfirmation */,
mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
mUsageStats, lockoutTracker, allowBackgroundAuthentication,
- null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
+ null /* sensorPrivacyManager */, 0 /* biometricStrength */,
+ mAuthenticationStateListeners) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 772ec8b..7648bd17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -51,6 +51,7 @@
import com.android.internal.R;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -89,6 +90,8 @@
private BiometricContext mBiometricContext;
@Mock
private BiometricStateCallback mBiometricStateCallback;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
private final TestLooper mLooper = new TestLooper();
private SensorProps[] mSensorProps;
@@ -119,8 +122,8 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
- mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
- mDaemon, new Handler(mLooper.getLooper()),
+ mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
+ mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@@ -154,7 +157,7 @@
final HidlFaceSensorConfig[] hidlFaceSensorConfig =
new HidlFaceSensorConfig[]{faceSensorConfig};
mFaceProvider = new FaceProvider(mContext,
- mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+ mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
mLockoutResetDispatcher, mBiometricContext, mDaemon,
new Handler(mLooper.getLooper()),
true /* resetLockoutRequiresChallenge */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index e558c4d..78c1e08 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -44,6 +44,7 @@
import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -81,6 +82,8 @@
private BiometricContext mBiometricContext;
@Mock
private BiometricStateCallback mBiometricStateCallback;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -116,8 +119,8 @@
Face10.sSystemClock = Clock.fixed(
Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
- mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
- mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
+ mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
+ sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
mBinder = new Binder();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 774ea5b..4ed6f74 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -451,6 +452,29 @@
}
@Test
+ public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+ }
+
+ @Test
public void cancelsAuthWhenNotInForeground() throws Exception {
final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
topTask.topActivity = new ComponentName("other", "thing");
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 10f27ca..72fa949 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -80,7 +80,7 @@
@BeforeClass
@JvmStatic
fun checkAllCasesUniquelyNamed() {
- val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase ->
+ val duplicateCaseNames = CASES.mapIndexed { _, testCase ->
testCase.failures.map {
makeTestName(testCase, it.first, Params.Type.FAILURE)
} + testCase.allowed.map {
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
index 150822b..c07c4d7 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
@@ -18,12 +18,13 @@
import android.content.Context
import android.util.Xml
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.SystemConfig
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
class SystemConfigNamedActorTest {
@@ -37,14 +38,11 @@
private const val PACKAGE_TWO = "com.test.actor.two"
}
- private val context: Context = InstrumentationRegistry.getContext()
+ private val context: Context = InstrumentationRegistry.getInstrumentation().context
@get:Rule
val tempFolder = TemporaryFolder(context.filesDir)
- @get:Rule
- val expected = ExpectedException.none()
-
private var uniqueCounter = 0
@Test
@@ -193,11 +191,9 @@
</config>
""".write()
- expected.expect(IllegalStateException::class.java)
- expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " +
+ val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+ assertEquals(exc.message, "Defining $ACTOR_ONE as $PACKAGE_ONE " +
"for the android namespace is not allowed")
-
- assertPermissions()
}
@Test
@@ -217,11 +213,9 @@
</config>
""".write()
- expected.expect(IllegalStateException::class.java)
- expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
+ val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+ assertEquals(exc.message, "Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
" defined as both $PACKAGE_ONE and $PACKAGE_TWO")
-
- assertPermissions()
}
private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
@@ -230,5 +224,5 @@
private fun assertPermissions() = SystemConfig(false).apply {
val parser = Xml.newPullParser()
readPermissions(parser, tempFolder.root, 0)
- }. let { assertThat(it.namedActors) }
+ }.let { assertThat(it.namedActors) }
}
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 bfd2df2d..e75afcc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -27,12 +27,13 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +44,7 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -59,7 +61,10 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.Color;
@@ -80,6 +85,7 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -100,6 +106,7 @@
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -132,6 +139,8 @@
KeyguardManager mKeyguardManager;
@Mock
private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@@ -171,11 +180,14 @@
private static final int CUSTOM_LIGHT_OFF = 10000;
private static final int MAX_VIBRATION_DELAY = 1000;
private static final float DEFAULT_VOLUME = 1.0f;
+ private BroadcastReceiver mAvalancheBroadcastReceiver;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
getContext().addMockSystemService(Vibrator.class, mVibrator);
+ getContext().addMockSystemService(PackageManager.class, mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -214,8 +226,9 @@
private void initAttentionHelper(TestableFlagResolver flagResolver) {
mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
- mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats,
- mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+ mAccessibilityManager, mPackageManager, mUserManager, mUsageStats,
+ mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+ mAttentionHelper.onSystemReady();
mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
mAttentionHelper.setAudioManager(mAudioManager);
mAttentionHelper.setSystemReady(true);
@@ -226,6 +239,29 @@
mAttentionHelper.setScreenOn(false);
mAttentionHelper.setInCallStateOffHook(false);
mAttentionHelper.mNotificationPulseEnabled = true;
+
+ if (Flags.crossAppPoliteNotifications()) {
+ // Capture BroadcastReceiver for avalanche triggers
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(getContext(), atLeastOnce()).registerReceiverAsUser(
+ broadcastReceiverCaptor.capture(),
+ any(), intentFilterCaptor.capture(), any(), any());
+ List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
+ List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
+
+ assertThat(broadcastReceivers.size()).isAtLeast(1);
+ assertThat(intentFilters.size()).isAtLeast(1);
+ for (int i = 0; i < intentFilters.size(); i++) {
+ final IntentFilter filter = intentFilters.get(i);
+ if (filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ mAvalancheBroadcastReceiver = broadcastReceivers.get(i);
+ }
+ }
+ assertThat(mAvalancheBroadcastReceiver).isNotNull();
+ }
}
//
@@ -2040,7 +2076,7 @@
}
@Test
- public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
+ public void testBeepVolume_politeNotif_AvalancheStrategy() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2048,6 +2084,11 @@
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);
+
NotificationRecord r = getBeepyNotification();
// set up internal state
@@ -2078,7 +2119,8 @@
}
@Test
- public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+ public void testBeepVolume_politeNotif_AvalancheStrategy_ChannelHasUserSound()
+ throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2086,6 +2128,11 @@
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);
+
NotificationRecord r = getBeepyNotification();
// set up internal state
@@ -2364,6 +2411,82 @@
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
+ @Test
+ public void testAvalancheStrategyTriggers() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ final int avalancheTimeoutMs = 100;
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT, avalancheTimeoutMs);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intents
+ for (String intentAction
+ : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ // Set the action and extras to trigger the avalanche strategy
+ Intent intent = new Intent(intentAction);
+ Pair<String, Boolean> extras =
+ NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+ .get(intentAction);
+ if (extras != null) {
+ intent.putExtra(extras.first, extras.second);
+ }
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isTrue();
+
+ // Wait for avalanche timeout
+ Thread.sleep(avalancheTimeoutMs + 1);
+
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+
+ @Test
+ public void testAvalancheStrategyTriggers_disabledExtras() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ initAttentionHelper(flagResolver);
+
+ for (String intentAction
+ : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ Intent intent = new Intent(intentAction);
+ Pair<String, Boolean> extras =
+ NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+ .get(intentAction);
+ // Test only for intents with extras
+ if (extras != null) {
+ // Set the action extras to NOT trigger the avalanche strategy
+ intent.putExtra(extras.first, !extras.second);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+ }
+
+ @Test
+ public void testAvalancheStrategyTriggers_nonAvalancheIntents() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ initAttentionHelper(flagResolver);
+
+ // Broadcast intents that are not avalanche triggers
+ final Set<String> notAvalancheTriggerIntents = Set.of(
+ Intent.ACTION_USER_ADDED,
+ Intent.ACTION_SCREEN_ON,
+ Intent.ACTION_POWER_CONNECTED
+ );
+ for (String intentAction : notAvalancheTriggerIntents) {
+ Intent intent = new Intent(intentAction);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2a89b02..31d6fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3722,6 +3722,68 @@
assertFalse(ar.moveFocusableActivityToTop("test"));
}
+ @Test
+ public void testPauseConfigDispatch() throws RemoteException {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+ TYPE_BASE_APPLICATION);
+ attrs.setTitle("AppWindow");
+ final TestWindowState appWindow = createWindowState(attrs, activity);
+ activity.addWindow(appWindow);
+
+ clearInvocations(mClientLifecycleManager);
+ clearInvocations(activity);
+
+ Configuration ro = activity.getRequestedOverrideConfiguration();
+ ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200));
+ activity.onRequestedOverrideConfigurationChanged(ro);
+ activity.ensureActivityConfiguration();
+ mWm.mRoot.performSurfacePlacement();
+
+ // policy will center the bounds, so just check for matching size here.
+ assertEquals(100, activity.getWindowConfiguration().getBounds().width());
+ assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+ // No scheduled transactions since it asked for a restart.
+ verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+ verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+ assertTrue(appWindow.mResizeReported);
+
+ // act like everything drew and went idle
+ appWindow.mResizeReported = false;
+ makeLastConfigReportedToClient(appWindow, true);
+
+ // Now pause dispatch and try to resize
+ activity.pauseConfigurationDispatch();
+
+ ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200));
+ activity.onRequestedOverrideConfigurationChanged(ro);
+ activity.ensureActivityConfiguration();
+ mWm.mRoot.performSurfacePlacement();
+
+ // Activity should get new config (core-side)
+ assertEquals(130, activity.getWindowConfiguration().getBounds().width());
+ // But windows should not get new config.
+ assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+ // The client shouldn't receive any changes
+ verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+ // and lastReported shouldn't be set.
+ verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+ // There should be no resize reported to client.
+ assertFalse(appWindow.mResizeReported);
+
+ // Now resume dispatch
+ activity.resumeConfigurationDispatch();
+ mWm.mRoot.performSurfacePlacement();
+
+ // Windows and client should now receive updates
+ verify(activity, times(2)).setLastReportedConfiguration(any(), any());
+ verify(mClientLifecycleManager, times(2)).scheduleTransaction(any());
+ assertEquals(130, appWindow.getWindowConfiguration().getBounds().width());
+ assertTrue(appWindow.mResizeReported);
+ }
+
private ICompatCameraControlCallback getCompatCameraControlCallback() {
return new ICompatCameraControlCallback.Stub() {
@Override
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 6759eef..752dc5e8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4794,6 +4794,7 @@
new WindowManager.LayoutParams(TYPE_STATUS_BAR);
final Binder owner = new Binder();
attrs.gravity = android.view.Gravity.TOP;
+ attrs.height = STATUS_BAR_HEIGHT;
attrs.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
attrs.setFitInsetsTypes(0 /* types */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 7c7e562..245b2c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -444,6 +444,10 @@
// Not ready if the task is still visible when the TaskFragment becomes empty.
doReturn(true).when(task).isVisibleRequested();
assertFalse(taskFragment.isReadyToTransit());
+
+ // Ready if the mAllowTransitionWhenEmpty flag is true.
+ taskFragment.setAllowTransitionWhenEmpty(true);
+ assertTrue(taskFragment.isReadyToTransit());
}
@Test
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index fe699af..a1407869 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,7 +21,6 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
@@ -31,7 +30,6 @@
import android.os.ResultReceiver;
import android.text.TextUtils;
-import com.android.internal.telecom.ClientTransactionalServiceRepository;
import com.android.internal.telecom.ICallControl;
import com.android.server.telecom.flags.Flags;
@@ -52,20 +50,13 @@
@SuppressLint("NotCloseable")
public final class CallControl {
private static final String TAG = CallControl.class.getSimpleName();
- private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
private final String mCallId;
private final ICallControl mServerInterface;
- private final PhoneAccountHandle mPhoneAccountHandle;
- private final ClientTransactionalServiceRepository mRepository;
/** @hide */
- public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
- @NonNull ClientTransactionalServiceRepository repository,
- @NonNull PhoneAccountHandle pah) {
+ public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) {
mCallId = callId;
mServerInterface = serverInterface;
- mRepository = repository;
- mPhoneAccountHandle = pah;
}
/**
@@ -97,16 +88,14 @@
*/
public void setActive(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.setActive(mCallId,
- new CallControlResultReceiver("setActive", executor, callback));
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.setActive(mCallId,
+ new CallControlResultReceiver("setActive", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -134,16 +123,12 @@
validateVideoState(videoState);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.answer(videoState, mCallId,
- new CallControlResultReceiver("answer", executor, callback));
+ try {
+ mServerInterface.answer(videoState, mCallId,
+ new CallControlResultReceiver("answer", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -165,16 +150,14 @@
*/
public void setInactive(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.setInactive(mCallId,
- new CallControlResultReceiver("setInactive", executor, callback));
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.setInactive(mCallId,
+ new CallControlResultReceiver("setInactive", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -213,15 +196,11 @@
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
validateDisconnectCause(disconnectCause);
- if (mServerInterface != null) {
- try {
- mServerInterface.disconnect(mCallId, disconnectCause,
- new CallControlResultReceiver("disconnect", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.disconnect(mCallId, disconnectCause,
+ new CallControlResultReceiver("disconnect", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -245,15 +224,13 @@
*/
public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.startCallStreaming(mCallId,
- new CallControlResultReceiver("startCallStreaming", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.startCallStreaming(mCallId,
+ new CallControlResultReceiver("startCallStreaming", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -281,15 +258,11 @@
Objects.requireNonNull(callEndpoint);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.requestCallEndpointChange(callEndpoint,
- new CallControlResultReceiver("endpointChange", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.requestCallEndpointChange(callEndpoint,
+ new CallControlResultReceiver("requestCallEndpointChange", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -313,20 +286,16 @@
* passed that details why the operation failed.
*/
@FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
- public void setMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
+ public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.setMuteState(isMuted,
- new CallControlResultReceiver("setMuteState", executor, callback));
+ try {
+ mServerInterface.setMuteState(isMuted,
+ new CallControlResultReceiver("requestMuteState", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -352,14 +321,10 @@
public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
Objects.requireNonNull(event);
Objects.requireNonNull(extras);
- if (mServerInterface != null) {
- try {
- mServerInterface.sendEvent(mCallId, event, extras);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.sendEvent(mCallId, event, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index a089f5c..63db297 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -580,6 +580,9 @@
mExtras = phoneAccount.getExtras();
mGroupId = phoneAccount.getGroupId();
mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
+ if (phoneAccount.hasSimultaneousCallingRestriction()) {
+ mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction();
+ }
}
/**
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 71e9184..467e89c 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -208,8 +208,7 @@
if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
// create the interface object that the client will interact with
- CallControl control = new CallControl(callId, callControl, mRepository,
- mPhoneAccountHandle);
+ CallControl control = new CallControl(callId, callControl);
// give the client the object via the OR that was passed into addCall
pendingControl.onResult(control);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1badf67..a73c46b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9430,16 +9430,6 @@
"missed_incoming_call_sms_originator_string_array";
/**
- * String array of Apn Type configurations.
- * The entries should be of form "APN_TYPE_NAME:priority".
- * priority is an integer that is sorted from highest to lowest.
- * example: cbs:5
- *
- * @hide
- */
- public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
-
- /**
* Network capability priority for determine the satisfy order in telephony. The priority is
* from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority.
* This allows other short-lived requests like MMS requests to be established. Emergency request
@@ -10755,17 +10745,14 @@
TimeUnit.DAYS.toMillis(1));
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
new String[0]);
- sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
- "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
- "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
- });
// Do not modify the priority unless you know what you are doing. This will have significant
// impacts on the order of data network setup.
sDefaults.putStringArray(
KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] {
"eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50",
- "ims:40", "dun:30", "enterprise:20", "internet:20"
+ "ims:40", "rcs:40", "dun:30", "enterprise:20", "internet:20",
+ "prioritize_bandwidth:20", "prioritize_latency:20"
});
sDefaults.putStringArray(
KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
@@ -10777,9 +10764,10 @@
// registration state changes) retry can still happen.
"permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
+ "-3|65543|65547|2252|2253|2254, retry_interval=2500",
- "capabilities=mms|supl|cbs, retry_interval=2000",
- "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
- + "5000|10000|15000|20000|40000|60000|120000|240000|"
+ "capabilities=mms|supl|cbs|rcs, retry_interval=2000",
+ "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|"
+ + "prioritize_bandwidth|prioritize_latency, retry_interval="
+ + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|"
+ "600000|1200000|1800000, maximum_retries=20"
});
sDefaults.putStringArray(
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
index 1ec1d5f..2f6a361 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -15,42 +15,181 @@
*/
package com.android.hoststubgen.nativesubstitution;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
public class SystemProperties_host {
+ private static final Object sLock = new Object();
+
+ /** Active system property values */
+ @GuardedBy("sLock")
+ private static Map<String, String> sValues;
+ /** Predicate tested to determine if a given key can be read. */
+ @GuardedBy("sLock")
+ private static Predicate<String> sKeyReadablePredicate;
+ /** Predicate tested to determine if a given key can be written. */
+ @GuardedBy("sLock")
+ private static Predicate<String> sKeyWritablePredicate;
+ /** Callback to trigger when values are changed */
+ @GuardedBy("sLock")
+ private static Runnable sChangeCallback;
+
+ /**
+ * Reverse mapping that provides a way back to an original key from the
+ * {@link System#identityHashCode(Object)} of {@link String#intern}.
+ */
+ @GuardedBy("sLock")
+ private static SparseArray<String> sKeyHandles = new SparseArray<>();
+
+ public static void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback) {
+ synchronized (sLock) {
+ sValues = Objects.requireNonNull(values);
+ sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
+ sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
+ sChangeCallback = Objects.requireNonNull(changeCallback);
+ sKeyHandles.clear();
+ }
+ }
+
+ public static void native_reset$ravenwood() {
+ synchronized (sLock) {
+ sValues = null;
+ sKeyReadablePredicate = null;
+ sKeyWritablePredicate = null;
+ sChangeCallback = null;
+ sKeyHandles.clear();
+ }
+ }
+
+ public static void native_set(String key, String val) {
+ synchronized (sLock) {
+ Objects.requireNonNull(key);
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (!sKeyWritablePredicate.test(key)) {
+ throw new IllegalArgumentException(
+ "Write access to system property '" + key + "' denied via RavenwoodRule");
+ }
+ if (key.startsWith("ro.") && sValues.containsKey(key)) {
+ throw new IllegalArgumentException(
+ "System property '" + key + "' already defined once; cannot redefine");
+ }
+ if ((val == null) || val.isEmpty()) {
+ sValues.remove(key);
+ } else {
+ sValues.put(key, val);
+ }
+ sChangeCallback.run();
+ }
+ }
+
public static String native_get(String key, String def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ Objects.requireNonNull(key);
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (!sKeyReadablePredicate.test(key)) {
+ throw new IllegalArgumentException(
+ "Read access to system property '" + key + "' denied via RavenwoodRule");
+ }
+ return sValues.getOrDefault(key, def);
+ }
}
+
public static int native_get_int(String key, int def) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Integer.parseInt(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static long native_get_long(String key, long def) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Long.parseLong(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static boolean native_get_boolean(String key, boolean def) {
- throw new RuntimeException("Not implemented yet");
+ return parseBoolean(native_get(key, ""), def);
}
public static long native_find(String name) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (sValues.containsKey(name)) {
+ name = name.intern();
+ final int handle = System.identityHashCode(name);
+ sKeyHandles.put(handle, name);
+ return handle;
+ } else {
+ return 0;
+ }
+ }
}
+
public static String native_get(long handle) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get(sKeyHandles.get((int) handle), "");
+ }
}
+
public static int native_get_int(long handle, int def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_int(sKeyHandles.get((int) handle), def);
+ }
}
+
public static long native_get_long(long handle, long def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_long(sKeyHandles.get((int) handle), def);
+ }
}
+
public static boolean native_get_boolean(long handle, boolean def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_boolean(sKeyHandles.get((int) handle), def);
+ }
}
- public static void native_set(String key, String def) {
- throw new RuntimeException("Not implemented yet");
- }
+
public static void native_add_change_callback() {
- throw new RuntimeException("Not implemented yet");
+ // Ignored; callback always registered via init above
}
+
public static void native_report_sysprop_change() {
- throw new RuntimeException("Not implemented yet");
+ // Report through callback always registered via init above
+ synchronized (sLock) {
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ sChangeCallback.run();
+ }
+ }
+
+ private static boolean parseBoolean(String val, boolean def) {
+ // Matches system/libbase/include/android-base/parsebool.h
+ if (val == null) return def;
+ switch (val) {
+ case "1":
+ case "on":
+ case "true":
+ case "y":
+ case "yes":
+ return true;
+ case "0":
+ case "false":
+ case "n":
+ case "no":
+ case "off":
+ return false;
+ default:
+ return def;
+ }
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8ca4732..76bac92 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -24,6 +24,7 @@
private val classes: ClassNodes,
val aidlPolicy: FilterPolicyWithReason?,
val featureFlagsPolicy: FilterPolicyWithReason?,
+ val syspropsPolicy: FilterPolicyWithReason?,
fallback: OutputFilter
) : DelegatingFilter(fallback) {
override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -33,6 +34,9 @@
if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
return featureFlagsPolicy
}
+ if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
+ return syspropsPolicy
+ }
return super.getPolicyForClass(className)
}
}
@@ -57,3 +61,13 @@
|| className.endsWith("/FeatureFlagsImpl")
|| className.endsWith("/FakeFeatureFlagsImpl");
}
+
+/**
+ * @return if a given class "seems like" a sysprops class.
+ */
+private fun ClassNodes.isSyspropsClass(className: String): Boolean {
+ // Matches template classes defined here:
+ // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/
+ return className.startsWith("android/sysprop/")
+ && className.endsWith("Properties")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index d38a6e3..7fdd944 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,6 +64,7 @@
var aidlPolicy: FilterPolicyWithReason? = null
var featureFlagsPolicy: FilterPolicyWithReason? = null
+ var syspropsPolicy: FilterPolicyWithReason? = null
try {
BufferedReader(FileReader(filename)).use { reader ->
@@ -141,6 +142,14 @@
featureFlagsPolicy =
policy.withReason("$FILTER_REASON (feature flags)")
}
+ SpecialClass.Sysprops -> {
+ if (syspropsPolicy != null) {
+ throw ParseException(
+ "Policy for sysprops already defined")
+ }
+ syspropsPolicy =
+ policy.withReason("$FILTER_REASON (sysprops)")
+ }
}
}
}
@@ -205,10 +214,10 @@
}
var ret: OutputFilter = imf
- if (aidlPolicy != null || featureFlagsPolicy != null) {
+ if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
log.d("AndroidHeuristicsFilter enabled")
ret = AndroidHeuristicsFilter(
- classes, aidlPolicy, featureFlagsPolicy, imf)
+ classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
}
return ret
}
@@ -218,6 +227,7 @@
NotSpecial,
Aidl,
FeatureFlags,
+ Sysprops,
}
private fun resolveSpecialClass(className: String): SpecialClass {
@@ -227,6 +237,7 @@
when (className.lowercase()) {
":aidl" -> return SpecialClass.Aidl
":feature_flags" -> return SpecialClass.FeatureFlags
+ ":sysprops" -> return SpecialClass.Sysprops
}
throw ParseException("Invalid special class name \"$className\"")
}