Merge "Delete test validation" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9c93c3a..e40cbc1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3443,7 +3443,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.ActivityPolicyExemption> CREATOR;
}
- public static final class ActivityPolicyExemption.Builder {
+ @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final class ActivityPolicyExemption.Builder {
ctor public ActivityPolicyExemption.Builder();
method @NonNull public android.companion.virtual.ActivityPolicyExemption build();
method @NonNull public android.companion.virtual.ActivityPolicyExemption.Builder setComponentName(@NonNull android.content.ComponentName);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ce0d38f..8dd4adc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3676,6 +3676,7 @@
public static final class Display.Mode implements android.os.Parcelable {
ctor public Display.Mode(int, int, float);
+ method public boolean isSynthetic();
method public boolean matches(int, int, float);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e57630b..68063c4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -233,6 +233,10 @@
private static final RateLimitingCache<List<RunningAppProcessInfo>> mRunningProcessesCache =
new RateLimitingCache<>(10, 4);
+ /** Rate-Limiting Cache that allows no more than 200 calls to the service per second. */
+ private static final RateLimitingCache<List<ProcessErrorStateInfo>> mErrorProcessesCache =
+ new RateLimitingCache<>(10, 2);
+
/**
* Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
* Will be called when a Uid has become frozen or unfrozen.
@@ -3685,6 +3689,16 @@
* specified.
*/
public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+ if (Flags.rateLimitGetProcessesInErrorState()) {
+ return mErrorProcessesCache.get(() -> {
+ return getProcessesInErrorStateInternal();
+ });
+ } else {
+ return getProcessesInErrorStateInternal();
+ }
+ }
+
+ private List<ProcessErrorStateInfo> getProcessesInErrorStateInternal() {
try {
return getService().getProcessesInErrorState();
} catch (RemoteException e) {
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index d9594d3..32e6e80 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -93,3 +93,14 @@
}
}
+flag {
+ namespace: "backstage_power"
+ name: "rate_limit_get_processes_in_error_state"
+ description: "Rate limit calls to getProcessesInErrorState using a cache"
+ is_fixed_read_only: true
+ bug: "361146083"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/core/java/android/companion/virtual/ActivityPolicyExemption.java b/core/java/android/companion/virtual/ActivityPolicyExemption.java
index c81bb43..dc285d4 100644
--- a/core/java/android/companion/virtual/ActivityPolicyExemption.java
+++ b/core/java/android/companion/virtual/ActivityPolicyExemption.java
@@ -118,6 +118,7 @@
/**
* Builder for {@link ActivityPolicyExemption}.
*/
+ @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
public static final class Builder {
private @Nullable ComponentName mComponentName;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index cb57c7b..abb0d8d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3767,7 +3767,7 @@
* <p>The Intent will have the following extra value:</p>
* <ul>
* <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> -
- * the phone number originally intended to be dialed.</li>
+ * the phone number dialed.</li>
* </ul>
* <p class="note">Starting in Android 15, this broadcast is no longer sent as an ordered
* broadcast. The <code>resultData</code> no longer has any effect and will not determine the
@@ -3800,6 +3800,14 @@
* {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
* permission to receive this Intent.</p>
*
+ * <p class="note">Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this broadcast is
+ * no longer sent as an ordered broadcast, and does not allow activity launches. This means
+ * that receivers may no longer change the phone number for the outgoing call, or cancel the
+ * outgoing call. This functionality is only possible using the
+ * {@link android.telecom.CallRedirectionService} API. Although background receivers are
+ * woken up to handle this intent, no guarantee is made as to the timeliness of the broadcast.
+ * </p>
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 48d2785..a60c48e 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -80,7 +80,6 @@
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -355,14 +354,7 @@
mCameraId = cameraId;
if (Flags.singleThreadExecutor()) {
mDeviceCallback = new ClientStateCallback(executor, callback);
- mDeviceExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread thread = Executors.defaultThreadFactory().newThread(r);
- thread.setName("CameraDeviceExecutor");
- return thread;
- }
- });
+ mDeviceExecutor = Executors.newSingleThreadExecutor();
} else {
mDeviceCallback = callback;
mDeviceExecutor = executor;
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 17d2790..013ec5f 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -28,7 +28,9 @@
import android.util.Log;
import android.view.WindowManager;
+import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
@@ -52,43 +54,51 @@
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub {
- private final DreamOverlayService mService;
+ private final WeakReference<DreamOverlayService> mService;
private boolean mShowComplications;
private ComponentName mDreamComponent;
IDreamOverlayCallback mDreamOverlayCallback;
- OverlayClient(DreamOverlayService service) {
+ OverlayClient(WeakReference<DreamOverlayService> service) {
mService = service;
}
+ private void applyToDream(Consumer<DreamOverlayService> consumer) {
+ final DreamOverlayService service = mService.get();
+
+ if (service != null) {
+ consumer.accept(service);
+ }
+ }
+
@Override
public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
String dreamComponent, boolean shouldShowComplications) throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
mDreamOverlayCallback = callback;
- mService.startDream(this, params);
+ applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
}
@Override
public void wakeUp() {
- mService.wakeUp(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.wakeUp(this));
}
@Override
public void endDream() {
- mService.endDream(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.endDream(this));
}
@Override
public void comeToFront() {
- mService.comeToFront(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.comeToFront(this));
}
@Override
public void onWakeRequested() {
if (Flags.dreamWakeRedirect()) {
- mService.onWakeRequested();
+ applyToDream(DreamOverlayService::onWakeRequested);
}
}
@@ -161,17 +171,24 @@
});
}
- private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+ private static class DreamOverlay extends IDreamOverlay.Stub {
+ private final WeakReference<DreamOverlayService> mService;
+
+ DreamOverlay(DreamOverlayService service) {
+ mService = new WeakReference<>(service);
+ }
+
@Override
public void getClient(IDreamOverlayClientCallback callback) {
try {
- callback.onDreamOverlayClient(
- new OverlayClient(DreamOverlayService.this));
+ callback.onDreamOverlayClient(new OverlayClient(mService));
} catch (RemoteException e) {
Log.e(TAG, "could not send client to callback", e);
}
}
- };
+ }
+
+ private final IDreamOverlay mDreamOverlay = new DreamOverlay(this);
public DreamOverlayService() {
}
@@ -195,6 +212,12 @@
}
}
+ @Override
+ public void onDestroy() {
+ mCurrentClient = null;
+ super.onDestroy();
+ }
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 40070c7..d33c95e 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -297,3 +297,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "typeface_redesign"
+ namespace: "text"
+ description: "Decouple variation settings, weight and style information from Typeface class"
+ bug: "361260253"
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 15b0c13..1f7ed8b 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2344,6 +2344,8 @@
* SurfaceControl.DisplayMode
* @hide
*/
+ @SuppressWarnings("UnflaggedApi") // For testing only
+ @TestApi
public boolean isSynthetic() {
return mIsSynthetic;
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index ec79f94..1083f64 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -1141,7 +1141,6 @@
// Customize activity transition animation
private CustomActivityTransition mCustomActivityOpenTransition;
private CustomActivityTransition mCustomActivityCloseTransition;
- private int mUserId;
private AnimationOptions(int type) {
mType = type;
@@ -1160,7 +1159,6 @@
mAnimations = in.readInt();
mCustomActivityOpenTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
mCustomActivityCloseTransition = in.readTypedObject(CustomActivityTransition.CREATOR);
- mUserId = in.readInt();
}
/** Make basic customized animation for a package */
@@ -1285,14 +1283,6 @@
return options;
}
- public void setUserId(int userId) {
- mUserId = userId;
- }
-
- public int getUserId() {
- return mUserId;
- }
-
public int getType() {
return mType;
}
@@ -1359,7 +1349,6 @@
dest.writeInt(mAnimations);
dest.writeTypedObject(mCustomActivityOpenTransition, flags);
dest.writeTypedObject(mCustomActivityCloseTransition, flags);
- dest.writeInt(mUserId);
}
@NonNull
@@ -1417,7 +1406,6 @@
if (mExitResId != DEFAULT_ANIMATION_RESOURCES_ID) {
sb.append(" exitResId=").append(mExitResId);
}
- sb.append(" mUserId=").append(mUserId);
sb.append('}');
return sb.toString();
}
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 201f267..238e6f5 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -49,7 +49,7 @@
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
-import android.os.UserHandle;
+import android.os.SystemProperties;
import android.util.Slog;
import android.view.InflateException;
import android.view.SurfaceControl;
@@ -187,44 +187,23 @@
return createHiddenByKeyguardExit(mContext, mInterpolator, onWallpaper, toShade, subtle);
}
- /** Load keyguard unocclude animation for user. */
- @Nullable
- public Animation loadKeyguardUnoccludeAnimation(int userId) {
- return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit, userId);
- }
-
- /** Same as {@code loadKeyguardUnoccludeAnimation} for current user. */
@Nullable
public Animation loadKeyguardUnoccludeAnimation() {
- return loadKeyguardUnoccludeAnimation(UserHandle.USER_CURRENT);
+ return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit);
}
- /** Load voice activity open animation for user. */
- @Nullable
- public Animation loadVoiceActivityOpenAnimation(boolean enter, int userId) {
- return loadDefaultAnimationRes(enter
- ? com.android.internal.R.anim.voice_activity_open_enter
- : com.android.internal.R.anim.voice_activity_open_exit, userId);
- }
-
- /** Same as {@code loadVoiceActivityOpenAnimation} for current user. */
@Nullable
public Animation loadVoiceActivityOpenAnimation(boolean enter) {
- return loadVoiceActivityOpenAnimation(enter, UserHandle.USER_CURRENT);
- }
-
- /** Load voice activity exit animation for user. */
- @Nullable
- public Animation loadVoiceActivityExitAnimation(boolean enter, int userId) {
return loadDefaultAnimationRes(enter
- ? com.android.internal.R.anim.voice_activity_close_enter
- : com.android.internal.R.anim.voice_activity_close_exit, userId);
+ ? com.android.internal.R.anim.voice_activity_open_enter
+ : com.android.internal.R.anim.voice_activity_open_exit);
}
- /** Same as {@code loadVoiceActivityExitAnimation} for current user. */
@Nullable
public Animation loadVoiceActivityExitAnimation(boolean enter) {
- return loadVoiceActivityExitAnimation(enter, UserHandle.USER_CURRENT);
+ return loadDefaultAnimationRes(enter
+ ? com.android.internal.R.anim.voice_activity_close_enter
+ : com.android.internal.R.anim.voice_activity_close_exit);
}
@Nullable
@@ -232,17 +211,10 @@
return loadAnimationRes(packageName, resId);
}
- /** Load cross profile app enter animation for user. */
- @Nullable
- public Animation loadCrossProfileAppEnterAnimation(int userId) {
- return loadAnimationRes(DEFAULT_PACKAGE,
- com.android.internal.R.anim.task_open_enter_cross_profile_apps, userId);
- }
-
- /** Same as {@code loadCrossProfileAppEnterAnimation} for current user. */
@Nullable
public Animation loadCrossProfileAppEnterAnimation() {
- return loadCrossProfileAppEnterAnimation(UserHandle.USER_CURRENT);
+ return loadAnimationRes(DEFAULT_PACKAGE,
+ com.android.internal.R.anim.task_open_enter_cross_profile_apps);
}
@Nullable
@@ -258,11 +230,11 @@
appRect.height(), 0, null);
}
- /** Load animation by resource Id from specific package for user. */
+ /** Load animation by resource Id from specific package. */
@Nullable
- public Animation loadAnimationRes(String packageName, int resId, int userId) {
+ public Animation loadAnimationRes(String packageName, int resId) {
if (ResourceId.isValid(resId)) {
- AttributeCache.Entry ent = getCachedAnimations(packageName, resId, userId);
+ AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
if (ent != null) {
return loadAnimationSafely(ent.context, resId, mTag);
}
@@ -270,22 +242,10 @@
return null;
}
- /** Same as {@code loadAnimationRes} for current user. */
- @Nullable
- public Animation loadAnimationRes(String packageName, int resId) {
- return loadAnimationRes(packageName, resId, UserHandle.USER_CURRENT);
- }
-
- /** Load animation by resource Id from android package for user. */
- @Nullable
- public Animation loadDefaultAnimationRes(int resId, int userId) {
- return loadAnimationRes(DEFAULT_PACKAGE, resId, userId);
- }
-
- /** Same as {@code loadDefaultAnimationRes} for current user. */
+ /** Load animation by resource Id from android package. */
@Nullable
public Animation loadDefaultAnimationRes(int resId) {
- return loadAnimationRes(DEFAULT_PACKAGE, resId, UserHandle.USER_CURRENT);
+ return loadAnimationRes(DEFAULT_PACKAGE, resId);
}
/** Load animation by attribute Id from specific LayoutParams */
@@ -418,10 +378,10 @@
}
@Nullable
- private AttributeCache.Entry getCachedAnimations(String packageName, int resId, int userId) {
+ private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
if (mDebug) {
- Slog.v(mTag, "Loading animations: package=" + packageName + " resId=0x"
- + Integer.toHexString(resId) + " for user=" + userId);
+ Slog.v(mTag, "Loading animations: package="
+ + packageName + " resId=0x" + Integer.toHexString(resId));
}
if (packageName != null) {
if ((resId & 0xFF000000) == 0x01000000) {
@@ -432,16 +392,11 @@
+ packageName);
}
return AttributeCache.instance().get(packageName, resId,
- com.android.internal.R.styleable.WindowAnimation, userId);
+ com.android.internal.R.styleable.WindowAnimation);
}
return null;
}
- @Nullable
- private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
- return getCachedAnimations(packageName, resId, UserHandle.USER_CURRENT);
- }
-
/** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
public int getAnimationStyleResId(@NonNull LayoutParams lp) {
int resId = lp.windowAnimations;
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index b82c660..34e0418 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -40,6 +40,8 @@
*/
@Deprecated
public class LogcatOnlyProtoLogImpl implements IProtoLog {
+ private static final String LOG_TAG = LogcatOnlyProtoLogImpl.class.getName();
+
@Override
public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
Object[] args) {
@@ -48,19 +50,21 @@
@Override
public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
- if (REQUIRE_PROTOLOGTOOL) {
- throw new RuntimeException(
- "REQUIRE_PROTOLOGTOOL not set to false before the first log call.");
+ if (REQUIRE_PROTOLOGTOOL && group.isLogToProto()) {
+ Log.w(LOG_TAG, "ProtoLog message not processed. Failed to log it to proto. "
+ + "Logging it below to logcat instead.");
}
- String formattedString = TextUtils.formatSimple(messageString, args);
- switch (logLevel) {
- case VERBOSE -> Log.v(group.getTag(), formattedString);
- case INFO -> Log.i(group.getTag(), formattedString);
- case DEBUG -> Log.d(group.getTag(), formattedString);
- case WARN -> Log.w(group.getTag(), formattedString);
- case ERROR -> Log.e(group.getTag(), formattedString);
- case WTF -> Log.wtf(group.getTag(), formattedString);
+ if (group.isLogToLogcat() || group.isLogToProto()) {
+ String formattedString = TextUtils.formatSimple(messageString, args);
+ switch (logLevel) {
+ case VERBOSE -> Log.v(group.getTag(), formattedString);
+ case INFO -> Log.i(group.getTag(), formattedString);
+ case DEBUG -> Log.d(group.getTag(), formattedString);
+ case WARN -> Log.w(group.getTag(), formattedString);
+ case ERROR -> Log.e(group.getTag(), formattedString);
+ case WTF -> Log.wtf(group.getTag(), formattedString);
+ }
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 660d3c9..bf77db7 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -63,6 +63,9 @@
* @param groups The ProtoLog groups that will be used in the process.
*/
public static void init(IProtoLogGroup... groups) {
+ // These tracing instances are only used when we cannot or do not preprocess the source
+ // files to extract out the log strings. Otherwise, the trace calls are replaced with calls
+ // directly to the generated tracing implementations.
if (android.tracing.Flags.perfettoProtologTracing()) {
synchronized (sInitLock) {
if (sProtoLogInstance != null) {
@@ -76,8 +79,6 @@
sProtoLogInstance = new PerfettoProtoLogImpl(groups);
}
} else {
- // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
- // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
sProtoLogInstance = new LogcatOnlyProtoLogImpl();
}
}
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 50832a5..8dd63cc 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -256,9 +256,21 @@
}
}
-static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
- jint texName, jboolean singleBufferMode, jobject weakThiz)
-{
+static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, jint texName,
+ jboolean singleBufferMode, jobject weakThiz) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<SurfaceTexture> surfaceTexture;
+ if (isDetached) {
+ surfaceTexture = new SurfaceTexture(GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+ } else {
+ surfaceTexture =
+ new SurfaceTexture(texName, GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+ }
+
+ if (singleBufferMode) {
+ surfaceTexture->setMaxBufferCount(1);
+ }
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -275,6 +287,7 @@
surfaceTexture = new SurfaceTexture(consumer, texName,
GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
if (surfaceTexture == 0) {
jniThrowException(env, OutOfResourcesException,
@@ -287,11 +300,27 @@
createProcessUniqueId()));
// If the current context is protected, inform the producer.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ surfaceTexture->setConsumerIsProtected(isProtectedContext());
+
+ SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
+ sp<Surface> surface = surfaceTexture->getSurface();
+ if (nullptr == surface) {
+ jniThrowException(env, IllegalStateException, "Unable to get surface from SurfaceTexture");
+ return;
+ }
+ sp<IGraphicBufferProducer> igbp = surface->getIGraphicBufferProducer();
+ if (nullptr == igbp) {
+ jniThrowException(env, IllegalStateException, "Unable to get IGBP from Surface");
+ return;
+ }
+ SurfaceTexture_setProducer(env, thiz, igbp);
+#else
consumer->setConsumerIsProtected(isProtectedContext());
SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
SurfaceTexture_setProducer(env, thiz, producer);
-
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
jniThrowRuntimeException(env,
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 7bc0d2f..ce7e858 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -69,6 +69,7 @@
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
@@ -136,7 +137,7 @@
mTestHandler = new TestHandler(null, mTestClock);
mTestHost = spy(new TestHost(mViewRoot));
mController = new InsetsController(mTestHost, (controller, id, type) -> {
- if (type == ime()) {
+ if (!Flags.refactorInsetsController() && type == ime()) {
return new InsetsSourceConsumer(id, type, controller.getState(),
Transaction::new, controller) {
@@ -260,7 +261,11 @@
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
// When using the animation thread, this must not invoke onReady()
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
});
@@ -277,7 +282,12 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
mController.show(all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
@@ -299,7 +309,11 @@
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations();
assertTrue(isRequestedVisible(mController, ime()));
mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty());
@@ -469,7 +483,12 @@
assertFalse(mController.getState().peekSource(ID_IME).isVisible());
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
// Gaining control shortly after
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
@@ -493,7 +512,12 @@
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
mController.cancelExistingAnimations();
@@ -558,7 +582,13 @@
@Test
public void testControlImeNotReady() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -571,7 +601,13 @@
verify(listener, never()).onReady(any(), anyInt());
// Pretend that IME is calling.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ // Send the IME control with leash, so that the animation can start
+ InsetsSourceControl ime = createControl(ID_IME, ime(), true /* hasLeash */);
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
// Ready gets deferred until next predraw
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -583,7 +619,13 @@
@Test
public void testControlImeNotReady_controlRevoked() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -604,7 +646,13 @@
@Test
public void testControlImeNotReady_timeout() {
- prepareControls();
+ if (!Flags.refactorInsetsController()) {
+ prepareControls();
+ } else {
+ // With the flag on, the IME control should not contain a leash, otherwise the custom
+ // animation will start immediately.
+ prepareControls(false /* imeControlHasLeash */);
+ }
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener listener =
mock(WindowInsetsAnimationControlListener.class);
@@ -655,7 +703,11 @@
mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
// Pretend IME is calling
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
@@ -886,7 +938,11 @@
// Showing invisible ime should only causes insets change once.
clearInvocations(mTestHost);
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
verify(mTestHost, times(1)).notifyInsetsChanged();
// Sending the same insets state should not cause insets change.
@@ -953,7 +1009,11 @@
assertNull(imeInsetsConsumer.getControl());
// Verify IME requested visibility should be updated to IME consumer from controller.
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
assertTrue(isRequestedVisible(mController, ime()));
mController.hide(ime());
@@ -966,7 +1026,11 @@
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -990,8 +1054,13 @@
public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() {
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ InsetsSourceControl ime = createControl(ID_IME, ime());
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1008,12 +1077,20 @@
assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
// verify show request is ignored during pre commit phase of predictive back anim
- mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ } else {
+ mController.onControlsChanged(new InsetsSourceControl[]{ime});
+ }
assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
// verify show request is applied during post commit phase of predictive back anim
mController.setPredictiveBackImeHideAnimInProgress(true);
- mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, null /* statsToken */);
+ } else {
+ mController.show(ime(), false /* fromIme */, null /* statsToken */);
+ }
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
// additionally verify that IME ends up visible
@@ -1027,7 +1104,11 @@
prepareControls();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// show ime as initial state
- mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ if (!Flags.refactorInsetsController()) {
+ mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+ } else {
+ mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+ }
mController.cancelExistingAnimations(); // fast forward show animation
mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1058,11 +1139,15 @@
}
private InsetsSourceControl createControl(int id, @InsetsType int type) {
+ return createControl(id, type, true);
+ }
+
+ private InsetsSourceControl createControl(int id, @InsetsType int type, boolean hasLeash) {
// Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
// attempt to release mLeash directly.
SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
- return new InsetsSourceControl(id, type, copy,
+ return new InsetsSourceControl(id, type, hasLeash ? copy : null,
(type & WindowInsets.Type.defaultVisible()) != 0, new Point(), Insets.NONE);
}
@@ -1071,9 +1156,13 @@
}
private InsetsSourceControl[] prepareControls() {
+ return prepareControls(true);
+ }
+
+ private InsetsSourceControl[] prepareControls(boolean imeControlHasLeash) {
final InsetsSourceControl navBar = createControl(ID_NAVIGATION_BAR, navigationBars());
final InsetsSourceControl statusBar = createControl(ID_STATUS_BAR, statusBars());
- final InsetsSourceControl ime = createControl(ID_IME, ime());
+ final InsetsSourceControl ime = createControl(ID_IME, ime(), imeControlHasLeash);
InsetsSourceControl[] controls = new InsetsSourceControl[3];
controls[0] = navBar;
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1a3aa8e..b338a2a 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,9 +46,6 @@
srcs: [
"src/com/android/wm/shell/common/bubbles/*.kt",
"src/com/android/wm/shell/common/bubbles/*.java",
- "src/com/android/wm/shell/common/desktopmode/*.kt",
- "src/com/android/wm/shell/pip/PipContentOverlay.java",
- "src/com/android/wm/shell/util/**/*.java",
],
path: "src",
}
@@ -208,6 +205,7 @@
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
":wm_shell-sources-kt",
":wm_shell-aidls",
+ ":wm_shell-shared-aidls",
],
resource_dirs: [
"res",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
similarity index 86%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
index c968e80..e21bf8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared;
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
index a2d2b9a..65e079e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
import android.annotation.IntDef;
import android.app.ActivityManager;
@@ -25,6 +25,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.wm.shell.shared.split.SplitBounds;
+
import java.util.Arrays;
import java.util.List;
import java.util.Set;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
index c968e80..f7ddf71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared.desktopmode;
parcelable DesktopModeTransitionSource;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
index dbbf1786..d15fbed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode
+package com.android.wm.shell.shared.desktopmode
import android.os.Parcel
import android.os.Parcelable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index ff2d46e..cf39415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip;
+package com.android.wm.shell.shared.pip;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
index 88b7528..7c1faa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared.split;
import android.graphics.Rect;
import android.os.Parcel;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3dc33c2..b508c1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1225,7 +1225,7 @@
mBubblePositioner.setBubbleBarLocation(location);
mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
if (mBubbleData.getSelectedBubble() != null) {
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ showExpandedViewForBubbleBar();
}
}
@@ -1243,7 +1243,7 @@
}
if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
// We did not remove the selected bubble. Expand it again
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ showExpandedViewForBubbleBar();
}
}
@@ -1997,15 +1997,10 @@
@Override
public void expansionChanged(boolean isExpanded) {
- if (mLayerView != null) {
- if (!isExpanded) {
- mLayerView.collapse();
- } else {
- BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
- if (selectedBubble != null) {
- mLayerView.showExpandedView(selectedBubble);
- }
- }
+ // in bubble bar mode, let the request to show the expanded view come from launcher.
+ // only collapse here if we're collapsing.
+ if (mLayerView != null && !isExpanded) {
+ mLayerView.collapse();
}
}
@@ -2151,6 +2146,13 @@
}
};
+ private void showExpandedViewForBubbleBar() {
+ BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+ if (selectedBubble != null && mLayerView != null) {
+ mLayerView.showExpandedView(selectedBubble);
+ }
+ }
+
private void updateOverflowButtonDot() {
BubbleOverflow overflow = mBubbleData.getOverflow();
if (overflow == null) return;
@@ -2532,6 +2534,15 @@
if (mLayerView != null) mLayerView.updateExpandedView();
});
}
+
+ @Override
+ public void showExpandedView() {
+ mMainExecutor.execute(() -> {
+ if (mLayerView != null) {
+ showExpandedViewForBubbleBar();
+ }
+ });
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5c78974..5779a8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -53,4 +53,6 @@
oneway void showShortcutBubble(in ShortcutInfo info) = 12;
oneway void showAppBubble(in Intent intent) = 13;
+
+ oneway void showExpandedView() = 14;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 31c8f1e..cca7500 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,8 +18,8 @@
import android.graphics.Region;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index eca3c1f..dba8c93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.desktopmode
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.sysui.ShellCommandHandler
import java.io.PrintWriter
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
index b24bd10..d6fccd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.desktopmode
import android.view.WindowManager.TransitionType
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TYPES
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index ffd534b..2852631 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -69,7 +69,6 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -89,6 +88,7 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreenController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 04506c1..80e106f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -41,7 +41,7 @@
import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 171378f..e87be52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -44,7 +44,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index a7ec203..b036e40e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -18,8 +18,8 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.window.RemoteTransition;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
/**
* Interface that is exposed to remote callers to manipulate desktop mode features.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b0c896f..4df649c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -43,6 +43,7 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.transition.Transitions;
import java.lang.annotation.Retention;
@@ -418,7 +419,7 @@
}
SurfaceControl getContentOverlayLeash() {
- return mContentOverlay == null ? null : mContentOverlay.mLeash;
+ return mContentOverlay == null ? null : mContentOverlay.getLeash();
}
void setColorContentOverlay(Context context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 2de545a..e4cd10f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -91,6 +91,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -361,8 +362,7 @@
SurfaceControl mPipOverlay;
/**
- * The app bounds used for the buffer size of the
- * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+ * The app bounds used for the buffer size of the {@link PipContentOverlay.PipAppIconOverlay}.
*
* Note that this is empty if the overlay is removed or if it's some other type of overlay
* defined in {@link PipContentOverlay}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index b102e40..05d1984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -75,6 +75,7 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.CounterRotatorHelper;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index ed18712..c6b1a72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -50,10 +50,10 @@
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipContentOverlay;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 4048c5b..ebfd357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@
import android.view.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
/**
* Interface that is exposed to remote callers to fetch recent tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 77b8663..8c5d1e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,8 +19,8 @@
import android.annotation.Nullable;
import android.graphics.Color;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.util.List;
import java.util.concurrent.Executor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 2f0af855..39bea1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -51,16 +51,16 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
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 7a9eb1c..c90da05 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
@@ -30,7 +30,7 @@
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
-import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0b5c751..95f864a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -133,13 +133,13 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.SplitBounds;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9b0fb20..4fc6c44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -18,8 +18,8 @@
import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
+import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -473,7 +473,7 @@
change.getLeash(),
startTransaction);
} else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
- && TransitionUtil.isClosingType(mode)) {
+ && TransitionUtil.isClosingType(mode)) {
// If there is a closing translucent task in an OPENING transition, we will
// actually select a CLOSING animation, so move the closing task into
// the animating part of the z-order.
@@ -767,12 +767,12 @@
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
(changeFlags & FLAG_SHOW_WALLPAPER) != 0);
} else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
- a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(options.getUserId());
+ a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
} else if ((changeFlags & FLAG_IS_VOICE_INTERACTION) != 0) {
if (isOpeningType) {
- a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter, options.getUserId());
+ a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
} else {
- a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter, options.getUserId());
+ a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
}
} else if (changeMode == TRANSIT_CHANGE) {
// In the absence of a specific adapter, we just want to keep everything stationary.
@@ -783,9 +783,9 @@
} else if (overrideType == ANIM_CUSTOM
&& (!isTask || options.getOverrideTaskTransition())) {
a = mTransitionAnimation.loadAnimationRes(options.getPackageName(), enter
- ? options.getEnterResId() : options.getExitResId(), options.getUserId());
+ ? options.getEnterResId() : options.getExitResId());
} else if (overrideType == ANIM_OPEN_CROSS_PROFILE_APPS && enter) {
- a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(options.getUserId());
+ a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
} else if (overrideType == ANIM_CLIP_REVEAL) {
a = mTransitionAnimation.createClipRevealAnimationLocked(type, wallpaperTransit, enter,
endBounds, endBounds, options.getTransitionBounds());
@@ -905,9 +905,9 @@
final Rect bounds = change.getEndAbsBounds();
// Show the right drawable depending on the user we're transitioning to.
final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)
- ? mContext.getDrawable(R.drawable.ic_account_circle)
- : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
- ? mEnterpriseThumbnailDrawable : null;
+ ? mContext.getDrawable(R.drawable.ic_account_circle)
+ : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
+ ? mEnterpriseThumbnailDrawable : null;
if (thumbnailDrawable == null) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
deleted file mode 100644
index 15797cd..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.util;
-
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
deleted file mode 100644
index 482aaab..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file KtProtolog.kt = file:platform/development:/tools/winscope/OWNERS
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 20a406f..1f95667 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -97,7 +97,6 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -107,6 +106,7 @@
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 8e87d0f..2bec3fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -26,7 +26,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
-import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -79,10 +79,10 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 114c331..deef378 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -318,7 +318,7 @@
rootView.setOnTouchListener { _, event ->
if (event.actionMasked == ACTION_OUTSIDE) {
onOutsideTouchListener?.invoke()
- false
+ return@setOnTouchListener false
}
true
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
index 518c00d..db4e93d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
@@ -18,11 +18,6 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.TASK_DRAG
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
@@ -33,6 +28,11 @@
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 058a26a..d248720 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -83,7 +83,6 @@
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
@@ -94,6 +93,7 @@
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e5157c9..e0463b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -48,7 +48,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 6736593..0c3f98a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -24,13 +24,13 @@
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e1fe4e9..a8d40db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -68,13 +68,13 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
import org.junit.After;
import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index bfb760b..248393c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -12,7 +12,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.shared.split.SplitBounds;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index f7ac3e4..da0aca7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -79,12 +79,12 @@
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksLimiter
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ac75c07..c1c30f5 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -49,6 +49,15 @@
#endif // __ANDROID__
}
+inline bool typeface_redesign() {
+#ifdef __ANDROID__
+ static bool flag = com_android_text_flags_typeface_redesign();
+ return flag;
+#else
+ return true;
+#endif // __ANDROID__
+}
+
} // namespace text_feature
} // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index f8574ee..1510ce1 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -27,6 +27,8 @@
#include <cutils/compiler.h>
#include <log/log.h>
#include <minikin/Layout.h>
+
+#include "FeatureFlags.h"
#include "MinikinSkia.h"
#include "Paint.h"
#include "Typeface.h"
@@ -71,27 +73,42 @@
static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
float saveSkewX = paint->getSkFont().getSkewX();
bool savefakeBold = paint->getSkFont().isEmbolden();
- const minikin::MinikinFont* curFont = nullptr;
- size_t start = 0;
- size_t nGlyphs = layout.nGlyphs();
- for (size_t i = 0; i < nGlyphs; i++) {
- const minikin::MinikinFont* nextFont = layout.typeface(i).get();
- if (i > 0 && nextFont != curFont) {
+ if (text_feature::typeface_redesign()) {
+ for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
+ uint32_t start = layout.getFontRunStart(runIdx);
+ uint32_t end = layout.getFontRunEnd(runIdx);
+ const minikin::FakedFont& fakedFont = layout.getFontRunFont(runIdx);
+
+ std::shared_ptr<minikin::MinikinFont> font = fakedFont.typeface();
SkFont* skfont = &paint->getSkFont();
- MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
- f(start, i);
+ MinikinFontSkia::populateSkFont(skfont, font.get(), fakedFont.fakery);
+ f(start, end);
skfont->setSkewX(saveSkewX);
skfont->setEmbolden(savefakeBold);
- start = i;
}
- curFont = nextFont;
- }
- if (nGlyphs > start) {
- SkFont* skfont = &paint->getSkFont();
- MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
- f(start, nGlyphs);
- skfont->setSkewX(saveSkewX);
- skfont->setEmbolden(savefakeBold);
+ } else {
+ const minikin::MinikinFont* curFont = nullptr;
+ size_t start = 0;
+ size_t nGlyphs = layout.nGlyphs();
+ for (size_t i = 0; i < nGlyphs; i++) {
+ const minikin::MinikinFont* nextFont = layout.typeface(i).get();
+ if (i > 0 && nextFont != curFont) {
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+ f(start, i);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
+ start = i;
+ }
+ curFont = nextFont;
+ }
+ if (nGlyphs > start) {
+ SkFont* skfont = &paint->getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+ f(start, nGlyphs);
+ skfont->setSkewX(saveSkewX);
+ skfont->setEmbolden(savefakeBold);
+ }
}
}
};
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index fd596d9..e427c97 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -16,6 +16,7 @@
#include "tests/common/TestContext.h"
+#include <com_android_graphics_libgui_flags.h>
#include <cutils/trace.h>
namespace android {
@@ -101,6 +102,14 @@
}
void TestContext::createOffscreenSurface() {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ mConsumer = new BufferItemConsumer(GRALLOC_USAGE_HW_COMPOSER, 4);
+ const ui::Size& resolution = getActiveDisplayResolution();
+ mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
+ mSurface = mConsumer->getSurface();
+ mSurface->setMaxDequeuedBufferCount(3);
+ mSurface->setAsyncMode(true);
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -110,6 +119,7 @@
const ui::Size& resolution = getActiveDisplayResolution();
mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
mSurface = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
}
void TestContext::waitForVsync() {
@@ -144,4 +154,4 @@
} // namespace test
} // namespace uirenderer
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 371e3d2..019b1e0 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -17,35 +17,31 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "ImageReader_JNI"
#define ATRACE_TAG ATRACE_TAG_CAMERA
-#include "android_media_Utils.h"
+#include <android/hardware_buffer_jni.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android_runtime/android_view_Surface.h>
+#include <com_android_graphics_libgui_flags.h>
#include <cutils/atomic.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
+#include <grallocusage/GrallocUsageConversion.h>
+#include <gui/BufferItemConsumer.h>
+#include <gui/Surface.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <stdint.h>
+#include <ui/Rect.h>
#include <utils/List.h>
-#include <utils/Trace.h>
+#include <utils/Log.h>
#include <utils/String8.h>
+#include <utils/Trace.h>
+#include <utils/misc.h>
#include <cstdio>
-#include <gui/BufferItemConsumer.h>
-#include <gui/Surface.h>
-
-#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/android_view_Surface.h>
-#include <android_runtime/android_graphics_GraphicBuffer.h>
-#include <android_runtime/android_hardware_HardwareBuffer.h>
-#include <grallocusage/GrallocUsageConversion.h>
-
-#include <private/android/AHardwareBufferHelpers.h>
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-#include <android/hardware_buffer_jni.h>
-
-#include <ui/Rect.h>
+#include "android_media_Utils.h"
#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext"
#define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID "mNativeBuffer"
@@ -393,18 +389,25 @@
}
sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages));
- sp<IGraphicBufferProducer> gbProducer;
- sp<IGraphicBufferConsumer> gbConsumer;
- BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
- sp<BufferItemConsumer> bufferConsumer;
String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
width, height, nativeHalFormat, maxImages, getpid(),
createProcessUniqueId());
uint64_t consumerUsage =
android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<BufferItemConsumer> bufferConsumer = new BufferItemConsumer(consumerUsage, maxImages,
+ /*controlledByApp*/ true);
+ sp<IGraphicBufferProducer> gbProducer =
+ bufferConsumer->getSurface()->getIGraphicBufferProducer();
+#else
+ sp<IGraphicBufferProducer> gbProducer;
+ sp<IGraphicBufferConsumer> gbConsumer;
+ BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
+ sp<BufferItemConsumer> bufferConsumer;
bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
/*controlledByApp*/true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
if (bufferConsumer == nullptr) {
jniThrowExceptionFmt(env, "java/lang/RuntimeException",
"Failed to allocate native buffer consumer for hal format 0x%x and usage 0x%x",
@@ -413,7 +416,11 @@
}
if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ bufferConsumer->setConsumerIsProtected(true);
+#else
gbConsumer->setConsumerIsProtected(true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
}
ctx->setBufferConsumer(bufferConsumer);
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index 1bb82f8..4637ccd 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -15,21 +15,23 @@
*/
// #define LOG_NDEBUG 0
-#include "base/logging.h"
-#include "base/utilities.h"
#include "core/gl_env.h"
-#include "core/shader_program.h"
-#include "core/vertex_frame.h"
-#include "system/window.h"
+
+#include <EGL/eglext.h>
+#include <com_android_graphics_libgui_flags.h>
+#include <gui/BufferQueue.h>
+#include <gui/GLConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
#include <map>
#include <string>
-#include <EGL/eglext.h>
-#include <gui/BufferQueue.h>
-#include <gui/Surface.h>
-#include <gui/GLConsumer.h>
-#include <gui/IGraphicBufferProducer.h>
+#include "base/logging.h"
+#include "base/utilities.h"
+#include "core/shader_program.h"
+#include "core/vertex_frame.h"
+#include "system/window.h"
namespace android {
namespace filterfw {
@@ -165,12 +167,18 @@
}
// Create dummy surface using a GLConsumer
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ surfaceTexture_ = new GLConsumer(0, GLConsumer::TEXTURE_EXTERNAL, /*useFenceSync=*/true,
+ /*isControlledByApp=*/false);
+ window_ = surfaceTexture_->getSurface();
+#else
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
surfaceTexture_ = new GLConsumer(consumer, 0, GLConsumer::TEXTURE_EXTERNAL,
true, false);
window_ = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
if (CheckEGLError("eglCreateWindowSurface")) return false;
diff --git a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
index ac47fbd..391b16d 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
+++ b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
@@ -23,7 +23,6 @@
import android.app.Activity;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
@@ -38,9 +37,7 @@
import com.android.egg.R;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.stream.IntStream;
public class PaintActivity extends Activity {
@@ -60,31 +57,28 @@
private View.OnClickListener buttonHandler = new View.OnClickListener() {
@Override
public void onClick(View view) {
- switch (view.getId()) {
- case R.id.btnBrush:
- view.setSelected(true);
- hideToolbar(colors);
- toggleToolbar(brushes);
- break;
- case R.id.btnColor:
- view.setSelected(true);
- hideToolbar(brushes);
- toggleToolbar(colors);
- break;
- case R.id.btnClear:
- painting.clear();
- break;
- case R.id.btnSample:
- sampling = true;
- view.setSelected(true);
- break;
- case R.id.btnZen:
- painting.setZenMode(!painting.getZenMode());
- view.animate()
- .setStartDelay(200)
- .setInterpolator(new OvershootInterpolator())
- .rotation(painting.getZenMode() ? 0f : 90f);
- break;
+ // With non final fields in the R class we can't switch on the
+ // id since the case values are no longer constants.
+ int viewId = view.getId();
+ if (viewId == R.id.btnBrush) {
+ view.setSelected(true);
+ hideToolbar(colors);
+ toggleToolbar(brushes);
+ } else if (viewId == R.id.btnColor) {
+ view.setSelected(true);
+ hideToolbar(brushes);
+ toggleToolbar(colors);
+ } else if (viewId == R.id.btnClear) {
+ painting.clear();
+ } else if (viewId == R.id.btnSample) {
+ sampling = true;
+ view.setSelected(true);
+ } else if (viewId == R.id.btnZen) {
+ painting.setZenMode(!painting.getZenMode());
+ view.animate()
+ .setStartDelay(200)
+ .setInterpolator(new OvershootInterpolator())
+ .rotation(painting.getZenMode() ? 0f : 90f);
}
}
};
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d0fb279..a129ac1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1384,3 +1384,13 @@
}
}
+flag {
+ name: "media_load_metadata_via_media_data_loader"
+ namespace: "systemui"
+ description: "Use MediaDataLoader for loading media metadata with better threading"
+ bug: "358350077"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
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 91a88bc..6f415ea 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
@@ -1154,7 +1154,7 @@
.then(selectableModifier)
.thenIf(!viewModel.isEditMode && !model.inQuietMode) {
Modifier.pointerInput(Unit) {
- observeTaps { viewModel.onTapWidget(model.componentName, model.priority) }
+ observeTaps { viewModel.onTapWidget(model.componentName, model.rank) }
}
}
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 38a3474..1137357 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -34,11 +34,11 @@
return remember(communalContent) {
ContentListState(
communalContent,
- { componentName, user, priority ->
+ { componentName, user, rank ->
viewModel.onAddWidget(
componentName,
user,
- priority,
+ rank,
widgetConfigurator,
)
},
@@ -56,10 +56,9 @@
class ContentListState
internal constructor(
communalContent: List<CommunalContentModel>,
- private val onAddWidget:
- (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit,
- private val onDeleteWidget: (id: Int, componentName: ComponentName, priority: Int) -> Unit,
- private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
+ private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
+ private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
+ private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
) {
var list = communalContent.toMutableStateList()
private set
@@ -74,7 +73,7 @@
if (list[indexToRemove].isWidgetContent()) {
val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
list.apply { removeAt(indexToRemove) }
- onDeleteWidget(widget.appWidgetId, widget.componentName, widget.priority)
+ onDeleteWidget(widget.appWidgetId, widget.componentName, widget.rank)
}
}
@@ -94,24 +93,24 @@
newItemUser: UserHandle? = null,
newItemIndex: Int? = null
) {
- // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
- // in the list. When persisted in DB, this leaves space for the new item (to be added) at
- // the correct priority.
- val widgetIdToPriorityMap: Map<Int, Int> =
+ // New widget added to the grid. Other widgets are shifted as needed at the database level.
+ if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
+ onAddWidget(newItemComponentName, newItemUser, /* rank= */ newItemIndex)
+ return
+ }
+
+ // No new widget, only reorder existing widgets.
+ val widgetIdToRankMap: Map<Int, Int> =
list
.mapIndexedNotNull { index, item ->
if (item is CommunalContentModel.WidgetContent) {
- item.appWidgetId to list.size - index
+ item.appWidgetId to index
} else {
null
}
}
.toMap()
- // reorder and then add the new widget
- onReorderWidgets(widgetIdToPriorityMap)
- if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
- onAddWidget(newItemComponentName, newItemUser, /* priority= */ list.size - newItemIndex)
- }
+ onReorderWidgets(widgetIdToRankMap)
}
/** Returns true if the item at given index is editable. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 0c29394..f2f7c87 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -193,7 +193,7 @@
val widgetExtra = event.maybeWidgetExtra() ?: return false
val (componentName, user) = widgetExtra
if (componentName != null && user != null) {
- // Placeholder isn't removed yet to allow the setting the right priority for items
+ // Placeholder isn't removed yet to allow the setting the right rank for items
// before adding in the new item.
contentListState.onSaveList(
newItemComponentName = componentName,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index 2bc8c87..b166737 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -26,15 +26,15 @@
import kotlinx.coroutines.launch
internal fun CoroutineScope.animateContent(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
transition: TransitionState.Transition,
oneOffAnimation: OneOffAnimation,
targetProgress: Float,
- startTransition: () -> Unit,
- finishTransition: () -> Unit,
+ chain: Boolean = true,
) {
// Start the transition. This will compute the TransformationSpec associated to [transition],
// which we need to initialize the Animatable that will actually animate it.
- startTransition()
+ layoutState.startTransition(transition, chain)
// The transition now contains the transformation spec that we should use to instantiate the
// Animatable.
@@ -59,7 +59,7 @@
try {
animatable.animateTo(targetProgress, animationSpec, initialVelocity)
} finally {
- finishTransition()
+ layoutState.finishTransition(transition)
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
new file mode 100644
index 0000000..e020f14
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import com.android.compose.animation.scene.content.state.TransitionState
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+
+/** Trigger a one-off transition to show or hide an overlay. */
+internal fun CoroutineScope.showOrHideOverlay(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ overlay: OverlayKey,
+ fromOrToScene: SceneKey,
+ isShowing: Boolean,
+ transitionKey: TransitionKey?,
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay?,
+ reversed: Boolean,
+): TransitionState.Transition.ShowOrHideOverlay {
+ val targetProgress = if (reversed) 0f else 1f
+ val (fromContent, toContent) =
+ if (isShowing xor reversed) {
+ fromOrToScene to overlay
+ } else {
+ overlay to fromOrToScene
+ }
+
+ val oneOffAnimation = OneOffAnimation()
+ val transition =
+ OneOffShowOrHideOverlayTransition(
+ overlay = overlay,
+ fromOrToScene = fromOrToScene,
+ fromContent = fromContent,
+ toContent = toContent,
+ isEffectivelyShown = isShowing,
+ key = transitionKey,
+ replacedTransition = replacedTransition,
+ oneOffAnimation = oneOffAnimation,
+ )
+
+ animateContent(
+ layoutState = layoutState,
+ transition = transition,
+ oneOffAnimation = oneOffAnimation,
+ targetProgress = targetProgress,
+ )
+
+ return transition
+}
+
+/** Trigger a one-off transition to replace an overlay by another one. */
+internal fun CoroutineScope.replaceOverlay(
+ layoutState: MutableSceneTransitionLayoutStateImpl,
+ fromOverlay: OverlayKey,
+ toOverlay: OverlayKey,
+ transitionKey: TransitionKey?,
+ replacedTransition: TransitionState.Transition.ReplaceOverlay?,
+ reversed: Boolean,
+): TransitionState.Transition.ReplaceOverlay {
+ val targetProgress = if (reversed) 0f else 1f
+ val effectivelyShownOverlay = if (reversed) fromOverlay else toOverlay
+
+ val oneOffAnimation = OneOffAnimation()
+ val transition =
+ OneOffOverlayReplacingTransition(
+ fromOverlay = fromOverlay,
+ toOverlay = toOverlay,
+ effectivelyShownOverlay = effectivelyShownOverlay,
+ key = transitionKey,
+ replacedTransition = replacedTransition,
+ oneOffAnimation = oneOffAnimation,
+ )
+
+ animateContent(
+ layoutState = layoutState,
+ transition = transition,
+ oneOffAnimation = oneOffAnimation,
+ targetProgress = targetProgress,
+ )
+
+ return transition
+}
+
+private class OneOffShowOrHideOverlayTransition(
+ overlay: OverlayKey,
+ fromOrToScene: SceneKey,
+ fromContent: ContentKey,
+ toContent: ContentKey,
+ override val isEffectivelyShown: Boolean,
+ override val key: TransitionKey?,
+ replacedTransition: TransitionState.Transition?,
+ private val oneOffAnimation: OneOffAnimation,
+) :
+ TransitionState.Transition.ShowOrHideOverlay(
+ overlay,
+ fromOrToScene,
+ fromContent,
+ toContent,
+ replacedTransition,
+ ) {
+ override val progress: Float
+ get() = oneOffAnimation.progress
+
+ override val progressVelocity: Float
+ get() = oneOffAnimation.progressVelocity
+
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+
+ override fun finish(): Job = oneOffAnimation.finish()
+}
+
+private class OneOffOverlayReplacingTransition(
+ fromOverlay: OverlayKey,
+ toOverlay: OverlayKey,
+ override val effectivelyShownOverlay: OverlayKey,
+ override val key: TransitionKey?,
+ replacedTransition: TransitionState.Transition?,
+ private val oneOffAnimation: OneOffAnimation,
+) : TransitionState.Transition.ReplaceOverlay(fromOverlay, toOverlay, replacedTransition) {
+ override val progress: Float
+ get() = oneOffAnimation.progress
+
+ override val progressVelocity: Float
+ get() = oneOffAnimation.progressVelocity
+
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+
+ override fun finish(): Job = oneOffAnimation.finish()
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 7eef5d6..4aa50b5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -412,7 +412,7 @@
if (canOverflow) transition.progress
else transition.progress.fastCoerceIn(0f, 1f)
}
- overscrollSpec.scene == transition.toContent -> 1f
+ overscrollSpec.content == transition.toContent -> 1f
else -> 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 8aa0690..abe079a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -166,11 +166,11 @@
}
animateContent(
+ layoutState = layoutState,
transition = transition,
oneOffAnimation = oneOffAnimation,
targetProgress = targetProgress,
- startTransition = { layoutState.startTransition(transition, chain) },
- finishTransition = { layoutState.finishTransition(transition) },
+ chain = chain,
)
return transition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 6ea0285..9b1740d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -310,9 +310,10 @@
val transition = elementState as? TransitionState.Transition
- // If this element is not supposed to be laid out now because the other content of its
- // transition is overscrolling, then lay out the element normally and don't place it.
- val overscrollScene = transition?.currentOverscrollSpec?.scene
+ // If this element is not supposed to be laid out now, either because it is not part of any
+ // ongoing transition or the other content of its transition is overscrolling, then lay out
+ // the element normally and don't place it.
+ val overscrollScene = transition?.currentOverscrollSpec?.content
val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
if (isOtherSceneOverscrolling) {
return doNotPlace(measurable, constraints)
@@ -845,7 +846,7 @@
transition: TransitionState.Transition,
): Boolean {
// If we are overscrolling, only place/compose the element in the overscrolling scene.
- val overscrollScene = transition.currentOverscrollSpec?.scene
+ val overscrollScene = transition.currentOverscrollSpec?.content
if (overscrollScene != null) {
return content == overscrollScene
}
@@ -1184,7 +1185,7 @@
val currentContent = currentContentState.content
if (transition is TransitionState.HasOverscrollProperties) {
val overscroll = transition.currentOverscrollSpec
- if (overscroll?.scene == currentContent) {
+ if (overscroll?.content == currentContent) {
val elementSpec =
overscroll.transformationSpec.transformations(element.key, currentContent)
val propertySpec = transformation(elementSpec) ?: return currentValue()
@@ -1210,7 +1211,7 @@
// TODO(b/290184746): Make sure that we don't overflow transformations associated to a
// range.
val directionSign = if (transition.isUpOrLeft) -1 else 1
- val isToContent = overscroll.scene == transition.toContent
+ val isToContent = overscroll.content == transition.toContent
val linearProgress = transition.progress.let { if (isToContent) it - 1f else it }
val progressConverter =
overscroll.progressConverter
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 21f11e4..5f5141e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -432,7 +432,7 @@
val progress =
when {
overscrollSpec == null -> transition.progress
- overscrollSpec.scene == transition.toScene -> 1f
+ overscrollSpec.content == transition.toScene -> 1f
else -> 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 74cd136..0ac6912 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -81,12 +81,15 @@
/**
* Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
- * the scenes we are animating from and/or to.
+ * the contents we are animating from and/or to.
*/
- fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean
+ fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean
- /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
- fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
+ /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
+ fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean
+
+ /** Whether we are transitioning from or to [content]. */
+ fun isTransitioningFromOrTo(content: ContentKey): Boolean
}
/** A [SceneTransitionLayoutState] whose target scene can be imperatively set. */
@@ -260,14 +263,19 @@
}
}
- override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
+ override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
val transition = currentTransition ?: return false
return transition.isTransitioning(from, to)
}
- override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+ override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
val transition = currentTransition ?: return false
- return transition.isTransitioningBetween(scene, other)
+ return transition.isTransitioningBetween(content, other)
+ }
+
+ override fun isTransitioningFromOrTo(content: ContentKey): Boolean {
+ val transition = currentTransition ?: return false
+ return transition.isTransitioningFromOrTo(content)
}
override fun setTargetScene(
@@ -293,10 +301,7 @@
*
* Important: you *must* call [finishTransition] once the transition is finished.
*/
- internal fun startTransition(
- transition: TransitionState.Transition.ChangeCurrentScene,
- chain: Boolean = true,
- ) {
+ internal fun startTransition(transition: TransitionState.Transition, chain: Boolean = true) {
checkThread()
// Set the current scene and overlays on the transition.
@@ -305,23 +310,23 @@
transition.currentOverlaysWhenTransitionStarted = currentState.currentOverlays
// Compute the [TransformationSpec] when the transition starts.
- val fromScene = transition.fromScene
- val toScene = transition.toScene
+ val fromContent = transition.fromContent
+ val toContent = transition.toContent
val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
// Update the transition specs.
transition.transformationSpec =
transitions
- .transitionSpec(fromScene, toScene, key = transition.key)
+ .transitionSpec(fromContent, toContent, key = transition.key)
.transformationSpec()
transition.previewTransformationSpec =
transitions
- .transitionSpec(fromScene, toScene, key = transition.key)
+ .transitionSpec(fromContent, toContent, key = transition.key)
.previewTransformationSpec()
if (orientation != null) {
transition.updateOverscrollSpecs(
- fromSpec = transitions.overscrollSpec(fromScene, orientation),
- toSpec = transitions.overscrollSpec(toScene, orientation),
+ fromSpec = transitions.overscrollSpec(fromContent, orientation),
+ toSpec = transitions.overscrollSpec(toContent, orientation),
)
} else {
transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
@@ -402,32 +407,27 @@
}
private fun setupTransitionLinks(transition: TransitionState.Transition) {
- when (transition) {
- is TransitionState.Transition.ChangeCurrentScene -> {
- stateLinks.fastForEach { stateLink ->
- val matchingLinks =
- stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
- if (matchingLinks.isEmpty()) return@fastForEach
- if (matchingLinks.size > 1) error("More than one link matched.")
+ stateLinks.fastForEach { stateLink ->
+ val matchingLinks =
+ stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
+ if (matchingLinks.isEmpty()) return@fastForEach
+ if (matchingLinks.size > 1) error("More than one link matched.")
- val targetCurrentScene = stateLink.target.transitionState.currentScene
- val matchingLink = matchingLinks[0]
+ val targetCurrentScene = stateLink.target.transitionState.currentScene
+ val matchingLink = matchingLinks[0]
- if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+ if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
- val linkedTransition =
- LinkedTransition(
- originalTransition = transition,
- fromScene = targetCurrentScene,
- toScene = matchingLink.targetTo,
- key = matchingLink.targetTransitionKey,
- )
+ val linkedTransition =
+ LinkedTransition(
+ originalTransition = transition,
+ fromScene = targetCurrentScene,
+ toScene = matchingLink.targetTo,
+ key = matchingLink.targetTransitionKey,
+ )
- stateLink.target.startTransition(linkedTransition)
- transition.activeTransitionLinks[stateLink] = linkedTransition
- }
- }
- else -> error("transition links are not supported with overlays yet")
+ stateLink.target.startTransition(linkedTransition)
+ transition.activeTransitionLinks[stateLink] = linkedTransition
}
}
@@ -552,12 +552,39 @@
checkThread()
// Overlay is already shown, do nothing.
- if (overlay in transitionState.currentOverlays) {
+ val currentState = transitionState
+ if (overlay in currentState.currentOverlays) {
return
}
- // TODO(b/353679003): Animate the overlay instead of instantly snapping to an Idle state.
- snapToScene(transitionState.currentScene, transitionState.currentOverlays + overlay)
+ val fromScene = currentState.currentScene
+ fun animate(
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.showOrHideOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ overlay = overlay,
+ fromOrToScene = fromScene,
+ isShowing = true,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (
+ currentState is TransitionState.Transition.ShowOrHideOverlay &&
+ currentState.overlay == overlay &&
+ currentState.fromOrToScene == fromScene
+ ) {
+ animate(
+ replacedTransition = currentState,
+ reversed = overlay == currentState.fromContent
+ )
+ } else {
+ animate()
+ }
}
override fun hideOverlay(
@@ -568,12 +595,36 @@
checkThread()
// Overlay is not shown, do nothing.
- if (!transitionState.currentOverlays.contains(overlay)) {
+ val currentState = transitionState
+ if (!currentState.currentOverlays.contains(overlay)) {
return
}
- // TODO(b/353679003): Animate the overlay instead of instantly snapping to an Idle state.
- snapToScene(transitionState.currentScene, transitionState.currentOverlays - overlay)
+ val toScene = currentState.currentScene
+ fun animate(
+ replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.showOrHideOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ overlay = overlay,
+ fromOrToScene = toScene,
+ isShowing = false,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (
+ currentState is TransitionState.Transition.ShowOrHideOverlay &&
+ currentState.overlay == overlay &&
+ currentState.fromOrToScene == toScene
+ ) {
+ animate(replacedTransition = currentState, reversed = overlay == currentState.toContent)
+ } else {
+ animate()
+ }
}
override fun replaceOverlay(
@@ -583,16 +634,45 @@
transitionKey: TransitionKey?
) {
checkThread()
- require(from in currentOverlays) {
+
+ val currentState = transitionState
+ require(from != to) {
+ "replaceOverlay must be called with different overlays (from = to = ${from.debugName})"
+ }
+ require(from in currentState.currentOverlays) {
"Overlay ${from.debugName} is not shown so it can't be replaced by ${to.debugName}"
}
- require(to !in currentOverlays) {
+ require(to !in currentState.currentOverlays) {
"Overlay ${to.debugName} is already shown so it can't replace ${from.debugName}"
}
- // TODO(b/353679003): Animate from into to instead of hiding/showing the overlays
- // separately.
- snapToScene(transitionState.currentScene, transitionState.currentOverlays - from + to)
+ fun animate(
+ replacedTransition: TransitionState.Transition.ReplaceOverlay? = null,
+ reversed: Boolean = false,
+ ) {
+ animationScope.replaceOverlay(
+ layoutState = this@MutableSceneTransitionLayoutStateImpl,
+ fromOverlay = if (reversed) to else from,
+ toOverlay = if (reversed) from else to,
+ transitionKey = transitionKey,
+ replacedTransition = replacedTransition,
+ reversed = reversed,
+ )
+ }
+
+ if (currentState is TransitionState.Transition.ReplaceOverlay) {
+ if (currentState.fromOverlay == from && currentState.toOverlay == to) {
+ animate(replacedTransition = currentState, reversed = false)
+ return
+ }
+
+ if (currentState.fromOverlay == to && currentState.toOverlay == from) {
+ animate(replacedTransition = currentState, reversed = true)
+ return
+ }
+ }
+
+ animate()
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d35d956..cefcff7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -49,16 +49,16 @@
) {
private val transitionCache =
mutableMapOf<
- SceneKey,
- MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+ ContentKey,
+ MutableMap<ContentKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
>()
private val overscrollCache =
- mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
+ mutableMapOf<ContentKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
internal fun transitionSpec(
- from: SceneKey,
- to: SceneKey,
+ from: ContentKey,
+ to: ContentKey,
key: TransitionKey?,
): TransitionSpecImpl {
return transitionCache
@@ -67,7 +67,11 @@
.getOrPut(key) { findSpec(from, to, key) }
}
- private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+ private fun findSpec(
+ from: ContentKey,
+ to: ContentKey,
+ key: TransitionKey?
+ ): TransitionSpecImpl {
val spec = transition(from, to, key) { it.from == from && it.to == to }
if (spec != null) {
return spec
@@ -93,8 +97,8 @@
}
private fun transition(
- from: SceneKey,
- to: SceneKey,
+ from: ContentKey,
+ to: ContentKey,
key: TransitionKey?,
filter: (TransitionSpecImpl) -> Boolean,
): TransitionSpecImpl? {
@@ -110,16 +114,16 @@
return match
}
- private fun defaultTransition(from: SceneKey, to: SceneKey) =
+ private fun defaultTransition(from: ContentKey, to: ContentKey) =
TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)
- internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
+ internal fun overscrollSpec(scene: ContentKey, orientation: Orientation): OverscrollSpecImpl? =
overscrollCache
.getOrPut(scene) { mutableMapOf() }
- .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } }
+ .getOrPut(orientation) { overscroll(scene, orientation) { it.content == scene } }
private fun overscroll(
- scene: SceneKey,
+ scene: ContentKey,
orientation: Orientation,
filter: (OverscrollSpecImpl) -> Boolean,
): OverscrollSpecImpl? {
@@ -264,10 +268,10 @@
previewTransformationSpec?.invoke()
}
-/** The definition of the overscroll behavior of the [scene]. */
+/** The definition of the overscroll behavior of the [content]. */
interface OverscrollSpec {
/** The scene we are over scrolling. */
- val scene: SceneKey
+ val content: ContentKey
/** The orientation of this [OverscrollSpec]. */
val orientation: Orientation
@@ -288,7 +292,7 @@
}
internal class OverscrollSpecImpl(
- override val scene: SceneKey,
+ override val content: ContentKey,
override val orientation: Orientation,
override val transformationSpec: TransformationSpecImpl,
override val progressConverter: ProgressConverter?,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 523e5bdd7..18e356f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -107,7 +107,7 @@
): OverscrollSpec {
val spec =
OverscrollSpecImpl(
- scene = scene,
+ content = scene,
orientation = orientation,
transformationSpec =
TransformationSpecImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 6f608cb..6bc1754 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -96,6 +96,8 @@
.approachLayout(
isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
) { measurable, constraints ->
+ // TODO(b/353679003): Use the ModifierNode API to set this *before* the approach
+ // pass is started.
targetSize = lookaheadSize
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 89b0040..59ddb13 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -23,7 +23,7 @@
/** A linked transition which is driven by a [originalTransition]. */
internal class LinkedTransition(
- private val originalTransition: TransitionState.Transition.ChangeCurrentScene,
+ private val originalTransition: TransitionState.Transition,
fromScene: SceneKey,
toScene: SceneKey,
override val key: TransitionKey? = null,
@@ -32,8 +32,8 @@
override val currentScene: SceneKey
get() {
return when (originalTransition.currentScene) {
- originalTransition.fromScene -> fromScene
- originalTransition.toScene -> toScene
+ originalTransition.fromContent -> fromScene
+ originalTransition.toContent -> toScene
else -> error("Original currentScene is neither FromScene nor ToScene")
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index c29bf21..c830ca4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene.transition.link
+import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayoutState
@@ -35,8 +36,8 @@
* target to `SceneA` from any current scene.
*/
class TransitionLink(
- val sourceFrom: SceneKey?,
- val sourceTo: SceneKey?,
+ val sourceFrom: ContentKey?,
+ val sourceTo: ContentKey?,
val targetFrom: SceneKey?,
val targetTo: SceneKey,
val targetTransitionKey: TransitionKey? = null,
@@ -50,15 +51,15 @@
}
internal fun isMatchingLink(
- transition: TransitionState.Transition.ChangeCurrentScene,
+ transition: TransitionState.Transition,
): Boolean {
- return (sourceFrom == null || sourceFrom == transition.fromScene) &&
- (sourceTo == null || sourceTo == transition.toScene)
+ return (sourceFrom == null || sourceFrom == transition.fromContent) &&
+ (sourceTo == null || sourceTo == transition.toContent)
}
- internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
- return (targetFrom == null || targetFrom == targetCurrentScene) &&
- targetTo != targetCurrentScene
+ internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean {
+ return (targetFrom == null || targetFrom == targetCurrentContent) &&
+ targetTo != targetCurrentContent
}
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 25be3f9..7d8e898 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -1020,7 +1020,7 @@
// We scrolled down, under scene C there is nothing, so we can use the overscroll spec
assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull()
- assertThat(layoutState.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneC)
+ assertThat(layoutState.currentTransition?.currentOverscrollSpec?.content).isEqualTo(SceneC)
val transition = layoutState.currentTransition
assertThat(transition).isNotNull()
assertThat(transition!!.progress).isEqualTo(-0.1f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index d4391e0..85db418 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -16,6 +16,8 @@
package com.android.compose.animation.scene
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
@@ -33,6 +35,7 @@
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestOverlays.OverlayA
@@ -49,8 +52,8 @@
@get:Rule val rule = createComposeRule()
@Composable
- private fun ContentScope.Foo() {
- Box(Modifier.element(TestElements.Foo).size(100.dp))
+ private fun ContentScope.Foo(width: Dp = 100.dp, height: Dp = 100.dp) {
+ Box(Modifier.element(TestElements.Foo).size(width, height))
}
@Test
@@ -316,4 +319,209 @@
// of the layout.
rule.onNodeWithTag(contentTag).assertSizeIsEqualTo(100.dp)
}
+
+ @Test
+ fun showAnimation() {
+ rule.testShowOverlayTransition(
+ fromSceneContent = {
+ Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+ Foo(width = 60.dp, height = 40.dp)
+ }
+ },
+ overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+ // size of 100x80dp, so at (40,20).
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ after {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+ }
+ }
+
+ @Test
+ fun hideAnimation() {
+ rule.testHideOverlayTransition(
+ toSceneContent = {
+ Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+ Foo(width = 60.dp, height = 40.dp)
+ }
+ },
+ overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from centered (in a 180x120dp Box) with a size of 100x80dp, so at (40,20),
+ // to (0,0) with a size of 60x40dp.
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ after {
+ rule
+ .onNode(isElement(TestElements.Foo, content = SceneA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ }
+ }
+ }
+
+ @Test
+ fun replaceAnimation() {
+ rule.testReplaceOverlayTransition(
+ currentSceneContent = { Box(Modifier.size(width = 180.dp, height = 120.dp)) },
+ fromContent = { Foo(width = 60.dp, height = 40.dp) },
+ fromAlignment = Alignment.TopStart,
+ toContent = { Foo(width = 100.dp, height = 80.dp) },
+ transition = {
+ // 4 frames of animation
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ ) {
+ // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+ // size of 100x80dp, so at (40,20).
+ before {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertSizeIsEqualTo(60.dp, 40.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+ rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+ }
+
+ at(16) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(70.dp, 50.dp)
+ .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+ }
+
+ at(32) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(80.dp, 60.dp)
+ .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+ }
+
+ at(48) {
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayA))
+ .assertExists()
+ .assertIsNotDisplayed()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(90.dp, 70.dp)
+ .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+ }
+
+ after {
+ rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+ rule
+ .onNode(isElement(TestElements.Foo, content = OverlayB))
+ .assertSizeIsEqualTo(100.dp, 80.dp)
+ .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3422a8e..69f2cba 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -495,7 +495,7 @@
// overscroll for SceneB is defined
progress.value = 1.1f
val overscrollSpec = assertThat(transition).hasOverscrollSpec()
- assertThat(overscrollSpec.scene).isEqualTo(SceneB)
+ assertThat(overscrollSpec.content).isEqualTo(SceneB)
}
@Test
@@ -516,7 +516,7 @@
// overscroll for SceneA is defined
progress.value = -0.1f
val overscrollSpec = assertThat(transition).hasOverscrollSpec()
- assertThat(overscrollSpec.scene).isEqualTo(SceneA)
+ assertThat(overscrollSpec.content).isEqualTo(SceneA)
// scroll from SceneA to SceneB
progress.value = 0.5f
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 7f26b98..1ebd3d9 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -16,9 +16,12 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -115,6 +118,97 @@
)
}
+/** Test the transition when showing [overlay] from [fromScene]. */
+fun ComposeContentTestRule.testShowOverlayTransition(
+ fromSceneContent: @Composable ContentScope.() -> Unit,
+ overlayContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ fromScene: SceneKey = TestScenes.SceneA,
+ overlay: OverlayKey = TestOverlays.OverlayA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ fromScene,
+ transitions = transitions { from(fromScene, overlay, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(fromScene) { fromSceneContent() }
+ overlay(overlay) { overlayContent() }
+ }
+ },
+ changeState = { state -> state.showOverlay(overlay, animationScope = this) },
+ builder = builder,
+ )
+}
+
+/** Test the transition when hiding [overlay] to [toScene]. */
+fun ComposeContentTestRule.testHideOverlayTransition(
+ toSceneContent: @Composable ContentScope.() -> Unit,
+ overlayContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ toScene: SceneKey = TestScenes.SceneA,
+ overlay: OverlayKey = TestOverlays.OverlayA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ toScene,
+ initialOverlays = setOf(overlay),
+ transitions = transitions { from(overlay, toScene, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(toScene) { toSceneContent() }
+ overlay(overlay) { overlayContent() }
+ }
+ },
+ changeState = { state -> state.hideOverlay(overlay, animationScope = this) },
+ builder = builder,
+ )
+}
+
+/** Test the transition when replace [from] to [to]. */
+fun ComposeContentTestRule.testReplaceOverlayTransition(
+ fromContent: @Composable ContentScope.() -> Unit,
+ toContent: @Composable ContentScope.() -> Unit,
+ transition: TransitionBuilder.() -> Unit,
+ currentSceneContent: @Composable ContentScope.() -> Unit = { Box(Modifier.fillMaxSize()) },
+ fromAlignment: Alignment = Alignment.Center,
+ toAlignment: Alignment = Alignment.Center,
+ from: OverlayKey = TestOverlays.OverlayA,
+ to: OverlayKey = TestOverlays.OverlayB,
+ currentScene: SceneKey = TestScenes.SceneA,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
+ testTransition(
+ state =
+ runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ currentScene,
+ initialOverlays = setOf(from),
+ transitions = transitions { from(from, to, builder = transition) },
+ )
+ },
+ transitionLayout = { state ->
+ SceneTransitionLayout(state) {
+ scene(currentScene) { currentSceneContent() }
+ overlay(from, fromAlignment) { fromContent() }
+ overlay(to, toAlignment) { toContent() }
+ }
+ },
+ changeState = { state -> state.replaceOverlay(from, to, animationScope = this) },
+ builder = builder,
+ )
+}
+
data class TransitionRecordingSpec(
val recordBefore: Boolean = true,
val recordAfter: Boolean = true,
@@ -188,6 +282,21 @@
"(${currentScene.debugName})"
}
+ testTransition(
+ state = state,
+ changeState = { state -> state.setTargetScene(to, coroutineScope = this) },
+ transitionLayout = transitionLayout,
+ builder = builder,
+ )
+}
+
+/** Test the transition from [state] to [to]. */
+fun ComposeContentTestRule.testTransition(
+ state: MutableSceneTransitionLayoutState,
+ changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
+ transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
+ builder: TransitionTestBuilder.() -> Unit,
+) {
val test = transitionTest(builder)
val assertionScope =
object : TransitionTestAssertionScope {
@@ -213,7 +322,7 @@
mainClock.autoAdvance = false
// Change the current scene.
- runOnUiThread { state.setTargetScene(to, coroutineScope) }
+ runOnUiThread { coroutineScope.changeState(state) }
waitForIdle()
mainClock.advanceTimeByFrame()
waitForIdle()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
index 201ed00..43db5a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
@@ -148,6 +148,7 @@
mKosmos.getWifiInteractor(),
mKosmos.getCommunalSceneInteractor(),
mLogBuffer);
+ mController.onInit();
}
@Test
@@ -517,6 +518,15 @@
verify(mDreamOverlayStateController).setDreamOverlayStatusBarVisible(false);
}
+ @Test
+ public void testStatusBarWindowStateControllerListenerLifecycle() {
+ ArgumentCaptor<StatusBarWindowStateListener> listenerCaptor =
+ ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+ verify(mStatusBarWindowStateController).addListener(listenerCaptor.capture());
+ mController.destroy();
+ verify(mStatusBarWindowStateController).removeListener(eq(listenerCaptor.getValue()));
+ }
+
private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
final ArgumentCaptor<StatusBarWindowStateListener>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index ad2c42f..eba395b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -115,21 +115,21 @@
.addWidget(
widgetId = 0,
componentName = defaultWidgets[0],
- priority = 3,
+ rank = 0,
userSerialNumber = 0,
)
verify(communalWidgetDao)
.addWidget(
widgetId = 1,
componentName = defaultWidgets[1],
- priority = 2,
+ rank = 1,
userSerialNumber = 0,
)
verify(communalWidgetDao)
.addWidget(
widgetId = 2,
componentName = defaultWidgets[2],
- priority = 1,
+ rank = 2,
userSerialNumber = 0,
)
}
@@ -150,7 +150,7 @@
.addWidget(
widgetId = anyInt(),
componentName = any(),
- priority = anyInt(),
+ rank = anyInt(),
userSerialNumber = anyInt(),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index ca81838..980a5ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -154,7 +154,7 @@
CommunalWidgetContentModel.Available(
appWidgetId = communalWidgetItemEntry.widgetId,
providerInfo = providerInfoA,
- priority = communalItemRankEntry.rank,
+ rank = communalItemRankEntry.rank,
)
)
@@ -190,12 +190,12 @@
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
- priority = 2,
+ rank = 2,
),
)
}
@@ -225,12 +225,12 @@
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
- priority = 2,
+ rank = 2,
),
)
@@ -248,12 +248,12 @@
appWidgetId = 1,
// Verify that provider info updated
providerInfo = providerInfoC,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Available(
appWidgetId = 2,
providerInfo = providerInfoB,
- priority = 2,
+ rank = 2,
),
)
}
@@ -263,7 +263,7 @@
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -273,12 +273,11 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorSuccess)
+ underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorSuccess)
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
- verify(communalWidgetDao)
- .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+ verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -289,7 +288,7 @@
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -299,7 +298,7 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
@@ -316,7 +315,7 @@
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -326,7 +325,7 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority) {
+ underTest.addWidget(provider, mainUser, rank) {
throw IllegalStateException("some error")
}
runCurrent()
@@ -345,7 +344,7 @@
testScope.runTest {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- val priority = 1
+ val rank = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
whenever(
@@ -355,12 +354,11 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
- verify(communalWidgetDao)
- .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+ verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -399,11 +397,11 @@
@Test
fun reorderWidgets_queryDb() =
testScope.runTest {
- val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
- underTest.updateWidgetOrder(widgetIdToPriorityMap)
+ val widgetIdToRankMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+ underTest.updateWidgetOrder(widgetIdToRankMap)
runCurrent()
- verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
+ verify(communalWidgetDao).updateWidgetOrder(widgetIdToRankMap)
// Verify backup requested
verify(backupManager).dataChanged()
@@ -691,11 +689,11 @@
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
CommunalWidgetContentModel.Pending(
appWidgetId = 2,
- priority = 2,
+ rank = 2,
componentName = ComponentName("pk_2", "cls_2"),
icon = fakeIcon,
user = mainUser,
@@ -730,7 +728,7 @@
.containsExactly(
CommunalWidgetContentModel.Pending(
appWidgetId = 1,
- priority = 1,
+ rank = 1,
componentName = ComponentName("pk_1", "cls_1"),
icon = fakeIcon,
user = mainUser,
@@ -750,7 +748,7 @@
CommunalWidgetContentModel.Available(
appWidgetId = 1,
providerInfo = providerInfoA,
- priority = 1,
+ rank = 1,
),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 57ce9de..8218178 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -150,8 +150,8 @@
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Smartspace available.
smartspaceRepository.setTimers(
@@ -212,8 +212,8 @@
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
val communalContent by collectLastValue(underTest.communalContent)
@@ -227,7 +227,7 @@
underTest.onDeleteWidget(
id = 0,
componentName = ComponentName("test_package", "test_class"),
- priority = 30,
+ rank = 30,
)
// Only one widget and CTA tile remain.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index cc945d6..fb151a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -101,7 +101,6 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
-import org.mockito.kotlin.times
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -213,8 +212,8 @@
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Smartspace available.
smartspaceRepository.setTimers(
@@ -303,7 +302,7 @@
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- widgetRepository.addWidget(appWidgetId = 1, priority = 1)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 1)
mediaRepository.mediaInactive()
smartspaceRepository.setTimers(emptyList())
@@ -660,8 +659,8 @@
)
// Widgets available
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Then hub shows widgets and the CTA tile
assertThat(communalContent).hasSize(3)
@@ -716,8 +715,8 @@
)
// And widgets available
- widgetRepository.addWidget(appWidgetId = 0, priority = 30)
- widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+ widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+ widgetRepository.addWidget(appWidgetId = 1, rank = 20)
// Then emits widgets and the CTA tile
assertThat(communalContent).hasSize(3)
@@ -770,7 +769,7 @@
@Test
fun onTapWidget_logEvent() {
- underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), priority = 10)
+ underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), rank = 10)
verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 91259a6..3e75ceb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -34,6 +34,7 @@
import android.os.CancellationSignal
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags as AConfigFlags
@@ -55,6 +56,7 @@
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -77,6 +79,9 @@
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.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
@@ -90,6 +95,7 @@
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -136,12 +142,12 @@
@Captor
private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
- private val testDispatcher = kosmos.testDispatcher
+ private val testDispatcher by lazy { kosmos.testDispatcher }
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val testScope = kosmos.testScope
- private val fakeUserRepository = kosmos.fakeUserRepository
- private val fakeExecutor = kosmos.fakeExecutor
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val testScope by lazy { kosmos.testScope }
+ private val fakeUserRepository by lazy { kosmos.fakeUserRepository }
+ private val fakeExecutor by lazy { kosmos.fakeExecutor }
private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
@@ -149,18 +155,19 @@
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
- private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
- private val deviceEntryFingerprintAuthRepository =
+ private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+ private val deviceEntryFingerprintAuthRepository by lazy {
kosmos.fakeDeviceEntryFingerprintAuthRepository
- private val trustRepository = kosmos.fakeTrustRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val powerInteractor = kosmos.powerInteractor
- private val keyguardInteractor = kosmos.keyguardInteractor
- private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
- private val displayStateInteractor = kosmos.displayStateInteractor
- private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
- private val displayRepository = kosmos.displayRepository
- private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+ }
+ private val trustRepository by lazy { kosmos.fakeTrustRepository }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
+ private val displayStateInteractor by lazy { kosmos.displayStateInteractor }
+ private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+ private val displayRepository by lazy { kosmos.displayRepository }
+ private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
private lateinit var featureFlags: FakeFeatureFlags
private var wasAuthCancelled = false
@@ -180,9 +187,11 @@
whenever(bypassController.bypassEnabled).thenReturn(true)
underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ if (!SceneContainerFlag.isEnabled) {
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
+ }
}
private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -227,6 +236,7 @@
powerInteractor,
keyguardInteractor,
alternateBouncerInteractor,
+ { kosmos.sceneInteractor },
faceDetectBuffer,
faceAuthBuffer,
keyguardTransitionInteractor,
@@ -547,6 +557,24 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainerEnabled_authenticateDoesNotRunWhenKeyguardIsGoingAway() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.UNDEFINED,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING
+ ),
+ validateStep = false
+ )
+ runCurrent()
+ }
+ }
+
+ @Test
fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
testScope.runTest {
testGatingCheckForFaceAuth {
@@ -595,6 +623,31 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainer_authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled = true)
+ bouncerRepository.setAlternateVisible(false)
+
+ // launch secure camera
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ keyguardRepository.setKeyguardOccluded(true)
+ kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "for-test")
+ runCurrent()
+ assertThat(canFaceAuthRun()).isFalse()
+
+ // but bouncer is shown after that.
+ kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+ )
+ runCurrent()
+
+ assertThat(canFaceAuthRun()).isTrue()
+ }
+
+ @Test
fun authenticateDoesNotRunOnUnsupportedPosture() =
testScope.runTest {
testGatingCheckForFaceAuth {
@@ -841,6 +894,24 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainer_faceDetectDoesNotRunWhenKeyguardGoingAway() =
+ testScope.runTest {
+ testGatingCheckForDetect(sceneContainerEnabled = true) {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.UNDEFINED,
+ value = 0.5f,
+ transitionState = TransitionState.RUNNING
+ ),
+ validateStep = false
+ )
+ runCurrent()
+ }
+ }
+
+ @Test
fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
testScope.runTest {
testGatingCheckForDetect {
@@ -1052,10 +1123,11 @@
}
private suspend fun TestScope.testGatingCheckForFaceAuth(
+ sceneContainerEnabled: Boolean = false,
gatingCheckModifier: suspend () -> Unit
) {
initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
gatingCheckModifier()
runCurrent()
@@ -1069,7 +1141,7 @@
faceAuthenticateIsNotCalled()
// flip the gating check back on.
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
assertThat(underTest.canRunFaceAuth.value).isTrue()
faceAuthenticateIsCalled()
@@ -1094,10 +1166,11 @@
}
private suspend fun TestScope.testGatingCheckForDetect(
+ sceneContainerEnabled: Boolean = false,
gatingCheckModifier: suspend () -> Unit
) {
initCollectors()
- allPreconditionsToRunFaceAuthAreTrue()
+ allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
// This will stop face auth from running but is required to be false for detect.
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
@@ -1145,12 +1218,22 @@
cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
}
- private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+ private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
+ sceneContainerEnabled: Boolean = false
+ ) {
fakeExecutor.runAllReady()
verify(faceManager, atLeastOnce())
.addLockoutResetCallback(faceLockoutResetCallback.capture())
trustRepository.setCurrentUserTrusted(false)
- keyguardRepository.setKeyguardGoingAway(false)
+ if (sceneContainerEnabled) {
+ // Keyguard is not going away
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(KeyguardState.OFF, KeyguardState.LOCKSCREEN, value = 1.0f),
+ validateStep = false
+ )
+ } else {
+ keyguardRepository.setKeyguardGoingAway(false)
+ }
powerInteractor.setAwakeForTest()
biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 6412276..3895595 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -62,6 +62,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -324,4 +325,13 @@
// enabled.
mController.onViewAttached();
}
+
+ @Test
+ public void destroy_cleansUpState() {
+ mController.destroy();
+ verify(mStateController).removeCallback(any());
+ verify(mAmbientStatusBarViewController).destroy();
+ verify(mComplicationHostViewController).destroy();
+ verify(mLowLightTransitionCoordinator).setLowLightEnterListener(ArgumentMatchers.isNull());
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 89ec3cf..29aa89c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -68,7 +68,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -89,7 +88,9 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -115,8 +116,6 @@
@Mock lateinit var mComplicationComponentFactory: ComplicationComponent.Factory
- @Mock lateinit var mComplicationComponent: ComplicationComponent
-
@Mock lateinit var mComplicationHostViewController: ComplicationHostViewController
@Mock lateinit var mComplicationVisibilityController: ComplicationLayoutEngine
@@ -125,20 +124,12 @@
lateinit var mDreamComplicationComponentFactory:
com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
- @Mock
- lateinit var mDreamComplicationComponent:
- com.android.systemui.dreams.complication.dagger.ComplicationComponent
-
@Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
@Mock lateinit var mDreamOverlayComponentFactory: DreamOverlayComponent.Factory
- @Mock lateinit var mDreamOverlayComponent: DreamOverlayComponent
-
@Mock lateinit var mAmbientTouchComponentFactory: AmbientTouchComponent.Factory
- @Mock lateinit var mAmbientTouchComponent: AmbientTouchComponent
-
@Mock lateinit var mDreamOverlayContainerView: DreamOverlayContainerView
@Mock lateinit var mDreamOverlayContainerViewController: DreamOverlayContainerViewController
@@ -170,10 +161,83 @@
private lateinit var communalRepository: FakeCommunalSceneRepository
private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
private lateinit var gestureInteractor: GestureInteractor
+ private lateinit var environmentComponents: EnvironmentComponents
@Captor var mViewCaptor: ArgumentCaptor<View>? = null
private lateinit var mService: DreamOverlayService
+ private class EnvironmentComponents(
+ val dreamsComplicationComponent:
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent,
+ val dreamOverlayComponent: DreamOverlayComponent,
+ val complicationComponent: ComplicationComponent,
+ val ambientTouchComponent: AmbientTouchComponent,
+ ) {
+ fun clearInvocations() {
+ clearInvocations(
+ dreamsComplicationComponent,
+ dreamOverlayComponent,
+ complicationComponent,
+ ambientTouchComponent
+ )
+ }
+
+ fun verifyNoMoreInteractions() {
+ Mockito.verifyNoMoreInteractions(
+ dreamsComplicationComponent,
+ dreamOverlayComponent,
+ complicationComponent,
+ ambientTouchComponent
+ )
+ }
+ }
+
+ private fun setupComponentFactories(
+ dreamComplicationComponentFactory:
+ com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory,
+ dreamOverlayComponentFactory: DreamOverlayComponent.Factory,
+ complicationComponentFactory: ComplicationComponent.Factory,
+ ambientTouchComponentFactory: AmbientTouchComponent.Factory
+ ): EnvironmentComponents {
+ val dreamOverlayComponent = mock<DreamOverlayComponent>()
+ whenever(dreamOverlayComponent.getDreamOverlayContainerViewController())
+ .thenReturn(mDreamOverlayContainerViewController)
+
+ val complicationComponent = mock<ComplicationComponent>()
+ whenever(complicationComponent.getComplicationHostViewController())
+ .thenReturn(mComplicationHostViewController)
+ whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry)
+
+ mCommunalInteractor = Mockito.spy(kosmos.communalInteractor)
+
+ whenever(complicationComponentFactory.create(any(), any(), any(), any()))
+ .thenReturn(complicationComponent)
+ whenever(complicationComponent.getVisibilityController())
+ .thenReturn(mComplicationVisibilityController)
+
+ val dreamComplicationComponent =
+ mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent>()
+ whenever(dreamComplicationComponent.getHideComplicationTouchHandler())
+ .thenReturn(mHideComplicationTouchHandler)
+ whenever(dreamComplicationComponentFactory.create(any(), any()))
+ .thenReturn(dreamComplicationComponent)
+
+ whenever(dreamOverlayComponentFactory.create(any(), any(), any()))
+ .thenReturn(dreamOverlayComponent)
+
+ val ambientTouchComponent = mock<AmbientTouchComponent>()
+ whenever(ambientTouchComponentFactory.create(any(), any()))
+ .thenReturn(ambientTouchComponent)
+ whenever(ambientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
+
+ return EnvironmentComponents(
+ dreamComplicationComponent,
+ dreamOverlayComponent,
+ complicationComponent,
+ ambientTouchComponent
+ )
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -183,27 +247,14 @@
communalRepository = kosmos.fakeCommunalSceneRepository
gestureInteractor = spy(kosmos.gestureInteractor)
- whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController())
- .thenReturn(mDreamOverlayContainerViewController)
- whenever(mComplicationComponent.getComplicationHostViewController())
- .thenReturn(mComplicationHostViewController)
- whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry)
+ environmentComponents =
+ setupComponentFactories(
+ mDreamComplicationComponentFactory,
+ mDreamOverlayComponentFactory,
+ mComplicationComponentFactory,
+ mAmbientTouchComponentFactory
+ )
- mCommunalInteractor = Mockito.spy(kosmos.communalInteractor)
-
- whenever(mComplicationComponentFactory.create(any(), any(), any(), any()))
- .thenReturn(mComplicationComponent)
- whenever(mComplicationComponent.getVisibilityController())
- .thenReturn(mComplicationVisibilityController)
- whenever(mDreamComplicationComponent.getHideComplicationTouchHandler())
- .thenReturn(mHideComplicationTouchHandler)
- whenever(mDreamComplicationComponentFactory.create(any(), any()))
- .thenReturn(mDreamComplicationComponent)
- whenever(mDreamOverlayComponentFactory.create(any(), any(), any()))
- .thenReturn(mDreamOverlayComponent)
- whenever(mAmbientTouchComponentFactory.create(any(), any()))
- .thenReturn(mAmbientTouchComponent)
- whenever(mAmbientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
whenever(mDreamOverlayContainerViewController.containerView)
.thenReturn(mDreamOverlayContainerView)
whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
@@ -570,9 +621,8 @@
// Assert that the overlay is not showing complications.
assertThat(mService.shouldShowComplications()).isFalse()
- Mockito.clearInvocations(mDreamOverlayComponent)
- Mockito.clearInvocations(mAmbientTouchComponent)
- Mockito.clearInvocations(mWindowManager)
+ environmentComponents.clearInvocations()
+ clearInvocations(mWindowManager)
// New dream starting with dream complications showing. Note that when a new dream is
// binding to the dream overlay service, it receives the same instance of IBinder as the
@@ -594,8 +644,11 @@
// Verify that new instances of overlay container view controller and overlay touch monitor
// are created.
- verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
- verify(mAmbientTouchComponent).getTouchMonitor()
+ verify(environmentComponents.dreamOverlayComponent).getDreamOverlayContainerViewController()
+ verify(environmentComponents.ambientTouchComponent).getTouchMonitor()
+
+ // Verify DreamOverlayContainerViewController is destroyed.
+ verify(mDreamOverlayContainerViewController).destroy()
}
@Test
@@ -1002,6 +1055,34 @@
.isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
}
+ @Test
+ fun testComponentsRecreatedBetweenDreams() {
+ clearInvocations(
+ mDreamComplicationComponentFactory,
+ mDreamOverlayComponentFactory,
+ mComplicationComponentFactory,
+ mAmbientTouchComponentFactory
+ )
+
+ mService.onEndDream()
+
+ setupComponentFactories(
+ mDreamComplicationComponentFactory,
+ mDreamOverlayComponentFactory,
+ mComplicationComponentFactory,
+ mAmbientTouchComponentFactory
+ )
+
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ environmentComponents.verifyNoMoreInteractions()
+ }
+
internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) {
val mLifecycles: MutableList<State> = ArrayList()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index f82beff..50b727c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SysuiTestableContext
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
@@ -105,6 +106,19 @@
assertThat(model).isEqualTo(newModel)
}
+ @Test
+ fun eduDeviceConnectionTimeDataChangedOnUpdate() =
+ testScope.runTest {
+ val newModel =
+ EduDeviceConnectionTime(
+ keyboardFirstConnectionTime = kosmos.fakeEduClock.instant(),
+ touchpadFirstConnectionTime = kosmos.fakeEduClock.instant(),
+ )
+ underTest.updateEduDeviceConnectionTime { newModel }
+ val model by collectLastValue(underTest.readEduDeviceConnectionTime())
+ assertThat(model).isEqualTo(newModel)
+ }
+
/** Test context which allows overriding getFilesDir path */
private class TestContext(context: Context, private val folder: File) :
SysuiTestableContext(context) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 23f923a..3aed79f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.education.domain.interactor
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,10 +27,15 @@
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.data.repository.touchpadRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -38,16 +44,23 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val contextualEduInteractor = kosmos.contextualEducationInteractor
+ private val touchpadRepository = kosmos.touchpadRepository
+ private val keyboardRepository = kosmos.keyboardRepository
+ private val userRepository = kosmos.fakeUserRepository
+
private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
private val eduClock = kosmos.fakeEduClock
@Before
fun setup() {
underTest.start()
+ contextualEduInteractor.start()
+ userRepository.setUserInfos(USER_INFOS)
}
@Test
@@ -67,7 +80,6 @@
}
@Test
- @kotlinx.coroutines.ExperimentalCoroutinesApi
fun newEducationNotificationOn2ndEducation() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
@@ -115,10 +127,103 @@
)
}
+ @Test
+ fun newTouchpadConnectionTimeOnFirstTouchpadConnected() =
+ testScope.runTest {
+ setIsAnyTouchpadConnected(true)
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(eduClock.instant())
+ }
+
+ @Test
+ fun unchangedTouchpadConnectionTimeOnSecondConnection() =
+ testScope.runTest {
+ val firstConnectionTime = eduClock.instant()
+ setIsAnyTouchpadConnected(true)
+ setIsAnyTouchpadConnected(false)
+
+ eduClock.offset(1.hours)
+ setIsAnyTouchpadConnected(true)
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(firstConnectionTime)
+ }
+
+ @Test
+ fun newTouchpadConnectionTimeOnUserChanged() =
+ testScope.runTest {
+ // Touchpad connected for user 0
+ setIsAnyTouchpadConnected(true)
+
+ // Change user
+ eduClock.offset(1.hours)
+ val newUserFirstConnectionTime = eduClock.instant()
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ runCurrent()
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.touchpadFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
+ }
+
+ @Test
+ fun newKeyboardConnectionTimeOnKeyboardConnected() =
+ testScope.runTest {
+ setIsAnyKeyboardConnected(true)
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(eduClock.instant())
+ }
+
+ @Test
+ fun unchangedKeyboardConnectionTimeOnSecondConnection() =
+ testScope.runTest {
+ val firstConnectionTime = eduClock.instant()
+ setIsAnyKeyboardConnected(true)
+ setIsAnyKeyboardConnected(false)
+
+ eduClock.offset(1.hours)
+ setIsAnyKeyboardConnected(true)
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(firstConnectionTime)
+ }
+
+ @Test
+ fun newKeyboardConnectionTimeOnUserChanged() =
+ testScope.runTest {
+ // Keyboard connected for user 0
+ setIsAnyKeyboardConnected(true)
+
+ // Change user
+ eduClock.offset(1.hours)
+ val newUserFirstConnectionTime = eduClock.instant()
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ runCurrent()
+
+ val model = contextualEduInteractor.getEduDeviceConnectionTime()
+ assertThat(model.keyboardFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
+ }
+
private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
// Increment max number of signal to try triggering education
for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
contextualEduInteractor.incrementSignalCount(gestureType)
}
}
+
+ private fun TestScope.setIsAnyTouchpadConnected(isConnected: Boolean) {
+ touchpadRepository.setIsAnyTouchpadConnected(isConnected)
+ runCurrent()
+ }
+
+ private fun TestScope.setIsAnyKeyboardConnected(isConnected: Boolean) {
+ keyboardRepository.setIsAnyKeyboardConnected(isConnected)
+ runCurrent()
+ }
+
+ companion object {
+ private val USER_INFOS =
+ listOf(
+ UserInfo(101, "Second User", 0),
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 2b2c121..aee72de2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -25,8 +25,11 @@
import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -260,6 +263,23 @@
}
@Test
+ fun triggersFaceAuthWhenLockscreenIsClicked() =
+ testScope.runTest {
+ collectLastValue(underTest.isMenuVisible)
+ runCurrent()
+ kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true
+
+ underTest.onClick(100.0f, 100.0f)
+ runCurrent()
+
+ val runningAuthRequest =
+ kosmos.fakeDeviceEntryFaceAuthRepository.runningAuthRequest.value
+ assertThat(runningAuthRequest?.first)
+ .isEqualTo(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED)
+ assertThat(runningAuthRequest?.second).isEqualTo(true)
+ }
+
+ @Test
fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
testScope.runTest {
val isMenuVisible by collectLastValue(underTest.isMenuVisible)
@@ -302,6 +322,7 @@
broadcastDispatcher = fakeBroadcastDispatcher,
accessibilityManager = kosmos.accessibilityManagerWrapper,
pulsingGestureListener = kosmos.pulsingGestureListener,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
)
setUpState()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
new file mode 100644
index 0000000..22e5896
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -0,0 +1,399 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.Notification
+import android.app.Notification.MediaStyle
+import android.app.PendingIntent
+import android.app.statusBarManager
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.media.AudioAttributes
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.service.notification.StatusBarNotification
+import androidx.media.utils.MediaConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val KEY = "KEY"
+private const val PACKAGE_NAME = "com.example.app"
+private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
+private const val APP_NAME = "SystemUI"
+private const val SESSION_ARTIST = "artist"
+private const val SESSION_TITLE = "title"
+private const val SESSION_EMPTY_TITLE = ""
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaDataLoaderTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
+ private val statusBarManager = kosmos.statusBarManager
+ private val mediaController = mock<MediaController>()
+ private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+ private val mediaFlags = kosmos.mediaFlags
+ private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+ private val session = MediaSession(context, "MediaDataLoaderTestSession")
+ private val metadataBuilder =
+ MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+
+ private val underTest: MediaDataLoader =
+ MediaDataLoader(
+ context,
+ testDispatcher,
+ testScope,
+ kosmos.activityStarter,
+ mediaControllerFactory,
+ mediaFlags,
+ kosmos.imageLoader,
+ statusBarManager
+ )
+
+ @Before
+ fun setUp() {
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
+ mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+ }
+
+ @Test
+ fun loadMediaData_returnsMediaData() =
+ testScope.runTest {
+ val song = "THIS_IS_A_SONG"
+ val artist = "THIS_IS_AN_ARTIST"
+ val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+ whenever(mediaController.playbackState)
+ .thenReturn(
+ PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 12, 1.0f).build()
+ )
+ whenever(mediaController.playbackInfo)
+ .thenReturn(
+ MediaController.PlaybackInfo(
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+ 0,
+ 0,
+ 0,
+ AudioAttributes.Builder().build(),
+ null
+ )
+ )
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, song)
+ .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
+ .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt)
+ .putLong(
+ MediaConstants.METADATA_KEY_IS_EXPLICIT,
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ )
+ .build()
+ )
+
+ val result = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result).isNotNull()
+ assertThat(result?.appIcon).isNotNull()
+ assertThat(result?.appIcon?.resId).isEqualTo(android.R.drawable.ic_media_pause)
+ assertThat(result?.artist).isEqualTo(artist)
+ assertThat(result?.song).isEqualTo(song)
+ assertThat(result?.artworkIcon).isNotNull()
+ assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(albumArt.width)
+ assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(albumArt.height)
+ assertThat(result?.token).isEqualTo(session.sessionToken)
+ assertThat(result?.device).isNull()
+ assertThat(result?.playbackLocation).isEqualTo(MediaData.PLAYBACK_LOCAL)
+ assertThat(result?.isPlaying).isTrue()
+ assertThat(result?.isExplicit).isTrue()
+ assertThat(result?.resumeAction).isNull()
+ assertThat(result?.resumeProgress).isNull()
+ }
+
+ @Test
+ fun loadMediaDataForResumption_returnsMediaData() =
+ testScope.runTest {
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
+
+ val song = "THIS_IS_A_SONG"
+ val artist = "THIS_IS_AN_ARTIST"
+ val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+ val extras = Bundle()
+ extras.putInt(
+ MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+ )
+ extras.putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.3)
+ extras.putLong(
+ MediaConstants.METADATA_KEY_IS_EXPLICIT,
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+ )
+
+ val description =
+ MediaDescription.Builder()
+ .setTitle(song)
+ .setSubtitle(artist)
+ .setIconBitmap(albumArt)
+ .setExtras(extras)
+ .build()
+
+ val intent =
+ PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+
+ val result =
+ underTest.loadMediaDataForResumption(
+ 0,
+ description,
+ Runnable {},
+ null,
+ session.sessionToken,
+ APP_NAME,
+ intent,
+ PACKAGE_NAME
+ )
+ assertThat(result).isNotNull()
+ assertThat(result?.appName).isEqualTo(APP_NAME)
+ assertThat(result?.song).isEqualTo(song)
+ assertThat(result?.artist).isEqualTo(artist)
+ assertThat(result?.artworkIcon).isNotNull()
+ assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(100)
+ assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(100)
+ assertThat(result?.token).isEqualTo(session.sessionToken)
+ assertThat(result?.clickIntent).isEqualTo(intent)
+ assertThat(result?.isExplicit).isTrue()
+ assertThat(result?.resumeProgress).isEqualTo(0.3)
+ }
+
+ @Test
+ fun loadMediaData_songNameFallbacks() =
+ testScope.runTest {
+ // Check ordering of Song resolution:
+ // DISPLAY_TITLE > TITLE > notification TITLE > notification TITLE_BIG
+
+ // DISPLAY_TITLE
+ whenever(mediaController.metadata)
+ .thenReturn(
+ MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "title1")
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+ .build()
+ )
+ val result1 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result1?.song).isEqualTo("title1")
+
+ // TITLE
+ whenever(mediaController.metadata)
+ .thenReturn(
+ MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+ .build()
+ )
+ val result2 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result2?.song).isEqualTo("title2")
+
+ // notification TITLE
+ val notif =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setContentTitle("notiftitle")
+ }
+ build()
+ }
+ whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+ val result3 = underTest.loadMediaData(KEY, notif)
+ assertThat(result3?.song).isEqualTo("notiftitle")
+
+ // Final fallback
+ whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+ val result4 = underTest.loadMediaData(KEY, createMediaNotification())
+ assertThat(result4?.song)
+ .isEqualTo(context.getString(R.string.controls_media_empty_title, result4?.appName))
+ }
+
+ @Test
+ fun loadMediaData_emptyTitle_hasPlaceholder() =
+ testScope.runTest {
+ val packageManager = mock<PackageManager>()
+ context.setMockPackageManager(packageManager)
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+
+ val result = underTest.loadMediaData(KEY, createMediaNotification())
+
+ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+ assertThat(result).isNotNull()
+ assertThat(result?.song).isEqualTo(placeholderTitle)
+ }
+
+ @Test
+ fun loadMediaData_emptyMetadata_usesNotificationTitle() =
+ testScope.runTest {
+ val packageManager = mock<PackageManager>()
+ context.setMockPackageManager(packageManager)
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaController.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+ val mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setContentTitle(SESSION_TITLE)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, mediaNotification)
+
+ assertThat(result).isNotNull()
+ assertThat(result?.song).isEqualTo(SESSION_TITLE)
+ }
+
+ @Test
+ fun loadMediaData_badArtwork_isNotUsed() =
+ testScope.runTest {
+ val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val mediaNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+ it.setLargeIcon(artwork)
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, mediaNotification)
+
+ assertThat(result).isNotNull()
+ }
+
+ @Test
+ fun loadMediaData_invalidTokenNoCrash() =
+ testScope.runTest {
+ val bundle = Bundle()
+ // wrong data type
+ bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) }
+ )
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, rcn)
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun testLoadMediaDataInBg_invalidMediaRemoteIntentNoCrash() =
+ testScope.runTest {
+ val bundle = Bundle()
+ // wrong data type
+ bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
+ val rcn =
+ SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.addExtras(bundle)
+ it.setStyle(
+ MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ }
+ )
+ }
+ build()
+ }
+
+ val result = underTest.loadMediaData(KEY, rcn)
+ assertThat(result).isNotNull()
+ }
+
+ private fun createMediaNotification(
+ mediaSession: MediaSession? = session,
+ applicationInfo: ApplicationInfo? = null
+ ): StatusBarNotification =
+ SbnBuilder().run {
+ setPkg(PACKAGE_NAME)
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply { setMediaSession(mediaSession?.sessionToken) })
+ if (applicationInfo != null) {
+ val bundle = Bundle()
+ bundle.putParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ applicationInfo
+ )
+ it.addExtras(bundle)
+ }
+ }
+ build()
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
index 789a473..f920b18 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
@@ -4,6 +4,7 @@
import android.util.Log
import android.view.View
import androidx.annotation.VisibleForTesting
+import androidx.core.text.util.LocalePreferences
typealias WeatherTouchAction = (View) -> Unit
@@ -54,12 +55,35 @@
}
}
- private fun readIntFromBundle(extras: Bundle, key: String): Int? =
+ private fun readIntFromBundle(extras: Bundle, key: String): Int? {
try {
- extras.getString(key)?.toInt()
+ return extras.getString(key)?.toInt()
} catch (e: Exception) {
- null
+ return null
}
+ }
+
+ fun getPlaceholderWeatherData(): WeatherData {
+ return getPlaceholderWeatherData(
+ LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
+ )
+ }
+
+ private const val DESCRIPTION_PLACEHODLER = ""
+ private const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
+ private const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
+ private val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
+
+ fun getPlaceholderWeatherData(useCelsius: Boolean): WeatherData {
+ return WeatherData(
+ description = DESCRIPTION_PLACEHODLER,
+ state = WEATHERICON_PLACEHOLDER,
+ temperature =
+ if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
+ else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
+ useCelsius = useCelsius,
+ )
+ }
}
// Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index fc6d20e..0c11d2f 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -27,6 +27,8 @@
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">true</bool>
+ <bool name="config_use_large_screen_shade_header">true</bool>
+
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">2</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index b438315..c594f1c 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -35,8 +35,6 @@
<!-- How many lines to show in the security footer -->
<integer name="qs_security_footer_maxLines">1</integer>
- <bool name="config_use_large_screen_shade_header">true</bool>
-
<!-- Whether to show bottom sheets edge to edge -->
<bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 18b7073..fd943d0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1869,6 +1869,8 @@
<!-- Text displayed indicating that the user is connected to a satellite signal. -->
<string name="satellite_connected_carrier_text">Satellite SOS</string>
+ <!-- Text displayed indicating that the user might be able to use satellite SOS. -->
+ <string name="satellite_emergency_only_carrier_text">Emergency calls or SOS</string>
<!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_managed_profile">Work profile</string>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
new file mode 100644
index 0000000..fe996b7
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
@@ -0,0 +1,81 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 3,
+ "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSerialNumber",
+ "columnName": "user_serial_number",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index fbe1399..02d39a4 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -48,6 +48,7 @@
"src/**/*.kt",
"src/**/*.aidl",
":wm_shell-aidls",
+ ":wm_shell-shared-aidls",
":wm_shell_util-sources",
],
static_libs: [
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 5dcf161..c1eae2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -477,6 +477,12 @@
smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
}
+ fun setFallbackWeatherData(data: WeatherData) {
+ if (weatherData != null) return
+ weatherData = data
+ clock?.run { events.onWeatherDataChanged(data) }
+ }
+
/**
* Sets this clock as showing in a secondary display.
*
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
index abdc333..04595a2 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.time.DateFormatUtil;
@@ -127,6 +128,9 @@
private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback =
this::onStatusBarItemsChanged;
+ private final StatusBarWindowStateListener mStatusBarWindowStateListener =
+ this::onSystemStatusBarStateChanged;
+
@Inject
public AmbientStatusBarViewController(
AmbientStatusBarView view,
@@ -161,10 +165,22 @@
mWifiInteractor = wifiInteractor;
mCommunalSceneInteractor = communalSceneInteractor;
mLogger = new DreamLogger(logBuffer, TAG);
+ }
+
+ @Override
+ protected void onInit() {
+ super.onInit();
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
- statusBarWindowStateController.addListener(this::onSystemStatusBarStateChanged);
+ mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ }
+
+ @Override
+ public void destroy() {
+ mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+
+ super.destroy();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 190bc15..d27e72a 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -122,4 +122,9 @@
* @param session
*/
void onSessionStart(TouchSession session);
+
+ /**
+ * Called when the handler is being torn down.
+ */
+ default void onDestroy() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index efa55e9..1be6f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -581,6 +581,10 @@
mBoundsFlow.cancel(new CancellationException());
}
+ for (TouchHandler handler : mHandlers) {
+ handler.onDestroy();
+ }
+
mInitialized = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index dff6352..8f1854f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,7 +26,7 @@
import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.res.R
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 3)
abstract class CommunalDatabase : RoomDatabase() {
abstract fun communalWidgetDao(): CommunalWidgetDao
@@ -55,7 +55,7 @@
context.resources.getString(R.string.config_communalDatabase)
)
.also { builder ->
- builder.addMigrations(MIGRATION_1_2)
+ builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
builder.fallbackToDestructiveMigration(dropAllTables = true)
callback?.let { callback -> builder.addCallback(callback) }
}
@@ -87,5 +87,21 @@
)
}
}
+
+ /**
+ * This migration reverses the ranks. For example, if the ranks are 2, 1, 0, then after the
+ * migration they will be 0, 1, 2.
+ */
+ @VisibleForTesting
+ val MIGRATION_2_3 =
+ object : Migration(2, 3) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i(TAG, "Migrating from version 2 to 3")
+ db.execSQL(
+ "UPDATE communal_item_rank_table " +
+ "SET rank = (SELECT MAX(rank) FROM communal_item_rank_table) - rank"
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 933a25a..93b86bd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@
.addWidget(
widgetId = id,
componentName = name,
- priority = defaultWidgets.size - index,
+ rank = index,
userSerialNumber = userSerialNumber,
)
}
@@ -132,10 +132,17 @@
@Query(
"SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
"ON communal_item_rank_table.uid = communal_widget_table.item_id " +
- "ORDER BY communal_item_rank_table.rank DESC"
+ "ORDER BY communal_item_rank_table.rank ASC"
)
fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
+ @Query(
+ "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
+ "ON communal_item_rank_table.uid = communal_widget_table.item_id " +
+ "ORDER BY communal_item_rank_table.rank ASC"
+ )
+ fun getWidgetsNow(): Map<CommunalItemRank, CommunalWidgetItem>
+
@Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
@@ -167,11 +174,11 @@
@Query("DELETE FROM communal_item_rank_table") fun clearCommunalItemRankTable()
@Transaction
- fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
- widgetIdToPriorityMap.forEach { (id, priority) ->
+ fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
+ widgetIdToRankMap.forEach { (id, rank) ->
val widget = getWidgetByIdNow(id)
if (widget != null) {
- updateItemRank(widget.itemId, priority)
+ updateItemRank(widget.itemId, rank)
}
}
}
@@ -180,13 +187,13 @@
fun addWidget(
widgetId: Int,
provider: ComponentName,
- priority: Int,
+ rank: Int? = null,
userSerialNumber: Int,
): Long {
return addWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
@@ -195,13 +202,27 @@
fun addWidget(
widgetId: Int,
componentName: String,
- priority: Int,
+ rank: Int? = null,
userSerialNumber: Int,
): Long {
+ val widgets = getWidgetsNow()
+
+ // If rank is not specified, rank it last by finding the current maximum rank and increment
+ // by 1. If the new widget is the first widget, set the rank to 0.
+ val newRank = rank ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
+
+ // Shift widgets after [rank], unless widget is added at the end.
+ if (rank != null) {
+ widgets.forEach { (rankEntry, widgetEntry) ->
+ if (rankEntry.rank < newRank) return@forEach
+ updateItemRank(widgetEntry.itemId, rankEntry.rank + 1)
+ }
+ }
+
return insertWidget(
widgetId = widgetId,
componentName = componentName,
- itemId = insertItemRank(priority),
+ itemId = insertItemRank(newRank),
userSerialNumber = userSerialNumber,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ad0bfc7..6cdd9ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -57,12 +57,17 @@
/** A flow of information about active communal widgets stored in database. */
val communalWidgets: Flow<List<CommunalWidgetContentModel>>
- /** Add a widget at the specified position in the app widget service and the database. */
+ /**
+ * Add a widget in the app widget service and the database.
+ *
+ * @param rank The rank of the widget determines its position in the grid. 0 is first place, 1
+ * is second, etc. If rank is not specified, widget is added at the end.
+ */
fun addWidget(
provider: ComponentName,
user: UserHandle,
- priority: Int,
- configurator: WidgetConfigurator? = null
+ rank: Int?,
+ configurator: WidgetConfigurator? = null,
) {}
/**
@@ -75,9 +80,9 @@
/**
* Update the order of widgets in the database.
*
- * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+ * @param widgetIdToRankMap mapping of the widget ids to the rank of the widget.
*/
- fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+ fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {}
/**
* Restores the database by reading a state file from disk and updating the widget ids according
@@ -121,7 +126,7 @@
CommunalWidgetEntry(
appWidgetId = widget.widgetId,
componentName = widget.componentName,
- priority = rank.rank,
+ rank = rank.rank,
providerInfo = providers[widget.widgetId]
)
}
@@ -151,8 +156,8 @@
override fun addWidget(
provider: ComponentName,
user: UserHandle,
- priority: Int,
- configurator: WidgetConfigurator?
+ rank: Int?,
+ configurator: WidgetConfigurator?,
) {
bgScope.launch {
val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
@@ -190,14 +195,14 @@
communalWidgetDao.addWidget(
widgetId = id,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userManager.getUserSerialNumber(user.identifier),
)
backupManager.dataChanged()
} else {
appWidgetHost.deleteAppWidgetId(id)
}
- logger.i("Added widget ${provider.flattenToString()} at position $priority.")
+ logger.i("Added widget ${provider.flattenToString()} at position $rank.")
}
}
@@ -211,11 +216,11 @@
}
}
- override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+ override fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
bgScope.launch {
- communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
+ communalWidgetDao.updateWidgetOrder(widgetIdToRankMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
- str1 = widgetIdToPriorityMap.toString()
+ str1 = widgetIdToRankMap.toString()
}
backupManager.dataChanged()
}
@@ -342,7 +347,7 @@
addWidget(
provider = ComponentName.unflattenFromString(widget.componentName)!!,
user = newUser,
- priority = widget.rank,
+ rank = widget.rank,
)
}
@@ -377,7 +382,7 @@
return CommunalWidgetContentModel.Available(
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
- priority = entry.priority,
+ rank = entry.rank,
)
}
@@ -394,7 +399,7 @@
return CommunalWidgetContentModel.Available(
appWidgetId = entry.appWidgetId,
providerInfo = entry.providerInfo!!,
- priority = entry.priority,
+ rank = entry.rank,
)
}
@@ -403,7 +408,7 @@
return if (componentName != null && session != null) {
CommunalWidgetContentModel.Pending(
appWidgetId = entry.appWidgetId,
- priority = entry.priority,
+ rank = entry.rank,
componentName = componentName,
icon = session.icon,
user = session.user,
@@ -416,7 +421,7 @@
private data class CommunalWidgetEntry(
val appWidgetId: Int,
val componentName: String,
- val priority: Int,
+ val rank: Int,
var providerInfo: AppWidgetProviderInfo? = null,
)
}
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 7181b15..2aa6c19 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
@@ -367,13 +367,16 @@
/** Dismiss the CTA tile from the hub in view mode. */
suspend fun dismissCtaTile() = communalPrefsInteractor.setCtaDismissed()
- /** Add a widget at the specified position. */
+ /**
+ * Add a widget at the specified rank. If rank is not provided, the widget will be added at the
+ * end.
+ */
fun addWidget(
componentName: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int? = null,
configurator: WidgetConfigurator?,
- ) = widgetRepository.addWidget(componentName, user, priority, configurator)
+ ) = widgetRepository.addWidget(componentName, user, rank, configurator)
/**
* Delete a widget by id. Called when user deletes a widget from the hub or a widget is
@@ -384,10 +387,10 @@
/**
* Reorder the widgets.
*
- * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+ * @param widgetIdToRankMap mapping of the widget ids to their new priorities.
*/
- fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
- widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+ fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) =
+ widgetRepository.updateWidgetOrder(widgetIdToRankMap)
/** Request to unpause work profile that is currently in quiet mode. */
fun unpauseWorkProfile() {
@@ -440,7 +443,7 @@
is CommunalWidgetContentModel.Available -> {
WidgetContent.Widget(
appWidgetId = widget.appWidgetId,
- priority = widget.priority,
+ rank = widget.rank,
providerInfo = widget.providerInfo,
appWidgetHost = appWidgetHost,
inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
@@ -449,7 +452,7 @@
is CommunalWidgetContentModel.Pending -> {
WidgetContent.PendingWidget(
appWidgetId = widget.appWidgetId,
- priority = widget.priority,
+ rank = widget.rank,
componentName = widget.componentName,
icon = widget.icon,
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 73c6ce3..4c821d4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -47,12 +47,12 @@
sealed interface WidgetContent : CommunalContentModel {
val appWidgetId: Int
- val priority: Int
+ val rank: Int
val componentName: ComponentName
data class Widget(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: CommunalAppWidgetHost,
val inQuietMode: Boolean,
@@ -71,7 +71,7 @@
data class DisabledWidget(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
val providerInfo: AppWidgetProviderInfo
) : WidgetContent {
override val key = KEY.disabledWidget(appWidgetId)
@@ -85,7 +85,7 @@
data class PendingWidget(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
override val componentName: ComponentName,
val icon: Bitmap? = null,
) : WidgetContent {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 9ce8cf7..7cfad60 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -31,7 +31,7 @@
private val statsLogProxy: StatsLogProxy,
) {
/** Logs an add widget event for metrics. No-op if widget is not loggable. */
- fun logAddWidget(componentName: String, rank: Int) {
+ fun logAddWidget(componentName: String, rank: Int?) {
if (!componentName.isLoggable()) {
return
}
@@ -39,7 +39,7 @@
statsLogProxy.writeCommunalHubWidgetEventReported(
SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
componentName,
- rank,
+ rank ?: -1,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 7cddb72..63b1a14 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -24,19 +24,19 @@
/** Encapsulates data for a communal widget. */
sealed interface CommunalWidgetContentModel {
val appWidgetId: Int
- val priority: Int
+ val rank: Int
/** Widget is ready to display */
data class Available(
override val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
- override val priority: Int,
+ override val rank: Int,
) : CommunalWidgetContentModel
/** Widget is pending installation */
data class Pending(
override val appWidgetId: Int,
- override val priority: Int,
+ override val rank: Int,
val componentName: ComponentName,
val icon: Bitmap?,
val user: UserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index b822133..6be94a7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -153,7 +153,7 @@
open fun onAddWidget(
componentName: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int? = null,
configurator: WidgetConfigurator? = null,
) {}
@@ -161,23 +161,23 @@
open fun onDeleteWidget(
id: Int,
componentName: ComponentName,
- priority: Int,
+ rank: Int,
) {}
/** Called as the UI detects a tap event on the widget. */
open fun onTapWidget(
componentName: ComponentName,
- priority: Int,
+ rank: Int,
) {}
/**
* Called as the UI requests reordering widgets.
*
- * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
- * add a new item in the middle, provide the priorities of existing widgets as if the new item
- * existed, and then, call [onAddWidget] to add the new item at intended order.
+ * @param widgetIdToRankMap mapping of the widget ids to its rank. When re-ordering to add a new
+ * item in the middle, provide the priorities of existing widgets as if the new item existed,
+ * and then, call [onAddWidget] to add the new item at intended order.
*/
- open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
+ open fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor with an optional preselected widget. */
open fun onOpenWidgetEditor(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 1a86c71..16788d1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -125,24 +125,24 @@
override fun onAddWidget(
componentName: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int?,
configurator: WidgetConfigurator?
) {
- communalInteractor.addWidget(componentName, user, priority, configurator)
- metricsLogger.logAddWidget(componentName.flattenToString(), priority)
+ communalInteractor.addWidget(componentName, user, rank, configurator)
+ metricsLogger.logAddWidget(componentName.flattenToString(), rank)
}
override fun onDeleteWidget(
id: Int,
componentName: ComponentName,
- priority: Int,
+ rank: Int,
) {
communalInteractor.deleteWidget(id)
- metricsLogger.logRemoveWidget(componentName.flattenToString(), priority)
+ metricsLogger.logRemoveWidget(componentName.flattenToString(), rank)
}
- override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
- communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
+ override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) =
+ communalInteractor.updateWidgetOrder(widgetIdToRankMap)
override fun onReorderWidgetStart() {
// Clear selection status
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index c0a18f2..4c762dc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -272,8 +272,8 @@
}
}
- override fun onTapWidget(componentName: ComponentName, priority: Int) {
- metricsLogger.logTapWidget(componentName.flattenToString(), priority)
+ override fun onTapWidget(componentName: ComponentName, rank: Int) {
+ metricsLogger.logTapWidget(componentName.flattenToString(), rank)
}
fun onClick() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 7b9868b..93c3a63 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -187,11 +187,11 @@
if (!isPendingWidgetDrag) {
val (componentName, user) = getWidgetExtraFromIntent(intent)
if (componentName != null && user != null) {
+ // Add widget at the end.
communalViewModel.onAddWidget(
componentName,
user,
- 0,
- widgetConfigurator
+ configurator = widgetConfigurator,
)
} else {
run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 9460eaf..d288cce 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,7 +57,10 @@
import com.android.systemui.log.SessionTracker
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.Scenes.Bouncer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -159,6 +162,7 @@
private val powerInteractor: PowerInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val sceneInteractor: dagger.Lazy<SceneInteractor>,
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -385,7 +389,16 @@
biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
"isFaceAuthEnrolledAndEnabled"
),
- Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+ Pair(
+ if (SceneContainerFlag.isEnabled) {
+ keyguardTransitionInteractor
+ .isInTransitionWhere(toStatePredicate = { it == KeyguardState.UNDEFINED })
+ .isFalse()
+ } else {
+ keyguardRepository.isKeyguardGoingAway.isFalse()
+ },
+ "keyguardNotGoingAway"
+ ),
Pair(
keyguardTransitionInteractor
.isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
@@ -397,7 +410,11 @@
.isFalse()
.or(
alternateBouncerInteractor.isVisible.or(
- keyguardInteractor.primaryBouncerShowing
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.get().transitionState.map { it.isIdle(Bouncer) }
+ } else {
+ keyguardInteractor.primaryBouncerShowing
+ }
)
),
"secureCameraNotActiveOrAnyBouncerIsShowing"
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index c536d6b..183e0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -46,6 +46,9 @@
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
@@ -90,6 +93,7 @@
private val powerInteractor: PowerInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val trustManager: TrustManager,
+ private val sceneInteractor: Lazy<SceneInteractor>,
deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
) : DeviceEntryFaceAuthInteractor {
@@ -103,9 +107,7 @@
keyguardUpdateMonitor.setFaceAuthInteractor(this)
observeFaceAuthStateUpdates()
faceAuthenticationLogger.interactorStarted()
- primaryBouncerInteractor
- .get()
- .isShowing
+ isBouncerVisible
.whenItFlipsToTrue()
.onEach {
faceAuthenticationLogger.bouncerVisibilityChanged()
@@ -181,19 +183,23 @@
// auth so that the switched user can unlock the device with face auth.
userRepository.selectedUser
.pairwise()
- .onEach { (previous, curr) ->
+ .filter { (previous, curr) ->
val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
- if (wasSwitching && !isSwitching) {
- resetLockedOutState(curr.userInfo.id)
- yield()
- runFaceAuth(
- FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
- // Fallback to detection if bouncer is not showing so that we can detect a
- // face and then show the bouncer to the user if face auth can't run
- fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing()
- )
- }
+ // User switching was in progress and is complete now.
+ wasSwitching && !isSwitching
+ }
+ .map { (_, curr) -> curr.userInfo.id }
+ .sample(isBouncerVisible, ::Pair)
+ .onEach { (userId, isBouncerCurrentlyVisible) ->
+ resetLockedOutState(userId)
+ yield()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
+ // Fallback to detection if bouncer is not showing so that we can detect a
+ // face and then show the bouncer to the user if face auth can't run
+ fallbackToDetect = !isBouncerCurrentlyVisible
+ )
}
.launchIn(applicationScope)
@@ -210,6 +216,14 @@
.launchIn(applicationScope)
}
+ private val isBouncerVisible: Flow<Boolean> by lazy {
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
+ } else {
+ primaryBouncerInteractor.get().isShowing
+ }
+ }
+
private suspend fun resetLockedOutState(currentUserId: Int) {
val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
repository.setLockedOut(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b45ebd8..24ac542 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
@@ -84,51 +85,62 @@
private var mCurrentBlurRadius: Float = 0f
+ private var mLifecycleFlowHandle: DisposableHandle? = null
+
fun init(view: View) {
this.view = view
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- dreamViewModel.dreamOverlayTranslationY.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int -> setElementsTranslationYAtPosition(px, position) },
- POSITION_TOP or POSITION_BOTTOM
- )
+ mLifecycleFlowHandle =
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ dreamViewModel.dreamOverlayTranslationY.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationYAtPosition(px, position)
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.dreamOverlayTranslationX.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int -> setElementsTranslationXAtPosition(px, position) },
- POSITION_TOP or POSITION_BOTTOM
- )
+ launch {
+ dreamViewModel.dreamOverlayTranslationX.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationXAtPosition(px, position)
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.dreamOverlayAlpha.collect { alpha ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = position,
- fadingOut = true,
- )
- },
- POSITION_TOP or POSITION_BOTTOM
- )
+ launch {
+ dreamViewModel.dreamOverlayAlpha.collect { alpha ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = position,
+ fadingOut = true,
+ )
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.transitionEnded.collect { _ ->
- mOverlayStateController.setExitAnimationsRunning(false)
+ launch {
+ dreamViewModel.transitionEnded.collect { _ ->
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
}
}
}
- }
+ }
+
+ fun destroy() {
+ mLifecycleFlowHandle?.dispose()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 76c7d23..bf6d266 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -59,6 +59,7 @@
import com.android.systemui.util.ViewController;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.DisposableHandle;
import kotlinx.coroutines.flow.FlowKt;
import java.util.Arrays;
@@ -185,6 +186,8 @@
}
};
+ private DisposableHandle mFlowHandle;
+
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
@@ -252,6 +255,17 @@
}
@Override
+ public void destroy() {
+ mStateController.removeCallback(mDreamOverlayStateCallback);
+ mStatusBarViewController.destroy();
+ mComplicationHostViewController.destroy();
+ mDreamOverlayAnimationsController.destroy();
+ mLowLightTransitionCoordinator.setLowLightEnterListener(null);
+
+ super.destroy();
+ }
+
+ @Override
protected void onViewAttached() {
mWakingUpFromSwipe = false;
mJitterStartTimeMillis = System.currentTimeMillis();
@@ -263,7 +277,7 @@
emptyRegion.recycle();
if (dreamHandlesBeingObscured()) {
- collectFlow(
+ mFlowHandle = collectFlow(
mView,
FlowKt.distinctUntilChanged(combineFlows(
mKeyguardTransitionInteractor.isFinishedIn(
@@ -295,6 +309,10 @@
@Override
protected void onViewDetached() {
+ if (mFlowHandle != null) {
+ mFlowHandle.dispose();
+ mFlowHandle = null;
+ }
mHandler.removeCallbacksAndMessages(null);
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 7a9537b..4c22763 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -60,7 +60,6 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.shared.log.CommunalUiEvent;
import com.android.systemui.communal.shared.model.CommunalScenes;
-import com.android.systemui.complication.Complication;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
@@ -70,8 +69,12 @@
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -129,17 +132,21 @@
*/
private boolean mBouncerShowing = false;
- private final ComplicationComponent mComplicationComponent;
+ private final com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+ mDreamComplicationComponentFactory;
+ private final ComplicationComponent.Factory mComplicationComponentFactory;
+ private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
+ private final AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
- private final AmbientTouchComponent mAmbientTouchComponent;
+ private final TouchInsetManager mTouchInsetManager;
+ private final LifecycleOwner mLifecycleOwner;
- private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
- mDreamComplicationComponent;
- private final DreamOverlayComponent mDreamOverlayComponent;
private ComponentName mCurrentBlockedGestureDreamActivityComponent;
+ private final ArrayList<Job> mFlows = new ArrayList<>();
+
/**
* This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
* handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -285,36 +292,27 @@
mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
mStateController = stateController;
mUiEventLogger = uiEventLogger;
+ mComplicationComponentFactory = complicationComponentFactory;
+ mDreamComplicationComponentFactory = dreamComplicationComponentFactory;
mDreamOverlayCallbackController = dreamOverlayCallbackController;
mWindowTitle = windowTitle;
mCommunalInteractor = communalInteractor;
mSystemDialogsCloser = systemDialogsCloser;
mGestureInteractor = gestureInteractor;
-
- final ViewModelStore viewModelStore = new ViewModelStore();
- final Complication.Host host =
- () -> mExecutor.execute(DreamOverlayService.this::requestExit);
-
- mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
- viewModelStore, touchInsetManager);
- mDreamComplicationComponent = dreamComplicationComponentFactory.create(
- mComplicationComponent.getVisibilityController(), touchInsetManager);
- mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
- mComplicationComponent.getComplicationHostViewController(), touchInsetManager);
- mAmbientTouchComponent = ambientTouchComponentFactory.create(lifecycleOwner,
- new HashSet<>(Arrays.asList(
- mDreamComplicationComponent.getHideComplicationTouchHandler(),
- mDreamOverlayComponent.getCommunalTouchHandler())));
+ mDreamOverlayComponentFactory = dreamOverlayComponentFactory;
+ mAmbientTouchComponentFactory = ambientTouchComponentFactory;
+ mTouchInsetManager = touchInsetManager;
+ mLifecycleOwner = lifecycleOwner;
mLifecycleRegistry = lifecycleOwner.getRegistry();
mExecutor.execute(() -> setLifecycleStateLocked(Lifecycle.State.CREATED));
- collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
- mIsCommunalAvailableCallback);
- collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
- mCommunalVisibleConsumer);
- collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
- mBouncerShowingConsumer);
+ mFlows.add(collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
+ mIsCommunalAvailableCallback));
+ mFlows.add(collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
+ mCommunalVisibleConsumer));
+ mFlows.add(collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
+ mBouncerShowingConsumer));
}
@NonNull
@@ -339,6 +337,11 @@
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
+ for (Job job : mFlows) {
+ job.cancel(new CancellationException());
+ }
+ mFlows.clear();
+
mExecutor.execute(() -> {
setLifecycleStateLocked(Lifecycle.State.DESTROYED);
@@ -353,6 +356,23 @@
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+ final ComplicationComponent complicationComponent = mComplicationComponentFactory.create(
+ mLifecycleOwner,
+ () -> mExecutor.execute(DreamOverlayService.this::requestExit),
+ new ViewModelStore(), mTouchInsetManager);
+ final com.android.systemui.dreams.complication.dagger.ComplicationComponent
+ dreamComplicationComponent = mDreamComplicationComponentFactory.create(
+ complicationComponent.getVisibilityController(), mTouchInsetManager);
+
+ final DreamOverlayComponent dreamOverlayComponent = mDreamOverlayComponentFactory.create(
+ mLifecycleOwner, complicationComponent.getComplicationHostViewController(),
+ mTouchInsetManager);
+ final AmbientTouchComponent ambientTouchComponent = mAmbientTouchComponentFactory.create(
+ mLifecycleOwner,
+ new HashSet<>(Arrays.asList(
+ dreamComplicationComponent.getHideComplicationTouchHandler(),
+ dreamOverlayComponent.getCommunalTouchHandler())));
+
setLifecycleStateLocked(Lifecycle.State.STARTED);
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -371,8 +391,8 @@
}
mDreamOverlayContainerViewController =
- mDreamOverlayComponent.getDreamOverlayContainerViewController();
- mTouchMonitor = mAmbientTouchComponent.getTouchMonitor();
+ dreamOverlayComponent.getDreamOverlayContainerViewController();
+ mTouchMonitor = ambientTouchComponent.getTouchMonitor();
mTouchMonitor.init();
mStateController.setShouldShowComplications(shouldShowComplications());
@@ -541,6 +561,10 @@
}
private void removeContainerViewFromParentLocked() {
+ if (mDreamOverlayContainerViewController == null) {
+ return;
+ }
+
View containerView = mDreamOverlayContainerViewController.getContainerView();
if (containerView == null) {
return;
@@ -559,8 +583,13 @@
return;
}
+ // This ensures the container view of the current dream is removed before
+ // the controller is potentially reset.
+ removeContainerViewFromParentLocked();
+
if (mStarted && mWindow != null) {
try {
+ mWindow.clearContentView();
mWindowManager.removeView(mWindow.getDecorView());
} catch (IllegalArgumentException e) {
Log.e(TAG, "Error removing decor view when resetting overlay", e);
@@ -571,7 +600,10 @@
mStateController.setLowLightActive(false);
mStateController.setEntryAnimationsFinished(false);
- mDreamOverlayContainerViewController = null;
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.destroy();
+ mDreamOverlayContainerViewController = null;
+ }
if (mTouchMonitor != null) {
mTouchMonitor.destroy();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index ee7b6f5..5ba780f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -33,7 +33,11 @@
import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -49,6 +53,8 @@
private final ConfigurationInteractor mConfigurationInteractor;
private Boolean mIsEnabled = false;
+ private ArrayList<Job> mFlows = new ArrayList<>();
+
private int mLayoutDirection = LayoutDirection.LTR;
@VisibleForTesting
@@ -70,17 +76,17 @@
mCommunalInteractor = communalInteractor;
mConfigurationInteractor = configurationInteractor;
- collectFlow(
+ mFlows.add(collectFlow(
mLifecycle,
mCommunalInteractor.isCommunalAvailable(),
mIsCommunalAvailableCallback
- );
+ ));
- collectFlow(
+ mFlows.add(collectFlow(
mLifecycle,
mConfigurationInteractor.getLayoutDirection(),
mLayoutDirectionCallback
- );
+ ));
}
@Override
@@ -140,4 +146,13 @@
}
});
}
+
+ @Override
+ public void onDestroy() {
+ for (Job job : mFlows) {
+ job.cancel(new CancellationException());
+ }
+ mFlows.clear();
+ TouchHandler.super.onDestroy();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
similarity index 67%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
index c968e80..8682848 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * 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.
@@ -14,6 +14,11 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package com.android.systemui.education.data.model
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+import java.time.Instant
+
+data class EduDeviceConnectionTime(
+ val keyboardFirstConnectionTime: Instant? = null,
+ val touchpadFirstConnectionTime: Instant? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 4fd79d7..01f838f 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -29,6 +29,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import java.time.Instant
import javax.inject.Inject
@@ -53,10 +54,16 @@
fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+ fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime>
+
suspend fun updateGestureEduModel(
gestureType: GestureType,
transform: (GestureEduModel) -> GestureEduModel
)
+
+ suspend fun updateEduDeviceConnectionTime(
+ transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+ )
}
/**
@@ -76,6 +83,8 @@
const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
const val USAGE_SESSION_START_TIME_SUFFIX = "_USAGE_SESSION_START_TIME"
const val LAST_EDUCATION_TIME_SUFFIX = "_LAST_EDUCATION_TIME"
+ const val KEYBOARD_FIRST_CONNECTION_TIME = "KEYBOARD_FIRST_CONNECTION_TIME"
+ const val TOUCHPAD_FIRST_CONNECTION_TIME = "TOUCHPAD_FIRST_CONNECTION_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -158,6 +167,37 @@
}
}
+ override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> =
+ prefData.map { preferences -> getEduDeviceConnectionTime(preferences) }
+
+ override suspend fun updateEduDeviceConnectionTime(
+ transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+ ) {
+ datastore.filterNotNull().first().edit { preferences ->
+ val currentModel = getEduDeviceConnectionTime(preferences)
+ val updatedModel = transform(currentModel)
+ setInstant(
+ preferences,
+ updatedModel.keyboardFirstConnectionTime,
+ getKeyboardFirstConnectionTimeKey()
+ )
+ setInstant(
+ preferences,
+ updatedModel.touchpadFirstConnectionTime,
+ getTouchpadFirstConnectionTimeKey()
+ )
+ }
+ }
+
+ private fun getEduDeviceConnectionTime(preferences: Preferences): EduDeviceConnectionTime {
+ return EduDeviceConnectionTime(
+ keyboardFirstConnectionTime =
+ preferences[getKeyboardFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) },
+ touchpadFirstConnectionTime =
+ preferences[getTouchpadFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) }
+ )
+ }
+
private fun getSignalCountKey(gestureType: GestureType): Preferences.Key<Int> =
intPreferencesKey(gestureType.name + SIGNAL_COUNT_SUFFIX)
@@ -173,6 +213,12 @@
private fun getLastEducationTimeKey(gestureType: GestureType): Preferences.Key<Long> =
longPreferencesKey(gestureType.name + LAST_EDUCATION_TIME_SUFFIX)
+ private fun getKeyboardFirstConnectionTimeKey(): Preferences.Key<Long> =
+ longPreferencesKey(KEYBOARD_FIRST_CONNECTION_TIME)
+
+ private fun getTouchpadFirstConnectionTimeKey(): Preferences.Key<Long> =
+ longPreferencesKey(TOUCHPAD_FIRST_CONNECTION_TIME)
+
private fun setInstant(
preferences: MutablePreferences,
instant: Instant?,
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index db5c386..10be26e 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -32,6 +33,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
@@ -67,6 +69,10 @@
.flowOn(backgroundDispatcher)
}
+ suspend fun getEduDeviceConnectionTime(): EduDeviceConnectionTime {
+ return repository.readEduDeviceConnectionTime().first()
+ }
+
suspend fun incrementSignalCount(gestureType: GestureType) {
repository.updateGestureEduModel(gestureType) {
it.copy(
@@ -100,4 +106,16 @@
it.copy(usageSessionStartTime = clock.instant(), signalCount = 1)
}
}
+
+ suspend fun updateKeyboardFirstConnectionTime() {
+ repository.updateEduDeviceConnectionTime {
+ it.copy(keyboardFirstConnectionTime = clock.instant())
+ }
+ }
+
+ suspend fun updateTouchpadFirstConnectionTime() {
+ repository.updateEduDeviceConnectionTime {
+ it.copy(touchpadFirstConnectionTime = clock.instant())
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index ad3335b..e88349b2 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -24,6 +24,7 @@
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
import java.time.Clock
import javax.inject.Inject
import kotlin.time.Duration.Companion.hours
@@ -39,6 +40,7 @@
constructor(
@Background private val backgroundScope: CoroutineScope,
private val contextualEducationInteractor: ContextualEducationInteractor,
+ private val userInputDeviceRepository: UserInputDeviceRepository,
@EduClock private val clock: Clock,
) : CoreStartable {
@@ -61,6 +63,32 @@
}
}
}
+
+ backgroundScope.launch {
+ userInputDeviceRepository.isAnyTouchpadConnectedForUser.collect {
+ if (
+ it.isConnected &&
+ contextualEducationInteractor
+ .getEduDeviceConnectionTime()
+ .touchpadFirstConnectionTime == null
+ ) {
+ contextualEducationInteractor.updateTouchpadFirstConnectionTime()
+ }
+ }
+ }
+
+ backgroundScope.launch {
+ userInputDeviceRepository.isAnyKeyboardConnectedForUser.collect {
+ if (
+ it.isConnected &&
+ contextualEducationInteractor
+ .getEduDeviceConnectionTime()
+ .keyboardFirstConnectionTime == null
+ ) {
+ contextualEducationInteractor.updateKeyboardFirstConnectionTime()
+ }
+ }
+ }
}
private fun isEducationNeeded(model: GestureEduModel): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index 567bf70..ca43871 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -35,6 +35,7 @@
import android.util.Log
import android.util.Size
import androidx.core.content.res.ResourcesCompat
+import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -162,20 +163,21 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Bitmap? {
- return try {
- ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
- configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
- decoder.allocator = allocator
+ ): Bitmap? =
+ traceSection("ImageLoader#loadBitmap") {
+ return try {
+ ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
}
- } catch (e: IOException) {
- Log.w(TAG, "Failed to load source $source", e)
- return null
- } catch (e: DecodeException) {
- Log.w(TAG, "Failed to decode source $source", e)
- return null
}
- }
/**
* Loads passed [Source] on a background thread and returns the [Drawable].
@@ -253,28 +255,31 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return try {
- loadDrawableSync(
- toImageDecoderSource(source, defaultContext),
- maxWidth,
- maxHeight,
- allocator
- )
- ?:
- // If we have a resource, retry fallback using the "normal" Resource loading system.
- // This will come into effect in cases like trying to load AnimatedVectorDrawable.
- if (source is Res) {
- val context = source.context ?: defaultContext
- ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
- } else {
- null
- }
- } catch (e: NotFoundException) {
- Log.w(TAG, "Couldn't load resource $source", e)
- null
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return try {
+ loadDrawableSync(
+ toImageDecoderSource(source, defaultContext),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ ?:
+ // If we have a resource, retry fallback using the "normal" Resource loading
+ // system.
+ // This will come into effect in cases like trying to load
+ // AnimatedVectorDrawable.
+ if (source is Res) {
+ val context = source.context ?: defaultContext
+ ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+ } else {
+ null
+ }
+ } catch (e: NotFoundException) {
+ Log.w(TAG, "Couldn't load resource $source", e)
+ null
+ }
}
- }
/**
* Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
@@ -297,20 +302,21 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return try {
- ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
- configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
- decoder.allocator = allocator
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return try {
+ ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+ configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+ decoder.allocator = allocator
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
}
- } catch (e: IOException) {
- Log.w(TAG, "Failed to load source $source", e)
- return null
- } catch (e: DecodeException) {
- Log.w(TAG, "Failed to decode source $source", e)
- return null
}
- }
/** Loads icon drawable while attempting to size restrict the drawable. */
@WorkerThread
@@ -320,55 +326,59 @@
@Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
@Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
- ): Drawable? {
- return when (icon.type) {
- Icon.TYPE_URI,
- Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
- val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
- loadDrawableSync(source, maxWidth, maxHeight, allocator)
- }
- Icon.TYPE_RESOURCE -> {
- val resources = resolveResourcesForIcon(context, icon)
- resources?.let {
+ ): Drawable? =
+ traceSection("ImageLoader#loadDrawable") {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadDrawableSync(source, maxWidth, maxHeight, allocator)
+ }
+ Icon.TYPE_RESOURCE -> {
+ val resources = resolveResourcesForIcon(context, icon)
+ resources?.let {
+ loadDrawableSync(
+ ImageDecoder.createSource(it, icon.resId),
+ maxWidth,
+ maxHeight,
+ allocator
+ )
+ }
+ // Fallback to non-ImageDecoder load if the attempt failed (e.g. the
+ // resource
+ // is a Vector drawable which ImageDecoder doesn't support.)
+ ?: loadIconDrawable(icon, context)
+ }
+ Icon.TYPE_BITMAP -> {
+ BitmapDrawable(context.resources, icon.bitmap)
+ }
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+ }
+ Icon.TYPE_DATA -> {
loadDrawableSync(
- ImageDecoder.createSource(it, icon.resId),
+ ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
maxWidth,
maxHeight,
allocator
)
}
- // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
- // is a Vector drawable which ImageDecoder doesn't support.)
- ?: loadIconDrawable(icon, context)
+ else -> {
+ // We don't recognize this icon, just fallback.
+ loadIconDrawable(icon, context)
+ }
+ }?.let { drawable ->
+ // Icons carry tint which we need to propagate down to a Drawable.
+ tintDrawable(icon, drawable)
+ drawable
}
- Icon.TYPE_BITMAP -> {
- BitmapDrawable(context.resources, icon.bitmap)
- }
- Icon.TYPE_ADAPTIVE_BITMAP -> {
- AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
- }
- Icon.TYPE_DATA -> {
- loadDrawableSync(
- ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
- maxWidth,
- maxHeight,
- allocator
- )
- }
- else -> {
- // We don't recognize this icon, just fallback.
- loadIconDrawable(icon, context)
- }
- }?.let { drawable ->
- // Icons carry tint which we need to propagate down to a Drawable.
- tintDrawable(icon, drawable)
- drawable
}
- }
@WorkerThread
fun loadIconDrawable(icon: Icon, context: Context): Drawable? {
- icon.loadDrawable(context)?.let { return it }
+ icon.loadDrawable(context)?.let {
+ return it
+ }
Log.w(TAG, "Failed to load drawable for $icon")
return null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 4cab2bb..a96d7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,7 @@
import android.app.StatusBarManager
import android.graphics.Point
+import android.util.Log
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -38,6 +39,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
@@ -87,6 +89,7 @@
sceneInteractorProvider: Provider<SceneInteractor>,
private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
private val fromLockscreenTransitionInteractor: Provider<FromLockscreenTransitionInteractor>,
+ private val fromOccludedTransitionInteractor: Provider<FromOccludedTransitionInteractor>,
sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>,
@Application applicationScope: CoroutineScope,
) {
@@ -484,7 +487,11 @@
/** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
fun dismissKeyguard() {
- fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ when (keyguardTransitionInteractor.transitionState.value.to) {
+ LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
+ else -> Log.v(TAG, "Keyguard was dismissed, no direct transition call needed")
+ }
}
fun onCameraLaunchDetected(source: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index cd49c6a..4a8ada7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -27,6 +27,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -67,6 +68,7 @@
broadcastDispatcher: BroadcastDispatcher,
private val accessibilityManager: AccessibilityManagerWrapper,
private val pulsingGestureListener: PulsingGestureListener,
+ private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
) {
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: StateFlow<Boolean> =
@@ -129,7 +131,8 @@
}
}
- /** Notifies that the user has long-pressed on the lock screen.
+ /**
+ * Notifies that the user has long-pressed on the lock screen.
*
* @param isA11yAction: Whether the action was performed as an a11y action
*/
@@ -174,6 +177,7 @@
/** Notifies that the lockscreen has been clicked at position [x], [y]. */
fun onClick(x: Float, y: Float) {
pulsingGestureListener.onSingleTapUp(x, y)
+ faceAuthInteractor.onNotificationPanelClicked()
}
/** Notifies that the lockscreen has been double clicked. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 51ce355..9ccfb50 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -79,6 +79,7 @@
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.clocks.ClockRegistry
@@ -188,6 +189,7 @@
init {
coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
disposables += DisposableHandle { coroutineScope.cancel() }
+ clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
if (KeyguardBottomAreaRefactor.isEnabled) {
quickAffordancesCombinedViewModel.enablePreviewMode(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 143d66b..24c57be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -16,9 +16,8 @@
package com.android.systemui.media.controls.domain.pipeline
+import android.annotation.MainThread
import android.annotation.SuppressLint
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
import android.app.PendingIntent
@@ -39,7 +38,6 @@
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
-import android.graphics.drawable.Animatable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -62,8 +60,10 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Dumpable
+import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -86,7 +86,6 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.tuner.TunerService
@@ -97,8 +96,13 @@
import com.android.systemui.util.time.SystemClock
import java.io.IOException
import java.io.PrintWriter
+import java.util.Collections
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
// URI fields to try loading album art from
private val ART_URIS =
@@ -167,8 +171,11 @@
class LegacyMediaDataManagerImpl(
private val context: Context,
@Background private val backgroundExecutor: Executor,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
@Main private val uiExecutor: Executor,
@Main private val foregroundExecutor: DelayableExecutor,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Application private val applicationScope: CoroutineScope,
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
dumpManager: DumpManager,
@@ -188,6 +195,7 @@
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
companion object {
@@ -219,7 +227,12 @@
// listeners are listeners that depend on MediaDataManager.
// TODO(b/159539991#comment5): Move internal listeners to separate package.
private val internalListeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
- private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+ private val mediaEntries: MutableMap<String, MediaData> =
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ Collections.synchronizedMap(LinkedHashMap())
+ } else {
+ LinkedHashMap()
+ }
// There should ONLY be at most one Smartspace media recommendation.
var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
@Keep private var smartspaceSession: SmartspaceSession? = null
@@ -245,8 +258,11 @@
constructor(
context: Context,
threadFactory: ThreadFactory,
+ @Background backgroundDispatcher: CoroutineDispatcher,
@Main uiExecutor: Executor,
@Main foregroundExecutor: DelayableExecutor,
+ @Main mainDispatcher: CoroutineDispatcher,
+ @Application applicationScope: CoroutineScope,
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
broadcastDispatcher: BroadcastDispatcher,
@@ -264,13 +280,17 @@
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ mediaDataLoader: dagger.Lazy<MediaDataLoader>,
) : this(
context,
// Loading bitmap for UMO background can take longer time, so it cannot run on the default
// background thread. Use a custom thread for media.
threadFactory.buildExecutorOnNewThread(TAG),
+ backgroundDispatcher,
uiExecutor,
foregroundExecutor,
+ mainDispatcher,
+ applicationScope,
mediaControllerFactory,
broadcastDispatcher,
dumpManager,
@@ -290,6 +310,7 @@
logger,
smartspaceManager,
keyguardUpdateMonitor,
+ mediaDataLoader,
)
private val appChangeReceiver =
@@ -464,16 +485,31 @@
logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
- backgroundExecutor.execute {
- loadMediaDataInBgForResumption(
- userId,
- desc,
- action,
- token,
- appName,
- appIntent,
- packageName
- )
+
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
+ } else {
+ backgroundExecutor.execute {
+ loadMediaDataInBgForResumption(
+ userId,
+ desc,
+ action,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
}
}
@@ -498,9 +534,90 @@
oldKey: String?,
isNewlyActiveEntry: Boolean = false,
) {
- backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ applicationScope.launch {
+ loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry)
+ }
+ } else {
+ backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
+ }
}
+ private suspend fun loadMediaDataWithLoader(
+ key: String,
+ sbn: StatusBarNotification,
+ oldKey: String?,
+ isNewlyActiveEntry: Boolean = false,
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val result = mediaDataLoader.get().loadMediaData(key, sbn)
+ if (result == null) {
+ Log.d(TAG, "No result from loadMediaData")
+ return@withContext
+ }
+
+ val currentEntry = mediaEntries[key]
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val resumeAction: Runnable? = currentEntry?.resumeAction
+ val hasCheckedForResume = currentEntry?.hasCheckedForResume == true
+ val active = currentEntry?.active ?: true
+
+ // We need to log the correct media added.
+ if (isNewlyActiveEntry) {
+ logSingleVsMultipleMediaAdded(result.appUid, sbn.packageName, instanceId)
+ logger.logActiveMediaAdded(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ } else if (result.playbackLocation != currentEntry?.playbackLocation) {
+ logger.logPlaybackLocationChange(
+ result.appUid,
+ sbn.packageName,
+ instanceId,
+ result.playbackLocation
+ )
+ }
+
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ key,
+ oldKey,
+ MediaData(
+ userId = sbn.normalizedUserId,
+ initialized = true,
+ app = result.appName,
+ appIcon = result.appIcon,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = sbn.packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = active,
+ resumeAction = resumeAction,
+ playbackLocation = result.playbackLocation,
+ notificationKey = key,
+ hasCheckedForResume = hasCheckedForResume,
+ isPlaying = result.isPlaying,
+ isClearable = !sbn.isOngoing,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ )
+ )
+ }
+ }
+
/** Add a listener for changes in this class */
override fun addListener(listener: MediaDataManager.Listener) {
// mediaDataFilter is the current end of the internal pipeline. Register external
@@ -697,6 +814,75 @@
notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
}
+ private suspend fun loadMediaDataForResumption(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ) =
+ withContext(backgroundDispatcher) {
+ val lastActive = systemClock.elapsedRealtime()
+ val currentEntry = mediaEntries[packageName]
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
+ val result =
+ mediaDataLoader
+ .get()
+ .loadMediaDataForResumption(
+ userId,
+ desc,
+ resumeAction,
+ currentEntry,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ if (result == null || desc.title.isNullOrBlank()) {
+ Log.d(TAG, "No MediaData result for resumption")
+ mediaEntries.remove(packageName)
+ return@withContext
+ }
+
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ withContext(mainDispatcher) {
+ onMediaDataLoaded(
+ packageName,
+ null,
+ MediaData(
+ userId = userId,
+ initialized = true,
+ app = result.appName,
+ appIcon = null,
+ artist = result.artist,
+ song = result.song,
+ artwork = result.artworkIcon,
+ actions = result.actionIcons,
+ actionsToShowInCompact = result.actionsToShowInCompact,
+ semanticActions = result.semanticActions,
+ packageName = packageName,
+ token = result.token,
+ clickIntent = result.clickIntent,
+ device = result.device,
+ active = false,
+ resumeAction = resumeAction,
+ resumption = true,
+ notificationKey = packageName,
+ hasCheckedForResume = true,
+ lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
+ instanceId = instanceId,
+ appUid = result.appUid,
+ isExplicit = result.isExplicit,
+ resumeProgress = result.resumeProgress,
+ )
+ )
+ }
+ }
+
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadMediaDataInBgForResumption(
userId: Int,
desc: MediaDescription,
@@ -780,6 +966,7 @@
}
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
fun loadMediaDataInBg(
key: String,
sbn: StatusBarNotification,
@@ -802,8 +989,7 @@
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
ApplicationInfo::class.java
- )
- ?: getAppInfoFromPackage(sbn.packageName)
+ ) ?: getAppInfoFromPackage(sbn.packageName)
// App name
val appName = getAppName(sbn, appInfo)
@@ -894,7 +1080,7 @@
var actionsToShowCollapsed: List<Int> = emptyList()
val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
if (semanticActions == null) {
- val actions = createActionsFromNotification(sbn)
+ val actions = createActionsFromNotification(context, activityStarter, sbn)
actionIcons = actions.first
actionsToShowCollapsed = actions.second
}
@@ -975,6 +1161,7 @@
}
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
try {
return context.packageManager.getApplicationInfo(packageName, 0)
@@ -984,6 +1171,7 @@
return null
}
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
if (name != null) {
@@ -997,264 +1185,19 @@
}
}
- /** Generate action buttons based on notification actions */
- private fun createActionsFromNotification(
- sbn: StatusBarNotification
- ): Pair<List<MediaAction>, List<Int>> {
- val notif = sbn.notification
- val actionIcons: MutableList<MediaAction> = ArrayList()
- val actions = notif.actions
- var actionsToShowCollapsed =
- notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
- ?: mutableListOf()
- if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
- Log.e(
- TAG,
- "Too many compact actions for ${sbn.key}," +
- "limiting to first $MAX_COMPACT_ACTIONS"
- )
- actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
- }
-
- if (actions != null) {
- for ((index, action) in actions.withIndex()) {
- if (index == MAX_NOTIFICATION_ACTIONS) {
- Log.w(
- TAG,
- "Too many notification actions for ${sbn.key}," +
- " limiting to first $MAX_NOTIFICATION_ACTIONS"
- )
- break
- }
- if (action.getIcon() == null) {
- if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}")
- actionsToShowCollapsed.remove(index)
- continue
- }
- val runnable =
- if (action.actionIntent != null) {
- Runnable {
- if (action.actionIntent.isActivity) {
- activityStarter.startPendingIntentDismissingKeyguard(
- action.actionIntent
- )
- } else if (action.isAuthenticationRequired()) {
- activityStarter.dismissKeyguardThenExecute(
- {
- var result = sendPendingIntent(action.actionIntent)
- result
- },
- {},
- true
- )
- } else {
- sendPendingIntent(action.actionIntent)
- }
- }
- } else {
- null
- }
- val mediaActionIcon =
- if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
- Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
- } else {
- action.getIcon()
- }
- .setTint(themeText)
- .loadDrawable(context)
- val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
- actionIcons.add(mediaAction)
- }
- }
- return Pair(actionIcons, actionsToShowCollapsed)
- }
-
- /**
- * Generates action button info for this media session based on the PlaybackState
- *
- * @param packageName Package name for the media app
- * @param controller MediaController for the current session
- * @return a Pair consisting of a list of media actions, and a list of ints representing which
- *
- * ```
- * of those actions should be shown in the compact player
- * ```
- */
private fun createActionsFromState(
packageName: String,
controller: MediaController,
user: UserHandle
): MediaButton? {
- val state = controller.playbackState
- if (state == null || !mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+ if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
}
-
- // First, check for standard actions
- val playOrPause =
- if (isConnectingState(state.state)) {
- // Spinner needs to be animating to render anything. Start it here.
- val drawable =
- context.getDrawable(com.android.internal.R.drawable.progress_small_material)
- (drawable as Animatable).start()
- MediaAction(
- drawable,
- null, // no action to perform when clicked
- context.getString(R.string.controls_media_button_connecting),
- context.getDrawable(R.drawable.ic_media_connecting_container),
- // Specify a rebind id to prevent the spinner from restarting on later binds.
- com.android.internal.R.drawable.progress_small_material
- )
- } else if (isPlayingState(state.state)) {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
- } else {
- getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
- }
- val prevButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
- val nextButton =
- getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
-
- // Then, create a way to build any custom actions that will be needed
- val customActions =
- state.customActions
- .asSequence()
- .filterNotNull()
- .map { getCustomAction(state, packageName, controller, it) }
- .iterator()
- fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
-
- // Finally, assign the remaining button slots: play/pause A B C D
- // A = previous, else custom action (if not reserved)
- // B = next, else custom action (if not reserved)
- // C and D are always custom actions
- val reservePrev =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
- ) == true
- val reserveNext =
- controller.extras?.getBoolean(
- MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
- ) == true
-
- val prevOrCustom =
- if (prevButton != null) {
- prevButton
- } else if (!reservePrev) {
- nextCustomAction()
- } else {
- null
- }
-
- val nextOrCustom =
- if (nextButton != null) {
- nextButton
- } else if (!reserveNext) {
- nextCustomAction()
- } else {
- null
- }
-
- return MediaButton(
- playOrPause,
- nextOrCustom,
- prevOrCustom,
- nextCustomAction(),
- nextCustomAction(),
- reserveNext,
- reservePrev
- )
- }
-
- /**
- * Create a [MediaAction] for a given action and media session
- *
- * @param controller MediaController for the session
- * @param stateActions The actions included with the session's [PlaybackState]
- * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
- * ```
- * [PlaybackState.ACTION_PLAY]
- * [PlaybackState.ACTION_PAUSE]
- * [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
- * [PlaybackState.ACTION_SKIP_TO_NEXT]
- * @return
- * ```
- *
- * A [MediaAction] with correct values set, or null if the state doesn't support it
- */
- private fun getStandardAction(
- controller: MediaController,
- stateActions: Long,
- @PlaybackState.Actions action: Long
- ): MediaAction? {
- if (!includesAction(stateActions, action)) {
- return null
- }
-
- return when (action) {
- PlaybackState.ACTION_PLAY -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_play),
- { controller.transportControls.play() },
- context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
- )
- }
- PlaybackState.ACTION_PAUSE -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_pause),
- { controller.transportControls.pause() },
- context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
- )
- }
- PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_prev),
- { controller.transportControls.skipToPrevious() },
- context.getString(R.string.controls_media_button_prev),
- null
- )
- }
- PlaybackState.ACTION_SKIP_TO_NEXT -> {
- MediaAction(
- context.getDrawable(R.drawable.ic_media_next),
- { controller.transportControls.skipToNext() },
- context.getString(R.string.controls_media_button_next),
- null
- )
- }
- else -> null
- }
- }
-
- /** Check whether the actions from a [PlaybackState] include a specific action */
- private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
- if (
- (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
- (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
- ) {
- return true
- }
- return (stateActions and action != 0L)
- }
-
- /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
- private fun getCustomAction(
- state: PlaybackState,
- packageName: String,
- controller: MediaController,
- customAction: PlaybackState.CustomAction
- ): MediaAction {
- return MediaAction(
- Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
- { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
- customAction.name,
- null
- )
+ return createActionsFromState(context, packageName, controller)
}
/** Load a bitmap from the various Art metadata URIs */
+ @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
for (uri in ART_URIS) {
val uriString = metadata.getString(uri)
@@ -1269,21 +1212,6 @@
return null
}
- private fun sendPendingIntent(intent: PendingIntent): Boolean {
- return try {
- val options = BroadcastOptions.makeBasic()
- options.setInteractive(true)
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
- intent.send(options.toBundle())
- true
- } catch (e: PendingIntent.CanceledException) {
- Log.d(TAG, "Intent canceled", e)
- false
- }
- }
-
/** Returns a bitmap if the user can access the given URI, else null */
private fun loadBitmapFromUriForUser(
uri: Uri,
@@ -1364,6 +1292,7 @@
)
}
+ @MainThread
fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
traceSection("MediaDataManager#onMediaDataLoaded") {
Assert.isMainThread()
@@ -1619,6 +1548,7 @@
* - If resumption is disabled, we only want to show active players
*/
override fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
override fun isRecommendationActive() = smartspaceMediaData.isActive
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
new file mode 100644
index 0000000..70189b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Icon
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import androidx.media.utils.MediaConstants
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS
+import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.util.kotlin.logI
+
+private const val TAG = "MediaActions"
+
+/**
+ * Generates action button info for this media session based on the PlaybackState
+ *
+ * @param packageName Package name for the media app
+ * @param controller MediaController for the current session
+ * @return a Pair consisting of a list of media actions, and a list of ints representing which of
+ * those actions should be shown in the compact player
+ */
+fun createActionsFromState(
+ context: Context,
+ packageName: String,
+ controller: MediaController,
+): MediaButton? {
+ val state = controller.playbackState ?: return null
+ // First, check for standard actions
+ val playOrPause =
+ if (isConnectingState(state.state)) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable =
+ context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material
+ )
+ } else if (isPlayingState(state.state)) {
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
+ } else {
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PLAY)
+ }
+ val prevButton =
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+ val nextButton =
+ getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT)
+
+ // Then, create a way to build any custom actions that will be needed
+ val customActions =
+ state.customActions
+ .asSequence()
+ .filterNotNull()
+ .map { getCustomAction(context, packageName, controller, it) }
+ .iterator()
+ fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+ // Finally, assign the remaining button slots: play/pause A B C D
+ // A = previous, else custom action (if not reserved)
+ // B = next, else custom action (if not reserved)
+ // C and D are always custom actions
+ val reservePrev =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
+ ) == true
+ val reserveNext =
+ controller.extras?.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
+ ) == true
+
+ val prevOrCustom =
+ if (prevButton != null) {
+ prevButton
+ } else if (!reservePrev) {
+ nextCustomAction()
+ } else {
+ null
+ }
+
+ val nextOrCustom =
+ if (nextButton != null) {
+ nextButton
+ } else if (!reserveNext) {
+ nextCustomAction()
+ } else {
+ null
+ }
+
+ return MediaButton(
+ playOrPause,
+ nextOrCustom,
+ prevOrCustom,
+ nextCustomAction(),
+ nextCustomAction(),
+ reserveNext,
+ reservePrev
+ )
+}
+
+/**
+ * Create a [MediaAction] for a given action and media session
+ *
+ * @param controller MediaController for the session
+ * @param stateActions The actions included with the session's [PlaybackState]
+ * @param action A [PlaybackState.Actions] value representing what action to generate. One of:
+ * [PlaybackState.ACTION_PLAY] [PlaybackState.ACTION_PAUSE]
+ * [PlaybackState.ACTION_SKIP_TO_PREVIOUS] [PlaybackState.ACTION_SKIP_TO_NEXT]
+ * @return A [MediaAction] with correct values set, or null if the state doesn't support it
+ */
+private fun getStandardAction(
+ context: Context,
+ controller: MediaController,
+ stateActions: Long,
+ @PlaybackState.Actions action: Long
+): MediaAction? {
+ if (!includesAction(stateActions, action)) {
+ return null
+ }
+
+ return when (action) {
+ PlaybackState.ACTION_PLAY -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_play),
+ { controller.transportControls.play() },
+ context.getString(R.string.controls_media_button_play),
+ context.getDrawable(R.drawable.ic_media_play_container)
+ )
+ }
+ PlaybackState.ACTION_PAUSE -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_pause),
+ { controller.transportControls.pause() },
+ context.getString(R.string.controls_media_button_pause),
+ context.getDrawable(R.drawable.ic_media_pause_container)
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_prev),
+ { controller.transportControls.skipToPrevious() },
+ context.getString(R.string.controls_media_button_prev),
+ null
+ )
+ }
+ PlaybackState.ACTION_SKIP_TO_NEXT -> {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_next),
+ { controller.transportControls.skipToNext() },
+ context.getString(R.string.controls_media_button_next),
+ null
+ )
+ }
+ else -> null
+ }
+}
+
+/** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
+private fun getCustomAction(
+ context: Context,
+ packageName: String,
+ controller: MediaController,
+ customAction: PlaybackState.CustomAction
+): MediaAction {
+ return MediaAction(
+ Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
+ { controller.transportControls.sendCustomAction(customAction, customAction.extras) },
+ customAction.name,
+ null
+ )
+}
+
+/** Check whether the actions from a [PlaybackState] include a specific action */
+private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean {
+ if (
+ (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) &&
+ (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)
+ ) {
+ return true
+ }
+ return (stateActions and action != 0L)
+}
+
+/** Generate action buttons based on notification actions */
+fun createActionsFromNotification(
+ context: Context,
+ activityStarter: ActivityStarter,
+ sbn: StatusBarNotification
+): Pair<List<MediaAction>, List<Int>> {
+ val notif = sbn.notification
+ val actionIcons: MutableList<MediaAction> = ArrayList()
+ val actions = notif.actions
+ var actionsToShowCollapsed =
+ notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList()
+ ?: mutableListOf()
+ if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
+ Log.e(
+ TAG,
+ "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS"
+ )
+ actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
+ }
+
+ actions?.let {
+ if (it.size > MAX_NOTIFICATION_ACTIONS) {
+ Log.w(
+ TAG,
+ "Too many notification actions for ${sbn.key}, " +
+ "limiting to first $MAX_NOTIFICATION_ACTIONS"
+ )
+ }
+
+ for ((index, action) in it.take(MAX_NOTIFICATION_ACTIONS).withIndex()) {
+ if (action.getIcon() == null) {
+ logI(TAG) { "No icon for action $index ${action.title}" }
+ actionsToShowCollapsed.remove(index)
+ continue
+ }
+
+ val runnable =
+ action.actionIntent?.let { actionIntent ->
+ Runnable {
+ when {
+ actionIntent.isActivity ->
+ activityStarter.startPendingIntentDismissingKeyguard(
+ action.actionIntent
+ )
+ action.isAuthenticationRequired ->
+ activityStarter.dismissKeyguardThenExecute(
+ { sendPendingIntent(action.actionIntent) },
+ {},
+ true
+ )
+ else -> sendPendingIntent(actionIntent)
+ }
+ }
+ }
+
+ val themeText =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ .defaultColor
+
+ val mediaActionIcon =
+ when (action.getIcon().type) {
+ Icon.TYPE_RESOURCE ->
+ Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
+ else -> action.getIcon()
+ }
+ .setTint(themeText)
+ .loadDrawable(context)
+
+ val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null)
+ actionIcons.add(mediaAction)
+ }
+ }
+ return Pair(actionIcons, actionsToShowCollapsed)
+}
+
+private fun sendPendingIntent(intent: PendingIntent): Boolean {
+ return try {
+ intent.send(
+ BroadcastOptions.makeBasic()
+ .apply {
+ setInteractive(true)
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ }
+ .toBundle()
+ )
+ true
+ } catch (e: PendingIntent.CanceledException) {
+ Log.d(TAG, "Intent canceled", e)
+ false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
new file mode 100644
index 0000000..f9fef8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.annotation.WorkerThread
+import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
+import android.app.PendingIntent
+import android.app.StatusBarManager
+import android.app.UriGrantsManager
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.drawable.Icon
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.net.Uri
+import android.os.Process
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.support.v4.media.MediaMetadataCompat
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
+import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.kotlin.logD
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.ensureActive
+
+/** Loads media information from media style [StatusBarNotification] classes. */
+@SysUISingleton
+class MediaDataLoader
+@Inject
+constructor(
+ @Application val context: Context,
+ @Main val mainDispatcher: CoroutineDispatcher,
+ @Background val backgroundScope: CoroutineScope,
+ private val activityStarter: ActivityStarter,
+ private val mediaControllerFactory: MediaControllerFactory,
+ private val mediaFlags: MediaFlags,
+ private val imageLoader: ImageLoader,
+ private val statusBarManager: StatusBarManager,
+) {
+ private val mediaProcessingJobs = ConcurrentHashMap<JobKey, Job>()
+
+ private val artworkWidth: Int =
+ context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
+ )
+ private val artworkHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+
+ private val themeText =
+ com.android.settingslib.Utils.getColorAttr(
+ context,
+ com.android.internal.R.attr.textColorPrimary
+ )
+ .defaultColor
+
+ /**
+ * Loads media data for a given [StatusBarNotification]. It does the loading on the background
+ * thread.
+ *
+ * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed. The method
+ * suspends until loading has completed or failed.
+ *
+ * If a new [loadMediaData] is issued while existing load is in progress, the existing (old)
+ * load will be cancelled.
+ */
+ suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? {
+ logD(TAG) { "Loading media data for $key..." }
+ val jobKey = JobKey(key, sbn)
+ val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) }
+ loadMediaJob.invokeOnCompletion { mediaProcessingJobs.remove(jobKey) }
+ val existingJob = mediaProcessingJobs.put(jobKey, loadMediaJob)
+ existingJob?.cancel("New processing job incoming.")
+ return loadMediaJob.await()
+ }
+
+ /** Loads media data, should be called from [backgroundScope]. */
+ @WorkerThread
+ private suspend fun loadMediaDataInBackground(
+ key: String,
+ sbn: StatusBarNotification,
+ ): MediaDataLoaderResult? =
+ traceCoroutine("MediaDataLoader#loadMediaData") {
+ val token =
+ sbn.notification.extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token::class.java
+ )
+ if (token == null) {
+ Log.i(TAG, "Token was null, not loading media info")
+ return null
+ }
+ val mediaController = mediaControllerFactory.create(token)
+ val metadata = mediaController.metadata
+ val notification: Notification = sbn.notification
+
+ val appInfo =
+ notification.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ ) ?: getAppInfoFromPackage(sbn.packageName)
+
+ // App name
+ val appName = getAppName(sbn, appInfo)
+
+ // Song name
+ var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+ if (song.isNullOrBlank()) {
+ song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+ }
+ if (song.isNullOrBlank()) {
+ song = HybridGroupManager.resolveTitle(notification)
+ }
+ if (song.isNullOrBlank()) {
+ // For apps that don't include a title, log and add a placeholder
+ song = context.getString(R.string.controls_media_empty_title, appName)
+ try {
+ statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier)
+ } catch (e: RuntimeException) {
+ Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}")
+ }
+ }
+
+ // Don't attempt to load bitmaps if the job was cancelled.
+ coroutineContext.ensureActive()
+
+ // Album art
+ var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
+ }
+ if (artworkBitmap == null) {
+ artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+ }
+ val artworkIcon =
+ if (artworkBitmap == null) {
+ notification.getLargeIcon()
+ } else {
+ Icon.createWithBitmap(artworkBitmap)
+ }
+
+ // Don't continue if we were cancelled during slow bitmap load.
+ coroutineContext.ensureActive()
+
+ // App Icon
+ val smallIcon = sbn.notification.smallIcon
+
+ // Explicit Indicator
+ val isExplicit =
+ MediaMetadataCompat.fromMediaMetadata(metadata)
+ ?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+ // Artist name
+ var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
+ if (artist.isNullOrBlank()) {
+ artist = HybridGroupManager.resolveText(notification)
+ }
+
+ // Device name (used for remote cast notifications)
+ val device: MediaDeviceData? = getDeviceInfoForRemoteCast(key, sbn)
+
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState, create actions from session
+ // info
+ // Otherwise, use the notification actions
+ var actionIcons: List<MediaAction> = emptyList()
+ var actionsToShowCollapsed: List<Int> = emptyList()
+ val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
+ logD(TAG) { "Semantic actions: $semanticActions" }
+ if (semanticActions == null) {
+ val actions = createActionsFromNotification(context, activityStarter, sbn)
+ actionIcons = actions.first
+ actionsToShowCollapsed = actions.second
+ logD(TAG) { "[!!] Semantic actions: $semanticActions" }
+ }
+
+ val playbackLocation = getPlaybackLocation(sbn, mediaController)
+ val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) }
+
+ val appUid = appInfo?.uid ?: Process.INVALID_UID
+ return MediaDataLoaderResult(
+ appName = appName,
+ appIcon = smallIcon,
+ artist = artist,
+ song = song,
+ artworkIcon = artworkIcon,
+ actionIcons = actionIcons,
+ actionsToShowInCompact = actionsToShowCollapsed,
+ semanticActions = semanticActions,
+ token = token,
+ clickIntent = notification.contentIntent,
+ device = device,
+ playbackLocation = playbackLocation,
+ isPlaying = isPlaying,
+ appUid = appUid,
+ isExplicit = isExplicit
+ )
+ }
+
+ /**
+ * Loads media data in background for a given set of resumption parameters. The method suspends
+ * until loading is complete or fails.
+ *
+ * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed.
+ */
+ suspend fun loadMediaDataForResumption(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ currentEntry: MediaData?,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ): MediaDataLoaderResult? {
+ val mediaData =
+ backgroundScope.async {
+ loadMediaDataForResumptionInBackground(
+ userId,
+ desc,
+ resumeAction,
+ currentEntry,
+ token,
+ appName,
+ appIntent,
+ packageName
+ )
+ }
+ return mediaData.await()
+ }
+
+ /** Loads media data for resumption, should be called from [backgroundScope]. */
+ @WorkerThread
+ private suspend fun loadMediaDataForResumptionInBackground(
+ userId: Int,
+ desc: MediaDescription,
+ resumeAction: Runnable,
+ currentEntry: MediaData?,
+ token: MediaSession.Token,
+ appName: String,
+ appIntent: PendingIntent,
+ packageName: String
+ ): MediaDataLoaderResult? =
+ traceCoroutine("MediaDataLoader#loadMediaDataForResumption") {
+ if (desc.title.isNullOrBlank()) {
+ Log.e(TAG, "Description incomplete")
+ return null
+ }
+
+ logD(TAG) { "adding track for $userId from browser: $desc" }
+
+ val appUid = currentEntry?.appUid ?: Process.INVALID_UID
+
+ // Album art
+ var artworkBitmap = desc.iconBitmap
+ if (artworkBitmap == null && desc.iconUri != null) {
+ artworkBitmap =
+ loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName)
+ }
+ val artworkIcon =
+ if (artworkBitmap != null) {
+ Icon.createWithBitmap(artworkBitmap)
+ } else {
+ null
+ }
+
+ val isExplicit =
+ desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+ MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+ val progress =
+ if (mediaFlags.isResumeProgressEnabled()) {
+ MediaDataUtils.getDescriptionProgress(desc.extras)
+ } else null
+
+ val mediaAction = getResumeMediaAction(resumeAction)
+ return MediaDataLoaderResult(
+ appName = appName,
+ appIcon = null,
+ artist = desc.subtitle,
+ song = desc.title,
+ artworkIcon = artworkIcon,
+ actionIcons = listOf(mediaAction),
+ actionsToShowInCompact = listOf(0),
+ semanticActions = MediaButton(playOrPause = mediaAction),
+ token = token,
+ clickIntent = appIntent,
+ device = null,
+ playbackLocation = 0,
+ isPlaying = null,
+ appUid = appUid,
+ isExplicit = isExplicit,
+ resumeAction = resumeAction,
+ resumeProgress = progress
+ )
+ }
+
+ private fun createActionsFromState(
+ packageName: String,
+ controller: MediaController,
+ user: UserHandle
+ ): MediaButton? {
+ if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+ return null
+ }
+
+ return createActionsFromState(context, packageName, controller)
+ }
+
+ private fun getPlaybackLocation(sbn: StatusBarNotification, mediaController: MediaController) =
+ when {
+ isRemoteCastNotification(sbn) -> MediaData.PLAYBACK_CAST_REMOTE
+ mediaController.playbackInfo?.playbackType ==
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> MediaData.PLAYBACK_LOCAL
+ else -> MediaData.PLAYBACK_CAST_LOCAL
+ }
+
+ /**
+ * Returns [MediaDeviceData] if the [StatusBarNotification] is a remote cast notification.
+ * `null` otherwise.
+ */
+ private fun getDeviceInfoForRemoteCast(
+ key: String,
+ sbn: StatusBarNotification
+ ): MediaDeviceData? {
+ val extras = sbn.notification.extras
+ val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
+ val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
+ val deviceIntent =
+ extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java)
+ logD(TAG) { "$key is RCN for $deviceName" }
+
+ if (deviceName != null && deviceIcon > -1) {
+ // Name and icon must be present, but intent may be null
+ val enabled = deviceIntent != null && deviceIntent.isActivity
+ val deviceDrawable =
+ Icon.createWithResource(sbn.packageName, deviceIcon)
+ .loadDrawable(sbn.getPackageContext(context))
+ return MediaDeviceData(
+ enabled,
+ deviceDrawable,
+ deviceName,
+ deviceIntent,
+ showBroadcastButton = false
+ )
+ }
+ return null
+ }
+
+ private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+ try {
+ return context.packageManager.getApplicationInfo(packageName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app info for $packageName", e)
+ return null
+ }
+ }
+
+ private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+ val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+ return when {
+ name != null -> name
+ appInfo != null -> context.packageManager.getApplicationLabel(appInfo).toString()
+ else -> sbn.packageName
+ }
+ }
+
+ /** Load a bitmap from the various Art metadata URIs */
+ private suspend fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
+ for (uri in ART_URIS) {
+ val uriString = metadata.getString(uri)
+ if (!TextUtils.isEmpty(uriString)) {
+ val albumArt = loadBitmapFromUri(Uri.parse(uriString))
+ // If we got cancelled during slow album art load, cancel the rest of
+ // the process.
+ coroutineContext.ensureActive()
+ if (albumArt != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "loaded art from $uri")
+ }
+ return albumArt
+ }
+ }
+ }
+ return null
+ }
+
+ private suspend fun loadBitmapFromUri(uri: Uri): Bitmap? {
+ // ImageDecoder requires a scheme of the following types
+ if (
+ uri.scheme !in
+ listOf(
+ ContentResolver.SCHEME_CONTENT,
+ ContentResolver.SCHEME_ANDROID_RESOURCE,
+ ContentResolver.SCHEME_FILE
+ )
+ ) {
+ Log.w(TAG, "Invalid album art uri $uri")
+ return null
+ }
+
+ val source = ImageLoader.Uri(uri)
+ return imageLoader.loadBitmap(
+ source,
+ artworkWidth,
+ artworkHeight,
+ allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ )
+ }
+
+ private suspend fun loadBitmapFromUriForUser(
+ uri: Uri,
+ userId: Int,
+ appUid: Int,
+ packageName: String
+ ): Bitmap? {
+ try {
+ val ugm = UriGrantsManager.getService()
+ ugm.checkGrantUriPermission_ignoreNonSystem(
+ appUid,
+ packageName,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, userId)
+ )
+ return loadBitmapFromUri(uri)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Failed to get URI permission: $e")
+ }
+ return null
+ }
+
+ /** Check whether this notification is an RCN */
+ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean =
+ sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
+
+ private fun getResumeMediaAction(action: Runnable): MediaAction {
+ return MediaAction(
+ Icon.createWithResource(context, R.drawable.ic_media_play)
+ .setTint(themeText)
+ .loadDrawable(context),
+ action,
+ context.getString(R.string.controls_media_resume),
+ context.getDrawable(R.drawable.ic_media_play_container)
+ )
+ }
+
+ private data class JobKey(val key: String, val sbn: StatusBarNotification) :
+ Pair<String, StatusBarNotification>(key, sbn)
+
+ companion object {
+ private const val TAG = "MediaDataLoader"
+ private val ART_URIS =
+ arrayOf(
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ )
+ }
+
+ /** Returned data from loader. */
+ data class MediaDataLoaderResult(
+ val appName: String?,
+ val appIcon: Icon?,
+ val artist: CharSequence?,
+ val song: CharSequence?,
+ val artworkIcon: Icon?,
+ val actionIcons: List<MediaAction>,
+ val actionsToShowInCompact: List<Int>,
+ val semanticActions: MediaButton?,
+ val token: MediaSession.Token?,
+ val clickIntent: PendingIntent?,
+ val device: MediaDeviceData?,
+ val playbackLocation: Int,
+ val isPlaying: Boolean?,
+ val appUid: Int,
+ val isExplicit: Boolean,
+ val resumeAction: Runnable? = null,
+ val resumeProgress: Double? = null
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 2dbe2aa..bf2aa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -20,7 +20,7 @@
import android.annotation.UserIdInt
import android.app.ActivityManager.RecentTaskInfo
import android.content.ComponentName
-import com.android.wm.shell.util.SplitBounds
+import com.android.wm.shell.shared.split.SplitBounds
data class RecentTask(
val taskId: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 01b1be9..82e58cc 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -23,7 +23,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index dd1fa76..bb4d894 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -36,10 +36,10 @@
import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
import com.android.systemui.res.R
import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
import java.util.Optional
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 0e127e3..83c3335 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -41,10 +41,11 @@
if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() && data.icon != null) {
icon = { data.icon }
} else {
- val defaultIconRes =
+ val iconRes =
if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
- iconRes = defaultIconRes
- icon = { resources.getDrawable(defaultIconRes, theme).asIcon() }
+ val icon = resources.getDrawable(iconRes, theme).asIcon()
+ this.iconRes = iconRes
+ this.icon = { icon }
}
activationState =
if (data.isActivated) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index f74c9a6..e9292f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -79,6 +79,7 @@
// NOTE: NotificationEntry.isClearable will internally check group children to ensure
// the group itself definitively clearable.
val isClearable = !isSensitiveContentProtectionActive && entry.isClearable
+ && !entry.isSensitive.value
when {
isSilent && isClearable -> hasClearableSilentNotifs = true
isSilent && !isClearable -> hasNonClearableSilentNotifs = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 7119145..48c974a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -117,6 +117,8 @@
protected HybridNotificationView mSingleLineView;
@Nullable public DisposableHandle mContractedBinderHandle;
+ @Nullable public DisposableHandle mExpandedBinderHandle;
+ @Nullable public DisposableHandle mHeadsUpBinderHandle;
private RemoteInputView mExpandedRemoteInput;
private RemoteInputView mHeadsUpRemoteInput;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index a5cd2a2..c342bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -46,6 +46,9 @@
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -286,11 +289,15 @@
}
FLAG_CONTENT_VIEW_EXPANDED ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
+ row.privateLayout.mExpandedBinderHandle?.dispose()
+ row.privateLayout.mExpandedBinderHandle = null
row.privateLayout.setExpandedChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
}
FLAG_CONTENT_VIEW_HEADS_UP ->
row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
+ row.privateLayout.mHeadsUpBinderHandle?.dispose()
+ row.privateLayout.mHeadsUpBinderHandle = null
row.privateLayout.setHeadsUpChild(null)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
row.privateLayout.setHeadsUpInflatedSmartReplies(null)
@@ -499,17 +506,87 @@
}
}
- if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
+ val richOngoingContentModel = inflationProgress.contentModel.richOngoingContentModel
+
+ if (
+ richOngoingContentModel != null &&
+ reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
+ ) {
logger.logAsyncTaskProgress(entry, "inflating RON view")
- inflationProgress.richOngoingNotificationViewHolder =
- inflationProgress.contentModel.richOngoingContentModel?.let {
+ val inflateContractedView = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0
+ val inflateExpandedView = reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0
+ val inflateHeadsUpView = reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0
+
+ inflationProgress.contractedRichOngoingNotificationViewHolder =
+ if (inflateContractedView) {
ronInflater.inflateView(
- contentModel = it,
+ contentModel = richOngoingContentModel,
existingView = row.privateLayout.contractedChild,
entry = entry,
systemUiContext = context,
- parentView = row.privateLayout
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.Contracted
)
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.contractedChild,
+ viewType = RichOngoingNotificationViewType.Contracted
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
+ }
+
+ inflationProgress.expandedRichOngoingNotificationViewHolder =
+ if (inflateExpandedView) {
+ ronInflater.inflateView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.expandedChild,
+ entry = entry,
+ systemUiContext = context,
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.Expanded
+ )
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.expandedChild,
+ viewType = RichOngoingNotificationViewType.Expanded
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
+ }
+
+ inflationProgress.headsUpRichOngoingNotificationViewHolder =
+ if (inflateHeadsUpView) {
+ ronInflater.inflateView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.headsUpChild,
+ entry = entry,
+ systemUiContext = context,
+ parentView = row.privateLayout,
+ viewType = RichOngoingNotificationViewType.HeadsUp
+ )
+ } else {
+ if (
+ ronInflater.canKeepView(
+ contentModel = richOngoingContentModel,
+ existingView = row.privateLayout.headsUpChild,
+ viewType = RichOngoingNotificationViewType.HeadsUp
+ )
+ ) {
+ KeepExistingView
+ } else {
+ NullContentView
+ }
}
}
@@ -618,7 +695,9 @@
var inflatedSmartReplyState: InflatedSmartReplyState? = null
var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
- var richOngoingNotificationViewHolder: InflatedContentViewHolder? = null
+ var contractedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+ var expandedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+ var headsUpRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
// Inflated SingleLineView that lacks the UI State
var inflatedSingleLineView: HybridNotificationView? = null
@@ -1428,14 +1507,21 @@
logger.logAsyncTaskProgress(entry, "finishing")
// before updating the content model, stop existing binding if necessary
- val hasRichOngoingContentModel = result.contentModel.richOngoingContentModel != null
- val requestedRichOngoing = reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
- val rejectedRichOngoing = requestedRichOngoing && !hasRichOngoingContentModel
- if (result.richOngoingNotificationViewHolder != null || rejectedRichOngoing) {
+ if (result.contractedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
row.privateLayout.mContractedBinderHandle?.dispose()
row.privateLayout.mContractedBinderHandle = null
}
+ if (result.expandedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+ row.privateLayout.mExpandedBinderHandle?.dispose()
+ row.privateLayout.mExpandedBinderHandle = null
+ }
+
+ if (result.headsUpRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+ row.privateLayout.mHeadsUpBinderHandle?.dispose()
+ row.privateLayout.mHeadsUpBinderHandle = null
+ }
+
// set the content model after disposal and before setting new rich ongoing view
entry.setContentModel(result.contentModel)
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
@@ -1477,19 +1563,53 @@
}
}
- // after updating the content model, set the view, then start the new binder
- result.richOngoingNotificationViewHolder?.let { viewHolder ->
- row.privateLayout.contractedChild = viewHolder.view
- row.privateLayout.expandedChild = null
- row.privateLayout.headsUpChild = null
- row.privateLayout.setExpandedInflatedSmartReplies(null)
- row.privateLayout.setHeadsUpInflatedSmartReplies(null)
- row.privateLayout.mContractedBinderHandle =
- viewHolder.binder.setupContentViewBinder()
- row.setExpandable(false)
+ val hasRichOngoingViewHolder =
+ result.contractedRichOngoingNotificationViewHolder != null ||
+ result.expandedRichOngoingNotificationViewHolder != null ||
+ result.headsUpRichOngoingNotificationViewHolder != null
+
+ if (hasRichOngoingViewHolder) {
+ // after updating the content model, set the view, then start the new binder
+ result.contractedRichOngoingNotificationViewHolder?.let { contractedViewHolder ->
+ if (contractedViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.contractedChild = contractedViewHolder.view
+ row.privateLayout.mContractedBinderHandle =
+ contractedViewHolder.binder.setupContentViewBinder()
+ } else if (contractedViewHolder == NullContentView) {
+ row.privateLayout.contractedChild = null
+ }
+ }
+
+ result.expandedRichOngoingNotificationViewHolder?.let { expandedViewHolder ->
+ if (expandedViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.expandedChild = expandedViewHolder.view
+ row.privateLayout.mExpandedBinderHandle =
+ expandedViewHolder.binder.setupContentViewBinder()
+ } else if (expandedViewHolder == NullContentView) {
+ row.privateLayout.expandedChild = null
+ }
+ }
+
+ result.headsUpRichOngoingNotificationViewHolder?.let { headsUpViewHolder ->
+ if (headsUpViewHolder is InflatedContentViewHolder) {
+ row.privateLayout.headsUpChild = headsUpViewHolder.view
+ row.privateLayout.mHeadsUpBinderHandle =
+ headsUpViewHolder.binder.setupContentViewBinder()
+ } else if (headsUpViewHolder == NullContentView) {
+ row.privateLayout.headsUpChild = null
+ }
+ }
+
+ // clean remoteViewCache when we don't keep existing views.
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+
+ // Since RONs don't support smart reply, remove them from HUNs and Expanded.
+ row.privateLayout.setExpandedInflatedSmartReplies(null)
+ row.privateLayout.setHeadsUpInflatedSmartReplies(null)
+
+ row.setExpandable(row.privateLayout.expandedChild != null)
}
Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
index e9c4960..828fc21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt
@@ -24,6 +24,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag
import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel
@@ -39,7 +42,35 @@
fun setupContentViewBinder(): DisposableHandle
}
-class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder)
+enum class RichOngoingNotificationViewType {
+ Contracted,
+ Expanded,
+ HeadsUp,
+}
+
+/**
+ * * Supertype of the 3 different possible result types of
+ * [RichOngoingNotificationViewInflater.inflateView].
+ */
+sealed interface ContentViewInflationResult {
+
+ /** Indicates that the content view should be removed if present. */
+ data object NullContentView : ContentViewInflationResult
+
+ /**
+ * Indicates that the content view (which *must be* present) should be unmodified during this
+ * inflation.
+ */
+ data object KeepExistingView : ContentViewInflationResult
+
+ /**
+ * Contains the new view and binder that should replace any existing content view for this slot.
+ */
+ data class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) :
+ ContentViewInflationResult
+}
+
+fun ContentViewInflationResult?.shouldDisposeViewBinder() = this !is KeepExistingView
/**
* Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
@@ -52,7 +83,14 @@
entry: NotificationEntry,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder?
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult
+
+ fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean
}
@SysUISingleton
@@ -68,8 +106,9 @@
entry: NotificationEntry,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder? {
- if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return null
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return NullContentView
val component = viewModelComponentFactory.create(entry)
return when (contentModel) {
is TimerContentModel ->
@@ -77,28 +116,55 @@
existingView,
component::createTimerViewModel,
systemUiContext,
- parentView
+ parentView,
+ viewType
)
is StopwatchContentModel -> TODO("Not yet implemented")
}
}
+ override fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean {
+ if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
+ return when (contentModel) {
+ is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
+ is StopwatchContentModel -> TODO("Not yet implemented")
+ }
+ }
+
private fun inflateTimerView(
existingView: View?,
createViewModel: () -> TimerViewModel,
systemUiContext: Context,
parentView: ViewGroup,
- ): InflatedContentViewHolder? {
- if (existingView is TimerView && !existingView.isReinflateNeeded()) return null
- val newView =
- LayoutInflater.from(systemUiContext)
- .inflate(
- R.layout.rich_ongoing_timer_notification,
- parentView,
- /* attachToRoot= */ false
- ) as TimerView
- return InflatedContentViewHolder(newView) {
- TimerViewBinder.bindWhileAttached(newView, createViewModel())
+ viewType: RichOngoingNotificationViewType,
+ ): ContentViewInflationResult {
+ if (existingView is TimerView && !existingView.isReinflateNeeded()) return KeepExistingView
+
+ return when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> {
+ val newView =
+ LayoutInflater.from(systemUiContext)
+ .inflate(
+ R.layout.rich_ongoing_timer_notification,
+ parentView,
+ /* attachToRoot= */ false
+ ) as TimerView
+ InflatedContentViewHolder(newView) {
+ TimerViewBinder.bindWhileAttached(newView, createViewModel())
+ }
+ }
+ RichOngoingNotificationViewType.Expanded,
+ RichOngoingNotificationViewType.HeadsUp -> NullContentView
}
}
+
+ private fun canKeepTimerView(
+ contentModel: TimerContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6a2c602..a16129b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -511,10 +511,12 @@
}
public int getTopPadding() {
+ SceneContainerFlag.assertInLegacyMode();
return mTopPadding;
}
public void setTopPadding(int topPadding) {
+ SceneContainerFlag.assertInLegacyMode();
mTopPadding = topPadding;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f26f840..d0c51bc2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -851,7 +851,7 @@
return; // the rest of the fields are not important in Flexiglass
}
- y = getTopPadding();
+ y = mAmbientState.getTopPadding();
drawDebugInfo(canvas, y, Color.RED, /* label= */ "getTopPadding() = " + y);
y = getLayoutHeight();
@@ -1231,9 +1231,11 @@
@Override
public void setStackTop(float stackTop) {
- mAmbientState.setStackTop(stackTop);
- // TODO(b/332574413): replace the following with using stackTop
- updateTopPadding(stackTop, isAddOrRemoveAnimationPending());
+ if (mAmbientState.getStackTop() != stackTop) {
+ mAmbientState.setStackTop(stackTop);
+ onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
+ setExpandedHeight(mExpandedHeight);
+ }
}
@Override
@@ -1386,28 +1388,30 @@
}
public int getTopPadding() {
- return mAmbientState.getTopPadding();
+ // TODO(b/332574413) replace all usages of getTopPadding()
+ if (SceneContainerFlag.isEnabled()) {
+ return (int) mAmbientState.getStackTop();
+ } else {
+ return mAmbientState.getTopPadding();
+ }
}
- private void setTopPadding(int topPadding, boolean animate) {
- if (getTopPadding() != topPadding) {
- mAmbientState.setTopPadding(topPadding);
- boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
- updateAlgorithmHeightAndPadding();
- updateContentHeight();
- if (mAmbientState.isOnKeyguard()
- && !mShouldUseSplitNotificationShade
- && mShouldSkipTopPaddingAnimationAfterFold) {
- mShouldSkipTopPaddingAnimationAfterFold = false;
- } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
- mTopPaddingNeedsAnimation = true;
- mNeedsAnimation = true;
- }
- updateStackPosition();
- requestChildrenUpdate();
- notifyHeightChangeListener(null, shouldAnimate);
- mAnimateNextTopPaddingChange = false;
+ private void onTopPaddingChanged(boolean animate) {
+ boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
+ updateAlgorithmHeightAndPadding();
+ updateContentHeight();
+ if (mAmbientState.isOnKeyguard()
+ && !mShouldUseSplitNotificationShade
+ && mShouldSkipTopPaddingAnimationAfterFold) {
+ mShouldSkipTopPaddingAnimationAfterFold = false;
+ } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+ mTopPaddingNeedsAnimation = true;
+ mNeedsAnimation = true;
}
+ updateStackPosition();
+ requestChildrenUpdate();
+ notifyHeightChangeListener(null, shouldAnimate);
+ mAnimateNextTopPaddingChange = false;
}
/**
@@ -1435,6 +1439,11 @@
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
+ // When scene container is active, we only want to recalculate stack heights.
+ if (SceneContainerFlag.isEnabled()) {
+ updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+ return;
+ }
float topOverscrollAmount = mShouldUseSplitNotificationShade
? getCurrentOverScrollAmount(true /* top */) : 0f;
final float endTopPosition = getTopPadding() + mExtraTopInsetForFullShadeTransition
@@ -1447,10 +1456,8 @@
if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
- if (!SceneContainerFlag.isEnabled()) {
- final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
- mAmbientState.setStackY(stackY);
- }
+ final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
+ mAmbientState.setStackY(stackY);
if (mOnStackYChanged != null) {
mOnStackYChanged.accept(listenerNeedsAnimation);
@@ -2708,6 +2715,7 @@
* @param animate whether to animate the change
*/
public void updateTopPadding(float qsHeight, boolean animate) {
+ SceneContainerFlag.assertInLegacyMode();
int topPadding = (int) qsHeight;
int minStackHeight = getLayoutMinHeightInternal();
if (topPadding + minStackHeight > getHeight()) {
@@ -2715,7 +2723,10 @@
} else {
mTopPaddingOverflow = 0;
}
- setTopPadding(topPadding, animate && !mKeyguardBypassEnabled);
+ if (mAmbientState.getTopPadding() != topPadding) {
+ mAmbientState.setTopPadding(topPadding);
+ onTopPaddingChanged(/* animate = */ animate && !mKeyguardBypassEnabled);
+ }
setExpandedHeight(mExpandedHeight);
}
@@ -4829,6 +4840,7 @@
}
public boolean isBelowLastNotification(float touchX, float touchY) {
+ SceneContainerFlag.assertInLegacyMode();
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
ExpandableView child = getChildAtIndex(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 55f0566..c25b30d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1235,6 +1235,7 @@
}
public boolean isBelowLastNotification(float x, float y) {
+ SceneContainerFlag.assertInLegacyMode();
return mView.isBelowLastNotification(x, y);
}
@@ -1328,6 +1329,7 @@
}
public int getTopPadding() {
+ SceneContainerFlag.assertInLegacyMode();
return mView.getTopPadding();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3dd265b..e3242d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2365,9 +2365,14 @@
// lock screen where users can use the UDFPS affordance to enter the device
mStatusBarKeyguardViewManager.reset(true);
} else if (mState == StatusBarState.KEYGUARD
- && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
- && mStatusBarKeyguardViewManager.isSecure()) {
- if (!relockWithPowerButtonImmediately()) {
+ && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
+ boolean needsBouncer = mStatusBarKeyguardViewManager.isSecure();
+ if (relockWithPowerButtonImmediately()) {
+ // Only request if SIM bouncer is needed
+ needsBouncer = mStatusBarKeyguardViewManager.needsFullscreenBouncer();
+ }
+
+ if (needsBouncer) {
Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
if (SceneContainerFlag.isEnabled()) {
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 199b5b67..37f2f19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -36,14 +36,12 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -76,37 +74,10 @@
@DeviceBasedSatelliteInputLog logBuffer: LogBuffer,
@DeviceBasedSatelliteTableLog tableLog: TableLogBuffer,
) : DeviceBasedSatelliteViewModel {
- private val shouldShowIcon: Flow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { allOos ->
- if (!allOos) {
- flowOf(false)
- } else {
- combine(
- interactor.isSatelliteAllowed,
- interactor.isSatelliteProvisioned,
- interactor.isWifiActive,
- airplaneModeRepository.isAirplaneMode
- ) { isSatelliteAllowed, isSatelliteProvisioned, isWifiActive, isAirplaneMode ->
- isSatelliteAllowed &&
- isSatelliteProvisioned &&
- !isWifiActive &&
- !isAirplaneMode
- }
- }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE_CONDITION,
- initialValue = false,
- )
// This adds a 10 seconds delay before showing the icon
- private val shouldActuallyShowIcon: StateFlow<Boolean> =
- shouldShowIcon
- .distinctUntilChanged()
+ private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
+ interactor.areAllConnectionsOutOfService
.flatMapLatest { shouldShow ->
if (shouldShow) {
logBuffer.log(
@@ -125,6 +96,45 @@
.logDiffsForTable(
tableLog,
columnPrefix = "vm",
+ columnName = COL_VISIBLE_FOR_OOS,
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private val canShowIcon =
+ combine(
+ interactor.isSatelliteAllowed,
+ interactor.isSatelliteProvisioned,
+ ) { allowed, provisioned ->
+ allowed && provisioned
+ }
+
+ private val showIcon =
+ canShowIcon
+ .flatMapLatest { canShow ->
+ if (!canShow) {
+ flowOf(false)
+ } else {
+ combine(
+ shouldShowIconForOosAfterHysteresis,
+ interactor.connectionState,
+ interactor.isWifiActive,
+ airplaneModeRepository.isAirplaneMode,
+ ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
+ if (isWifiActive || isAirplaneMode) {
+ false
+ } else {
+ showForOos ||
+ connectionState == SatelliteConnectionState.On ||
+ connectionState == SatelliteConnectionState.Connected
+ }
+ }
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
columnName = COL_VISIBLE,
initialValue = false,
)
@@ -132,7 +142,7 @@
override val icon: StateFlow<Icon?> =
combine(
- shouldActuallyShowIcon,
+ showIcon,
interactor.connectionState,
interactor.signalStrength,
) { shouldShow, state, signalStrength ->
@@ -146,7 +156,7 @@
override val carrierText: StateFlow<String?> =
combine(
- shouldActuallyShowIcon,
+ showIcon,
interactor.connectionState,
) { shouldShow, connectionState ->
logBuffer.log(
@@ -156,7 +166,7 @@
bool1 = shouldShow
str1 = connectionState.name
},
- { "Updating carrier text. shouldActuallyShow=$bool1 connectionState=$str1" }
+ { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
)
if (shouldShow) {
when (connectionState) {
@@ -165,28 +175,30 @@
context.getString(R.string.satellite_connected_carrier_text)
SatelliteConnectionState.Off,
SatelliteConnectionState.Unknown -> {
- null
+ // If we're showing the satellite icon opportunistically, use the
+ // emergency-only version of the carrier string
+ context.getString(R.string.satellite_emergency_only_carrier_text)
}
}
} else {
null
}
}
- .onEach {
- logBuffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = it },
- { "Resulting carrier text = $str1" }
- )
- }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_CARRIER_TEXT,
+ initialValue = null,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
companion object {
private const val TAG = "DeviceBasedSatelliteViewModel"
private val DELAY_DURATION = 10.seconds
- const val COL_VISIBLE_CONDITION = "visCondition"
+ const val COL_VISIBLE_FOR_OOS = "visibleForOos"
const val COL_VISIBLE = "visible"
+ const val COL_CARRIER_TEXT = "carrierText"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 28ac2c0..055671c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -28,6 +28,7 @@
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -62,7 +63,9 @@
/**
* Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
* [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
- * during onViewAttached() and removing during onViewRemoved()
+ * during onViewAttached() and removing during onViewRemoved().
+ *
+ * @return a disposable handle in order to cancel the flow in the future.
*/
@JvmOverloads
fun <T> collectFlow(
@@ -71,8 +74,8 @@
consumer: Consumer<T>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
state: Lifecycle.State = Lifecycle.State.CREATED,
-) {
- view.repeatWhenAttached(coroutineContext) {
+): DisposableHandle {
+ return view.repeatWhenAttached(coroutineContext) {
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 50efc21..5f6ad92 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -60,7 +60,6 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.onehanded.OneHanded;
@@ -70,6 +69,7 @@
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 5600b87..a18d272 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -711,6 +711,16 @@
}
@Test
+ public void testDestroy_cleansUpHandler() {
+ final TouchHandler touchHandler = createTouchHandler();
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+ environment.destroyMonitor();
+ verify(touchHandler).onDestroy();
+ }
+
+ @Test
public void testLastSessionPop_createsNewInputSession() {
final TouchHandler touchHandler = createTouchHandler();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index e60848b..6e9b24f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -123,19 +123,19 @@
FakeWidgetMetadata(
widgetId = 11,
componentName = "com.android.fakePackage1/fakeWidget1",
- rank = 3,
+ rank = 0,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 12,
componentName = "com.android.fakePackage2/fakeWidget2",
- rank = 2,
+ rank = 1,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 13,
componentName = "com.android.fakePackage3/fakeWidget3",
- rank = 1,
+ rank = 2,
userSerialNumber = 10,
),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index 983a435..edc8c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -69,19 +69,19 @@
FakeWidgetMetadata(
widgetId = 11,
componentName = "com.android.fakePackage1/fakeWidget1",
- rank = 3,
+ rank = 0,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 12,
componentName = "com.android.fakePackage2/fakeWidget2",
- rank = 2,
+ rank = 1,
userSerialNumber = 0,
),
FakeWidgetMetadata(
widgetId = 13,
componentName = "com.android.fakePackage3/fakeWidget3",
- rank = 1,
+ rank = 2,
userSerialNumber = 10,
),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index eb0ab78..ad25502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -72,6 +72,82 @@
databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
}
+ @Test
+ fun migrate2To3_noGapBetweenRanks_ranksReversed() {
+ // Create a communal database in version 2
+ val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+ // Populate some fake data
+ val fakeRanks =
+ listOf(
+ FakeCommunalItemRank(3),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(1),
+ FakeCommunalItemRank(0),
+ )
+ databaseV2.insertRanks(fakeRanks)
+
+ // Verify fake ranks populated
+ databaseV2.verifyRanksInOrder(fakeRanks)
+
+ // Run migration and get database V3
+ val databaseV3 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 3,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_2_3,
+ )
+
+ // Verify ranks are reversed
+ databaseV3.verifyRanksInOrder(
+ listOf(
+ FakeCommunalItemRank(0),
+ FakeCommunalItemRank(1),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(3),
+ )
+ )
+ }
+
+ @Test
+ fun migrate2To3_withGapBetweenRanks_ranksReversed() {
+ // Create a communal database in version 2
+ val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+ // Populate some fake data with gaps between ranks
+ val fakeRanks =
+ listOf(
+ FakeCommunalItemRank(9),
+ FakeCommunalItemRank(7),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(0),
+ )
+ databaseV2.insertRanks(fakeRanks)
+
+ // Verify fake ranks populated
+ databaseV2.verifyRanksInOrder(fakeRanks)
+
+ // Run migration and get database V3
+ val databaseV3 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 3,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_2_3,
+ )
+
+ // Verify ranks are reversed
+ databaseV3.verifyRanksInOrder(
+ listOf(
+ FakeCommunalItemRank(0),
+ FakeCommunalItemRank(2),
+ FakeCommunalItemRank(7),
+ FakeCommunalItemRank(9),
+ )
+ )
+ }
+
private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
widgets.forEach { widget ->
execSQL(
@@ -117,6 +193,25 @@
assertThat(cursor.isAfterLast).isTrue()
}
+ private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
+ ranks.forEach { rank ->
+ execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
+ }
+ }
+
+ private fun SupportSQLiteDatabase.verifyRanksInOrder(ranks: List<FakeCommunalItemRank>) {
+ val cursor = query("SELECT * FROM communal_item_rank_table ORDER BY uid")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ ranks.forEach { rank ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("rank"))).isEqualTo(rank.rank)
+ cursor.moveToNext()
+ }
+
+ // Verify there is no more columns
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
/**
* Returns the expected data after migration from V1 to V2, which is simply that the new user
* serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
@@ -143,6 +238,10 @@
val userSerialNumber: Int,
)
+ private data class FakeCommunalItemRank(
+ val rank: Int,
+ )
+
companion object {
private const val DATABASE_NAME = "communal_db"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index d670508..d4d966a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -67,11 +67,11 @@
@Test
fun addWidget_readValueInDb() =
testScope.runTest {
- val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+ val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
@@ -81,11 +81,11 @@
@Test
fun deleteWidget_notInDb_returnsFalse() =
testScope.runTest {
- val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+ val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
@@ -97,11 +97,11 @@
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber
)
}
@@ -115,17 +115,48 @@
}
@Test
+ fun addWidget_rankNotSpecified_widgetAddedAtTheEnd(): Unit =
+ testScope.runTest {
+ val widgets by collectLastValue(communalWidgetDao.getWidgets())
+
+ // Verify database is empty
+ assertThat(widgets).isEmpty()
+
+ // Add widgets one by one without specifying rank
+ val widgetsToAdd = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
+ widgetsToAdd.forEach {
+ val (widgetId, provider, _, userSerialNumber) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ userSerialNumber = userSerialNumber
+ )
+ }
+
+ // Verify new each widget is added at the end
+ assertThat(widgets)
+ .containsExactly(
+ communalItemRankEntry1,
+ communalWidgetItemEntry1,
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
+ communalItemRankEntry3,
+ communalWidgetItemEntry3,
+ )
+ }
+
+ @Test
fun deleteWidget_emitsActiveWidgetsInDb() =
testScope.runTest {
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
@@ -148,32 +179,32 @@
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
.containsExactly(
- communalItemRankEntry2,
- communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
)
.inOrder()
- // swapped priorities
- val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
- communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+ // swapped ranks
+ val widgetIdsToRankMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 0)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToRankMap)
assertThat(widgets())
.containsExactly(
- communalItemRankEntry1.copy(rank = 2),
+ communalItemRankEntry2.copy(rank = 0),
+ communalWidgetItemEntry2,
+ communalItemRankEntry1.copy(rank = 1),
communalWidgetItemEntry1,
- communalItemRankEntry2.copy(rank = 1),
- communalWidgetItemEntry2
)
.inOrder()
}
@@ -181,53 +212,56 @@
@Test
fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
testScope.runTest {
- val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+ val existingWidgets = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
existingWidgets.forEach {
- val (widgetId, provider, priority, userSerialNumber) = it
+ val (widgetId, provider, rank, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
- priority = priority,
+ rank = rank,
userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
.containsExactly(
- communalItemRankEntry2,
- communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
+ communalItemRankEntry3,
+ communalWidgetItemEntry3,
)
.inOrder()
- // map with no item in the middle at index 1
- val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
- communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
- assertThat(widgets())
- .containsExactly(
- communalItemRankEntry2.copy(rank = 3),
- communalWidgetItemEntry2,
- communalItemRankEntry1.copy(rank = 1),
- communalWidgetItemEntry1,
- )
- .inOrder()
- // add the new middle item that we left space for.
+ // add a new widget at rank 1.
communalWidgetDao.addWidget(
- widgetId = widgetInfo3.widgetId,
- provider = widgetInfo3.provider,
- priority = 2,
- userSerialNumber = widgetInfo3.userSerialNumber,
+ widgetId = 4,
+ provider = ComponentName("pk_name", "cls_name_4"),
+ rank = 1,
+ userSerialNumber = 0,
)
+
+ val newRankEntry = CommunalItemRank(uid = 4L, rank = 1)
+ val newWidgetEntry =
+ CommunalWidgetItem(
+ uid = 4L,
+ widgetId = 4,
+ componentName = "pk_name/cls_name_4",
+ itemId = 4L,
+ userSerialNumber = 0,
+ )
assertThat(widgets())
.containsExactly(
- communalItemRankEntry2.copy(rank = 3),
- communalWidgetItemEntry2,
- communalItemRankEntry3.copy(rank = 2),
- communalWidgetItemEntry3,
- communalItemRankEntry1.copy(rank = 1),
+ communalItemRankEntry1.copy(rank = 0),
communalWidgetItemEntry1,
+ newRankEntry,
+ newWidgetEntry,
+ communalItemRankEntry2.copy(rank = 2),
+ communalWidgetItemEntry2,
+ communalItemRankEntry3.copy(rank = 3),
+ communalWidgetItemEntry3,
)
.inOrder()
}
@@ -261,11 +295,11 @@
assertThat(widgets).containsExactlyEntriesIn(expected)
}
- private fun addWidget(metadata: FakeWidgetMetadata, priority: Int? = null) {
+ private fun addWidget(metadata: FakeWidgetMetadata, rank: Int? = null) {
communalWidgetDao.addWidget(
widgetId = metadata.widgetId,
provider = metadata.provider,
- priority = priority ?: metadata.priority,
+ rank = rank ?: metadata.rank,
userSerialNumber = metadata.userSerialNumber,
)
}
@@ -273,7 +307,7 @@
data class FakeWidgetMetadata(
val widgetId: Int,
val provider: ComponentName,
- val priority: Int,
+ val rank: Int,
val userSerialNumber: Int,
)
@@ -282,26 +316,26 @@
FakeWidgetMetadata(
widgetId = 1,
provider = ComponentName("pk_name", "cls_name_1"),
- priority = 1,
+ rank = 0,
userSerialNumber = 0,
)
val widgetInfo2 =
FakeWidgetMetadata(
widgetId = 2,
provider = ComponentName("pk_name", "cls_name_2"),
- priority = 2,
+ rank = 1,
userSerialNumber = 0,
)
val widgetInfo3 =
FakeWidgetMetadata(
widgetId = 3,
provider = ComponentName("pk_name", "cls_name_3"),
- priority = 3,
+ rank = 2,
userSerialNumber = 10,
)
- val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
- val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
- val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
+ val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.rank)
+ val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.rank)
+ val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.rank)
val communalWidgetItemEntry1 =
CommunalWidgetItem(
uid = 1L,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 0b7a3ed..6aecc0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -22,6 +22,7 @@
import android.os.PowerManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.keyguardUpdateMonitor
import com.android.keyguard.trustManager
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -52,12 +54,15 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -113,6 +118,7 @@
powerInteractor,
fakeBiometricSettingsRepository,
trustManager,
+ { kosmos.sceneInteractor },
deviceEntryFaceAuthStatusInteractor,
)
}
@@ -279,6 +285,22 @@
}
@Test
+ @EnableSceneContainer
+ fun withSceneContainerEnabled_faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+ testScope.runTest {
+ underTest.start()
+
+ kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+ kosmos.sceneInteractor.setTransitionState(
+ MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, false))
+ }
+
+ @Test
fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
testScope.runTest {
underTest.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index fc7f693..d13419e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.dock.DockManagerFake
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
@@ -152,9 +153,7 @@
dockManager = DockManagerFake()
biometricSettingsRepository = FakeBiometricSettingsRepository()
val featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
- }
+ FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
val keyguardInteractor = withDeps.keyguardInteractor
@@ -223,6 +222,7 @@
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManager,
pulsingGestureListener = kosmos.pulsingGestureListener,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
)
underTest =
KeyguardBottomAreaViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index bdee936..fd53b5ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -38,19 +38,28 @@
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Bundle
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.Flags.MEDIA_REMOTE_RESUME
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_RECOMMENDATIONS
+import com.android.systemui.flags.Flags.MEDIA_RETAIN_SESSIONS
+import com.android.systemui.flags.Flags.MEDIA_SESSION_ACTIONS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
@@ -58,16 +67,21 @@
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.MediaControllerFactory
-import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -91,6 +105,8 @@
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
@@ -111,13 +127,13 @@
return Mockito.anyObject<T>()
}
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class LegacyMediaDataManagerImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
- @Mock lateinit var mediaControllerFactory: MediaControllerFactory
@Mock lateinit var controller: MediaController
@Mock lateinit var transportControls: MediaController.TransportControls
@Mock lateinit var playbackInfo: MediaController.PlaybackInfo
@@ -136,7 +152,6 @@
@Mock lateinit var mediaDataFilter: LegacyMediaDataFilterImpl
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
- @Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@@ -144,7 +159,6 @@
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
lateinit var validRecommendationList: List<SmartspaceAction>
@Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
- @Mock private lateinit var mediaFlags: MediaFlags
@Mock private lateinit var logger: MediaUiEventLogger
lateinit var mediaDataManager: LegacyMediaDataManagerImpl
lateinit var mediaNotification: StatusBarNotification
@@ -159,6 +173,26 @@
@Mock private lateinit var ugm: IUriGrantsManager
@Mock private lateinit var imageSource: ImageDecoder.Source
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.progressionOf(
+ Flags.FLAG_MEDIA_LOAD_METADATA_VIA_MEDIA_DATA_LOADER
+ )
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = testKosmos()
+ private val testDispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
+ private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+ private val activityStarter = kosmos.activityStarter
+ private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
private val originalSmartspaceSetting =
@@ -188,12 +222,16 @@
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
1
)
+
mediaDataManager =
LegacyMediaDataManagerImpl(
context = context,
backgroundExecutor = backgroundExecutor,
+ backgroundDispatcher = testDispatcher,
uiExecutor = uiExecutor,
foregroundExecutor = foregroundExecutor,
+ mainDispatcher = testDispatcher,
+ applicationScope = testScope,
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
@@ -209,10 +247,11 @@
useQsMediaPlayer = true,
systemClock = clock,
tunerService = tunerService,
- mediaFlags = mediaFlags,
+ mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
keyguardUpdateMonitor = keyguardUpdateMonitor,
+ mediaDataLoader = { kosmos.mediaDataLoader },
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -248,7 +287,7 @@
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
- whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+ mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -278,10 +317,11 @@
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, false)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, false)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, false)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
}
@@ -310,49 +350,51 @@
}
@Test
- fun testsetInactive_resume_dismissesMedia() {
- // WHEN resume controls are present, and time out
- val desc =
- MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
-
- backgroundExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(null),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
+ fun testsetInactive_resume_dismissesMedia() =
+ testScope.runTest {
+ // WHEN resume controls are present, and time out
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
)
- mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
- verify(logger)
- .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+ runCurrent()
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
- // THEN it is removed and listeners are informed
- foregroundExecutor.advanceClockToLast()
- foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
- }
+ mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
+ verify(logger)
+ .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+
+ // THEN it is removed and listeners are informed
+ foregroundExecutor.advanceClockToLast()
+ foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
+ }
@Test
fun testLoadsMetadataOnBackground() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
}
@Test
@@ -370,8 +412,7 @@
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -389,8 +430,7 @@
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -417,11 +457,9 @@
@Test
fun testOnMetaDataLoaded_conservesActiveFlag() {
- whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
mediaDataManager.addListener(listener)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -465,8 +503,7 @@
}
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -552,8 +589,7 @@
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -583,8 +619,7 @@
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then a media control is created with a placeholder title string
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -625,8 +660,7 @@
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then the media control is added using the notification's title
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -734,8 +768,7 @@
// GIVEN that the manager has two notifications with resume actions
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+ testScope.assertRunAllReady(foreground = 2, background = 2)
verify(listener)
.onMediaDataLoaded(
@@ -822,7 +855,7 @@
@Test
fun testOnNotificationRemoved_withResumption_isRemoteAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a notification with a resume action, but is not local
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -853,7 +886,7 @@
@Test
fun testOnNotificationRemoved_withResumption_isRcnAndRemoteAllowed() {
// With the flag enabled to allow remote media to resume
- whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_REMOTE_RESUME, true)
// GIVEN that the manager has a remote cast notification
addNotificationAndLoad(remoteCastNotification)
@@ -972,7 +1005,7 @@
@Test
fun testAddResumptionControls_hasPartialProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with partial progress
val progress = 0.5
@@ -999,7 +1032,7 @@
@Test
fun testAddResumptionControls_hasNotPlayedProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have not been played
val extras =
@@ -1024,7 +1057,7 @@
@Test
fun testAddResumptionControls_hasFullProgress() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added with progress info
val extras =
@@ -1050,7 +1083,7 @@
@Test
fun testAddResumptionControls_hasNoExtras() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that do not have any extras
val desc =
@@ -1068,7 +1101,7 @@
@Test
fun testAddResumptionControls_hasEmptyTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have empty title
val desc =
@@ -1087,8 +1120,7 @@
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1102,7 +1134,7 @@
@Test
fun testAddResumptionControls_hasBlankTitle() {
- whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
// WHEN resumption controls are added that have a blank title
val desc =
@@ -1121,8 +1153,7 @@
)
// Resumption controls are not added.
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(0)
+ testScope.assertRunAllReady(foreground = 0, background = 1)
verify(listener, never())
.onMediaDataLoaded(
eq(PACKAGE_NAME),
@@ -1189,8 +1220,7 @@
mediaDataManager.onNotificationAdded(KEY, notif)
// THEN it still loads
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1307,7 +1337,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1333,7 +1363,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
val extras =
Bundle().apply {
putString("package_name", PACKAGE_NAME)
@@ -1367,7 +1397,7 @@
@Test
fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1399,7 +1429,7 @@
@Test
fun testSetRecommendationInactive_notifiesListeners() {
- whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
val instanceId = instanceIdSequence.lastInstanceId
@@ -1479,8 +1509,7 @@
fun testOnMediaDataTimedOut_updatesLastActiveTime() {
// GIVEN that the manager has a notification
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// WHEN the notification times out
clock.advanceTime(100)
@@ -1588,8 +1617,7 @@
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_COMPACT_ACTIONS are actually set
verify(listener)
@@ -1624,8 +1652,7 @@
// WHEN the notification is loaded
mediaDataManager.onNotificationAdded(KEY, notif)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
// THEN only the first MAX_NOTIFICATION_ACTIONS are actually included
verify(listener)
@@ -1644,7 +1671,7 @@
@Test
fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action"
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
whenever(controller.playbackState).thenReturn(null)
val notifWithAction =
@@ -1659,8 +1686,7 @@
}
mediaDataManager.onNotificationAdded(KEY, notifWithAction)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1679,7 +1705,7 @@
@Test
fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions =
PlaybackState.ACTION_PLAY or
PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1723,7 +1749,7 @@
@Test
fun testPlaybackActions_noPrevNext_usesCustom() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1755,7 +1781,7 @@
@Test
fun testPlaybackActions_connecting() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder =
PlaybackState.Builder()
@@ -1776,7 +1802,7 @@
@Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
customDesc.forEach {
@@ -1814,7 +1840,7 @@
@Test
fun testPlaybackActions_playPause_hasButton() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val stateActions = PlaybackState.ACTION_PLAY_PAUSE
val stateBuilder = PlaybackState.Builder().setActions(stateActions)
whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1851,8 +1877,7 @@
// update to remote cast
mediaDataManager.onNotificationAdded(KEY, remoteCastNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(logger)
.logPlaybackLocationChange(
anyInt(),
@@ -1914,7 +1939,7 @@
@Test
fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
whenever(controller.playbackState).thenReturn(state)
@@ -1935,46 +1960,48 @@
}
@Test
- fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
- val desc =
- MediaDescription.Builder().run {
- setTitle(SESSION_TITLE)
- build()
- }
- val state =
- PlaybackState.Builder()
- .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
- .setActions(PlaybackState.ACTION_PLAY_PAUSE)
- .build()
+ fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() =
+ testScope.runTest {
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ val state =
+ PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+ .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+ .build()
- // Add resumption controls in order to have semantic actions.
- // To make sure that they are not null after changing state.
- mediaDataManager.addResumptionControls(
- USER_ID,
- desc,
- Runnable {},
- session.sessionToken,
- APP_NAME,
- pendingIntent,
- PACKAGE_NAME
- )
- backgroundExecutor.runAllReady()
- foregroundExecutor.runAllReady()
-
- stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
-
- verify(listener)
- .onMediaDataLoaded(
- eq(PACKAGE_NAME),
- eq(PACKAGE_NAME),
- capture(mediaDataCaptor),
- eq(true),
- eq(0),
- eq(false)
+ // Add resumption controls in order to have semantic actions.
+ // To make sure that they are not null after changing state.
+ mediaDataManager.addResumptionControls(
+ USER_ID,
+ desc,
+ Runnable {},
+ session.sessionToken,
+ APP_NAME,
+ pendingIntent,
+ PACKAGE_NAME
)
- assertThat(mediaDataCaptor.value.isPlaying).isFalse()
- assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
- }
+ runCurrent()
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+
+ stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(PACKAGE_NAME),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+ }
@Test
fun testPlaybackStateNull_Pause_keyExists_callsListener() {
@@ -2036,7 +2063,7 @@
@Test
fun testRetain_notifPlayer_notifRemoved_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added, times out, and then removed
addNotificationAndLoad()
@@ -2066,7 +2093,7 @@
@Test
fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and times out
addNotificationAndLoad()
@@ -2084,7 +2111,7 @@
@Test
fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control based on notification is added and then removed, without timing out
addNotificationAndLoad()
@@ -2101,7 +2128,7 @@
@Test
fun testRetain_canResume_removeWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
// When a media control that supports resumption is added
addNotificationAndLoad()
@@ -2133,8 +2160,8 @@
@Test
fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2153,8 +2180,8 @@
@Test
fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2187,8 +2214,8 @@
@Test
fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2207,8 +2234,8 @@
@Test
fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2241,7 +2268,7 @@
@Test
fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control with PlaybackState actions is added, times out,
@@ -2268,7 +2295,7 @@
@Test
fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions is added, and then the session is destroyed
@@ -2287,7 +2314,7 @@
@Test
fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
addPlaybackStateAction()
// When a media control using session actions and that does allow resumption is added,
@@ -2320,8 +2347,8 @@
@Test
fun testSessionDestroyed_noNotificationKey_stillRemoved() {
- whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
- whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
+ fakeFeatureFlags.set(MEDIA_SESSION_ACTIONS, true)
// When a notiifcation is added and then removed before it is fully processed
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
@@ -2392,6 +2419,23 @@
assertThat(mediaDataCaptor.value.artwork).isNull()
}
+ private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) {
+ runCurrent()
+ if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+ // It doesn't make much sense to count tasks when we use coroutines in loader
+ // so this check is skipped in that scenario.
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ } else {
+ if (background > 0) {
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(background)
+ }
+ if (foreground > 0) {
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(foreground)
+ }
+ }
+ }
+
/** Helper function to add a basic media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
addNotificationAndLoad(mediaNotification)
@@ -2400,8 +2444,7 @@
/** Helper function to add the given notification and capture the resulting MediaData */
private fun addNotificationAndLoad(sbn: StatusBarNotification) {
mediaDataManager.onNotificationAdded(KEY, sbn)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -2435,8 +2478,8 @@
pendingIntent,
packageName
)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+ testScope.assertRunAllReady(foreground = 1, background = 1)
verify(listener)
.onMediaDataLoaded(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index f531a3f..3e3aa4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,9 +16,9 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index 8fbd3c8..69b7b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -29,8 +29,8 @@
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import java.util.Optional
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 505f799..3f6617b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -197,6 +197,7 @@
() -> sceneInteractor,
() -> mKosmos.getFromGoneTransitionInteractor(),
() -> mKosmos.getFromLockscreenTransitionInteractor(),
+ () -> mKosmos.getFromOccludedTransitionInteractor(),
() -> mKosmos.getSharedNotificationContainerInteractor(),
mTestScope);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index ad6aca1..3c583f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
@@ -45,8 +46,8 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -66,6 +67,7 @@
SensitiveNotificationProtectionController
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
+ @Mock private lateinit var row: ExpandableNotificationRow
@Before
fun setUp() {
@@ -74,6 +76,8 @@
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
entry = NotificationEntryBuilder().setSection(section).build()
+ entry.row = row
+ entry.setSensitive(false, false)
coordinator =
StackCoordinator(
groupExpansionManagerImpl,
@@ -189,4 +193,17 @@
.setNotifStats(NotifStats(1, false, false, true, false))
verifyZeroInteractions(stackController)
}
+
+ @Test
+ @EnableFlags(
+ FooterViewRefactor.FLAG_NAME
+ )
+ fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
+ entry.setSensitive(true, true)
+ whenever(section.bucket).thenReturn(BUCKET_ALERTING)
+ afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ verify(activeNotificationsInteractor)
+ .setNotifStats(NotifStats(1, hasNonClearableAlertingNotifs = true, false, false, false))
+ verifyZeroInteractions(stackController)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 491919a..30a1214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -35,6 +35,9 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView
+import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -74,6 +77,7 @@
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -125,8 +129,10 @@
): RichOngoingContentModel? = fakeRonContentModel
}
- private var fakeRonViewHolder: InflatedContentViewHolder? = null
- private val fakeRonViewInflater =
+ private var fakeContractedRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeExpandedRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeHeadsUpRonViewHolder: ContentViewInflationResult = NullContentView
+ private var fakeRonViewInflater =
spy(
object : RichOngoingNotificationViewInflater {
override fun inflateView(
@@ -134,8 +140,20 @@
existingView: View?,
entry: NotificationEntry,
systemUiContext: Context,
- parentView: ViewGroup
- ): InflatedContentViewHolder? = fakeRonViewHolder
+ parentView: ViewGroup,
+ viewType: RichOngoingNotificationViewType
+ ): ContentViewInflationResult =
+ when (viewType) {
+ RichOngoingNotificationViewType.Contracted -> fakeContractedRonViewHolder
+ RichOngoingNotificationViewType.Expanded -> fakeExpandedRonViewHolder
+ RichOngoingNotificationViewType.HeadsUp -> fakeHeadsUpRonViewHolder
+ }
+
+ override fun canKeepView(
+ contentModel: RichOngoingContentModel,
+ existingView: View?,
+ viewType: RichOngoingNotificationViewType
+ ): Boolean = false
}
)
@@ -149,6 +167,7 @@
.setContentText("Text")
.setStyle(Notification.BigTextStyle().bigText("big text"))
testHelper = NotificationTestHelper(mContext, mDependency)
+ testHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL)
row = spy(testHelper.createRow(builder.build()))
notificationInflater =
NotificationRowContentBinderImpl(
@@ -388,15 +407,62 @@
@Test
fun testRonModelRequiredForRonView() {
fakeRonContentModel = null
- val ronView = View(context)
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+ val contractedRonView = View(context)
+ val expandedRonView = View(context)
+ val headsUpRonView = View(context)
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = contractedRonView, binder = mock())
+ fakeExpandedRonViewHolder =
+ InflatedContentViewHolder(view = expandedRonView, binder = mock())
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = headsUpRonView, binder = mock())
+
// WHEN inflater inflates
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
- verify(fakeRonViewInflater, never()).inflateView(any(), any(), any(), any(), any())
+ val contentToInflate =
+ FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+ inflateAndWait(notificationInflater, contentToInflate, row)
+ verifyZeroInteractions(fakeRonViewInflater)
}
@Test
- fun testRonModelTriggersInflationOfRonView() {
+ fun testRonModelCleansUpRemoteViews() {
+ val ronView = View(context)
+
+ val entry = row.entry
+
+ fakeRonContentModel = mock<TimerContentModel>()
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = ronView, binder = mock<DeferredContentViewBinder>())
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // VERIFY
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_CONTRACTED))
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_EXPANDED))
+ verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_HEADS_UP))
+ }
+
+ @Test
+ fun testRonModelCleansUpSmartReplies() {
+ val ronView = View(context)
+
+ val privateLayout = spy(row.privateLayout)
+
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mock<TimerContentModel>()
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // VERIFY
+ verify(privateLayout).setExpandedInflatedSmartReplies(eq(null))
+ verify(privateLayout).setHeadsUpInflatedSmartReplies(eq(null))
+ }
+
+ @Test
+ fun testRonModelTriggersInflationOfContractedRonView() {
val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
val mockBinder = mock<DeferredContentViewBinder>()
@@ -405,18 +471,229 @@
val privateLayout = row.privateLayout
fakeRonContentModel = mockRonModel
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
// VERIFY that the inflater is invoked
verify(fakeRonViewInflater)
- .inflateView(eq(mockRonModel), any(), eq(entry), any(), eq(privateLayout))
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.Contracted)
+ )
assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView)
verify(mockBinder).setupContentViewBinder()
}
@Test
- fun ronViewAppliesElementsInOrder() {
+ fun testRonModelTriggersInflationOfExpandedRonView() {
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ val entry = row.entry
+ val privateLayout = row.privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // VERIFY that the inflater is invoked
+ verify(fakeRonViewInflater)
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.Expanded)
+ )
+ assertThat(row.privateLayout.expandedChild).isSameInstanceAs(ronView)
+ verify(mockBinder).setupContentViewBinder()
+ }
+
+ @Test
+ fun testRonModelTriggersInflationOfHeadsUpRonView() {
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ val entry = row.entry
+ val privateLayout = row.privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // VERIFY that the inflater is invoked
+ verify(fakeRonViewInflater)
+ .inflateView(
+ eq(mockRonModel),
+ any(),
+ eq(entry),
+ any(),
+ eq(privateLayout),
+ eq(RichOngoingNotificationViewType.HeadsUp)
+ )
+ assertThat(row.privateLayout.headsUpChild).isSameInstanceAs(ronView)
+ verify(mockBinder).setupContentViewBinder()
+ }
+
+ @Test
+ fun keepExistingViewForContractedRonNotChangingContractedChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mContractedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeContractedRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // THEN do not dispose old contracted binder handle and change contracted child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setContractedChild(any())
+ }
+
+ @Test
+ fun keepExistingViewForExpandedRonNotChangingExpandedChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // THEN do not dispose old expanded binder handle and change expanded child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setExpandedChild(any())
+ }
+
+ @Test
+ fun keepExistingViewForHeadsUpRonNotChangingHeadsUpChild() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = KeepExistingView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // THEN - do not dispose old heads up binder handle and change heads up child
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verifyZeroInteractions(oldHandle)
+ verify(privateLayout, never()).setHeadsUpChild(any())
+ }
+
+ @Test
+ fun nullContentViewForContractedRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mContractedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeContractedRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setContractedChild(eq(null))
+ }
+ }
+
+ @Test
+ fun nullContentViewForExpandedRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setExpandedChild(eq(null))
+ }
+ }
+
+ @Test
+ fun nullContentViewForHeadsUpRonAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = NullContentView
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, cache) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setHeadsUpChild(eq(null))
+ }
+ }
+
+ @Test
+ fun contractedRonViewAppliesElementsInOrder() {
val oldHandle = mock<DisposableHandle>()
val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
@@ -429,7 +706,8 @@
row.privateLayout = privateLayout
fakeRonContentModel = mockRonModel
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+ fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
// WHEN inflater inflates
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
@@ -443,16 +721,89 @@
}
@Test
- fun testRonNotReinflating() {
- val handle0 = mock<DisposableHandle>()
- val handle1 = mock<DisposableHandle>()
+ fun expandedRonViewAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ row.privateLayout.mExpandedBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, mockBinder) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setExpandedChild(eq(ronView))
+ verify(mockBinder).setupContentViewBinder()
+ }
+ }
+
+ @Test
+ fun headsUpRonViewAppliesElementsInOrder() {
+ val oldHandle = mock<DisposableHandle>()
+ val mockRonModel = mock<TimerContentModel>()
+ val ronView = View(context)
+ val mockBinder = mock<DeferredContentViewBinder>()
+
+ row.privateLayout.mHeadsUpBinderHandle = oldHandle
+ val entry = spy(row.entry)
+ row.entry = entry
+ val privateLayout = spy(row.privateLayout)
+ row.privateLayout = privateLayout
+
+ fakeRonContentModel = mockRonModel
+ fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+ // WHEN inflater inflates
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+ // Validate that these 4 steps happen in this precise order
+ inOrder(oldHandle, entry, privateLayout, mockBinder) {
+ verify(oldHandle).dispose()
+ verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+ verify(privateLayout).setHeadsUpChild(eq(ronView))
+ verify(mockBinder).setupContentViewBinder()
+ }
+ }
+
+ @Test
+ fun testRonNotReinflating() {
+ val oldContractedBinderHandle = mock<DisposableHandle>()
+ val oldExpandedBinderHandle = mock<DisposableHandle>()
+ val oldHeadsUpBinderHandle = mock<DisposableHandle>()
+
+ val contractedBinderHandle = mock<DisposableHandle>()
+ val expandedBinderHandle = mock<DisposableHandle>()
+ val headsUpBinderHandle = mock<DisposableHandle>()
+
+ val contractedRonView = View(context)
+ val expandedRonView = View(context)
+ val headsUpRonView = View(context)
+
val mockRonModel1 = mock<TimerContentModel>()
val mockRonModel2 = mock<TimerContentModel>()
- val mockBinder1 = mock<DeferredContentViewBinder>()
- doReturn(handle1).whenever(mockBinder1).setupContentViewBinder()
- row.privateLayout.mContractedBinderHandle = handle0
+ val mockContractedViewBinder = mock<DeferredContentViewBinder>()
+ val mockExpandedViewBinder = mock<DeferredContentViewBinder>()
+ val mockHeadsUpViewBinder = mock<DeferredContentViewBinder>()
+
+ doReturn(contractedBinderHandle).whenever(mockContractedViewBinder).setupContentViewBinder()
+ doReturn(expandedBinderHandle).whenever(mockExpandedViewBinder).setupContentViewBinder()
+ doReturn(headsUpBinderHandle).whenever(mockHeadsUpViewBinder).setupContentViewBinder()
+
+ row.privateLayout.mContractedBinderHandle = oldContractedBinderHandle
+ row.privateLayout.mExpandedBinderHandle = oldExpandedBinderHandle
+ row.privateLayout.mHeadsUpBinderHandle = oldHeadsUpBinderHandle
val entry = spy(row.entry)
row.entry = entry
val privateLayout = spy(row.privateLayout)
@@ -460,31 +811,87 @@
// WHEN inflater inflates both a model and a view
fakeRonContentModel = mockRonModel1
- fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder1)
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+ fakeContractedRonViewHolder =
+ InflatedContentViewHolder(view = contractedRonView, binder = mockContractedViewBinder)
+ fakeExpandedRonViewHolder =
+ InflatedContentViewHolder(view = expandedRonView, binder = mockExpandedViewBinder)
+ fakeHeadsUpRonViewHolder =
+ InflatedContentViewHolder(view = headsUpRonView, binder = mockHeadsUpViewBinder)
+
+ val contentToInflate =
+ FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+ inflateAndWait(notificationInflater, contentToInflate, row)
// Validate that these 4 steps happen in this precise order
- inOrder(handle0, entry, privateLayout, mockBinder1, handle1) {
- verify(handle0).dispose()
+ inOrder(
+ oldContractedBinderHandle,
+ oldExpandedBinderHandle,
+ oldHeadsUpBinderHandle,
+ entry,
+ privateLayout,
+ mockContractedViewBinder,
+ mockExpandedViewBinder,
+ mockHeadsUpViewBinder,
+ contractedBinderHandle,
+ expandedBinderHandle,
+ headsUpBinderHandle
+ ) {
+ verify(oldContractedBinderHandle).dispose()
+ verify(oldExpandedBinderHandle).dispose()
+ verify(oldHeadsUpBinderHandle).dispose()
+
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 })
- verify(privateLayout).setContractedChild(eq(ronView))
- verify(mockBinder1).setupContentViewBinder()
- verify(handle1, never()).dispose()
+
+ verify(privateLayout).setContractedChild(eq(contractedRonView))
+ verify(mockContractedViewBinder).setupContentViewBinder()
+
+ verify(privateLayout).setExpandedChild(eq(expandedRonView))
+ verify(mockExpandedViewBinder).setupContentViewBinder()
+
+ verify(privateLayout).setHeadsUpChild(eq(headsUpRonView))
+ verify(mockHeadsUpViewBinder).setupContentViewBinder()
+
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
}
- clearInvocations(handle0, entry, privateLayout, mockBinder1, handle1)
+ clearInvocations(
+ oldContractedBinderHandle,
+ oldExpandedBinderHandle,
+ oldHeadsUpBinderHandle,
+ entry,
+ privateLayout,
+ mockContractedViewBinder,
+ mockExpandedViewBinder,
+ mockHeadsUpViewBinder,
+ contractedBinderHandle,
+ expandedBinderHandle,
+ headsUpBinderHandle
+ )
// THEN when the inflater inflates just a model
fakeRonContentModel = mockRonModel2
- fakeRonViewHolder = null
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+ fakeContractedRonViewHolder = KeepExistingView
+ fakeExpandedRonViewHolder = KeepExistingView
+ fakeHeadsUpRonViewHolder = KeepExistingView
+
+ inflateAndWait(notificationInflater, contentToInflate, row)
// Validate that for reinflation, the only thing we do us update the model
- verify(handle1, never()).dispose()
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 })
verify(privateLayout, never()).setContractedChild(any())
- verify(mockBinder1, never()).setupContentViewBinder()
- verify(handle1, never()).dispose()
+ verify(privateLayout, never()).setExpandedChild(any())
+ verify(privateLayout, never()).setHeadsUpChild(any())
+ verify(mockContractedViewBinder, never()).setupContentViewBinder()
+ verify(mockExpandedViewBinder, never()).setupContentViewBinder()
+ verify(mockHeadsUpViewBinder, never()).setupContentViewBinder()
+ verify(contractedBinderHandle, never()).dispose()
+ verify(expandedBinderHandle, never()).dispose()
+ verify(headsUpBinderHandle, never()).dispose()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index c3cc33f..bf31f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -45,6 +45,7 @@
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
@@ -88,7 +89,7 @@
}
@Test
- fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
+ fun icon_null_satelliteNotAllowed() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -108,7 +109,30 @@
}
@Test
- fun icon_nullWhenShouldNotShow_notAllOos() =
+ fun icon_null_connectedAndNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is not allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN satellite state is Connected. (this should not ever occur, but still)
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null despite the connected state
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_null_notAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -127,9 +151,28 @@
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_nullWhenShouldNotShow_isEmergencyOnly() =
+ fun icon_null_allOosAndNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null because it is not allowed
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_null_isEmergencyOnly() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -158,7 +201,7 @@
}
@Test
- fun icon_nullWhenShouldNotShow_apmIsEnabled() =
+ fun icon_null_apmIsEnabled() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -177,9 +220,8 @@
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_satelliteIsOn() =
+ fun icon_notNull_satelliteAllowedAndAllOos() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -201,7 +243,6 @@
assertThat(latest).isInstanceOf(Icon::class.java)
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun icon_hysteresisWhenEnablingIcon() =
testScope.runTest {
@@ -234,9 +275,56 @@
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_deviceIsProvisioned() =
+ fun icon_ignoresHysteresis_whenConnected() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN satellite reports that it is Connected
+ repo.connectionState.value = SatelliteConnectionState.Connected
+
+ // THEN icon is non null because we are connected, despite the normal OOS icon waiting
+ // 10 seconds for hysteresis
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+
+ @Test
+ fun icon_ignoresHysteresis_whenOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN satellite reports that it is Connected
+ repo.connectionState.value = SatelliteConnectionState.On
+
+ // THEN icon is non null because the connection state is On, despite the normal OOS icon
+ // waiting 10 seconds for hysteresis
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+
+ @Test
+ fun icon_satelliteIsProvisioned() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -267,7 +355,6 @@
assertThat(latest).isInstanceOf(Icon::class.java)
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun icon_wifiIsActive() =
testScope.runTest {
@@ -324,7 +411,28 @@
}
@Test
- fun carrierText_nullWhenShouldNotShow_notAllOos() =
+ fun carrierText_null_notAllOos() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.carrierText)
+
+ // GIVEN satellite is allowed + off
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+ repo.connectionState.value = SatelliteConnectionState.Off
+
+ // GIVEN all icons are not OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = true
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN carrier text is null because we have service
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun carrierText_notNull_notAllOos_butConnected() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -340,39 +448,9 @@
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
- // THEN carrier text is null because we have service
- assertThat(latest).isNull()
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun carrierText_nullWhenShouldNotShow_isEmergencyOnly() =
- testScope.runTest {
- val latest by collectLastValue(underTest.carrierText)
-
- // GIVEN satellite is allowed + connected
- repo.isSatelliteAllowedForCurrentLocation.value = true
- repo.connectionState.value = SatelliteConnectionState.Connected
-
- // GIVEN all icons are OOS
- val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
- i1.isInService.value = false
- i1.isEmergencyOnly.value = false
-
- // GIVEN apm is disabled
- airplaneModeRepository.setIsAirplaneMode(false)
-
- // Wait for delay to be completed
- advanceTimeBy(10.seconds)
-
- // THEN carrier text is set because we don't have service
+ // THEN carrier text is not null, because it is connected
+ // This case should never happen, but let's test it anyway
assertThat(latest).isNotNull()
-
- // GIVEN the connection is emergency only
- i1.isEmergencyOnly.value = true
-
- // THEN carrier text is null because we have emergency connection
- assertThat(latest).isNull()
}
@Test
@@ -396,7 +474,6 @@
assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_satelliteIsOn() =
testScope.runTest {
@@ -421,9 +498,8 @@
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_hysteresisWhenEnablingText() =
+ fun carrierText_noHysteresisWhenEnablingText_connected() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -439,23 +515,10 @@
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
- // THEN carrier text is null because of the hysteresis
- assertThat(latest).isNull()
-
- // Wait for delay to be completed
- advanceTimeBy(10.seconds)
-
- // THEN carrier text is set after the delay
+ // THEN carrier text is not null because we skip hysteresis when connected
assertThat(latest).isNotNull()
-
- // GIVEN apm is enabled
- airplaneModeRepository.setIsAirplaneMode(true)
-
- // THEN carrier text is null immediately
- assertThat(latest).isNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_deviceIsProvisioned() =
testScope.runTest {
@@ -489,7 +552,6 @@
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_wifiIsActive() =
testScope.runTest {
@@ -526,9 +588,8 @@
assertThat(latest).isNotNull()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_connectionStateUnknown_null() =
+ fun carrierText_connectionStateUnknown_usesEmergencyOnlyText() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -544,12 +605,12 @@
// Wait for delay to be completed
advanceTimeBy(10.seconds)
- assertThat(latest).isNull()
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun carrierText_connectionStateOff_null() =
+ fun carrierText_connectionStateOff_usesEmergencyOnlyText() =
testScope.runTest {
val latest by collectLastValue(underTest.carrierText)
@@ -565,10 +626,10 @@
// Wait for delay to be completed
advanceTimeBy(10.seconds)
- assertThat(latest).isNull()
+ assertThat(latest)
+ .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_connectionStateOn_notConnectedString() =
testScope.runTest {
@@ -590,7 +651,6 @@
.isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun carrierText_connectionStateConnected_connectedString() =
testScope.runTest {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
similarity index 76%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
copy to packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
index c968e80..6251ae9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package com.android.wm.shell.common.desktopmode;
+package android.app
-parcelable DesktopModeTransitionSource;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.statusBarManager by Kosmos.Fixture { mock<StatusBarManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index c00454f..5d7e7c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -30,7 +30,7 @@
override fun addWidget(
provider: ComponentName,
user: UserHandle,
- priority: Int,
+ rank: Int?,
configurator: WidgetConfigurator?
) {
coroutineScope.launch {
@@ -38,7 +38,7 @@
val providerInfo = AppWidgetProviderInfo().apply { this.provider = provider }
val configured = configurator?.configureWidget(id) ?: true
if (configured) {
- onConfigured(id, providerInfo, priority)
+ onConfigured(id, providerInfo, rank ?: -1)
}
}
}
@@ -46,14 +46,14 @@
fun addWidget(
appWidgetId: Int,
componentName: String = "pkg/cls",
- priority: Int = 0,
+ rank: Int = 0,
category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
userId: Int = 0,
) {
fakeDatabase[appWidgetId] =
CommunalWidgetContentModel.Available(
appWidgetId = appWidgetId,
- priority = priority,
+ rank = rank,
providerInfo =
AppWidgetProviderInfo().apply {
provider = ComponentName.unflattenFromString(componentName)!!
@@ -73,14 +73,14 @@
fun addPendingWidget(
appWidgetId: Int,
componentName: String = "pkg/cls",
- priority: Int = 0,
+ rank: Int = 0,
icon: Bitmap? = null,
userId: Int = 0,
) {
fakeDatabase[appWidgetId] =
CommunalWidgetContentModel.Pending(
appWidgetId = appWidgetId,
- priority = priority,
+ rank = rank,
componentName = ComponentName.unflattenFromString(componentName)!!,
icon = icon,
user = UserHandle(userId),
@@ -97,8 +97,8 @@
override fun abortRestoreWidgets() {}
- private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
+ private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, rank: Int) {
_communalWidgets.value +=
- listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority))
+ listOf(CommunalWidgetContentModel.Available(id, providerInfo, rank))
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index b9be04d..3dfe0ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -33,6 +33,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +58,7 @@
powerInteractor = powerInteractor,
biometricSettingsRepository = biometricSettingsRepository,
trustManager = trustManager,
+ sceneInteractor = { sceneInteractor },
deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index cdfb297..fb4e2fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.education.data.repository
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
import com.android.systemui.education.data.model.GestureEduModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,22 +28,34 @@
private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0))
private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+
+ private val userEduDeviceConnectionTimeMap = mutableMapOf<Int, EduDeviceConnectionTime>()
+ private val _eduDeviceConnectionTime = MutableStateFlow(EduDeviceConnectionTime())
+ private val eduDeviceConnectionTime = _eduDeviceConnectionTime.asStateFlow()
+
private var currentUser: Int = 0
override fun setUser(userId: Int) {
if (!userGestureMap.contains(userId)) {
userGestureMap[userId] = GestureEduModel(userId = userId)
+ userEduDeviceConnectionTimeMap[userId] = EduDeviceConnectionTime()
}
// save data of current user to the map
userGestureMap[currentUser] = _gestureEduModels.value
+ userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value
// switch to data of new user
_gestureEduModels.value = userGestureMap[userId]!!
+ _eduDeviceConnectionTime.value = userEduDeviceConnectionTimeMap[userId]!!
}
override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
return gestureEduModelsFlow
}
+ override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> {
+ return eduDeviceConnectionTime
+ }
+
override suspend fun updateGestureEduModel(
gestureType: GestureType,
transform: (GestureEduModel) -> GestureEduModel
@@ -50,4 +63,11 @@
val currentModel = _gestureEduModels.value
_gestureEduModels.value = transform(currentModel)
}
+
+ override suspend fun updateEduDeviceConnectionTime(
+ transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+ ) {
+ val currentModel = _eduDeviceConnectionTime.value
+ _eduDeviceConnectionTime.value = transform(currentModel)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index 5088677..88ab170 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -17,14 +17,26 @@
package com.android.systemui.education.domain.interactor
import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository
+import com.android.systemui.keyboard.data.repository.keyboardRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.touchpad.data.repository.touchpadRepository
+import com.android.systemui.user.data.repository.userRepository
var Kosmos.keyboardTouchpadEduInteractor by
Kosmos.Fixture {
KeyboardTouchpadEduInteractor(
backgroundScope = testScope.backgroundScope,
contextualEducationInteractor = contextualEducationInteractor,
+ userInputDeviceRepository =
+ UserInputDeviceRepository(
+ testDispatcher,
+ keyboardRepository,
+ touchpadRepository,
+ userRepository
+ ),
clock = fakeEduClock
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index a95609e..f5232ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -54,6 +54,7 @@
sceneInteractor: SceneInteractor = mock(),
fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(),
+ fromOccludedTransitionInteractor: FromOccludedTransitionInteractor = mock(),
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null,
powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
testScope: CoroutineScope = TestScope(),
@@ -100,6 +101,7 @@
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
sharedNotificationContainerInteractor = { sncInteractor },
applicationScope = testScope,
),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 5ab56e9..e85114d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -38,6 +38,7 @@
sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor },
applicationScope = testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 73799b6..769612c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -20,6 +20,7 @@
import android.view.accessibility.accessibilityManagerWrapper
import com.android.internal.logging.uiEventLogger
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
@@ -38,5 +39,6 @@
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManagerWrapper,
pulsingGestureListener = pulsingGestureListener,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 9fe66eb..953363d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromOccludedTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -128,6 +129,7 @@
val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
val fromLockscreenTransitionInteractor by lazy { kosmos.fromLockscreenTransitionInteractor }
+ val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor }
val fromPrimaryBouncerTransitionInteractor by lazy {
kosmos.fromPrimaryBouncerTransitionInteractor
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
new file mode 100644
index 0000000..a5690a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.app.statusBarManager
+import android.content.testableContext
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.mediaDataLoader by
+ Kosmos.Fixture {
+ MediaDataLoader(
+ testableContext,
+ testDispatcher,
+ testScope,
+ activityStarter,
+ fakeMediaControllerFactory,
+ mediaFlags,
+ imageLoader,
+ statusBarManager
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index cc1ad1f..2127a88 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -28,7 +28,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
import com.android.systemui.plugins.activityStarter
@@ -46,7 +46,7 @@
uiExecutor = fakeExecutor,
foregroundExecutor = fakeExecutor,
handler = fakeExecutorHandler,
- mediaControllerFactory = mediaControllerFactory,
+ mediaControllerFactory = fakeMediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
index b98f557..c479ce6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerKosmos.kt
@@ -22,8 +22,8 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.localMediaManagerFactory
-import com.android.systemui.media.controls.util.mediaControllerFactory
import com.android.systemui.media.muteawait.mediaMuteAwaitConnectionManagerFactory
import com.android.systemui.statusbar.policy.configurationController
@@ -31,7 +31,7 @@
Kosmos.Fixture {
MediaDeviceManager(
context = applicationContext,
- controllerFactory = mediaControllerFactory,
+ controllerFactory = fakeMediaControllerFactory,
localMediaManagerFactory = localMediaManagerFactory,
mr2manager = { MediaRouter2Manager.getInstance(applicationContext) },
muteAwaitConnectionManagerFactory = mediaMuteAwaitConnectionManagerFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
index 6ec6378..b7660e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
@@ -19,7 +19,7 @@
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.util.time.systemClock
@@ -27,7 +27,7 @@
val Kosmos.mediaTimeoutListener by
Kosmos.Fixture {
MediaTimeoutListener(
- mediaControllerFactory = mediaControllerFactory,
+ mediaControllerFactory = fakeMediaControllerFactory,
mainExecutor = fakeExecutor,
logger = MediaTimeoutLogger(logcatLogBuffer("MediaTimeoutLogBuffer")),
statusBarStateController = statusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
new file mode 100644
index 0000000..7f8348e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.MediaSession.Token
+
+class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
+
+ private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+
+ override fun create(token: MediaSession.Token): android.media.session.MediaController {
+ if (token !in mediaControllersForToken) {
+ super.create(token)
+ }
+ return mediaControllersForToken[token]!!
+ }
+
+ fun setControllerForToken(token: Token, mediaController: MediaController) {
+ mediaControllersForToken[token] = mediaController
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
index 1ce6e82..7ee58fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
@@ -19,4 +19,5 @@
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-val Kosmos.mediaControllerFactory by Kosmos.Fixture { MediaControllerFactory(applicationContext) }
+val Kosmos.fakeMediaControllerFactory by
+ Kosmos.Fixture { FakeMediaControllerFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index ae8b411..f84c3bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.scene.domain.resolver.sceneFamilyResolvers
import com.android.systemui.scene.shared.logger.sceneLogger
-val Kosmos.sceneInteractor by
+val Kosmos.sceneInteractor: SceneInteractor by
Kosmos.Fixture {
SceneInteractor(
applicationScope = applicationCoroutineScope,
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a7b2eb1..8fe33d1 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -929,9 +929,9 @@
// For ordered broadcast, check if the receivers for the new broadcast is a superset
// of those for the previous one as skipping and removing only one of them could result
// in an inconsistent state.
- if (testRecord.ordered || testRecord.prioritized) {
+ if (testRecord.ordered) {
return containsAllReceivers(r, testRecord, recordsLookupCache);
- } else if (testRecord.resultTo != null) {
+ } else if (testRecord.prioritized || testRecord.resultTo != null) {
return testRecord.getDeliveryState(testIndex) == DELIVERY_DEFERRED
? r.containsReceiver(testRecord.receivers.get(testIndex))
: containsAllReceivers(r, testRecord, recordsLookupCache);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index d9e9e00..cf2cdc1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -33,6 +33,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Printer;
import android.util.Slog;
@@ -115,7 +116,11 @@
final var selectedImi = selectedIndex >= 0 ? items.get(selectedIndex).mImi : null;
final var languageSettingsIntent = selectedImi != null
? selectedImi.createImeLanguageSettingsActivityIntent() : null;
- final boolean hasLanguageSettingsButton = languageSettingsIntent != null;
+ final boolean isDeviceProvisioned = Settings.Global.getInt(
+ dialogWindowContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+ 0) != 0;
+ final boolean hasLanguageSettingsButton = languageSettingsIntent != null
+ && isDeviceProvisioned;
if (hasLanguageSettingsButton) {
final View buttonBar = contentView
.requireViewById(com.android.internal.R.id.button_bar);
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 ac56043..b35a0a7 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -71,6 +71,7 @@
import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
+import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
import static libcore.io.IoUtils.closeQuietly;
@@ -1388,14 +1389,22 @@
@NonNull
private static NetworkStats removeEmptyEntries(NetworkStats stats) {
- NetworkStats ret = new NetworkStats(0, 1);
+ final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
for (NetworkStats.Entry e : stats) {
if (e.getRxBytes() != 0 || e.getRxPackets() != 0 || e.getTxBytes() != 0
|| e.getTxPackets() != 0 || e.getOperations() != 0) {
- ret = ret.addEntry(e);
+ entries.add(e);
}
}
- return ret;
+ if (isAddEntriesSupported()) {
+ return new NetworkStats(0, entries.size()).addEntries(entries);
+ } else {
+ NetworkStats outputStats = new NetworkStats(0L, 1);
+ for (NetworkStats.Entry e : entries) {
+ outputStats = outputStats.addEntry(e);
+ }
+ return outputStats;
+ }
}
private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@@ -1720,11 +1729,19 @@
@NonNull
private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
@NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
- NetworkStats ret = new NetworkStats(0, 1);
+ final ArrayList<NetworkStats.Entry> entries = new ArrayList();
for (NetworkStats.Entry e : stats) {
- ret = ret.addEntry(slicer.apply(e));
+ entries.add(slicer.apply(e));
}
- return ret;
+ if (isAddEntriesSupported()) {
+ return new NetworkStats(0, entries.size()).addEntries(entries);
+ } else {
+ NetworkStats outputStats = new NetworkStats(0L, 1);
+ for (NetworkStats.Entry e : entries) {
+ outputStats = outputStats.addEntry(e);
+ }
+ return outputStats;
+ }
}
private void registerWifiBytesTransferBackground() {
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
index de58852..0318bdd 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
@@ -24,6 +24,9 @@
import android.app.usage.NetworkStats;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.stats.Flags;
+
+import java.util.ArrayList;
/**
* Utility methods for accessing {@link android.net.NetworkStats}.
@@ -35,12 +38,21 @@
*/
public static android.net.NetworkStats fromPublicNetworkStats(
NetworkStats publiceNetworkStats) {
- android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+ final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
while (publiceNetworkStats.hasNextBucket()) {
NetworkStats.Bucket bucket = new NetworkStats.Bucket();
publiceNetworkStats.getNextBucket(bucket);
- final android.net.NetworkStats.Entry entry = fromBucket(bucket);
- stats = stats.addEntry(entry);
+ entries.add(fromBucket(bucket));
+ }
+ android.net.NetworkStats stats = new android.net.NetworkStats(0L, 1);
+ // The new API is only supported on devices running the mainline version of `NetworkStats`.
+ // It should always be used when available for memory efficiency.
+ if (isAddEntriesSupported()) {
+ stats = stats.addEntries(entries);
+ } else {
+ for (android.net.NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
}
return stats;
}
@@ -106,4 +118,8 @@
}
return 0;
}
+
+ public static boolean isAddEntriesSupported() {
+ return Flags.netstatsUseAddEntries();
+ }
}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index f360837..afea303 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,6 +1,20 @@
package: "com.android.server.stats"
container: "system"
+# Note: To ensure compatibility across all release configurations, initiate the ramp-up process
+# only after the 'com.android.net.flags.netstats_add_entries' flag has been fully deployed.
+# This flag provides the necessary API from the Connectivity module.
+# The flag was added because the flag 'com.android.net.flags.netstats_add_entries' for API
+# is already being rolled out, and modifying behavior during an active rollout might
+# lead to unwanted issues.
+flag {
+ name: "netstats_use_add_entries"
+ namespace: "statsd"
+ description: "Use NetworkStats#addEntries to reduce memory footprint"
+ bug: "335680025"
+ is_fixed_read_only: true
+}
+
flag {
name: "add_mobile_bytes_transfer_by_proc_state_puller"
namespace: "statsd"
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c1e859d..e27b2be 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -864,9 +864,8 @@
if (transition != null) {
if (changed) {
// Always set as scene transition because it expects to be a jump-cut.
- transition.setOverrideAnimation(
- TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), r,
- null, null);
+ transition.setOverrideAnimation(TransitionInfo.AnimationOptions
+ .makeSceneTransitionAnimOptions(), null, null);
r.mTransitionController.requestStartTransition(transition,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
@@ -911,9 +910,8 @@
&& under.returningOptions.getAnimationType()
== ANIM_SCENE_TRANSITION) {
// Pass along the scene-transition animation-type
- transition.setOverrideAnimation(TransitionInfo
- .AnimationOptions.makeSceneTransitionAnimOptions(), r,
- null, null);
+ transition.setOverrideAnimation(TransitionInfo.AnimationOptions
+ .makeSceneTransitionAnimOptions(), null, null);
}
} else {
transition.abort();
@@ -1510,7 +1508,7 @@
r.mOverrideTaskTransition);
r.mTransitionController.setOverrideAnimation(
TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
- enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition), r,
+ enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition),
null /* startCallback */, null /* finishCallback */);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index aeb5de5..47d4740 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -226,7 +226,6 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
-import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -234,7 +233,6 @@
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
import static com.android.server.wm.TaskPersister.DEBUG;
@@ -355,7 +353,6 @@
import android.window.SplashScreenView;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
-import android.window.TransitionInfo;
import android.window.TransitionInfo.AnimationOptions;
import android.window.WindowContainerToken;
import android.window.WindowOnBackInvokedDispatcher;
@@ -638,12 +635,6 @@
private SizeConfigurationBuckets mSizeConfigurations;
- /**
- * The precomputed display insets for resolving configuration. It will be non-null if
- * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
- */
- private AppCompatDisplayInsets mAppCompatDisplayInsets;
-
@VisibleForTesting
final TaskFragment.ConfigOverrideHint mResolveConfigHint;
@@ -793,22 +784,6 @@
@NonNull
final AppCompatController mAppCompatController;
- /**
- * The scale to fit at least one side of the activity to its parent. If the activity uses
- * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5.
- */
- private float mSizeCompatScale = 1f;
-
- /**
- * The bounds in global coordinates for activity in size compatibility mode.
- * @see ActivityRecord#hasSizeCompatBounds()
- */
- private Rect mSizeCompatBounds;
-
- // Whether this activity is in size compatibility mode because its bounds don't fit in parent
- // naturally.
- private boolean mInSizeCompatModeForBounds = false;
-
// Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
// requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -1258,10 +1233,6 @@
if (mPendingRelaunchCount != 0) {
pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
}
- if (mSizeCompatScale != 1f || mSizeCompatBounds != null) {
- pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds="
- + mSizeCompatBounds);
- }
if (mRemovingFromDisplay) {
pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
}
@@ -4970,9 +4941,8 @@
newIntents.add(intent);
}
- final boolean isSleeping() {
- final Task rootTask = getRootTask();
- return rootTask != null ? rootTask.shouldSleepActivities() : mAtmService.isSleepingLocked();
+ boolean isSleeping() {
+ return task != null ? task.shouldSleepActivities() : mAtmService.isSleepingLocked();
}
/**
@@ -4996,7 +4966,7 @@
final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
callerToken);
boolean unsent = true;
- final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
+ final boolean isTopActivityWhileSleeping = isSleeping() && isTopRunningActivity();
// We want to immediately deliver the intent to the activity if:
// - It is currently resumed or paused. i.e. it is currently visible to the user and we want
@@ -5063,8 +5033,7 @@
// controller but don't clear the animation information from the options since they
// need to be sent to the animating activity.
mTransitionController.setOverrideAnimation(
- TransitionInfo.AnimationOptions.makeSceneTransitionAnimOptions(), this,
- null, null);
+ AnimationOptions.makeSceneTransitionAnimOptions(), null, null);
return;
}
applyOptionsAnimation(mPendingOptions, intent);
@@ -5187,8 +5156,7 @@
}
if (options != null) {
- mTransitionController.setOverrideAnimation(options, this, startCallback,
- finishCallback);
+ mTransitionController.setOverrideAnimation(options, startCallback, finishCallback);
}
}
@@ -5573,10 +5541,7 @@
return false;
}
if (!mDisplayContent.mAppTransition.isTransitionSet()) {
- // Defer committing visibility for non-home app which is animating by recents.
- if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
- return false;
- }
+ return false;
}
if (mWaitForEnteringPinnedMode && mVisible == visible) {
// If the visibility is not changed during enter PIP, we don't want to include it in
@@ -5730,8 +5695,7 @@
private void postApplyAnimation(boolean visible, boolean fromTransition) {
final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
- | ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
if (!delayed && !usingShellTransitions) {
// We aren't delayed anything, but exiting windows rely on the animation finished
// callback being called in case the ActivityRecord was pretending to be delayed,
@@ -5753,7 +5717,7 @@
// animation and aren't in RESUMED state. Otherwise, we'll update client visibility in
// onAnimationFinished or activityStopped.
if (visible || (mState != RESUMED && (usingShellTransitions || !isAnimating(
- PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)))) {
+ PARENTS, ANIMATION_TYPE_APP_TRANSITION)))) {
setClientVisible(visible);
}
@@ -5865,7 +5829,7 @@
void setState(State state, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
- this, getState(), state, reason);
+ this, mState, state, reason);
if (state == mState) {
// No need to do anything if state doesn't change.
@@ -6190,7 +6154,7 @@
// Now for any activities that aren't visible to the user, make sure they no longer are
// keeping the screen frozen.
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState());
+ Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + mState);
}
try {
final boolean canEnterPictureInPicture = checkEnterPictureInPictureState(
@@ -6206,7 +6170,7 @@
}
setVisibility(false);
- switch (getState()) {
+ switch (mState) {
case STOPPING:
case STOPPED:
// Reset the flag indicating that an app can enter picture-in-picture once the
@@ -6442,7 +6406,7 @@
mTaskSupervisor.mStoppingActivities.remove(this);
if (getDisplayArea().allResumedActivitiesComplete()) {
// Construct the compat environment at a relatively stable state if needed.
- updateAppCompatDisplayInsets();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
mRootWindowContainer.executeAppTransitionForAllDisplay();
}
@@ -7731,7 +7695,7 @@
// Ensure that the activity content is hidden when the decor surface is boosted to
// prevent UI redressing attack.
&& !isDecorSurfaceBoosted)
- || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
@@ -8075,7 +8039,7 @@
!= getRequestedConfigurationOrientation(false /*forDisplay */)) {
// Do not change the requested configuration now, because this will be done when setting
// the orientation below with the new mAppCompatDisplayInsets
- clearSizeCompatModeAttributes();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatModeAttributes();
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Setting requested orientation %s for %s",
@@ -8208,18 +8172,7 @@
@Nullable
AppCompatDisplayInsets getAppCompatDisplayInsets() {
- if (mAppCompatController.getTransparentPolicy().isRunning()) {
- return mAppCompatController.getTransparentPolicy().getInheritedAppCompatDisplayInsets();
- }
- return mAppCompatDisplayInsets;
- }
-
- /**
- * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
- * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
- */
- boolean hasAppCompatDisplayInsetsWithoutInheritance() {
- return mAppCompatDisplayInsets != null;
+ return mAppCompatController.getAppCompatSizeCompatModePolicy().getAppCompatDisplayInsets();
}
/**
@@ -8227,7 +8180,9 @@
* density than its parent or its bounds don't fit in parent naturally.
*/
boolean inSizeCompatMode() {
- if (mInSizeCompatModeForBounds) {
+ final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
+ if (scmPolicy.isInSizeCompatModeForBounds()) {
return true;
}
if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets()
@@ -8324,69 +8279,7 @@
@Override
boolean hasSizeCompatBounds() {
- return mSizeCompatBounds != null;
- }
-
- // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
- private void updateAppCompatDisplayInsets() {
- if (getAppCompatDisplayInsets() != null || !shouldCreateAppCompatDisplayInsets()) {
- // The override configuration is set only once in size compatibility mode.
- return;
- }
-
- Configuration overrideConfig = getRequestedOverrideConfiguration();
- final Configuration fullConfig = getConfiguration();
-
- // Ensure the screen related fields are set. It is used to prevent activity relaunch
- // when moving between displays. For screenWidthDp and screenWidthDp, because they
- // are relative to bounds and density, they will be calculated in
- // {@link Task#computeConfigResourceOverrides} and the result will also be
- // relatively fixed.
- overrideConfig.colorMode = fullConfig.colorMode;
- overrideConfig.densityDpi = fullConfig.densityDpi;
- // The smallest screen width is the short side of screen bounds. Because the bounds
- // and density won't be changed, smallestScreenWidthDp is also fixed.
- overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
- if (ActivityInfo.isFixedOrientation(getOverrideOrientation())) {
- // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
- // apply runtime rotation changes.
- overrideConfig.windowConfiguration.setRotation(
- fullConfig.windowConfiguration.getRotation());
- }
-
- final Rect letterboxedContainerBounds = mAppCompatController
- .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
-
- // The role of AppCompatDisplayInsets is like the override bounds.
- mAppCompatDisplayInsets =
- new AppCompatDisplayInsets(
- mDisplayContent, this, letterboxedContainerBounds,
- mResolveConfigHint.mUseOverrideInsetsForConfig);
- }
-
- private void clearSizeCompatModeAttributes() {
- mInSizeCompatModeForBounds = false;
- final float lastSizeCompatScale = mSizeCompatScale;
- mSizeCompatScale = 1f;
- if (mSizeCompatScale != lastSizeCompatScale) {
- forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
- }
- mSizeCompatBounds = null;
- mAppCompatDisplayInsets = null;
- mAppCompatController.getTransparentPolicy().clearInheritedAppCompatDisplayInsets();
- }
-
- @VisibleForTesting
- void clearSizeCompatMode() {
- clearSizeCompatModeAttributes();
- // Clear config override in #updateAppCompatDisplayInsets().
- final int activityType = getActivityType();
- final Configuration overrideConfig = getRequestedOverrideConfiguration();
- overrideConfig.unset();
- // Keep the activity type which was set when attaching to a task to prevent leaving it
- // undefined.
- overrideConfig.windowConfiguration.setActivityType(activityType);
- onRequestedOverrideConfigurationChanged(overrideConfig);
+ return mAppCompatController.getAppCompatSizeCompatModePolicy().hasSizeCompatBounds();
}
@Override
@@ -8404,7 +8297,9 @@
@Override
float getCompatScale() {
- return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
+ // We need to invoke {#getCompatScale()} only if the CompatScale is not available.
+ return mAppCompatController.getAppCompatSizeCompatModePolicy()
+ .getCompatScaleIfAvailable(ActivityRecord.super::getCompatScale);
}
@Override
@@ -8472,8 +8367,11 @@
resolveAspectRatioRestriction(newParentConfiguration);
}
final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
if (appCompatDisplayInsets != null) {
- resolveSizeCompatModeConfiguration(newParentConfiguration, appCompatDisplayInsets);
+ scmPolicy.resolveSizeCompatModeConfiguration(newParentConfiguration,
+ appCompatDisplayInsets, mTmpBounds);
} else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
// We ignore activities' requested orientation in multi-window modes. They may be
// taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8491,13 +8389,14 @@
if (!Flags.immersiveAppRepositioning()
&& !mAppCompatController.getAppCompatAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio()
- && !mInSizeCompatModeForBounds
+ && !scmPolicy.isInSizeCompatModeForBounds()
&& !mAppCompatController.getAppCompatAspectRatioOverrides()
.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
- if (isFixedOrientationLetterboxAllowed || mAppCompatDisplayInsets != null
+ if (isFixedOrientationLetterboxAllowed
+ || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
// In fullscreen, can be letterboxed for aspect ratio.
|| !inMultiWindowMode()) {
updateResolvedBoundsPosition(newParentConfiguration);
@@ -8505,7 +8404,7 @@
boolean isIgnoreOrientationRequest = mDisplayContent != null
&& mDisplayContent.getIgnoreOrientationRequest();
- if (mAppCompatDisplayInsets == null
+ if (!scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
// for size compat mode set in updateAppCompatDisplayInsets
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
@@ -8554,7 +8453,7 @@
info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
!matchParentBounds(),
- mAppCompatDisplayInsets != null,
+ scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance(),
shouldCreateAppCompatDisplayInsets());
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8578,7 +8477,7 @@
return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride);
}
- private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
+ void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@NonNull Configuration parentConfig) {
task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
// Reset the temp info which should only take effect for the specified computation.
@@ -8630,7 +8529,9 @@
if (mAppCompatController.getTransparentPolicy().isRunning()) {
return mAppCompatController.getTransparentPolicy().getInheritedAppCompatState();
}
- if (mInSizeCompatModeForBounds) {
+ final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
+ if (scmPolicy.isInSizeCompatModeForBounds()) {
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
}
// Letterbox for fixed orientation. This check returns true only when an activity is
@@ -8666,8 +8567,9 @@
if (resolvedBounds.isEmpty()) {
return;
}
- final Rect screenResolvedBounds =
- mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
+ final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
final float screenResolvedBoundsWidth = screenResolvedBounds.width();
@@ -8723,14 +8625,10 @@
- screenResolvedBounds.top + parentAppBounds.top);
}
}
-
- if (mSizeCompatBounds != null) {
- mSizeCompatBounds.offset(offsetX , offsetY);
- final int dy = mSizeCompatBounds.top - resolvedBounds.top;
- final int dx = mSizeCompatBounds.left - resolvedBounds.left;
- offsetBounds(resolvedConfig, dx, dy);
- } else {
- offsetBounds(resolvedConfig, offsetX, offsetY);
+ // If in SCM, apply offset to resolved bounds relative to size compat bounds. If
+ // not, apply directly to resolved bounds.
+ if (!scmPolicy.applyOffsetIfNeeded(resolvedBounds, resolvedConfig, offsetX, offsetY)) {
+ AppCompatUtils.offsetBounds(resolvedConfig, offsetX, offsetY);
}
// If the top is aligned with parentAppBounds add the vertical insets back so that the app
@@ -8738,9 +8636,7 @@
if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top
&& !isImmersiveMode) {
resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top;
- if (mSizeCompatBounds != null) {
- mSizeCompatBounds.top = parentBounds.top;
- }
+ scmPolicy.alignToTopIfNeeded(parentBounds);
}
// Since bounds has changed, the configuration needs to be computed accordingly.
@@ -8750,13 +8646,7 @@
// easier to resolve the relative position in parent container. However, if the activity is
// scaled, the position should follow the scale because the configuration will be sent to
// the client which is expected to be in a scaled environment.
- if (mSizeCompatScale != 1f) {
- final int screenPosX = resolvedBounds.left;
- final int screenPosY = resolvedBounds.top;
- final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
- final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
- offsetBounds(resolvedConfig, dx, dy);
- }
+ scmPolicy.applySizeCompatScaleIfNeeded(resolvedBounds, resolvedConfig);
}
boolean isImmersiveMode(@NonNull Rect parentBounds) {
@@ -8778,7 +8668,9 @@
@NonNull Rect getScreenResolvedBounds() {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
- return mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
+ return scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
}
void recomputeConfiguration() {
@@ -8930,8 +8822,10 @@
return;
}
final AppCompatDisplayInsets mAppCompatDisplayInsets = getAppCompatDisplayInsets();
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
- if (mAppCompatDisplayInsets != null
+ if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
&& !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
@@ -8984,7 +8878,7 @@
.applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
containingBoundsWithInsets, containingBounds);
- if (mAppCompatDisplayInsets != null) {
+ if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) {
mAppCompatDisplayInsets.getBoundsByRotation(mTmpBounds,
newParentConfig.windowConfiguration.getRotation());
if (resolvedBounds.width() != mTmpBounds.width()
@@ -9045,246 +8939,15 @@
}
}
- /**
- * Resolves consistent screen configuration for orientation and rotation changes without
- * inheriting the parent bounds.
- */
- private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
- @NonNull AppCompatDisplayInsets appCompatDisplayInsets) {
- final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
-
- // When an activity needs to be letterboxed because of fixed orientation, use fixed
- // orientation bounds (stored in resolved bounds) instead of parent bounds since the
- // activity will be displayed within them even if it is in size compat mode. They should be
- // saved here before resolved bounds are overridden below.
- final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
- ? mAppCompatController.getAppCompatAspectRatioPolicy()
- .isAspectRatioApplied()
- : mAppCompatController.getAppCompatAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio();
- final Rect containerBounds = useResolvedBounds
- ? new Rect(resolvedBounds)
- : newParentConfiguration.windowConfiguration.getBounds();
- final Rect containerAppBounds = useResolvedBounds
- ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
- : mResolveConfigHint.mParentAppBoundsOverride;
-
- final int requestedOrientation = getRequestedConfigurationOrientation();
- final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
- final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig
- ? mResolveConfigHint.mTmpOverrideConfigOrientation
- : newParentConfiguration.orientation;
- final int orientation = orientationRequested
- ? requestedOrientation
- // We should use the original orientation of the activity when possible to avoid
- // forcing the activity in the opposite orientation.
- : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
- ? appCompatDisplayInsets.mOriginalRequestedOrientation
- : parentOrientation;
- int rotation = newParentConfiguration.windowConfiguration.getRotation();
- final boolean isFixedToUserRotation = mDisplayContent == null
- || mDisplayContent.getDisplayRotation().isFixedToUserRotation();
- if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) {
- // Use parent rotation because the original display can be rotated.
- resolvedConfig.windowConfiguration.setRotation(rotation);
- } else {
- final int overrideRotation = resolvedConfig.windowConfiguration.getRotation();
- if (overrideRotation != ROTATION_UNDEFINED) {
- rotation = overrideRotation;
- }
- }
-
- // Use compat insets to lock width and height. We should not use the parent width and height
- // because apps in compat mode should have a constant width and height. The compat insets
- // are locked when the app is first launched and are never changed after that, so we can
- // rely on them to contain the original and unchanging width and height of the app.
- final Rect containingAppBounds = new Rect();
- final Rect containingBounds = mTmpBounds;
- appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
- orientation, orientationRequested, isFixedToUserRotation);
- resolvedBounds.set(containingBounds);
- // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
- if (!appCompatDisplayInsets.mIsFloating) {
- mAppCompatController.getAppCompatAspectRatioPolicy()
- .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
- containingBounds);
- }
-
- // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
- // are calculated in compat container space. The actual position on screen will be applied
- // later, so the calculation is simpler that doesn't need to involve offset from parent.
- mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
- computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
- // Use current screen layout as source because the size of app is independent to parent.
- resolvedConfig.screenLayout = computeScreenLayout(
- getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
- resolvedConfig.screenHeightDp);
-
- // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
- // the parent bounds appropriately.
- if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
- resolvedConfig.orientation = parentOrientation;
- }
-
- // Below figure is an example that puts an activity which was launched in a larger container
- // into a smaller container.
- // The outermost rectangle is the real display bounds.
- // "@" is the container app bounds (parent bounds or fixed orientation bounds)
- // "#" is the {@code resolvedBounds} that applies to application.
- // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
- // ------------------------------
- // | |
- // | @@@@*********@@@@### |
- // | @ * * @ # |
- // | @ * * @ # |
- // | @ * * @ # |
- // | @@@@*********@@@@ # |
- // ---------#--------------#-----
- // # #
- // ################
- // The application is still layouted in "#" since it was launched, and it will be visually
- // scaled and positioned to "*".
-
- final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
-
- // Calculates the scale the size compatibility bounds into the region which is available
- // to application.
- final float lastSizeCompatScale = mSizeCompatScale;
- updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
-
- final int containerTopInset = containerAppBounds.top - containerBounds.top;
- final boolean topNotAligned =
- containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
- if (mSizeCompatScale != 1f || topNotAligned) {
- if (mSizeCompatBounds == null) {
- mSizeCompatBounds = new Rect();
- }
- mSizeCompatBounds.set(resolvedAppBounds);
- mSizeCompatBounds.offsetTo(0, 0);
- mSizeCompatBounds.scale(mSizeCompatScale);
- // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
- mSizeCompatBounds.bottom += containerTopInset;
- } else {
- mSizeCompatBounds = null;
- }
- if (mSizeCompatScale != lastSizeCompatScale) {
- forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
- }
-
- // The position will be later adjusted in updateResolvedBoundsPosition.
- // Above coordinates are in "@" space, now place "*" and "#" to screen space.
- final boolean fillContainer = resolvedBounds.equals(containingBounds);
- final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
- final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
-
- if (screenPosX != 0 || screenPosY != 0) {
- if (mSizeCompatBounds != null) {
- mSizeCompatBounds.offset(screenPosX, screenPosY);
- }
- // Add the global coordinates and remove the local coordinates.
- final int dx = screenPosX - resolvedBounds.left;
- final int dy = screenPosY - resolvedBounds.top;
- offsetBounds(resolvedConfig, dx, dy);
- }
-
- mInSizeCompatModeForBounds =
- isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds);
- }
-
- void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
- mSizeCompatScale = mAppCompatController.getTransparentPolicy()
- .findOpaqueNotFinishingActivityBelow()
- .map(activityRecord -> activityRecord.mSizeCompatScale)
- .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
- }
-
- private float calculateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
- final int contentW = resolvedAppBounds.width();
- final int contentH = resolvedAppBounds.height();
- final int viewportW = containerAppBounds.width();
- final int viewportH = containerAppBounds.height();
- // Allow an application to be up-scaled if its window is smaller than its
- // original container or if it's a freeform window in desktop mode.
- boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
- || (canEnterDesktopMode(mAtmService.mContext)
- && getWindowingMode() == WINDOWING_MODE_FREEFORM);
- return shouldAllowUpscaling ? Math.min(
- (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
- }
-
- private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
- if (mAppCompatController.getTransparentPolicy().isRunning()) {
- // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
- // is letterboxed.
- return false;
- }
- final int appWidth = appBounds.width();
- final int appHeight = appBounds.height();
- final int containerAppWidth = containerBounds.width();
- final int containerAppHeight = containerBounds.height();
-
- if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
- // Matched the container bounds.
- return false;
- }
- if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
- // Both sides are smaller than the container.
- return true;
- }
- if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
- // One side is larger than the container.
- return true;
- }
-
- // The rest of the condition is that only one side is smaller than the container, but it
- // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
- final float maxAspectRatio = getMaxAspectRatio();
- if (maxAspectRatio > 0) {
- final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
- / Math.min(appWidth, appHeight);
- if (aspectRatio >= maxAspectRatio) {
- // The current size has reached the max aspect ratio.
- return false;
- }
- }
- final float minAspectRatio = getMinAspectRatio();
- if (minAspectRatio > 0) {
- // The activity should have at least the min aspect ratio, so this checks if the
- // container still has available space to provide larger aspect ratio.
- final float containerAspectRatio =
- (0.5f + Math.max(containerAppWidth, containerAppHeight))
- / Math.min(containerAppWidth, containerAppHeight);
- if (containerAspectRatio <= minAspectRatio) {
- // The long side has reached the parent.
- return false;
- }
- }
- return true;
- }
-
- /** @return The horizontal / vertical offset of putting the content in the center of viewport.*/
- private static int getCenterOffset(int viewportDim, int contentDim) {
- return (int) ((viewportDim - contentDim + 1) * 0.5f);
- }
-
- private static void offsetBounds(Configuration inOutConfig, int offsetX, int offsetY) {
- inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY);
- inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY);
- }
-
@Override
public Rect getBounds() {
// TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
final Rect superBounds = super.getBounds();
+ final AppCompatSizeCompatModePolicy scmPolicy =
+ mAppCompatController.getAppCompatSizeCompatModePolicy();
return mAppCompatController.getTransparentPolicy().findOpaqueNotFinishingActivityBelow()
.map(ActivityRecord::getBounds)
- .orElseGet(() -> {
- if (mSizeCompatBounds != null) {
- return mSizeCompatBounds;
- }
- return superBounds;
- });
+ .orElseGet(() -> scmPolicy.getAppSizeCompatBoundsIfAvailable(superBounds));
}
@Override
@@ -9615,7 +9278,7 @@
if (mVisibleRequested) {
// Calling from here rather than resolveOverrideConfiguration to ensure that this is
// called after full config is updated in ConfigurationContainer#onConfigurationChanged.
- updateAppCompatDisplayInsets();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
}
// Short circuit: if the two full configurations are equal (the common case), then there is
@@ -9955,7 +9618,7 @@
// Reset the existing override configuration so it can be updated according to the latest
// configuration.
- clearSizeCompatMode();
+ mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
if (!attachedToProcess()) {
return;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2d8e3db..0f108c5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3617,6 +3617,11 @@
final long token = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
+ boolean isPowerModePreApplied = false;
+ if (mPowerModeReasons == 0) {
+ startPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+ isPowerModePreApplied = true;
+ }
// Keyguard asked us to clear the home task snapshot before going away, so do that.
if ((flags & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
mActivityClientController.invalidateHomeTaskSnapshot(null /* token */);
@@ -3625,9 +3630,19 @@
mDemoteTopAppReasons |= DEMOTE_TOP_REASON_DURING_UNLOCKING;
}
- mRootWindowContainer.forAllDisplays(displayContent -> {
- mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
- });
+ boolean foundResumed = false;
+ for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+ final DisplayContent dc = mRootWindowContainer.getChildAt(i);
+ final boolean wasNoResumed = dc.mFocusedApp == null
+ || !dc.mFocusedApp.isState(RESUMED);
+ mKeyguardController.keyguardGoingAway(dc.mDisplayId, flags);
+ if (wasNoResumed && dc.mFocusedApp != null && dc.mFocusedApp.isState(RESUMED)) {
+ foundResumed = true;
+ }
+ }
+ if (isPowerModePreApplied && !foundResumed) {
+ endPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+ }
}
WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
if (wallpaperManagerInternal != null) {
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 3c3b773..173362c 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -46,6 +46,8 @@
private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
@NonNull
private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
+ @NonNull
+ private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -67,6 +69,8 @@
wmService.mAppCompatConfiguration);
mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
+ mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
+ mAppCompatOverrides);
}
@NonNull
@@ -152,9 +156,15 @@
return mAppCompatOverrides.getAppCompatLetterboxOverrides();
}
+ @NonNull
+ AppCompatSizeCompatModePolicy getAppCompatSizeCompatModePolicy() {
+ return mAppCompatSizeCompatModePolicy;
+ }
+
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
getTransparentPolicy().dump(pw, prefix);
getAppCompatLetterboxPolicy().dump(pw, prefix);
+ getAppCompatSizeCompatModePolicy().dump(pw, prefix);
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
new file mode 100644
index 0000000..3be266e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.window.flags.Flags;
+
+import java.io.PrintWriter;
+import java.util.function.DoubleSupplier;
+
+/**
+ * Encapsulate logic related to the SizeCompatMode.
+ */
+class AppCompatSizeCompatModePolicy {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatOverrides mAppCompatOverrides;
+
+ // Whether this activity is in size compatibility mode because its bounds don't fit in parent
+ // naturally.
+ private boolean mInSizeCompatModeForBounds = false;
+ /**
+ * The scale to fit at least one side of the activity to its parent. If the activity uses
+ * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5.
+ */
+ private float mSizeCompatScale = 1f;
+
+ /**
+ * The bounds in global coordinates for activity in size compatibility mode.
+ * @see #hasSizeCompatBounds()
+ */
+ private Rect mSizeCompatBounds;
+
+ /**
+ * The precomputed display insets for resolving configuration. It will be non-null if
+ * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
+ */
+ @Nullable
+ private AppCompatDisplayInsets mAppCompatDisplayInsets;
+
+ AppCompatSizeCompatModePolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatOverrides appCompatOverrides) {
+ mActivityRecord = activityRecord;
+ mAppCompatOverrides = appCompatOverrides;
+ }
+
+ boolean isInSizeCompatModeForBounds() {
+ return mInSizeCompatModeForBounds;
+ }
+
+ void setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds) {
+ mInSizeCompatModeForBounds = inSizeCompatModeForBounds;
+ }
+
+ boolean hasSizeCompatBounds() {
+ return mSizeCompatBounds != null;
+ }
+
+ /**
+ * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
+ * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
+ */
+ boolean hasAppCompatDisplayInsetsWithoutInheritance() {
+ return mAppCompatDisplayInsets != null;
+ }
+
+ @Nullable
+ AppCompatDisplayInsets getAppCompatDisplayInsets() {
+ final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController
+ .getTransparentPolicy();
+ if (transparentPolicy.isRunning()) {
+ return transparentPolicy.getInheritedAppCompatDisplayInsets();
+ }
+ return mAppCompatDisplayInsets;
+ }
+
+ float getCompatScaleIfAvailable(@NonNull DoubleSupplier scaleWhenNotAvailable) {
+ return hasSizeCompatBounds() ? mSizeCompatScale
+ : (float) scaleWhenNotAvailable.getAsDouble();
+ }
+
+ @NonNull
+ Rect getAppSizeCompatBoundsIfAvailable(@NonNull Rect boundsWhenNotAvailable) {
+ return hasSizeCompatBounds() ? mSizeCompatBounds : boundsWhenNotAvailable;
+ }
+
+ @NonNull
+ Rect replaceResolvedBoundsIfNeeded(@NonNull Rect resolvedBounds) {
+ return hasSizeCompatBounds() ? mSizeCompatBounds : resolvedBounds;
+ }
+
+ boolean applyOffsetIfNeeded(@NonNull Rect resolvedBounds,
+ @NonNull Configuration resolvedConfig, int offsetX, int offsetY) {
+ if (hasSizeCompatBounds()) {
+ mSizeCompatBounds.offset(offsetX , offsetY);
+ final int dy = mSizeCompatBounds.top - resolvedBounds.top;
+ final int dx = mSizeCompatBounds.left - resolvedBounds.left;
+ AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+ return true;
+ }
+ return false;
+ }
+
+ void alignToTopIfNeeded(@NonNull Rect parentBounds) {
+ if (hasSizeCompatBounds()) {
+ mSizeCompatBounds.top = parentBounds.top;
+ }
+ }
+
+ void applySizeCompatScaleIfNeeded(@NonNull Rect resolvedBounds,
+ @NonNull Configuration resolvedConfig) {
+ if (mSizeCompatScale != 1f) {
+ final int screenPosX = resolvedBounds.left;
+ final int screenPosY = resolvedBounds.top;
+ final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
+ final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
+ AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+ }
+ }
+
+ void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) {
+ mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
+ .findOpaqueNotFinishingActivityBelow()
+ .map(activityRecord -> mSizeCompatScale)
+ .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+ }
+
+ void clearSizeCompatModeAttributes() {
+ mInSizeCompatModeForBounds = false;
+ final float lastSizeCompatScale = mSizeCompatScale;
+ mSizeCompatScale = 1f;
+ if (mSizeCompatScale != lastSizeCompatScale) {
+ mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
+ false /* traverseTopToBottom */);
+ }
+ mSizeCompatBounds = null;
+ mAppCompatDisplayInsets = null;
+ mActivityRecord.mAppCompatController.getTransparentPolicy()
+ .clearInheritedAppCompatDisplayInsets();
+ }
+
+ void clearSizeCompatMode() {
+ clearSizeCompatModeAttributes();
+ // Clear config override in #updateAppCompatDisplayInsets().
+ final int activityType = mActivityRecord.getActivityType();
+ final Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
+ overrideConfig.unset();
+ // Keep the activity type which was set when attaching to a task to prevent leaving it
+ // undefined.
+ overrideConfig.windowConfiguration.setActivityType(activityType);
+ mActivityRecord.onRequestedOverrideConfigurationChanged(overrideConfig);
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ if (mSizeCompatScale != 1f || hasSizeCompatBounds()) {
+ pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds="
+ + mSizeCompatBounds);
+ }
+ }
+
+ /**
+ * Resolves consistent screen configuration for orientation and rotation changes without
+ * inheriting the parent bounds.
+ */
+ void resolveSizeCompatModeConfiguration(@NonNull Configuration newParentConfiguration,
+ @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds) {
+ final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
+ final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+
+ // When an activity needs to be letterboxed because of fixed orientation, use fixed
+ // orientation bounds (stored in resolved bounds) instead of parent bounds since the
+ // activity will be displayed within them even if it is in size compat mode. They should be
+ // saved here before resolved bounds are overridden below.
+ final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
+ .getAppCompatAspectRatioPolicy();
+ final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
+ ? aspectRatioPolicy.isAspectRatioApplied()
+ : aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio();
+ final Rect containerBounds = useResolvedBounds
+ ? new Rect(resolvedBounds)
+ : newParentConfiguration.windowConfiguration.getBounds();
+ final Rect containerAppBounds = useResolvedBounds
+ ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
+ : mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
+
+ final int requestedOrientation = mActivityRecord.getRequestedConfigurationOrientation();
+ final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
+ final int parentOrientation = mActivityRecord.mResolveConfigHint.mUseOverrideInsetsForConfig
+ ? mActivityRecord.mResolveConfigHint.mTmpOverrideConfigOrientation
+ : newParentConfiguration.orientation;
+ final int orientation = orientationRequested
+ ? requestedOrientation
+ // We should use the original orientation of the activity when possible to avoid
+ // forcing the activity in the opposite orientation.
+ : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+ ? appCompatDisplayInsets.mOriginalRequestedOrientation
+ : parentOrientation;
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ final boolean isFixedToUserRotation = mActivityRecord.mDisplayContent == null
+ || mActivityRecord.mDisplayContent.getDisplayRotation().isFixedToUserRotation();
+ if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) {
+ // Use parent rotation because the original display can be rotated.
+ resolvedConfig.windowConfiguration.setRotation(rotation);
+ } else {
+ final int overrideRotation = resolvedConfig.windowConfiguration.getRotation();
+ if (overrideRotation != ROTATION_UNDEFINED) {
+ rotation = overrideRotation;
+ }
+ }
+
+ // Use compat insets to lock width and height. We should not use the parent width and height
+ // because apps in compat mode should have a constant width and height. The compat insets
+ // are locked when the app is first launched and are never changed after that, so we can
+ // rely on them to contain the original and unchanging width and height of the app.
+ final Rect containingAppBounds = new Rect();
+ final Rect containingBounds = tmpBounds;
+ appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
+ orientation, orientationRequested, isFixedToUserRotation);
+ resolvedBounds.set(containingBounds);
+ // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
+ if (!appCompatDisplayInsets.mIsFloating) {
+ mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+ .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
+ containingBounds);
+ }
+
+ // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
+ // are calculated in compat container space. The actual position on screen will be applied
+ // later, so the calculation is simpler that doesn't need to involve offset from parent.
+ mActivityRecord.mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
+ mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+ // Use current screen layout as source because the size of app is independent to parent.
+ resolvedConfig.screenLayout = ActivityRecord.computeScreenLayout(
+ mActivityRecord.getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
+ resolvedConfig.screenHeightDp);
+
+ // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
+ // the parent bounds appropriately.
+ if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
+ resolvedConfig.orientation = parentOrientation;
+ }
+
+ // Below figure is an example that puts an activity which was launched in a larger container
+ // into a smaller container.
+ // The outermost rectangle is the real display bounds.
+ // "@" is the container app bounds (parent bounds or fixed orientation bounds)
+ // "#" is the {@code resolvedBounds} that applies to application.
+ // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
+ // ------------------------------
+ // | |
+ // | @@@@*********@@@@### |
+ // | @ * * @ # |
+ // | @ * * @ # |
+ // | @ * * @ # |
+ // | @@@@*********@@@@ # |
+ // ---------#--------------#-----
+ // # #
+ // ################
+ // The application is still layouted in "#" since it was launched, and it will be visually
+ // scaled and positioned to "*".
+
+ final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+ // Calculates the scale the size compatibility bounds into the region which is available
+ // to application.
+ final float lastSizeCompatScale = mSizeCompatScale;
+ updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
+
+ final int containerTopInset = containerAppBounds.top - containerBounds.top;
+ final boolean topNotAligned =
+ containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
+ if (mSizeCompatScale != 1f || topNotAligned) {
+ if (mSizeCompatBounds == null) {
+ mSizeCompatBounds = new Rect();
+ }
+ mSizeCompatBounds.set(resolvedAppBounds);
+ mSizeCompatBounds.offsetTo(0, 0);
+ mSizeCompatBounds.scale(mSizeCompatScale);
+ // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
+ mSizeCompatBounds.bottom += containerTopInset;
+ } else {
+ mSizeCompatBounds = null;
+ }
+ if (mSizeCompatScale != lastSizeCompatScale) {
+ mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
+ false /* traverseTopToBottom */);
+ }
+
+ // The position will be later adjusted in updateResolvedBoundsPosition.
+ // Above coordinates are in "@" space, now place "*" and "#" to screen space.
+ final boolean fillContainer = resolvedBounds.equals(containingBounds);
+ final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
+ final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
+
+ if (screenPosX != 0 || screenPosY != 0) {
+ if (hasSizeCompatBounds()) {
+ mSizeCompatBounds.offset(screenPosX, screenPosY);
+ }
+ // Add the global coordinates and remove the local coordinates.
+ final int dx = screenPosX - resolvedBounds.left;
+ final int dy = screenPosY - resolvedBounds.top;
+ AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+ }
+
+ mInSizeCompatModeForBounds = isInSizeCompatModeForBounds(resolvedAppBounds,
+ containerAppBounds);
+ }
+
+ // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+ void updateAppCompatDisplayInsets() {
+ if (getAppCompatDisplayInsets() != null
+ || !mActivityRecord.shouldCreateAppCompatDisplayInsets()) {
+ // The override configuration is set only once in size compatibility mode.
+ return;
+ }
+
+ Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
+ final Configuration fullConfig = mActivityRecord.getConfiguration();
+
+ // Ensure the screen related fields are set. It is used to prevent activity relaunch
+ // when moving between displays. For screenWidthDp and screenWidthDp, because they
+ // are relative to bounds and density, they will be calculated in
+ // {@link Task#computeConfigResourceOverrides} and the result will also be
+ // relatively fixed.
+ overrideConfig.colorMode = fullConfig.colorMode;
+ overrideConfig.densityDpi = fullConfig.densityDpi;
+ // The smallest screen width is the short side of screen bounds. Because the bounds
+ // and density won't be changed, smallestScreenWidthDp is also fixed.
+ overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
+ if (ActivityInfo.isFixedOrientation(mActivityRecord.getOverrideOrientation())) {
+ // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
+ // apply runtime rotation changes.
+ overrideConfig.windowConfiguration.setRotation(
+ fullConfig.windowConfiguration.getRotation());
+ }
+
+ final Rect letterboxedContainerBounds = mActivityRecord.mAppCompatController
+ .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
+
+ // The role of AppCompatDisplayInsets is like the override bounds.
+ mAppCompatDisplayInsets =
+ new AppCompatDisplayInsets(mActivityRecord.mDisplayContent, mActivityRecord,
+ letterboxedContainerBounds, mActivityRecord.mResolveConfigHint
+ .mUseOverrideInsetsForConfig);
+ }
+
+
+ private boolean isInSizeCompatModeForBounds(final @NonNull Rect appBounds,
+ final @NonNull Rect containerBounds) {
+ if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()) {
+ // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
+ // is letterboxed.
+ return false;
+ }
+ final int appWidth = appBounds.width();
+ final int appHeight = appBounds.height();
+ final int containerAppWidth = containerBounds.width();
+ final int containerAppHeight = containerBounds.height();
+
+ if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
+ // Matched the container bounds.
+ return false;
+ }
+ if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
+ // Both sides are smaller than the container.
+ return true;
+ }
+ if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
+ // One side is larger than the container.
+ return true;
+ }
+
+ // The rest of the condition is that only one side is smaller than the container, but it
+ // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
+ final float maxAspectRatio = mActivityRecord.getMaxAspectRatio();
+ if (maxAspectRatio > 0) {
+ final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
+ / Math.min(appWidth, appHeight);
+ if (aspectRatio >= maxAspectRatio) {
+ // The current size has reached the max aspect ratio.
+ return false;
+ }
+ }
+ final float minAspectRatio = mActivityRecord.getMinAspectRatio();
+ if (minAspectRatio > 0) {
+ // The activity should have at least the min aspect ratio, so this checks if the
+ // container still has available space to provide larger aspect ratio.
+ final float containerAspectRatio =
+ (0.5f + Math.max(containerAppWidth, containerAppHeight))
+ / Math.min(containerAppWidth, containerAppHeight);
+ if (containerAspectRatio <= minAspectRatio) {
+ // The long side has reached the parent.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds,
+ @NonNull Rect containerAppBounds) {
+ final int contentW = resolvedAppBounds.width();
+ final int contentH = resolvedAppBounds.height();
+ final int viewportW = containerAppBounds.width();
+ final int viewportH = containerAppBounds.height();
+ // Allow an application to be up-scaled if its window is smaller than its
+ // original container or if it's a freeform window in desktop mode.
+ boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
+ || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext)
+ && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM);
+ return shouldAllowUpscaling ? Math.min(
+ (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index e3ff851..69421d0 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -251,6 +251,11 @@
}
}
+ static void offsetBounds(@NonNull Configuration inOutConfig, int offsetX, int offsetY) {
+ inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY);
+ inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY);
+ }
+
private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) {
info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 06bdc04..197bd5a 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,7 +68,6 @@
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -173,41 +172,6 @@
|| !transitionGoodToGoForTaskFragments()) {
return;
}
- final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
- ConfigurationContainer::isActivityTypeRecents);
- // In order to avoid visual clutter caused by a conflict between app transition
- // animation and recents animation, app transition is delayed until recents finishes.
- // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
- // task switcher (isRecentsInOpening=true), app transition must start even though
- // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
- // When 1P launcher is used, this animation is controlled by the launcher outside of
- // the app transition, so delaying app transition doesn't cause visible delay. After
- // recents finishes, app transition is handled just to commit visibility on apps.
- if (!isRecentsInOpening) {
- final ArraySet<WindowContainer> participants = new ArraySet<>();
- participants.addAll(mDisplayContent.mOpeningApps);
- participants.addAll(mDisplayContent.mChangingContainers);
- boolean deferForRecents = false;
- for (int i = 0; i < participants.size(); i++) {
- WindowContainer wc = participants.valueAt(i);
- final ActivityRecord activity = getAppFromContainer(wc);
- if (activity == null) {
- continue;
- }
- // Don't defer recents animation if one of activity isn't running for it, that one
- // might be started from quickstep.
- if (!activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
- deferForRecents = false;
- break;
- }
- deferForRecents = true;
- }
- if (deferForRecents) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for recents animation to finish");
- return;
- }
- }
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f23dafe..ddbfd70 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -49,7 +49,6 @@
import static java.lang.Integer.MAX_VALUE;
import android.annotation.Nullable;
-import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
import android.os.IBinder;
@@ -111,7 +110,7 @@
* draw the live-tile above the recents activity, we also need to provide that activity as a
* z-layering reference so that we can place the recents input consumer above it.
*/
- private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
+ private WeakReference<Task> mActiveRecentsTask = null;
private WeakReference<Task> mActiveRecentsLayerRef = null;
private class UpdateInputWindows implements Runnable {
@@ -388,13 +387,13 @@
/**
* Inform InputMonitor when recents is active so it can enable the recents input consumer.
- * @param activity The active recents activity. {@code null} means recents is not active.
+ * @param task The active recents task. {@code null} means recents is not active.
* @param layer A task whose Z-layer is used as a reference for how to sort the consumer.
*/
- void setActiveRecents(@Nullable ActivityRecord activity, @Nullable Task layer) {
- final boolean clear = activity == null;
- final boolean wasActive = mActiveRecentsActivity != null && mActiveRecentsLayerRef != null;
- mActiveRecentsActivity = clear ? null : new WeakReference<>(activity);
+ void setActiveRecents(@Nullable Task task, @Nullable Task layer) {
+ final boolean clear = task == null;
+ final boolean wasActive = mActiveRecentsTask != null && mActiveRecentsLayerRef != null;
+ mActiveRecentsTask = clear ? null : new WeakReference<>(task);
mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer);
if (clear && wasActive) {
setUpdateInputWindowsNeededLw();
@@ -415,7 +414,7 @@
if (recentsAnimationInputConsumer != null && focus != null) {
// Apply recents input consumer when the focusing window is in recents animation.
final boolean shouldApplyRecentsInputConsumer =
- getWeak(mActiveRecentsActivity) != null && focus.inTransition()
+ getWeak(mActiveRecentsTask) != null && focus.inTransition()
// only take focus from the recents activity to avoid intercepting
// events before the gesture officially starts.
&& focus.isActivityTypeHomeOrRecents();
@@ -564,7 +563,6 @@
private boolean mAddRecentsAnimationInputConsumerHandle;
private boolean mInDrag;
- private final Rect mTmpRect = new Rect();
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -581,18 +579,15 @@
resetInputConsumers(mInputTransaction);
// Update recents input consumer layer if active
- final ActivityRecord activeRecents = getWeak(mActiveRecentsActivity);
+ final Task activeRecents = getWeak(mActiveRecentsTask);
if (mAddRecentsAnimationInputConsumerHandle && activeRecents != null
&& activeRecents.getSurfaceControl() != null) {
WindowContainer layer = getWeak(mActiveRecentsLayerRef);
layer = layer != null ? layer : activeRecents;
// Handle edge-case for SUW where windows don't exist yet
if (layer.getSurfaceControl() != null) {
- final WindowState targetAppMainWindow = activeRecents.findMainWindow();
- if (targetAppMainWindow != null) {
- targetAppMainWindow.getBounds(mTmpRect);
- mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
- }
+ mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
+ activeRecents.getBounds());
mRecentsAnimationInputConsumer.show(mInputTransaction, layer);
mAddRecentsAnimationInputConsumerHandle = false;
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9cfd396..57f9be0 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -32,8 +32,8 @@
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -585,7 +585,6 @@
ANIMATION_TYPE_APP_TRANSITION,
ANIMATION_TYPE_SCREEN_ROTATION,
ANIMATION_TYPE_DIMMER,
- ANIMATION_TYPE_RECENTS,
ANIMATION_TYPE_WINDOW_ANIMATION,
ANIMATION_TYPE_INSETS_CONTROL,
ANIMATION_TYPE_TOKEN_TRANSFORM,
@@ -604,7 +603,6 @@
case ANIMATION_TYPE_APP_TRANSITION: return "app_transition";
case ANIMATION_TYPE_SCREEN_ROTATION: return "screen_rotation";
case ANIMATION_TYPE_DIMMER: return "dimmer";
- case ANIMATION_TYPE_RECENTS: return "recents_animation";
case ANIMATION_TYPE_WINDOW_ANIMATION: return "window_animation";
case ANIMATION_TYPE_INSETS_CONTROL: return "insets_animation";
case ANIMATION_TYPE_TOKEN_TRANSFORM: return "token_transform";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d6bd55f..21be0fc 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -94,7 +94,6 @@
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.TaskProto.AFFINITY;
import static com.android.server.wm.TaskProto.BOUNDS;
import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
@@ -2966,8 +2965,7 @@
/** Checking if self or its child tasks are animated by recents animation. */
boolean isAnimatingByRecents() {
- return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
- || mTransitionController.isTransientHide(this);
+ return mTransitionController.isTransientHide(this);
}
WindowState getTopVisibleAppMainWindow() {
@@ -6196,26 +6194,6 @@
ActivityOptions.abort(options);
}
- boolean shouldSleepActivities() {
- final DisplayContent display = mDisplayContent;
- final boolean isKeyguardGoingAway = (mDisplayContent != null)
- ? mDisplayContent.isKeyguardGoingAway()
- : mRootWindowContainer.getDefaultDisplay().isKeyguardGoingAway();
-
- // Do not sleep activities in this root task if we're marked as focused and the keyguard
- // is in the process of going away.
- if (isKeyguardGoingAway && isFocusedRootTaskOnDisplay()
- // Avoid resuming activities on secondary displays since we don't want bubble
- // activities to be resumed while bubble is still collapsed.
- // TODO(b/113840485): Having keyguard going away state for secondary displays.
- && display != null
- && display.isDefaultDisplay) {
- return false;
- }
-
- return display != null ? display.isSleeping() : mAtmService.isSleepingLocked();
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c8139fa..f58b322 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2145,8 +2145,22 @@
}
boolean shouldSleepActivities() {
- final Task task = getRootTask();
- return task != null && task.shouldSleepActivities();
+ final DisplayContent dc = mDisplayContent;
+ if (dc == null) {
+ return mAtmService.isSleepingLocked();
+ }
+ if (!dc.isSleeping()) {
+ return false;
+ }
+ // In case the unlocking order is keyguard-going-away -> screen-turning-on (display is
+ // sleeping by screen-off-token which may be notified to release from power manager's
+ // thread), keep the activities resume-able to avoid extra activity lifecycle when
+ // performing keyguard-going-away. This only applies to default display because currently
+ // the per-display keyguard-going-away state is assigned from a global signal.
+ if (!dc.isDefaultDisplay || !dc.isKeyguardGoingAway()) {
+ return true;
+ }
+ return !shouldBeVisible(null /* starting */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4f12d31..e25db7e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -950,13 +950,10 @@
* Set animation options for collecting transition by ActivityRecord.
* @param options AnimationOptions captured from ActivityOptions
*/
- void setOverrideAnimation(@Nullable AnimationOptions options, @NonNull ActivityRecord r,
+ void setOverrideAnimation(@Nullable AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
if (!isCollecting()) return;
mOverrideOptions = options;
- if (mOverrideOptions != null) {
- mOverrideOptions.setUserId(r.mUserId);
- }
sendRemoteCallback(mClientAnimationStartCallback);
mClientAnimationStartCallback = startCallback;
mClientAnimationFinishCallback = finishCallback;
@@ -1456,7 +1453,7 @@
// Clean up input monitors (for recents)
final DisplayContent dc =
mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
- dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
+ dc.getInputMonitor().setActiveRecents(null /* task */, null /* layer */);
dc.getInputMonitor().updateInputWindowsLw(false /* force */);
}
if (mTransientLaunches != null) {
@@ -2252,7 +2249,7 @@
// Recents has an input-consumer to grab input from the "live tile" app. Set that up here
final InputConsumerImpl recentsAnimationInputConsumer =
dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
- ActivityRecord recentsActivity = null;
+ Task recentsTask = null;
if (recentsAnimationInputConsumer != null) {
// Find the top-most going-away task and the recents activity. The top-most
// is used as layer reference while the recents is used for registering the consumer
@@ -2267,20 +2264,20 @@
final int activityType = taskInfo.topActivityType;
final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_RECENTS;
- if (isRecents && recentsActivity == null) {
- recentsActivity = task.getTopVisibleActivity();
+ if (isRecents && recentsTask == null) {
+ recentsTask = task;
} else if (!isRecents && topNonRecentsTask == null) {
topNonRecentsTask = task;
}
}
- if (recentsActivity != null && topNonRecentsTask != null) {
+ if (recentsTask != null && topNonRecentsTask != null) {
recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
topNonRecentsTask.getBounds());
- dc.getInputMonitor().setActiveRecents(recentsActivity, topNonRecentsTask);
+ dc.getInputMonitor().setActiveRecents(recentsTask, topNonRecentsTask);
}
}
- if (recentsActivity == null) {
+ if (recentsTask == null) {
// No recents activity on `dc`, its probably on a different display.
return;
}
@@ -2814,7 +2811,7 @@
}
}
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
- "Transition Root: " + leashReference.getName())
+ "Transition Root: " + leashReference.getName())
.setCallsite("Transition.calculateTransitionRoots").build();
rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
// Update layers to start transaction because we prevent assignment during collect, so
@@ -2985,7 +2982,7 @@
// Create the options based on this change's custom animations and layout
// parameters
animOptions = getOptions(activityRecord /* customAnimActivity */,
- activityRecord /* animLpActivity */);
+ activityRecord /* animLpActivity */);
if (!change.hasFlags(FLAG_TRANSLUCENT)) {
// If this change is not translucent, its options are going to be
// inherited by the changes below
@@ -3010,7 +3007,6 @@
}
}
if (animOptions != null) {
- animOptions.setUserId(activityRecord.mUserId);
change.setAnimationOptions(animOptions);
}
}
@@ -3071,9 +3067,6 @@
animOptions);
animOptions = addCustomActivityTransition(customAnimActivity, false /* open */,
animOptions);
- if (animOptions != null) {
- animOptions.setUserId(customAnimActivity.mUserId);
- }
}
// Layout parameters
@@ -3087,12 +3080,10 @@
// are running an app starting animation, in which case we don't want the app to be
// able to change its animation directly.
if (animOptions != null) {
- animOptions.setUserId(animLpActivity.mUserId);
animOptions.addOptionsFromLayoutParameters(animLp);
} else {
animOptions = TransitionInfo.AnimationOptions
.makeAnimOptionsFromLayoutParameters(animLp);
- animOptions.setUserId(animLpActivity.mUserId);
}
}
return animOptions;
@@ -3118,7 +3109,6 @@
if (animOptions == null) {
animOptions = TransitionInfo.AnimationOptions
.makeCommonAnimOptions(activity.packageName);
- animOptions.setUserId(activity.mUserId);
}
animOptions.addCustomActivityTransition(open, customAnim.mEnterAnim,
customAnim.mExitAnim, customAnim.mBackgroundColor);
@@ -3247,7 +3237,7 @@
// Remote animations always win, but fullscreen windows override non-fullscreen windows.
ActivityRecord result = lookForTopWindowWithFilter(sortedTargets,
w -> w.getRemoteAnimationDefinition() != null
- && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
+ && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
if (result != null) {
return result;
}
@@ -3294,7 +3284,7 @@
private void validateKeyguardOcclusion() {
if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
mController.mStateValidators.add(
- mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
+ mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange);
}
}
@@ -3898,9 +3888,9 @@
/** @return true if all tracked subtrees are ready. */
boolean allReady() {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " allReady query: used=%b " + "override=%b defer=%d states=[%s]", mUsed,
- mReadyOverride, mDeferReadyDepth, groupsToString());
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
+ + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
+ groupsToString());
// If the readiness has never been touched, mUsed will be false. We never want to
// consider a transition ready if nothing has been reported on it.
if (!mUsed) return false;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 6b11ada..9bbf102 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -52,8 +52,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.ProtoLog;
import com.android.server.FgThread;
import com.android.window.flags.Flags;
@@ -880,10 +880,10 @@
}
/** @see Transition#setOverrideAnimation */
- void setOverrideAnimation(TransitionInfo.AnimationOptions options, ActivityRecord r,
+ void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
if (mCollectingTransition == null) return;
- mCollectingTransition.setOverrideAnimation(options, r, startCallback, finishCallback);
+ mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback);
}
void setNoAnimation(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 016b65e..f1941af 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -201,8 +201,10 @@
// never has letterbox.
return true;
}
+ final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
- || mActivityRecord.hasAppCompatDisplayInsetsWithoutInheritance()) {
+ || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) {
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03342d3..13334a5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -19,7 +19,6 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -218,8 +217,8 @@
private void updateRunningExpensiveAnimationsLegacy() {
final boolean runningExpensiveAnimations =
mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
- | ANIMATION_TYPE_RECENTS /* typesToCheck */);
+ ANIMATION_TYPE_APP_TRANSITION
+ | ANIMATION_TYPE_SCREEN_ROTATION /* typesToCheck */);
if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
mService.mSnapshotController.setPause(true);
mTransaction.setEarlyWakeupStart();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a980b77..6995027 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -51,7 +51,6 @@
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -1242,8 +1241,7 @@
*/
boolean inTransitionSelfOrParent() {
if (!mTransitionController.isShellTransitionsEnabled()) {
- return isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+ return isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION);
}
return inTransition();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index ad88062..80f3c44 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -20,7 +20,7 @@
import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
@@ -118,7 +118,7 @@
mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
mWindowContainer.getDisplayContent().getWindowCornerRadius()),
mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
- ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION);
}
private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4b91e27..9b55ed2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -129,7 +129,6 @@
import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -2725,8 +2724,7 @@
win.mTransitionController.mAnimatingExitWindows.add(win);
reason = "inTransition";
}
- } else if (win.isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+ } else if (win.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
// Already animating as part of a legacy app-transition.
reason = "inLegacyTransition";
}
@@ -8947,6 +8945,22 @@
// display it's on to the top since that window won't be able to get focus anyway.
return;
}
+
+ final ActivityRecord touchedApp = t.getActivityRecord();
+ if (touchedApp != null && touchedApp.getTask() != null) {
+ final ActivityRecord top = touchedApp.getTask().topRunningActivity();
+ if (top != touchedApp && top.getTaskFragment().getBounds().contains(
+ touchedApp.getTaskFragment().getBounds())) {
+ // This is a special case where the pointer-down-outside focus on an Activity that's
+ // entirely occluded by the task top running activity, this is possible if the
+ // pointer-down-outside-focus event is delayed (after new activity started on top).
+ // In that case, drop the event to prevent changing focus to a background activity.
+ Slog.w(TAG, "onPointerDownOutsideFocusLocked, drop event because " + touchedApp
+ + " is occluded and should not be focused.");
+ return;
+ }
+ }
+
clearPointerDownOutsideFocusRunnable();
if (shouldDelayTouchOutside(t)) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81bce18..c8e4b0a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -128,7 +128,6 @@
import static com.android.server.wm.MoveAnimationSpecProto.TO;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -581,7 +580,7 @@
* is guaranteed to be cleared.
*/
static final int EXIT_ANIMATING_TYPES = ANIMATION_TYPE_APP_TRANSITION
- | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS;
+ | ANIMATION_TYPE_WINDOW_ANIMATION;
/** Currently running an exit animation? */
boolean mAnimatingExit;
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 2c5a453..dc048ef 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -35,7 +35,6 @@
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance,
WindowTracingDataSource.TlsState, Void> {
@@ -77,15 +76,11 @@
private static final String TAG = "WindowTracingDataSource";
@NonNull
- private final WeakReference<Consumer<Config>> mOnStartCallback;
- @NonNull
- private final WeakReference<Consumer<Config>> mOnStopCallback;
+ private final WeakReference<WindowTracingPerfetto> mWindowTracing;
- public WindowTracingDataSource(@NonNull Consumer<Config> onStart,
- @NonNull Consumer<Config> onStop) {
+ public WindowTracingDataSource(WindowTracingPerfetto windowTracing) {
super(DATA_SOURCE_NAME);
- mOnStartCallback = new WeakReference(onStart);
- mOnStopCallback = new WeakReference(onStop);
+ mWindowTracing = new WeakReference<>(windowTracing);
Producer.init(InitArguments.DEFAULTS);
DataSourceParams params =
@@ -94,6 +89,7 @@
PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
.build();
register(params);
+ Log.i(TAG, "Registered with perfetto service");
}
@Override
@@ -103,17 +99,17 @@
return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) {
@Override
protected void onStart(StartCallbackArguments args) {
- Consumer<Config> callback = mOnStartCallback.get();
- if (callback != null) {
- callback.accept(mConfig);
+ WindowTracingPerfetto windowTracing = mWindowTracing.get();
+ if (windowTracing != null) {
+ windowTracing.onStart(mConfig);
}
}
@Override
protected void onStop(StopCallbackArguments args) {
- Consumer<Config> callback = mOnStopCallback.get();
- if (callback != null) {
- callback.accept(mConfig);
+ WindowTracingPerfetto windowTracing = mWindowTracing.get();
+ if (windowTracing != null) {
+ windowTracing.onStop(mConfig);
}
}
};
diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
index cf948ca..22d6c86 100644
--- a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
+++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
@@ -35,8 +35,7 @@
private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger();
private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger();
- private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(
- this::onStart, this::onStop);
+ private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(this);
WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
this(service, choreographer, service.mGlobalLock);
@@ -156,7 +155,7 @@
return mCountSessionsOnTransaction.get() > 0;
}
- private void onStart(WindowTracingDataSource.Config config) {
+ void onStart(WindowTracingDataSource.Config config) {
if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
mCountSessionsOnFrame.incrementAndGet();
} else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
@@ -168,7 +167,7 @@
log(WHERE_START_TRACING);
}
- private void onStop(WindowTracingDataSource.Config config) {
+ void onStop(WindowTracingDataSource.Config config) {
if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
mCountSessionsOnFrame.decrementAndGet();
} else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0ba74c6..100b548 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1176,6 +1176,17 @@
verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
verifyPendingRecords(redQueue, List.of(screenOff));
verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
+
+ final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false);
+ screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+ "testDeliveryGroupPolicy_prioritized_diffReceivers");
+ mImpl.enqueueBroadcastLocked(screenOffRecord);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
}
/**
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 1423811..0d8b720 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -705,7 +705,7 @@
assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
// Clear size compat.
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
activity.ensureActivityConfiguration();
mDisplayContent.sendNewConfiguration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index d7cef59..a7a08b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -499,7 +499,7 @@
activity.setRequestedOrientation(screenOrientation);
}
// Make sure to use the provided configuration to construct the size compat fields.
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
activity.ensureActivityConfiguration();
// Make sure the display configuration reflects the change of activity.
if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 9950541..b6e393d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,10 +39,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
@@ -51,9 +49,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.graphics.Rect;
import android.os.Binder;
@@ -377,41 +373,6 @@
}
@Test
- public void testDelayWhileRecents() {
- final DisplayContent dc = createNewDisplay(Display.STATE_ON);
- doReturn(false).when(dc).onDescendantOrientationChanged(any());
- final Task task = createTask(dc);
-
- // Simulate activity1 launches activity2.
- final ActivityRecord activity1 = createActivityRecord(task);
- activity1.setVisible(true);
- activity1.setVisibleRequested(false);
- activity1.allDrawn = true;
- final ActivityRecord activity2 = createActivityRecord(task);
- activity2.setVisible(false);
- activity2.setVisibleRequested(true);
- activity2.allDrawn = true;
-
- dc.mClosingApps.add(activity1);
- dc.mOpeningApps.add(activity2);
- dc.prepareAppTransition(TRANSIT_OPEN);
- assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
-
- // Wait until everything in animation handler get executed to prevent the exiting window
- // from being removed during WindowSurfacePlacer Traversal.
- waitUntilHandlersIdle();
-
- // Start recents
- doReturn(true).when(task)
- .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
-
- dc.mAppTransitionController.handleAppTransitionReady();
-
- verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
- verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
- }
-
- @Test
public void testGetAnimationStyleResId() {
// Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
// specifying window type.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index d8d5729..ea175a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -24,6 +24,8 @@
import android.graphics.PixelFormat;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
@@ -72,6 +74,7 @@
* Checks that scheduling with all the state set and manually triggering the show does succeed.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
@@ -99,6 +102,7 @@
* all the state becomes available.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_noInitialState() {
final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
@@ -126,6 +130,7 @@
* does continue and succeed when the runnable is started.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedAfterPrepareSurfaces() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
@@ -158,6 +163,7 @@
* when the surface placement happens.
*/
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testScheduleShowIme_delayedSurfacePlacement() {
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
makeWindowVisibleAndDrawn(ime);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0dc56f8..964264d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,6 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -202,6 +203,11 @@
getController().onImeControlTargetChanged(base);
base.setRequestedVisibleTypes(ime(), ime());
getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // to set the serverVisibility, the IME needs to be drawn and onPostLayout be called.
+ mImeWindow.mWinAnimator.mDrawState = HAS_DRAWN;
+ getController().onPostLayout();
+ }
// Send our spy window (app) into the system so that we can detect the invocation.
final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -500,6 +506,12 @@
getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // The IME is only set to shown, after onPostLayout is called and all preconditions
+ // (serverVisible, no givenInsetsPending, etc.) are fulfilled
+ getController().getImeSourceProvider().onPostLayout();
+ }
+
getController().updateAboveInsetsState(true /* notifyInsetsChange */);
assertNotNull(app.getInsetsState().peekSource(ID_IME));
verify(app, atLeastOnce()).notifyInsetsChanged();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 9007733..6adf0fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1260,25 +1260,40 @@
@Test
public void testShouldSleepActivities() {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ task.mDisplayContent = mock(DisplayContent.class);
// When focused activity and keyguard is going away, we should not sleep regardless
// of the display state, but keyguard-going-away should only take effects on default
- // display since there is no keyguard on secondary displays (yet).
- verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+ // display because the keyguard-going-away state of secondary displays are already the
+ // same as default display.
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
- verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
// When not the focused root task, defer to display sleeping state.
- verifyShouldSleepActivities(false /* focusedRootTask */, true /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, false /* isVisibleTask */, true /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
// If keyguard is going away, defer to the display sleeping state.
- verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
- verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+ verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
}
+ private static void verifyShouldSleepActivities(Task task, boolean isVisibleTask,
+ boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
+ boolean expected) {
+ final DisplayContent display = task.mDisplayContent;
+ display.isDefaultDisplay = isDefaultDisplay;
+ doReturn(keyguardGoingAway).when(display).isKeyguardGoingAway();
+ doReturn(displaySleeping).when(display).isSleeping();
+ doReturn(isVisibleTask).when(task).shouldBeVisible(null /* starting */);
+
+ assertEquals(expected, task.shouldSleepActivities());
+ }
+
@Test
public void testNavigateUpTo() {
final ActivityStartController controller = mock(ActivityStartController.class);
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 5bb4378..f743401 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -613,7 +613,7 @@
assertFalse(mActivity.mDisplayContent.shouldImeAttachedToApp());
// Recompute the natural configuration without resolving size compat configuration.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
mActivity.onConfigurationChanged(mTask.getConfiguration());
// It should keep non-attachable because the resolved bounds will be computed according to
// the aspect ratio that won't match its parent bounds.
@@ -706,7 +706,7 @@
/ originalBounds.width()));
// Recompute the natural configuration in the new display.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
mActivity.ensureActivityConfiguration();
// Because the display cannot rotate, the portrait activity will fit the short side of
// display with keeping portrait bounds [200, 0 - 700, 1000] in center.
@@ -1482,7 +1482,7 @@
// After changing the orientation to portrait the override should be applied.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1200, activity.getBounds().height());
@@ -1511,7 +1511,7 @@
// After changing the orientation to portrait the override should be applied.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// The per-package override forces the activity into a 3:2 aspect ratio
assertEquals(1200, activity.getBounds().height());
@@ -1538,7 +1538,7 @@
// After changing the orientation to landscape the override shouldn't be applied.
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// The per-package override should have no effect
assertEquals(1200, activity.getBounds().height());
@@ -3054,7 +3054,10 @@
false /* deferPause */);
// App still in size compat, and the bounds don't change.
- verify(mActivity, never()).clearSizeCompatMode();
+ final AppCompatSizeCompatModePolicy scmPolicy = mActivity.mAppCompatController
+ .getAppCompatSizeCompatModePolicy();
+ spyOn(scmPolicy);
+ verify(scmPolicy, never()).clearSizeCompatMode();
assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
assertDownScaled();
@@ -3896,7 +3899,7 @@
private void recomputeNaturalConfigurationOfUnresizableActivity() {
// Recompute the natural configuration of the non-resizable activity and the split screen.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
// Draw letterbox.
mActivity.setVisible(false);
@@ -4827,7 +4830,7 @@
assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
// Activity should exit size compat with new density.
- mActivity.clearSizeCompatMode();
+ mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
assertFitted();
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
@@ -5013,7 +5016,7 @@
activity.setRequestedOrientation(screenOrientation);
}
// Make sure to use the provided configuration to construct the size compat fields.
- activity.clearSizeCompatMode();
+ activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
activity.ensureActivityConfiguration();
// Make sure the display configuration reflects the change of activity.
if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6c8a7ac..9981a4d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -23,7 +23,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -96,18 +95,18 @@
@Test
public void testRunAnimation() {
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
- ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION);
final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
OnAnimationFinishedCallback.class);
assertAnimating(mAnimatable);
verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
- verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_RECENTS),
+ verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
callbackCaptor.capture());
- callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_RECENTS, mSpec);
+ callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
assertNotAnimating(mAnimatable);
assertTrue(mAnimatable.mFinishedCallbackCalled);
- assertEquals(ANIMATION_TYPE_RECENTS, mAnimatable.mFinishedAnimationType);
+ assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
verify(mTransaction).remove(eq(mAnimatable.mLeash));
// TODO: Verify reparenting once we use mPendingTransaction to reparent it back
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 6c6edd1..56fca31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -2004,10 +2004,10 @@
@DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_disableAnimOptionsPerChange() {
- ActivityRecord r = initializeOverrideAnimationOptionsTest();
+ initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCommonAnimOptions("testPackage");
- mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2018,10 +2018,10 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_fromStyleAnimOptions() {
- ActivityRecord r = initializeOverrideAnimationOptionsTest();
+ initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCommonAnimOptions("testPackage");
- mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2044,10 +2044,10 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_sceneAnimOptions() {
- ActivityRecord r = initializeOverrideAnimationOptionsTest();
+ initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeSceneTransitionAnimOptions();
- mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2070,10 +2070,10 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_crossProfileAnimOptions() {
- ActivityRecord r = initializeOverrideAnimationOptionsTest();
+ initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCrossProfileAnimOptions();
- mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
null /* finishCallback */);
final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2098,13 +2098,13 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptions() {
- ActivityRecord r = initializeOverrideAnimationOptionsTest();
+ initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCustomAnimOptions("testPackage", Resources.ID_NULL,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
Color.GREEN, false /* overrideTaskTransition */);
- mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2131,7 +2131,7 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_haveTaskFragmentAnimParams() {
- ActivityRecord r = initializeOverrideAnimationOptionsTest();
+ initializeOverrideAnimationOptionsTest();
final TaskFragment embeddedTf = mTransition.mTargets.get(2).mContainer.asTaskFragment();
embeddedTf.setAnimationParams(new TaskFragmentAnimationParams.Builder()
@@ -2144,7 +2144,7 @@
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
Color.GREEN, false /* overrideTaskTransition */);
- mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
null /* finishCallback */);
final TransitionInfo.Change displayChange = mInfo.getChanges().get(0);
@@ -2180,13 +2180,13 @@
@EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOverrideAnimationOptionsToInfoIfNecessary_customAnimOptionsWithTaskOverride() {
- ActivityRecord r = initializeOverrideAnimationOptionsTest();
+ initializeOverrideAnimationOptionsTest();
TransitionInfo.AnimationOptions options = TransitionInfo.AnimationOptions
.makeCustomAnimOptions("testPackage", Resources.ID_NULL,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID,
Color.GREEN, true /* overrideTaskTransition */);
- mTransition.setOverrideAnimation(options, r, null /* startCallback */,
+ mTransition.setOverrideAnimation(options, null /* startCallback */,
null /* finishCallback */);
mTransition.overrideAnimationOptionsToInfoIfNecessary(mInfo);
@@ -2212,7 +2212,7 @@
options.getBackgroundColor(), activityChange.getBackgroundColor());
}
- private ActivityRecord initializeOverrideAnimationOptionsTest() {
+ private void initializeOverrideAnimationOptionsTest() {
mTransition = createTestTransition(TRANSIT_OPEN);
// Test set AnimationOptions for Activity and Task.
@@ -2240,7 +2240,6 @@
embeddedTf.getAnimationLeash()));
mInfo.addChange(new TransitionInfo.Change(null /* container */,
nonEmbeddedActivity.getAnimationLeash()));
- return nonEmbeddedActivity;
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index fa28d11..7a440e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -323,7 +323,10 @@
ta.launchTransparentActivityInTask();
a.assertNotNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
- a.applyToTopActivity(ActivityRecord::clearSizeCompatMode);
+ a.applyToTopActivity((top) -> {
+ top.mAppCompatController.getAppCompatSizeCompatModePolicy()
+ .clearSizeCompatMode();
+ });
a.assertNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
});
});
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index d537bd7..88ce3a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -40,14 +40,12 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -405,30 +403,6 @@
}
@Test
- public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() {
- final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
- mAppWindow.mActivityRecord, "imeAppTarget");
- mDisplayContent.setImeInputTarget(imeAppTarget);
- mDisplayContent.setImeLayeringTarget(imeAppTarget);
- mDisplayContent.setImeControlTarget(imeAppTarget);
- mDisplayContent.updateImeParent();
-
- // Simulate the ime layering target task is animating with recents animation.
- final Task imeAppTargetTask = imeAppTarget.getTask();
- final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator;
- spyOn(imeTargetTaskAnimator);
- doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType();
- doReturn(true).when(imeTargetTaskAnimator).isAnimating();
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- // Ime should on top of the application window when in recents animation and keep
- // attached on app.
- assertTrue(mDisplayContent.shouldImeAttachedToApp());
- assertWindowHigher(mImeWindow, imeAppTarget);
- }
-
- @Test
public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() {
final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
mAppWindow.mActivityRecord, "imeAppTarget");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3e8b326..7481daa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1777,8 +1777,8 @@
/**
* Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
- * to indicate user to decide whether current SIM should be preferred for all
- * data / voice / sms. {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
+ * to indicate the current SIM should be preferred for all data / voice / sms.
+ * {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
* which subscription should be the default subscription.
* @hide
*/
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index b0f68f7..f68ae2c 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -98,6 +98,18 @@
}
@Override
+ public void onServiceDisconnected() {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onServiceDisconnected());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
if (mCallback != null) {
final long token = Binder.clearCallingIdentity();
@@ -247,13 +259,13 @@
mService = null;
synchronized (mProxyDataLock) {
if (!mCallbackProxyCache.isEmpty()) {
- mCallbackProxyCache.keySet().forEach(
- SharedConnectivityClientCallback::onServiceDisconnected);
+ mCallbackProxyCache.values().forEach(
+ SharedConnectivityCallbackProxy::onServiceDisconnected);
mCallbackProxyCache.clear();
}
if (!mProxyMap.isEmpty()) {
- mProxyMap.keySet().forEach(
- SharedConnectivityClientCallback::onServiceDisconnected);
+ mProxyMap.values().forEach(
+ SharedConnectivityCallbackProxy::onServiceDisconnected);
mProxyMap.clear();
}
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
index 521f943..7b892af 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -32,4 +32,5 @@
oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
oneway void onServiceConnected();
+ oneway void onServiceDisconnected();
}