Merge "Add missing api flags in package manager" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index c362e95..e02803d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18526,11 +18526,13 @@
public class BiometricManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int);
+ method @FlaggedApi("android.hardware.biometrics.last_authentication_time") @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public long getLastAuthenticationTime(int);
method @NonNull @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public android.hardware.biometrics.BiometricManager.Strings getStrings(int);
field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf
+ field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL
field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
}
@@ -18576,6 +18578,7 @@
field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa
field public static final int BIOMETRIC_ERROR_VENDOR = 8; // 0x8
+ field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL
}
public abstract static class BiometricPrompt.AuthenticationCallback {
@@ -28503,12 +28506,12 @@
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
- method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder enableSafeMode(boolean);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+ method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
}
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 943eee4..61d8702 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -18,6 +18,7 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -296,4 +297,15 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef({BIOMETRIC_LOCKOUT_NONE, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT})
@interface LockoutMode {}
+
+ //
+ // Other miscellaneous constants
+ //
+
+ /**
+ * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when there has
+ * been no successful authentication for the given authenticator since boot.
+ */
+ @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+ long BIOMETRIC_NO_AUTHENTICATION = -1;
}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 82694ee..90bbca8 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -23,6 +23,8 @@
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_BIOMETRIC_MANAGER_CAN_AUTHENTICATE;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,6 +32,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.KeyguardManager;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
@@ -86,6 +89,17 @@
BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
/**
+ * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when no matching
+ * successful authentication has been performed since boot.
+ */
+ @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+ public static final long BIOMETRIC_NO_AUTHENTICATION =
+ BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+
+ private static final int GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG;
+
+ /**
* @hide
*/
@IntDef({BIOMETRIC_SUCCESS,
@@ -637,5 +651,58 @@
}
}
+
+ /**
+ * Gets the last time the user successfully authenticated using one of the given authenticators.
+ * The returned value is time in
+ * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} (time since
+ * boot, including sleep).
+ * <p>
+ * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} is returned in the case where there
+ * has been no successful authentication using any of the given authenticators since boot.
+ * <p>
+ * Currently, only {@link Authenticators#DEVICE_CREDENTIAL} and
+ * {@link Authenticators#BIOMETRIC_STRONG} are accepted. {@link IllegalArgumentException} will
+ * be thrown if {@code authenticators} contains other authenticator types.
+ * <p>
+ * Note that this may return successful authentication times even if the device is currently
+ * locked. You may use {@link KeyguardManager#isDeviceLocked()} to determine if the device
+ * is unlocked or not. Additionally, this method may return valid times for an authentication
+ * method that is no longer available. For instance, if the user unlocked the device with a
+ * {@link Authenticators#BIOMETRIC_STRONG} authenticator but then deleted that authenticator
+ * (e.g., fingerprint data), this method will still return the time of that unlock for
+ * {@link Authenticators#BIOMETRIC_STRONG} if it is the most recent successful event. The caveat
+ * is that {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} will be returned if the device
+ * no longer has a secure lock screen at all, even if there were successful authentications
+ * performed before the lock screen was made insecure.
+ *
+ * @param authenticators bit field consisting of constants defined in {@link Authenticators}.
+ * @return the time of last authentication or
+ * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION}
+ * @throws IllegalArgumentException if {@code authenticators} contains values other than
+ * {@link Authenticators#DEVICE_CREDENTIAL} and {@link Authenticators#BIOMETRIC_STRONG} or is
+ * 0.
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ @ElapsedRealtimeLong
+ @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+ public long getLastAuthenticationTime(
+ @BiometricManager.Authenticators.Types int authenticators) {
+ if (authenticators == 0
+ || (GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS & authenticators) != authenticators) {
+ throw new IllegalArgumentException(
+ "Only BIOMETRIC_STRONG and DEVICE_CREDENTIAL authenticators may be used.");
+ }
+
+ if (mService != null) {
+ try {
+ return mService.getLastAuthenticationTime(UserHandle.myUserId(), authenticators);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ } else {
+ return BIOMETRIC_NO_AUTHENTICATION;
+ }
+ }
}
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index c2e5c0b..5bdbe2b5 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -57,6 +57,9 @@
// Checks if biometrics can be used.
int canAuthenticate(String opPackageName, int userId, int authenticators);
+ // Gets the time of last authentication for the given user and authenticators.
+ long getLastAuthenticationTime(int userId, int authenticators);
+
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 18c8d1b..058f302 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -53,6 +53,10 @@
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);
+ // Gets the time of last authentication for the given user and authenticators.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ long getLastAuthenticationTime(int userId, int authenticators);
+
// Checks if any biometrics are enrolled.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 66429e5..979f570 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -1,6 +1,13 @@
package: "android.hardware.biometrics"
flag {
+ name: "last_authentication_time"
+ namespace: "wallet_integration"
+ description: "Feature flag for adding getLastAuthenticationTime API to BiometricManager"
+ bug: "301979982"
+}
+
+flag {
name: "add_key_agreement_crypto_object"
namespace: "biometrics"
description: "Feature flag for adding KeyAgreement api to CryptoObject."
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 779a8db..6f11d3a 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -493,7 +493,7 @@
/**
* Check whether safe mode is enabled
*
- * @see Builder#enableSafeMode(boolean)
+ * @see Builder#setSafeModeEnabled(boolean)
*/
@FlaggedApi(FLAG_SAFE_MODE_CONFIG)
public boolean isSafeModeEnabled() {
@@ -815,8 +815,8 @@
*
* <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter
* safe mode. In safe mode, the VCN Network will be torn down and the system will restore
- * connectivity by allowing underlying cellular networks to be used as default. At the same
- * time, VCN will continue to retry until it succeeds.
+ * connectivity by allowing underlying cellular or WiFi networks to be used as default. At
+ * the same time, VCN will continue to retry until it succeeds.
*
* <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users
* might not have connectivity, and may not have access to carrier-owned underlying
@@ -826,7 +826,7 @@
*/
@FlaggedApi(FLAG_SAFE_MODE_CONFIG)
@NonNull
- public Builder enableSafeMode(boolean enabled) {
+ public Builder setSafeModeEnabled(boolean enabled) {
mIsSafeModeDisabled = !enabled;
return this;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9963092..9f931b4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5914,6 +5914,7 @@
*
* @hide
*/
+ @Readable
public static final String TOUCHPAD_NATURAL_SCROLLING = "touchpad_natural_scrolling";
/**
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 2d2dd24..b4b3e92 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -18,7 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.security.keymint.HardwareAuthToken;
+import android.hardware.security.keymint.HardwareAuthenticatorType;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
@@ -37,7 +39,10 @@
public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR;
- private static IKeystoreAuthorization getService() {
+ /**
+ * @return an instance of IKeystoreAuthorization
+ */
+ public static IKeystoreAuthorization getService() {
return IKeystoreAuthorization.Stub.asInterface(
ServiceManager.checkService("android.security.authorization"));
}
@@ -100,4 +105,24 @@
}
}
+ /**
+ * Gets the last authentication time of the given user and authenticators.
+ *
+ * @param userId user id
+ * @param authenticatorTypes an array of {@link HardwareAuthenticatorType}.
+ * @return the last authentication time or
+ * {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}.
+ */
+ public static long getLastAuthenticationTime(
+ long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
+ try {
+ return getService().getLastAuthTime(userId, authenticatorTypes);
+ } catch (RemoteException | NullPointerException e) {
+ Log.w(TAG, "Can not connect to keystore", e);
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ } catch (ServiceSpecificException e) {
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ }
+ }
+
}
diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java
index af188a9..464714f 100644
--- a/keystore/java/android/security/GateKeeper.java
+++ b/keystore/java/android/security/GateKeeper.java
@@ -45,8 +45,19 @@
@UnsupportedAppUsage
public static long getSecureUserId() throws IllegalStateException {
+ return getSecureUserId(UserHandle.myUserId());
+ }
+
+ /**
+ * Return the secure user id for a given user id
+ * @param userId the user id, e.g. 0
+ * @return the secure user id or {@link GateKeeper#INVALID_SECURE_USER_ID} if no such mapping
+ * for the given user id is found.
+ * @throws IllegalStateException if there is an error retrieving the secure user id
+ */
+ public static long getSecureUserId(int userId) throws IllegalStateException {
try {
- return getService().getSecureUserId(UserHandle.myUserId());
+ return getService().getSecureUserId(userId);
} catch (RemoteException e) {
throw new IllegalStateException(
"Failed to obtain secure user ID from gatekeeper", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index ea7b2e9..64294c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -103,6 +103,7 @@
import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.taskview.TaskViewFactoryController;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -613,14 +614,22 @@
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ HomeTransitionObserver homeTransitionObserver) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
pool, displayController, mainExecutor, mainHandler, animExecutor,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer, homeTransitionObserver);
+ }
+
+ @WMSingleton
+ @Provides
+ static HomeTransitionObserver provideHomeTransitionObserver(Context context,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new HomeTransitionObserver(context, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index a533ca5..47769a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -78,6 +78,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldAnimationController;
@@ -380,9 +381,10 @@
static RecentsTransitionHandler provideRecentsTransitionHandler(
ShellInit shellInit,
Transitions transitions,
- Optional<RecentTasksController> recentTasksController) {
+ Optional<RecentTasksController> recentTasksController,
+ HomeTransitionObserver homeTransitionObserver) {
return new RecentsTransitionHandler(shellInit, transitions,
- recentTasksController.orElse(null));
+ recentTasksController.orElse(null), homeTransitionObserver);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 1898ea7..3b48c67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -22,6 +22,8 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
@@ -29,6 +31,7 @@
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -52,9 +55,10 @@
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
- Optional<PipController> pipController) {
+ Optional<PipController> pipController,
+ @NonNull PipScheduler pipScheduler) {
return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
- pipBoundsAlgorithm);
+ pipBoundsAlgorithm, pipScheduler);
}
@WMSingleton
@@ -73,4 +77,12 @@
pipDisplayLayoutState));
}
}
+
+ @WMSingleton
+ @Provides
+ static PipScheduler providePipScheduler(Context context,
+ PipBoundsState pipBoundsState,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipScheduler(context, pipBoundsState, mainExecutor);
+ }
}
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 412a5b5..8e12991 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
@@ -343,9 +343,8 @@
task.taskId
)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.token, WINDOWING_MODE_MULTI_WINDOW)
wct.setBounds(task.token, Rect())
- wct.setDensityDpi(task.token, getDefaultDensityDpi())
+ addMoveToSplitChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -827,7 +826,9 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+ // Explicitly setting multi-window at task level interferes with animations.
+ // Let task inherit windowing mode once transition is complete instead.
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED)
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
new file mode 100644
index 0000000..9bb383f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.PipTransitionController;
+
+/**
+ * Scheduler for Shell initiated PiP transitions and animations.
+ */
+public class PipScheduler {
+ private static final String TAG = PipScheduler.class.getSimpleName();
+ private static final String BROADCAST_FILTER = PipScheduler.class.getCanonicalName();
+
+ private final Context mContext;
+ private final PipBoundsState mPipBoundsState;
+ private final ShellExecutor mMainExecutor;
+ private PipSchedulerReceiver mSchedulerReceiver;
+ private PipTransitionController mPipTransitionController;
+
+ // pinned PiP task's WC token
+ @Nullable
+ private WindowContainerToken mPipTaskToken;
+
+ // pinned PiP task's leash
+ @Nullable
+ private SurfaceControl mPinnedTaskLeash;
+
+ // the leash of the original task of the PiP activity;
+ // used to synchronize app drawings in the multi-activity case
+ @Nullable
+ private SurfaceControl mOriginalTaskLeash;
+
+ /**
+ * A temporary broadcast receiver to initiate exit PiP via expand.
+ * This will later be modified to be triggered by the PiP menu.
+ */
+ private class PipSchedulerReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ scheduleExitPipViaExpand();
+ }
+ }
+
+ public PipScheduler(Context context, PipBoundsState pipBoundsState,
+ ShellExecutor mainExecutor) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mMainExecutor = mainExecutor;
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ // temporary broadcast receiver to initiate exit PiP via expand
+ mSchedulerReceiver = new PipSchedulerReceiver();
+ ContextCompat.registerReceiver(mContext, mSchedulerReceiver,
+ new IntentFilter(BROADCAST_FILTER), ContextCompat.RECEIVER_EXPORTED);
+ }
+ }
+
+ void setPipTransitionController(PipTransitionController pipTransitionController) {
+ mPipTransitionController = pipTransitionController;
+ }
+
+ void setPinnedTaskLeash(SurfaceControl pinnedTaskLeash) {
+ mPinnedTaskLeash = pinnedTaskLeash;
+ }
+
+ void setOriginalTaskLeash(SurfaceControl originalTaskLeash) {
+ mOriginalTaskLeash = originalTaskLeash;
+ }
+
+ void setPipTaskToken(@Nullable WindowContainerToken pipTaskToken) {
+ mPipTaskToken = pipTaskToken;
+ }
+
+ @Nullable
+ private WindowContainerTransaction getExitPipViaExpandTransaction() {
+ if (mPipTaskToken == null || mPinnedTaskLeash == null) {
+ return null;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ // final expanded bounds to be inherited from the parent
+ wct.setBounds(mPipTaskToken, null);
+ // if we are hitting a multi-activity case
+ // windowing mode change will reparent to original host task
+ wct.setWindowingMode(mPipTaskToken, WINDOWING_MODE_UNDEFINED);
+ return wct;
+ }
+
+ /**
+ * Schedules exit PiP via expand transition.
+ */
+ public void scheduleExitPipViaExpand() {
+ WindowContainerTransaction wct = getExitPipViaExpandTransaction();
+ if (wct != null) {
+ mMainExecutor.execute(() -> {
+ mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
+ null /* destinationBounds */);
+ });
+ }
+ }
+
+ void onExitPip() {
+ mPipTaskToken = null;
+ mPinnedTaskLeash = null;
+ mOriginalTaskLeash = null;
+ }
+}
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 d704b09..7d3bd65 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
@@ -16,8 +16,12 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
@@ -26,6 +30,7 @@
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
@@ -43,8 +48,15 @@
* Implementation of transitions for PiP on phone.
*/
public class PipTransition extends PipTransitionController {
+ private static final String TAG = PipTransition.class.getSimpleName();
+
+ private PipScheduler mPipScheduler;
+ @Nullable
+ private WindowContainerToken mPipTaskToken;
@Nullable
private IBinder mAutoEnterButtonNavTransition;
+ @Nullable
+ private IBinder mExitViaExpandTransition;
public PipTransition(
@NonNull ShellInit shellInit,
@@ -52,9 +64,13 @@
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipMenuController pipMenuController,
- PipBoundsAlgorithm pipBoundsAlgorithm) {
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipScheduler pipScheduler) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
+
+ mPipScheduler = pipScheduler;
+ mPipScheduler.setPipTransitionController(this);
}
@Override
@@ -64,6 +80,18 @@
}
}
+ @Override
+ public void startExitTransition(int type, WindowContainerTransaction out,
+ @android.annotation.Nullable Rect destinationBounds) {
+ if (out == null) {
+ return;
+ }
+ IBinder transition = mTransitions.startTransition(type, out, this);
+ if (type == TRANSIT_EXIT_PIP) {
+ mExitViaExpandTransition = transition;
+ }
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -84,8 +112,18 @@
}
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {}
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishT) {}
+
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
+ // cache the original task token to check for multi-activity case later
final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
@@ -93,6 +131,8 @@
// calculate the entry bounds and notify core to move task to pinned with final bounds
final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ mPipBoundsState.setBounds(entryBounds);
+
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
return wct;
@@ -121,19 +161,59 @@
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition == mAutoEnterButtonNavTransition) {
+ mAutoEnterButtonNavTransition = null;
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and the relevant leashes
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+ mPipScheduler.setPinnedTaskLeash(pipChange.getLeash());
+ // check if we entered PiP from a multi-activity task and set the original task leash
+ final int lastParentTaskId = pipChange.getTaskInfo().lastParentTaskIdBeforePip;
+ final boolean isSingleActivity = lastParentTaskId == INVALID_TASK_ID;
+ mPipScheduler.setOriginalTaskLeash(isSingleActivity ? null :
+ findChangeByTaskId(info, lastParentTaskId).getLeash());
+
startTransaction.apply();
finishCallback.onTransitionFinished(null);
return true;
+ } else if (transition == mExitViaExpandTransition) {
+ mExitViaExpandTransition = null;
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ onExitPip();
+ return true;
}
return false;
}
- @Override
- public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {}
+ @Nullable
+ private TransitionInfo.Change getPipChange(TransitionInfo info) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+ return change;
+ }
+ }
+ return null;
+ }
- @Override
- public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
- @Nullable SurfaceControl.Transaction finishT) {}
+ @Nullable
+ private TransitionInfo.Change findChangeByTaskId(TransitionInfo info, int taskId) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().taskId == taskId) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ private void onExitPip() {
+ mPipTaskToken = null;
+ mPipScheduler.onExitPip();
+ }
}
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 d277eef..c20d23e 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
@@ -59,6 +59,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.TransitionUtil;
@@ -85,11 +86,15 @@
*/
private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
+ private final HomeTransitionObserver mHomeTransitionObserver;
+
public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
- @Nullable RecentTasksController recentTasksController) {
+ @Nullable RecentTasksController recentTasksController,
+ HomeTransitionObserver homeTransitionObserver) {
mTransitions = transitions;
mExecutor = transitions.getMainExecutor();
mRecentTasksController = recentTasksController;
+ mHomeTransitionObserver = homeTransitionObserver;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
if (recentTasksController == null) return;
shellInit.addInitCallback(() -> {
@@ -911,6 +916,11 @@
Slog.e(TAG, "Duplicate call to finish");
return;
}
+ if (!toHome) {
+ // For some transitions, we may have notified home activity that it became visible.
+ // We need to notify the observer that we are no longer going home.
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
+ "willFinishToHome=%b state=%d",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index f561aa5..b528089d15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -38,24 +38,15 @@
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
- private final SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
+ private SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
mListener;
private @NonNull final Context mContext;
private @NonNull final ShellExecutor mMainExecutor;
- private @NonNull final Transitions mTransitions;
-
public HomeTransitionObserver(@NonNull Context context,
- @NonNull ShellExecutor mainExecutor,
- @NonNull Transitions transitions) {
+ @NonNull ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
- mTransitions = transitions;
-
- mListener = new SingleInstanceRemoteListener<>(this,
- c -> mTransitions.registerObserver(this),
- c -> mTransitions.unregisterObserver(this));
-
}
@Override
@@ -72,7 +63,7 @@
final int mode = change.getMode();
if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
&& TransitionUtil.isOpenOrCloseMode(mode)) {
- mListener.call(l -> l.onHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)));
+ notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode));
}
}
}
@@ -92,7 +83,14 @@
* Sets the home transition listener that receives any transitions resulting in a change of
*
*/
- public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ public void setHomeTransitionListener(Transitions transitions,
+ IHomeTransitionListener listener) {
+ if (mListener == null) {
+ mListener = new SingleInstanceRemoteListener<>(this,
+ c -> transitions.registerObserver(this),
+ c -> transitions.unregisterObserver(this));
+ }
+
if (listener != null) {
mListener.register(listener);
} else {
@@ -100,6 +98,16 @@
}
}
+ /**
+ * Notifies the listener that the home visibility has changed.
+ * @param isVisible true when home activity is visible, false otherwise.
+ */
+ public void notifyHomeVisibilityChanged(boolean isVisible) {
+ if (mListener != null) {
+ mListener.call(l -> l.onHomeVisibilityChanged(isVisible));
+ }
+ }
+
@Override
public Context getContext() {
return mContext;
@@ -113,7 +121,7 @@
/**
* Invalidates this controller, preventing future calls to send updates.
*/
- public void invalidate() {
- mTransitions.unregisterObserver(this);
+ public void invalidate(Transitions transitions) {
+ transitions.unregisterObserver(this);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 718c704..ab5c063 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -192,6 +192,8 @@
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
+ private HomeTransitionObserver mHomeTransitionObserver;
+
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
@@ -267,10 +269,11 @@
@NonNull DisplayController displayController,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
- @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor animExecutor,
+ @NonNull HomeTransitionObserver observer) {
this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor,
- new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));
+ new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit), observer);
}
public Transitions(@NonNull Context context,
@@ -283,7 +286,8 @@
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
+ @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ @NonNull HomeTransitionObserver observer) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -302,6 +306,7 @@
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
+ mHomeTransitionObserver = observer;
}
private void onInit() {
@@ -351,7 +356,7 @@
}
private ExternalInterfaceBinder createExternalInterface() {
- return new IShellTransitionsImpl(mContext, getMainExecutor(), this);
+ return new IShellTransitionsImpl(this);
}
@Override
@@ -1397,12 +1402,9 @@
private static class IShellTransitionsImpl extends IShellTransitions.Stub
implements ExternalInterfaceBinder {
private Transitions mTransitions;
- private final HomeTransitionObserver mHomeTransitionObserver;
- IShellTransitionsImpl(Context context, ShellExecutor executor, Transitions transitions) {
+ IShellTransitionsImpl(Transitions transitions) {
mTransitions = transitions;
- mHomeTransitionObserver = new HomeTransitionObserver(context, executor,
- mTransitions);
}
/**
@@ -1410,7 +1412,7 @@
*/
@Override
public void invalidate() {
- mHomeTransitionObserver.invalidate();
+ mTransitions.mHomeTransitionObserver.invalidate(mTransitions);
mTransitions = null;
}
@@ -1440,7 +1442,8 @@
public void setHomeTransitionListener(IHomeTransitionListener listener) {
executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
(transitions) -> {
- mHomeTransitionObserver.setHomeTransitionListener(listener);
+ transitions.mHomeTransitionObserver.setHomeTransitionListener(mTransitions,
+ listener);
});
}
}
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 716f8b8..e206039 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
@@ -82,6 +82,7 @@
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -238,7 +239,7 @@
mSplitScreenController = splitScreenController;
mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
@Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
if (visible) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null && DesktopModeStatus.isEnabled()
@@ -391,10 +392,10 @@
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
- mTaskOperations.closeTask(mTaskToken);
if (isTaskInSplitScreen(mTaskId)) {
- RunningTaskInfo remainingTask = getOtherSplitTask(mTaskId);
- mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
+ mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId);
+ } else {
+ mTaskOperations.closeTask(mTaskToken);
}
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
@@ -417,8 +418,12 @@
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
- mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
+ if (isTaskInSplitScreen(mTaskId)) {
+ mSplitScreenController.moveTaskToFullscreen(mTaskId);
+ } else {
+ mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
+ }
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
mDesktopTasksController.ifPresent(c -> {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index cc4db22..fff65f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -74,6 +74,7 @@
import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -373,7 +374,7 @@
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 7a8a2a9..ea7c0d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -75,24 +75,24 @@
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final DisplayController mDisplayController = mock(DisplayController.class);
+ private IHomeTransitionListener mListener;
private Transitions mTransition;
+ private HomeTransitionObserver mHomeTransitionObserver;
@Before
public void setUp() {
+ mListener = mock(IHomeTransitionListener.class);
+ when(mListener.asBinder()).thenReturn(mock(IBinder.class));
+
+ mHomeTransitionObserver = new HomeTransitionObserver(mContext, mMainExecutor);
mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class),
mOrganizer, mTransactionPool, mDisplayController, mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mHomeTransitionObserver);
+ mHomeTransitionObserver.setHomeTransitionListener(mTransition, mListener);
}
@Test
public void testHomeActivityWithOpenModeNotifiesHomeIsVisible() throws RemoteException {
- IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
- when(listener.asBinder()).thenReturn(mock(IBinder.class));
-
- HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
- mTransition);
- observer.setHomeTransitionListener(listener);
-
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -101,23 +101,16 @@
setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN);
- observer.onTransitionReady(mock(IBinder.class),
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
info,
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
- verify(listener, times(1)).onHomeVisibilityChanged(true);
+ verify(mListener, times(1)).onHomeVisibilityChanged(true);
}
@Test
public void testHomeActivityWithCloseModeNotifiesHomeIsNotVisible() throws RemoteException {
- IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
- when(listener.asBinder()).thenReturn(mock(IBinder.class));
-
- HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
- mTransition);
- observer.setHomeTransitionListener(listener);
-
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -126,38 +119,30 @@
setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_TO_BACK);
- observer.onTransitionReady(mock(IBinder.class),
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
info,
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
- verify(listener, times(1)).onHomeVisibilityChanged(false);
+ verify(mListener, times(1)).onHomeVisibilityChanged(false);
}
@Test
public void testNonHomeActivityDoesNotTriggerCallback() throws RemoteException {
- IHomeTransitionListener listener = mock(IHomeTransitionListener.class);
- when(listener.asBinder()).thenReturn(mock(IBinder.class));
-
- HomeTransitionObserver observer = new HomeTransitionObserver(mContext, mMainExecutor,
- mTransition);
- observer.setHomeTransitionListener(listener);
-
TransitionInfo info = mock(TransitionInfo.class);
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
when(change.getTaskInfo()).thenReturn(taskInfo);
when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
-
setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_UNDEFINED, TRANSIT_TO_BACK);
- observer.onTransitionReady(mock(IBinder.class),
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
info,
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
- verify(listener, times(0)).onHomeVisibilityChanged(anyBoolean());
+ verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index da83d4c0..4e300d9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -146,7 +146,7 @@
ShellInit shellInit = mock(ShellInit.class);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
// One from Transitions, one from RootTaskDisplayAreaOrganizer
verify(shellInit).addInitCallback(any(), eq(t));
verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
@@ -158,7 +158,7 @@
ShellController shellController = mock(ShellController.class);
final Transitions t = new Transitions(mContext, shellInit, shellController,
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
verify(shellController, times(1)).addExternalInterface(
eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
@@ -1075,10 +1075,10 @@
final Transitions transitions =
new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
new RecentsTransitionHandler(shellInit, transitions,
- mock(RecentTasksController.class));
+ mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
shellInit.init();
@@ -1623,7 +1623,7 @@
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor);
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 89c6ecc..88abf69 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -165,6 +165,7 @@
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
+ "com_android_systemui_shared_flags_lib",
"androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
@@ -443,6 +444,7 @@
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
+ "com_android_systemui_shared_flags_lib",
"flag-junit-base",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index dc4208e..e842967 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -1,3 +1,31 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+/**
+ * These flags are meant only for internal use in SystemUI and its variants.
+ * For shared, cross-process flags, see //frameworks/libs/systemui/aconfig
+ */
+
+package {
+ default_visibility: [
+ "//visibility:override",
+ "//frameworks/base/packages/SystemUI:__subpackages__",
+ ],
+}
+
aconfig_declarations {
name: "com_android_systemui_flags",
package: "com.android.systemui",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 46d418a..3780468 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -14,6 +14,7 @@
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -29,12 +30,9 @@
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-
-object Scenes {
- val Blank = SceneKey(name = "blank")
- val Communal = SceneKey(name = "communal")
-}
+import kotlinx.coroutines.flow.transform
object Communal {
object Elements {
@@ -43,7 +41,7 @@
}
val sceneTransitions = transitions {
- from(Scenes.Blank, to = Scenes.Communal) {
+ from(TransitionSceneKey.Blank, to = TransitionSceneKey.Communal) {
spec = tween(durationMillis = 500)
translate(Communal.Elements.Content, Edge.Right)
@@ -58,8 +56,14 @@
* handling and transitions before the full Flexiglass layout is ready.
*/
@Composable
-fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
- val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+fun CommunalContainer(
+ modifier: Modifier = Modifier,
+ viewModel: CommunalViewModel,
+) {
+ val currentScene: SceneKey by
+ viewModel.currentScene
+ .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() }
+ .collectAsState(TransitionSceneKey.Blank)
// Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
var showSceneTransitionLayout by remember { mutableStateOf(true) }
@@ -70,16 +74,19 @@
SceneTransitionLayout(
modifier = modifier.fillMaxSize(),
currentScene = currentScene,
- onChangeScene = setCurrentScene,
+ onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) },
transitions = sceneTransitions,
) {
- scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+ scene(
+ TransitionSceneKey.Blank,
+ userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal)
+ ) {
BlankScene { showSceneTransitionLayout = false }
}
scene(
- Scenes.Communal,
- userActions = mapOf(Swipe.Right to Scenes.Blank),
+ TransitionSceneKey.Communal,
+ userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank),
) {
CommunalScene(viewModel, modifier = modifier)
}
@@ -121,3 +128,17 @@
) {
Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
}
+
+// TODO(b/293899074): Remove these conversions once Compose can be used throughout SysUI.
+object TransitionSceneKey {
+ val Blank = CommunalSceneKey.Blank.toTransitionSceneKey()
+ val Communal = CommunalSceneKey.Communal.toTransitionSceneKey()
+}
+
+fun CommunalSceneKey.toTransitionSceneKey(): SceneKey {
+ return SceneKey(name = toString(), identity = this)
+}
+
+fun SceneKey.toCommunalSceneKey(): CommunalSceneKey {
+ return this.identity as CommunalSceneKey
+}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 9c368eb..5b59e7d 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -53,6 +53,7 @@
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
+ "tracinglib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
"androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt
index 7719246..637ce5f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt
@@ -19,7 +19,7 @@
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.forEach
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import java.lang.ref.WeakReference
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceContextElement.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceContextElement.kt
deleted file mode 100644
index 7d1b65a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceContextElement.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tracing
-
-import com.android.systemui.tracing.TraceUtils.Companion.instant
-import com.android.systemui.tracing.TraceUtils.Companion.traceCoroutine
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CopyableThreadContextElement
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/**
- * Used for safely persisting [TraceData] state when coroutines are suspended and resumed.
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @see traceCoroutine
- */
-@OptIn(DelicateCoroutinesApi::class)
-@ExperimentalCoroutinesApi
-class TraceContextElement(private val traceData: TraceData = TraceData()) :
- CopyableThreadContextElement<TraceData?> {
-
- companion object Key : CoroutineContext.Key<TraceContextElement>
-
- override val key: CoroutineContext.Key<TraceContextElement> = Key
-
- @OptIn(ExperimentalStdlibApi::class)
- override fun updateThreadContext(context: CoroutineContext): TraceData? {
- val oldState = threadLocalTrace.get()
- oldState?.endAllOnThread()
- threadLocalTrace.set(traceData)
- instant("resuming ${context[CoroutineDispatcher]}")
- traceData.beginAllOnThread()
- return oldState
- }
-
- @OptIn(ExperimentalStdlibApi::class)
- override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
- instant("suspending ${context[CoroutineDispatcher]}")
- traceData.endAllOnThread()
- threadLocalTrace.set(oldState)
- oldState?.beginAllOnThread()
- }
-
- override fun copyForChild(): CopyableThreadContextElement<TraceData?> {
- return TraceContextElement(traceData.copy())
- }
-
- override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
- return TraceContextElement(traceData.copy())
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceData.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceData.kt
deleted file mode 100644
index b68d38c..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceData.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tracing
-
-import android.os.Build
-import android.util.Log
-import com.android.systemui.tracing.TraceUtils.Companion.beginSlice
-import com.android.systemui.tracing.TraceUtils.Companion.endSlice
-import com.android.systemui.tracing.TraceUtils.Companion.traceCoroutine
-import kotlin.random.Random
-
-/**
- * Used for giving each thread a unique [TraceData] for thread-local storage. `null` by default.
- * [threadLocalTrace] can only be used when it is paired with a [TraceContextElement].
- *
- * This ThreadLocal will be `null` if either 1) we aren't in a coroutine, or 2) the coroutine we are
- * in does not have a [TraceContextElement].
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @see traceCoroutine
- */
-val threadLocalTrace = ThreadLocal<TraceData?>()
-
-/**
- * Used for storing trace sections so that they can be added and removed from the currently running
- * thread when the coroutine is suspended and resumed.
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @see traceCoroutine
- */
-class TraceData {
- private var slices = mutableListOf<TraceSection>()
-
- /** Adds current trace slices back to the current thread. Called when coroutine is resumed. */
- fun beginAllOnThread() {
- slices.forEach { beginSlice(it.name) }
- }
-
- /**
- * Removes all current trace slices from the current thread. Called when coroutine is suspended.
- */
- fun endAllOnThread() {
- for (i in 0..slices.size) {
- endSlice()
- }
- }
-
- /**
- * Creates a new trace section with a unique ID and adds it to the current trace data. The slice
- * will also be added to the current thread immediately. This slice will not propagate to parent
- * coroutines, or to child coroutines that have already started. The unique ID is used to verify
- * that the [endSpan] is corresponds to a [beginSpan].
- */
- fun beginSpan(name: String): Int {
- val newSlice = TraceSection(name, Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE))
- slices.add(newSlice)
- beginSlice(name)
- return newSlice.id
- }
-
- /**
- * Used by [TraceContextElement] when launching a child coroutine so that the child coroutine's
- * state is isolated from the parent.
- */
- fun copy(): TraceData {
- return TraceData().also { it.slices.addAll(slices) }
- }
-
- /**
- * Ends the trace section and validates it corresponds with an earlier call to [beginSpan]. The
- * trace slice will immediately be removed from the current thread. This information will not
- * propagate to parent coroutines, or to child coroutines that have already started.
- */
- fun endSpan(id: Int) {
- val v = slices.removeLast()
- if (v.id != id) {
- if (STRICT_MODE) {
- throw IllegalArgumentException(errorMsg)
- } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, errorMsg)
- }
- }
- endSlice()
- }
-
- companion object {
- private const val TAG = "TraceData"
- const val INVALID_SPAN = -1
- const val FIRST_VALID_SPAN = 1
-
- /**
- * If true, throw an exception instead of printing a warning when trace sections beginnings
- * and ends are mismatched.
- */
- private val STRICT_MODE = Build.IS_ENG
-
- private const val errorMsg =
- "Mismatched trace section. This likely means you are accessing the trace local " +
- "storage (threadLocalTrace) without a corresponding CopyableThreadContextElement." +
- " This could happen if you are using a global dispatcher like Dispatchers.IO." +
- " To fix this, use one of the coroutine contexts provided by the dagger scope " +
- "(e.g. \"@Main CoroutineContext\")."
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceSection.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceSection.kt
deleted file mode 100644
index 469d9a2..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceSection.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tracing
-
-import com.android.systemui.tracing.TraceUtils.Companion.traceCoroutine
-
-/**
- * Represents a section of code executing in a coroutine. This can be split up into multiple slices
- * on different threads as the coroutine is suspended and resumed.
- *
- * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
- * because [traceCoroutine] is a Public-API inline function.
- *
- * @param name the name of the slice to appear on the current thread's track.
- * @param id used for matching the beginning and end of trace sections and validating correctness
- * @see traceCoroutine
- */
-data class TraceSection(
- val name: String,
- val id: Int,
-)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceStateLogger.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceStateLogger.kt
deleted file mode 100644
index 3e235f5..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceStateLogger.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tracing
-
-import android.os.Trace
-
-/**
- * Utility class used to log state changes easily in a track with a custom name.
- *
- * Example of usage:
- * ```kotlin
- * class MyClass {
- * val screenStateLogger = TraceStateLogger("Screen state")
- *
- * fun onTurnedOn() { screenStateLogger.log("on") }
- * fun onTurnedOff() { screenStateLogger.log("off") }
- * }
- * ```
- *
- * This creates a new slice in a perfetto trace only if the state is different than the previous
- * one.
- */
-class TraceStateLogger(
- private val trackName: String,
- private val logOnlyIfDifferent: Boolean = true,
- private val instantEvent: Boolean = true
-) {
-
- private var previousValue: String? = null
-
- /** If needed, logs the value to a track with name [trackName]. */
- fun log(newValue: String) {
- if (instantEvent) {
- Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, newValue)
- }
- if (logOnlyIfDifferent && previousValue == newValue) return
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, 0)
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, newValue, 0)
- previousValue = newValue
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceUtils.kt
deleted file mode 100644
index 12a20ae..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/tracing/TraceUtils.kt
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tracing
-
-import android.os.Trace
-import android.os.TraceNameSupplier
-import android.util.Log
-import com.android.systemui.tracing.TraceData.Companion.FIRST_VALID_SPAN
-import com.android.systemui.tracing.TraceData.Companion.INVALID_SPAN
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-import kotlin.coroutines.coroutineContext
-import kotlin.random.Random
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-
-/**
- * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
- * after the passed block.
- */
-inline fun <T> traceSection(tag: String, block: () -> T): T =
- if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
- Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
- try {
- block()
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_APP)
- }
- } else {
- block()
- }
-
-class TraceUtils {
- companion object {
- const val TAG = "TraceUtils"
- private const val DEBUG_COROUTINE_TRACING = false
- const val DEFAULT_TRACK_NAME = "AsyncTraces"
-
- inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
- return Runnable { traceSection(tag) { block() } }
- }
-
- /**
- * Helper function for creating a Runnable object that implements TraceNameSupplier.
- *
- * This is useful for posting Runnables to Handlers with meaningful names.
- */
- inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
- return object : Runnable, TraceNameSupplier {
- override fun getTraceName(): String = tag
- override fun run() = block()
- }
- }
-
- /**
- * Cookie used for async traces. Shouldn't be public, but to use it inside inline methods
- * there is no other way around.
- */
- val lastCookie = AtomicInteger(0)
-
- /**
- * Creates an async slice in a track called "AsyncTraces".
- *
- * This can be used to trace coroutine code. Note that all usages of this method will appear
- * under a single track.
- */
- inline fun <T> traceAsync(method: String, block: () -> T): T =
- traceAsync(DEFAULT_TRACK_NAME, method, block)
-
- /**
- * Creates an async slice in a track with [trackName] while [block] runs.
- *
- * This can be used to trace coroutine code. [method] will be the name of the slice,
- * [trackName] of the track. The track is one of the rows visible in a perfetto trace inside
- * SystemUI process.
- */
- inline fun <T> traceAsync(trackName: String, method: String, block: () -> T): T {
- val cookie = lastCookie.incrementAndGet()
- Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie)
- try {
- return block()
- } finally {
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
- }
- }
-
- /**
- * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
- * tracing.
- *
- * @see traceCoroutine
- */
- inline fun CoroutineScope.launch(
- crossinline spanName: () -> String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> Unit
- ): Job = launch(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
- * tracing.
- *
- * @see traceCoroutine
- */
- inline fun CoroutineScope.launch(
- spanName: String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> Unit
- ): Job = launch(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
- * tracing
- *
- * @see traceCoroutine
- */
- inline fun <T> CoroutineScope.async(
- crossinline spanName: () -> String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> T
- ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
- * tracing.
- *
- * @see traceCoroutine
- */
- inline fun <T> CoroutineScope.async(
- spanName: String,
- context: CoroutineContext = EmptyCoroutineContext,
- // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
- crossinline block: suspend CoroutineScope.() -> T
- ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- inline fun <T> runBlocking(
- crossinline spanName: () -> String,
- context: CoroutineContext,
- crossinline block: suspend () -> T
- ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- inline fun <T> runBlocking(
- spanName: String,
- context: CoroutineContext,
- crossinline block: suspend CoroutineScope.() -> T
- ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- suspend inline fun <T> withContext(
- spanName: String,
- context: CoroutineContext,
- crossinline block: suspend CoroutineScope.() -> T
- ): T = withContext(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
- *
- * @see traceCoroutine
- */
- suspend inline fun <T> withContext(
- crossinline spanName: () -> String,
- context: CoroutineContext,
- crossinline block: suspend CoroutineScope.() -> T
- ): T = withContext(context) { traceCoroutine(spanName) { block() } }
-
- /**
- * A hacky way to propagate the value of the COROUTINE_TRACING flag for static usage in this
- * file. It should only every be set to true during startup. Once true, it cannot be set to
- * false again.
- */
- var coroutineTracingIsEnabled = false
- set(v) {
- if (v) field = true
- }
-
- /**
- * Traces a section of work of a `suspend` [block]. The trace sections will appear on the
- * thread that is currently executing the [block] of work. If the [block] is suspended, all
- * trace sections added using this API will end until the [block] is resumed, which could
- * happen either on this thread or on another thread. If a child coroutine is started, it
- * will inherit the trace sections of its parent. The child will continue to print these
- * trace sections whether or not the parent coroutine is still running them.
- *
- * The current [CoroutineContext] must have a [TraceContextElement] for this API to work.
- * Otherwise, the trace sections will be dropped.
- *
- * For example, in the following trace, Thread #1 ran some work, suspended, then continued
- * working on Thread #2. Meanwhile, Thread #2 created a new child coroutine which inherited
- * its trace sections. Then, the original coroutine resumed on Thread #1 before ending.
- * Meanwhile Thread #3 is still printing trace sections from its parent because they were
- * copied when it was created. There is no way for the parent to communicate to the child
- * that it marked these slices as completed. While this might seem counterintuitive, it
- * allows us to pinpoint the origin of the child coroutine's work.
- *
- * ```
- * Thread #1 | [==== Slice A ====] [==== Slice A ====]
- * | [==== B ====] [=== B ===]
- * --------------------------------------------------------------------------------------
- * Thread #2 | [====== Slice A ======]
- * | [========= B =========]
- * | [===== C ======]
- * --------------------------------------------------------------------------------------
- * Thread #3 | [== Slice A ==] [== Slice A ==]
- * | [===== B =====] [===== B =====]
- * | [===== C =====] [===== C =====]
- * | [=== D ===]
- * ```
- *
- * @param name The name of the code section to appear in the trace
- * @see endSlice
- * @see traceCoroutine
- */
- @OptIn(ExperimentalCoroutinesApi::class)
- suspend inline fun <T> traceCoroutine(
- spanName: Lazy<String>,
- crossinline block: suspend () -> T
- ): T {
- // For coroutine tracing to work, trace spans must be added and removed even when
- // tracing is not active (i.e. when TRACE_TAG_APP is disabled). Otherwise, when the
- // coroutine resumes when tracing is active, we won't know its name.
- val tracer = getTraceData(spanName)
- val coroutineSpanCookie = tracer?.beginSpan(spanName.value) ?: INVALID_SPAN
-
- // For now, also trace to "AsyncTraces". This will allow us to verify the correctness
- // of the COROUTINE_TRACING feature flag.
- val asyncTraceCookie =
- if (Trace.isTagEnabled(Trace.TRACE_TAG_APP))
- Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE)
- else INVALID_SPAN
- if (asyncTraceCookie != INVALID_SPAN) {
- Trace.asyncTraceForTrackBegin(
- Trace.TRACE_TAG_APP,
- DEFAULT_TRACK_NAME,
- spanName.value,
- asyncTraceCookie
- )
- }
- try {
- return block()
- } finally {
- if (asyncTraceCookie != INVALID_SPAN) {
- Trace.asyncTraceForTrackEnd(
- Trace.TRACE_TAG_APP,
- DEFAULT_TRACK_NAME,
- asyncTraceCookie
- )
- }
- tracer?.endSpan(coroutineSpanCookie)
- }
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- suspend fun getTraceData(spanName: Lazy<String>): TraceData? {
- if (!coroutineTracingIsEnabled) {
- logVerbose("Experimental flag COROUTINE_TRACING is off", spanName)
- } else if (coroutineContext[TraceContextElement] == null) {
- logVerbose("Current CoroutineContext is missing TraceContextElement", spanName)
- } else {
- return threadLocalTrace.get().also {
- if (it == null) logVerbose("ThreadLocal TraceData is null", spanName)
- }
- }
- return null
- }
-
- private fun logVerbose(logMessage: String, spanName: Lazy<String>) {
- if (DEBUG_COROUTINE_TRACING && Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "$logMessage. Dropping trace section: \"${spanName.value}\"")
- }
- }
-
- /** @see traceCoroutine */
- suspend inline fun <T> traceCoroutine(
- spanName: String,
- crossinline block: suspend () -> T
- ): T = traceCoroutine(lazyOf(spanName)) { block() }
-
- /** @see traceCoroutine */
- suspend inline fun <T> traceCoroutine(
- crossinline spanName: () -> String,
- crossinline block: suspend () -> T
- ): T = traceCoroutine(lazy(LazyThreadSafetyMode.PUBLICATION) { spanName() }) { block() }
-
- /**
- * Writes a trace message to indicate that a given section of code has begun running __on
- * the current thread__. This must be followed by a corresponding call to [endSlice] in a
- * reasonably short amount of time __on the same thread__ (i.e. _before_ the thread becomes
- * idle again and starts running other, unrelated work).
- *
- * Calls to [beginSlice] and [endSlice] may be nested, and they will render in Perfetto as
- * follows:
- * ```
- * Thread #1 | [==========================]
- * | [==============]
- * | [====]
- * ```
- *
- * This function is provided for convenience to wrap a call to [Trace.traceBegin], which is
- * more verbose to call than [Trace.beginSection], but has the added benefit of not throwing
- * an [IllegalArgumentException] if the provided string is longer than 127 characters. We
- * use the term "slice" instead of "section" to be consistent with Perfetto.
- *
- * # Avoiding malformed traces
- *
- * Improper usage of this API will lead to malformed traces with long slices that sometimes
- * never end. This will look like the following:
- * ```
- * Thread #1 | [===================================================================== ...
- * | [==============] [====================================== ...
- * | [=======] [======] [===================== ...
- * | [=======]
- * ```
- *
- * To avoid this, [beginSlice] and [endSlice] should never be called from `suspend` blocks
- * (instead, use [traceCoroutine] for tracing suspending functions). While it would be
- * technically okay to call from a suspending function if that function were to only wrap
- * non-suspending blocks with [beginSlice] and [endSlice], doing so is risky because suspend
- * calls could be mistakenly added to that block as the code is refactored.
- *
- * Additionally, it is _not_ okay to call [beginSlice] when registering a callback and match
- * it with a call to [endSlice] inside that callback, even if the callback runs on the same
- * thread. Doing so would cause malformed traces because the [beginSlice] wasn't closed
- * before the thread became idle and started running unrelated work.
- *
- * @param sliceName The name of the code section to appear in the trace
- * @see endSlice
- * @see traceCoroutine
- */
- fun beginSlice(sliceName: String) {
- Trace.traceBegin(Trace.TRACE_TAG_APP, sliceName)
- }
-
- /**
- * Writes a trace message to indicate that a given section of code has ended. This call must
- * be preceded by a corresponding call to [beginSlice]. See [beginSlice] for important
- * information regarding usage.
- *
- * @see beginSlice
- * @see traceCoroutine
- */
- fun endSlice() {
- Trace.traceEnd(Trace.TRACE_TAG_APP)
- }
-
- /**
- * Writes a trace message indicating that an instant event occurred on the current thread.
- * Unlike slices, instant events have no duration and do not need to be matched with another
- * call. Perfetto will display instant events using an arrow pointing to the timestamp they
- * occurred:
- * ```
- * Thread #1 | [==============] [======]
- * | [====] ^
- * | ^
- * ```
- *
- * @param eventName The name of the event to appear in the trace.
- */
- fun instant(eventName: String) {
- Trace.instant(Trace.TRACE_TAG_APP, eventName)
- }
-
- /**
- * Writes a trace message indicating that an instant event occurred on the given track.
- * Unlike slices, instant events have no duration and do not need to be matched with another
- * call. Perfetto will display instant events using an arrow pointing to the timestamp they
- * occurred:
- * ```
- * Async | [==============] [======]
- * Track | [====] ^
- * Name | ^
- * ```
- *
- * @param trackName The track where the event should appear in the trace.
- * @param eventName The name of the event to appear in the trace.
- */
- fun instantForTrack(trackName: String, eventName: String) {
- Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, eventName)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 52923a7..345f15c 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.authentication.domain.interactor
+import com.android.app.tracing.TraceUtils.Companion.async
+import com.android.app.tracing.TraceUtils.Companion.withContext
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
@@ -27,8 +29,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.tracing.TraceUtils.Companion.async
-import com.android.systemui.tracing.TraceUtils.Companion.withContext
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 949c117..10e7227 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -53,6 +53,7 @@
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
import com.android.app.animation.Interpolators
+import com.android.app.tracing.traceSection
import com.android.internal.annotations.VisibleForTesting
import com.android.keyguard.KeyguardPINView
import com.android.systemui.Dumpable
@@ -63,7 +64,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.res.R
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 485e512..9cab17e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -1,15 +1,28 @@
package com.android.systemui.communal.data.repository
import com.android.systemui.FeatureFlags
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
+
+ /**
+ * Target scene as requested by the underlying [SceneTransitionLayout] or through
+ * [setDesiredScene].
+ */
+ val desiredScene: StateFlow<CommunalSceneKey>
+
+ /** Updates the requested scene. */
+ fun setDesiredScene(desiredScene: CommunalSceneKey)
}
@SysUISingleton
@@ -23,4 +36,12 @@
get() =
featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
featureFlags.communalHub()
+
+ private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
+ MutableStateFlow(CommunalSceneKey.Blank)
+ override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow()
+
+ override fun setDesiredScene(desiredScene: CommunalSceneKey) {
+ _desiredScene.value = desiredScene
+ }
}
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 6238707..ccccbb6 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
@@ -19,10 +19,13 @@
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
/** Encapsulates business-logic related to communal mode. */
@SysUISingleton
@@ -47,4 +50,22 @@
* (have an allocated id).
*/
val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
+
+ /**
+ * Target scene as requested by the underlying [SceneTransitionLayout] or through
+ * [onSceneChanged].
+ */
+ val desiredScene: StateFlow<CommunalSceneKey> = communalRepository.desiredScene
+
+ /**
+ * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the
+ * [CommunalSceneKey.Communal].
+ */
+ val isCommunalShowing: Flow<Boolean> =
+ communalRepository.desiredScene.map { it == CommunalSceneKey.Communal }
+
+ /** Callback received whenever the [SceneTransitionLayout] finishes a scene transition. */
+ fun onSceneChanged(newScene: CommunalSceneKey) {
+ communalRepository.setDesiredScene(newScene)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
new file mode 100644
index 0000000..2be909c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.model
+
+/** Definition of the possible scenes for the communal UI. */
+sealed class CommunalSceneKey(
+ private val loggingName: String,
+) {
+ /** The communal scene containing the hub UI. */
+ object Communal : CommunalSceneKey("communal")
+
+ /** The default scene, shows nothing and is only there to allow swiping to communal. */
+ object Blank : CommunalSceneKey("blank")
+
+ override fun toString(): String {
+ return loggingName
+ }
+}
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 390b580..de9b563 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
@@ -20,11 +20,13 @@
import android.content.Context
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@SysUISingleton
@@ -33,7 +35,7 @@
constructor(
@Application private val context: Context,
private val appWidgetHost: AppWidgetHost,
- communalInteractor: CommunalInteractor,
+ private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
) {
/** Whether communal hub should show tutorial content. */
@@ -54,4 +56,9 @@
)
}
}
+
+ val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
+ fun onSceneChanged(scene: CommunalSceneKey) {
+ communalInteractor.onSceneChanged(scene)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 2ba687b..9e54b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -26,12 +26,12 @@
import android.os.Trace
import android.util.Log
import android.view.Display
+import com.android.app.tracing.traceSection
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.DisplayEvent
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.Compile
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 1c2ff4b..36d789e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -16,12 +16,14 @@
package com.android.systemui.flags;
+import static com.android.systemui.Flags.exampleFlag;
import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
import static com.android.systemui.flags.FlagManager.EXTRA_NAME;
import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+import static com.android.systemui.shared.Flags.exampleSharedFlag;
import static java.util.Objects.requireNonNull;
@@ -539,6 +541,8 @@
pw.println("can override: true");
pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());
pw.println("booleans: " + mBooleanFlagCache.size());
+ pw.println("example_flag: " + exampleFlag());
+ pw.println("example_shared_flag: " + exampleSharedFlag());
// Sort our flags for dumping
TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache);
dumpBooleanMap.forEach((key, value) -> pw.println(" sysui_flag_" + key + ": " + value));
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 4b98526..c490ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -30,13 +30,13 @@
import android.os.Binder
import android.os.Bundle
import android.util.Log
+import com.android.app.tracing.TraceUtils.Companion.runBlocking
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
-import com.android.systemui.tracing.TraceUtils.Companion.runBlocking
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index 5c1b731..f9b89b1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -17,7 +17,7 @@
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 0e795ae..c4962a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,6 +18,7 @@
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.app.tracing.TraceUtils.Companion.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
@@ -31,7 +32,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.tracing.TraceUtils.Companion.launch
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index fbe92e3..448411e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,6 +22,7 @@
import android.content.Context
import android.content.Intent
import android.util.Log
+import com.android.app.tracing.TraceUtils.Companion.withContext
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
@@ -48,7 +49,6 @@
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.tracing.TraceUtils.Companion.withContext
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 601aebe..3e8b49d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -56,6 +56,7 @@
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
@@ -86,7 +87,6 @@
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.tracing.traceSection
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index d3bc61b..a252470 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -34,6 +34,7 @@
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.traceSection
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -66,7 +67,6 @@
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.Utils
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.animation.requiresRemeasuring
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index f3d41aa..b1ff708 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -34,8 +34,11 @@
import android.view.ViewGroupOverlay
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
+import com.android.app.tracing.traceSection
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -53,10 +56,11 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
private val TAG: String = MediaHierarchyManager::class.java.simpleName
@@ -96,11 +100,13 @@
private val mediaManager: MediaDataManager,
private val keyguardViewController: KeyguardViewController,
private val dreamOverlayStateController: DreamOverlayStateController,
+ private val communalInteractor: CommunalInteractor,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
panelEventsEvents: ShadeStateEvents,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
+ @Application private val coroutineScope: CoroutineScope,
private val splitShadeStateController: SplitShadeStateController,
private val logger: MediaViewLogger,
) {
@@ -209,7 +215,7 @@
else result.setIntersect(animationStartClipping, targetClipping)
}
- private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1)
+ private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_COMMUNAL_HUB + 1)
/**
* The last location where this view was at before going to the desired location. This is useful
* for guided transitions.
@@ -401,6 +407,9 @@
}
}
+ /** Is the communal UI showing */
+ private var isCommunalShowing: Boolean = false
+
/**
* The current cross fade progress. 0.5f means it's just switching between the start and the end
* location and the content is fully faded, while 0.75f means that we're halfway faded in again
@@ -563,6 +572,14 @@
settingsObserver,
UserHandle.USER_ALL
)
+
+ // Listen to the communal UI state.
+ coroutineScope.launch {
+ communalInteractor.isCommunalShowing.collect { value ->
+ isCommunalShowing = value
+ updateDesiredLocation(forceNoAnimation = true)
+ }
+ }
}
private fun updateConfiguration() {
@@ -1115,6 +1132,9 @@
qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
+ // TODO(b/308813166): revisit logic once interactions between the hub and
+ // shade/keyguard state are finalized
+ isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
else -> LOCATION_QQS
}
@@ -1224,6 +1244,9 @@
/** Attached on the dream overlay */
const val LOCATION_DREAM_OVERLAY = 3
+ /** Attached to a view in the communal UI grid */
+ const val LOCATION_COMMUNAL_HUB = 4
+
/** Attached at the root of the hierarchy in an overlay */
const val IN_OVERLAY = -1000
@@ -1261,7 +1284,8 @@
MediaHierarchyManager.LOCATION_QS,
MediaHierarchyManager.LOCATION_QQS,
MediaHierarchyManager.LOCATION_LOCKSCREEN,
- MediaHierarchyManager.LOCATION_DREAM_OVERLAY
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
+ MediaHierarchyManager.LOCATION_COMMUNAL_HUB
]
)
@Retention(AnnotationRetention.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
index 0129c49..1f711cf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
@@ -16,8 +16,8 @@
package com.android.systemui.media.controls.ui
+import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.animation.MeasurementOutput
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 6b82746..1ec43c5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -20,6 +20,7 @@
import android.content.res.Configuration
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.app.tracing.traceSection
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
@@ -27,7 +28,6 @@
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tracing.traceSection
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionLayoutController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 20ea60f..16a703a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -20,11 +20,11 @@
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaLocation
+import com.android.systemui.res.R
import java.lang.IllegalArgumentException
import javax.inject.Inject
@@ -154,6 +154,8 @@
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
+ MediaHierarchyManager.LOCATION_COMMUNAL_HUB ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_COMMUNAL
else -> throw IllegalArgumentException("Unknown media carousel location $location")
}
logger.log(event)
@@ -276,6 +278,8 @@
MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
@UiEvent(doc = "The media carousel moved to the dream state")
MEDIA_CAROUSEL_LOCATION_DREAM(1040),
+ @UiEvent(doc = "The media carousel moved to the communal hub UI")
+ MEDIA_CAROUSEL_LOCATION_COMMUNAL(1520),
@UiEvent(doc = "A media recommendation card was added to the media carousel")
MEDIA_RECOMMENDATION_ADDED(1041),
@UiEvent(doc = "A media recommendation card was removed from the media carousel")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 888cd0b..8f752e5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -46,6 +46,7 @@
String QUICK_QS_PANEL = "media_quick_qs_panel";
String KEYGUARD = "media_keyguard";
String DREAM = "dream";
+ String COMMUNAL_HUB = "communal_Hub";
/** */
@Provides
@@ -87,6 +88,16 @@
return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
}
+ /** */
+ @Provides
+ @SysUISingleton
+ @Named(COMMUNAL_HUB)
+ static MediaHost providesCommunalMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+ MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+ MediaHostStatesManager statesManager) {
+ return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+ }
+
/** Provides a logging buffer related to the media tap-to-transfer chip on the sender device. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 1416c10..a950539 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -29,12 +29,12 @@
import android.view.RemoteAnimationTarget
import android.view.WindowManager
import android.view.WindowManagerGlobal
+import com.android.app.tracing.TraceUtils.Companion.launch
import com.android.internal.infra.ServiceConnector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.tracing.TraceUtils.Companion.launch
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 8b3548b..f56f416 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,7 +20,7 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.TraceUtils.Companion.launch
import kotlinx.coroutines.CoroutineScope
import java.util.function.Consumer
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index c6b2cf5..713ede6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import com.android.systemui.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.TraceUtils.Companion.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 385c813..38d00f7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -18,9 +18,9 @@
import android.media.MediaPlayer
import android.util.Log
+import com.android.app.tracing.TraceUtils.Companion.async
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.tracing.TraceUtils.Companion.async
import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ccac533..f6c25e0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -5,6 +5,7 @@
import android.util.Log
import android.view.Display
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import com.android.app.tracing.TraceUtils.Companion.launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
@@ -13,7 +14,6 @@
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
-import com.android.systemui.tracing.TraceUtils.Companion.launch
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
index b2bdb72..a1a2ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
@@ -21,7 +21,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.Assert
import com.android.systemui.util.ListenerSet
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import java.util.Collections.unmodifiableList
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicReference
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 860697b..64970e4 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
@@ -26,7 +26,7 @@
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index e8afac5..3809ea0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -28,7 +28,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Compile
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
index 1f6f42d..1dd6242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -23,7 +23,7 @@
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.util.NamedListenerSet
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* Set of classes that represent the various events that [NotifCollection] can dispatch to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index ca8e4fe..9fc4e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -23,7 +23,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.util.Compile
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* Converts a notif list (the output of the ShadeListBuilder) into a NodeSpec, an abstract
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index c2791a0..9b55210 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -26,7 +26,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index c6d8500..61e6f65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -18,7 +18,7 @@
import android.annotation.MainThread
import android.view.View
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* Given a "spec" that describes a "tree" of views, adds and removes views from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 2c59ee8..1935866 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -26,7 +26,7 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 8064f04..12ee54d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,13 +32,64 @@
*/
@SysUISingleton
class ActiveNotificationListRepository @Inject constructor() {
- /**
- * Notifications actively presented to the user in the notification stack.
- *
- * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
- */
- val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>())
+ /** Notifications actively presented to the user in the notification list. */
+ val activeNotifications = MutableStateFlow(ActiveNotificationsStore())
/** Are any already-seen notifications currently filtered out of the active list? */
val hasFilteredOutSeenNotifications = MutableStateFlow(false)
}
+
+/** Represents the notification list, comprised of groups and individual notifications. */
+data class ActiveNotificationsStore(
+ /** Notification groups, stored by key. */
+ val groups: Map<String, ActiveNotificationGroupModel> = emptyMap(),
+ /** All individual notifications, including top-level and group children, stored by key. */
+ val individuals: Map<String, ActiveNotificationModel> = emptyMap(),
+ /**
+ * Ordered top-level list of entries in the notification list (either groups or individual),
+ * represented as [Key]s. The associated [ActiveNotificationEntryModel] can be retrieved by
+ * invoking [get].
+ */
+ val renderList: List<Key> = emptyList(),
+) {
+ operator fun get(key: Key): ActiveNotificationEntryModel? {
+ return when (key) {
+ is Key.Group -> groups[key.key]
+ is Key.Individual -> individuals[key.key]
+ }
+ }
+
+ /** Unique key identifying an [ActiveNotificationEntryModel] in the store. */
+ sealed class Key {
+ data class Individual(val key: String) : Key()
+ data class Group(val key: String) : Key()
+ }
+
+ /** Mutable builder for an [ActiveNotificationsStore]. */
+ class Builder {
+ private val groups = mutableMapOf<String, ActiveNotificationGroupModel>()
+ private val individuals = mutableMapOf<String, ActiveNotificationModel>()
+ private val renderList = mutableListOf<Key>()
+
+ fun build() = ActiveNotificationsStore(groups, individuals, renderList)
+
+ fun addEntry(entry: ActiveNotificationEntryModel) {
+ when (entry) {
+ is ActiveNotificationModel -> addIndividualNotif(entry)
+ is ActiveNotificationGroupModel -> addNotifGroup(entry)
+ }
+ }
+
+ fun addIndividualNotif(notif: ActiveNotificationModel) {
+ renderList.add(Key.Individual(notif.key))
+ individuals[notif.key] = notif
+ }
+
+ fun addNotifGroup(group: ActiveNotificationGroupModel) {
+ renderList.add(Key.Group(group.key))
+ groups[group.key] = group
+ individuals[group.summary.key] = group.summary
+ group.children.forEach { individuals[it.key] = it }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index bfec60bc..85ba205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,6 +28,16 @@
repository: ActiveNotificationListRepository,
) {
/** Notifications actively presented to the user in the notification stack, in order. */
- val notifications: Flow<Collection<ActiveNotificationModel>> =
- repository.activeNotifications.map { it.values }
+ val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
+ repository.activeNotifications.map { store ->
+ store.renderList.map { key ->
+ val entry =
+ store[key]
+ ?: error("Could not find notification with key $key in active notif store.")
+ when (entry) {
+ is ActiveNotificationGroupModel -> entry.summary
+ is ActiveNotificationModel -> entry
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 604ecbc..6f4ed9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.flow.update
-private typealias ModelStore = Map<String, ActiveNotificationModel>
-
/**
* Logic for passing information from the
* [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
@@ -37,106 +40,166 @@
private val sectionStyleProvider: SectionStyleProvider,
) {
/**
- * Sets the current list of rendered notification entries as displayed in the notification
- * stack.
- *
- * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications
+ * Sets the current list of rendered notification entries as displayed in the notification list.
*/
fun setRenderedList(entries: List<ListEntry>) {
repository.activeNotifications.update { existingModels ->
- entries.associateBy(
- keySelector = { it.key },
- valueTransform = { it.toModel(existingModels) },
- )
+ buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+ entries.forEach(::addListEntry)
+ }
+ }
+ }
+}
+
+private fun buildActiveNotificationsStore(
+ existingModels: ActiveNotificationsStore,
+ sectionStyleProvider: SectionStyleProvider,
+ block: ActiveNotificationsStoreBuilder.() -> Unit
+): ActiveNotificationsStore =
+ ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+
+private class ActiveNotificationsStoreBuilder(
+ private val existingModels: ActiveNotificationsStore,
+ private val sectionStyleProvider: SectionStyleProvider,
+) {
+ private val builder = ActiveNotificationsStore.Builder()
+
+ fun build(): ActiveNotificationsStore = builder.build()
+
+ /**
+ * Convert a [ListEntry] into [ActiveNotificationEntryModel]s, and add them to the
+ * [ActiveNotificationsStore]. Special care is taken to avoid re-allocating models if the result
+ * would be identical to an existing model (at the expense of additional computations).
+ */
+ fun addListEntry(entry: ListEntry) {
+ when (entry) {
+ is GroupEntry -> {
+ entry.summary?.let { summary ->
+ val summaryModel = summary.toModel()
+ val childModels = entry.children.map { it.toModel() }
+ builder.addNotifGroup(
+ existingModels.createOrReuse(
+ key = entry.key,
+ summary = summaryModel,
+ children = childModels
+ )
+ )
+ }
+ }
+ else -> {
+ entry.representativeEntry?.let { notifEntry ->
+ builder.addIndividualNotif(notifEntry.toModel())
+ }
+ }
}
}
- private fun ListEntry.toModel(
- existingModels: ModelStore,
- ): ActiveNotificationModel =
+ private fun NotificationEntry.toModel(): ActiveNotificationModel =
existingModels.createOrReuse(
key = key,
- groupKey = representativeEntry?.sbn?.groupKey,
+ groupKey = sbn.groupKey,
isAmbient = sectionStyleProvider.isMinimized(this),
- isRowDismissed = representativeEntry?.isRowDismissed == true,
+ isRowDismissed = isRowDismissed,
isSilent = sectionStyleProvider.isSilent(this),
- isLastMessageFromReply = representativeEntry?.isLastMessageFromReply == true,
- isSuppressedFromStatusBar = representativeEntry?.shouldSuppressStatusBar() == true,
- isPulsing = representativeEntry?.showingPulsing() == true,
- aodIcon = representativeEntry?.icons?.aodIcon?.sourceIcon,
- shelfIcon = representativeEntry?.icons?.shelfIcon?.sourceIcon,
- statusBarIcon = representativeEntry?.icons?.statusBarIcon?.sourceIcon,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = shouldSuppressStatusBar(),
+ isPulsing = showingPulsing(),
+ aodIcon = icons.aodIcon?.sourceIcon,
+ shelfIcon = icons.shelfIcon?.sourceIcon,
+ statusBarIcon = icons.statusBarIcon?.sourceIcon,
)
+}
- private fun ModelStore.createOrReuse(
- key: String,
- groupKey: String?,
- isAmbient: Boolean,
- isRowDismissed: Boolean,
- isSilent: Boolean,
- isLastMessageFromReply: Boolean,
- isSuppressedFromStatusBar: Boolean,
- isPulsing: Boolean,
- aodIcon: Icon?,
- shelfIcon: Icon?,
- statusBarIcon: Icon?
- ): ActiveNotificationModel {
- return this[key]?.takeIf {
- it.isCurrent(
- key = key,
- groupKey = groupKey,
- isAmbient = isAmbient,
- isRowDismissed = isRowDismissed,
- isSilent = isSilent,
- isLastMessageFromReply = isLastMessageFromReply,
- isSuppressedFromStatusBar = isSuppressedFromStatusBar,
- isPulsing = isPulsing,
- aodIcon = aodIcon,
- shelfIcon = shelfIcon,
- statusBarIcon = statusBarIcon
- )
- }
- ?: ActiveNotificationModel(
- key = key,
- groupKey = groupKey,
- isAmbient = isAmbient,
- isRowDismissed = isRowDismissed,
- isSilent = isSilent,
- isLastMessageFromReply = isLastMessageFromReply,
- isSuppressedFromStatusBar = isSuppressedFromStatusBar,
- isPulsing = isPulsing,
- aodIcon = aodIcon,
- shelfIcon = shelfIcon,
- statusBarIcon = statusBarIcon,
- )
+private fun ActiveNotificationsStore.createOrReuse(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+): ActiveNotificationModel {
+ return individuals[key]?.takeIf {
+ it.isCurrent(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon
+ )
}
+ ?: ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
+}
- private fun ActiveNotificationModel.isCurrent(
- key: String,
- groupKey: String?,
- isAmbient: Boolean,
- isRowDismissed: Boolean,
- isSilent: Boolean,
- isLastMessageFromReply: Boolean,
- isSuppressedFromStatusBar: Boolean,
- isPulsing: Boolean,
- aodIcon: Icon?,
- shelfIcon: Icon?,
- statusBarIcon: Icon?
- ): Boolean {
- return when {
- key != this.key -> false
- groupKey != this.groupKey -> false
- isAmbient != this.isAmbient -> false
- isRowDismissed != this.isRowDismissed -> false
- isSilent != this.isSilent -> false
- isLastMessageFromReply != this.isLastMessageFromReply -> false
- isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
- isPulsing != this.isPulsing -> false
- aodIcon != this.aodIcon -> false
- shelfIcon != this.shelfIcon -> false
- statusBarIcon != this.statusBarIcon -> false
- else -> true
- }
+private fun ActiveNotificationModel.isCurrent(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+): Boolean {
+ return when {
+ key != this.key -> false
+ groupKey != this.groupKey -> false
+ isAmbient != this.isAmbient -> false
+ isRowDismissed != this.isRowDismissed -> false
+ isSilent != this.isSilent -> false
+ isLastMessageFromReply != this.isLastMessageFromReply -> false
+ isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
+ isPulsing != this.isPulsing -> false
+ aodIcon != this.aodIcon -> false
+ shelfIcon != this.shelfIcon -> false
+ statusBarIcon != this.statusBarIcon -> false
+ else -> true
+ }
+}
+
+private fun ActiveNotificationsStore.createOrReuse(
+ key: String,
+ summary: ActiveNotificationModel,
+ children: List<ActiveNotificationModel>,
+): ActiveNotificationGroupModel {
+ return groups[key]?.takeIf { it.isCurrent(key, summary, children) }
+ ?: ActiveNotificationGroupModel(key, summary, children)
+}
+
+private fun ActiveNotificationGroupModel.isCurrent(
+ key: String,
+ summary: ActiveNotificationModel,
+ children: List<ActiveNotificationModel>,
+): Boolean {
+ return when {
+ key != this.key -> false
+ summary != this.summary -> false
+ children != this.children -> false
+ else -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 05c88e0..9e8654a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -34,7 +34,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
index 00d873e..30e2f0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -48,7 +48,7 @@
showPulsing: Boolean = true,
): Flow<Set<ActiveNotificationModel>> {
return combine(
- activeNotificationsInteractor.notifications,
+ activeNotificationsInteractor.topLevelRepresentativeNotifications,
keyguardViewStateRepository.areNotificationsFullyHidden,
) { notifications, notifsFullyHidden ->
notifications
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 53631e3..82626acc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -61,7 +61,7 @@
darkIconInteractor.tintAreas,
darkIconInteractor.tintColor,
// Included so that tints are re-applied after entries are changed.
- notificationsInteractor.notifications,
+ notificationsInteractor.topLevelRepresentativeNotifications,
) { areas, tint, _ ->
NotificationIconColorLookup { viewBounds: Rect ->
if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
index c89f2fa..f096dd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -21,12 +21,12 @@
import android.util.Log
import android.util.StatsEvent
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.tracing.traceSection
import java.lang.Exception
import java.util.concurrent.Executor
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 78370ba..eb1c1ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -17,9 +17,17 @@
import android.graphics.drawable.Icon
-/** Model for entries in the notification stack. */
+/**
+ * Model for a top-level "entry" in the notification list, either an
+ * [individual notification][ActiveNotificationModel], or a [group][ActiveNotificationGroupModel].
+ */
+sealed class ActiveNotificationEntryModel
+
+/**
+ * Model for an individual notification in the notification list. These can appear as either an
+ * individual top-level notification, or as a child or summary of a [ActiveNotificationGroupModel].
+ */
data class ActiveNotificationModel(
- /** Notification key associated with this entry. */
val key: String,
/** Notification group key associated with this entry. */
val groupKey: String?,
@@ -47,4 +55,11 @@
val shelfIcon: Icon?,
/** Icon to display in the status bar. */
val statusBarIcon: Icon?,
-)
+) : ActiveNotificationEntryModel()
+
+/** Model for a group of notifications. */
+data class ActiveNotificationGroupModel(
+ val key: String,
+ val summary: ActiveNotificationModel,
+ val children: List<ActiveNotificationModel>,
+) : ActiveNotificationEntryModel()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index a98efba..af2ca26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
+import com.android.app.tracing.traceSection
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.reinflateAndBindLatest
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -31,7 +32,6 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tracing.traceSection
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
object NotificationListViewBinder {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 894549d..cba72d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -38,7 +38,7 @@
import com.android.systemui.util.leak.RotationUtils.Rotation
import com.android.systemui.util.leak.RotationUtils.getExactRotation
import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import java.io.PrintWriter
import java.lang.Math.max
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 1eea348..fb586ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -31,7 +31,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.tracing.TraceUtils
+import com.android.app.tracing.TraceUtils
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import com.android.systemui.flags.FeatureFlags
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index fa9256f..2797b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -162,6 +162,7 @@
.setContentText(context.getString(R.string.stylus_battery_low_subtitle))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setLocalOnly(true)
+ .setOnlyAlertOnce(true)
.setAutoCancel(true)
.build()
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index c3c0291..2afb435 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -48,7 +48,7 @@
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
import java.util.concurrent.Executor
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
index dfff7c4..12b8845 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -17,10 +17,10 @@
import android.content.Context
import android.os.Trace
+import com.android.app.tracing.TraceStateLogger
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.tracing.TraceStateLogger
import com.android.systemui.unfold.system.DeviceStateRepository
import com.android.systemui.unfold.updates.FoldStateRepository
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
index 1e0f420..45d742f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
@@ -16,7 +16,7 @@
package com.android.systemui.util
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import java.util.concurrent.CopyOnWriteArrayList
import java.util.function.Consumer
diff --git a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
index cec9580..f49d616 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
@@ -20,7 +20,7 @@
import android.util.AttributeSet
import android.view.Choreographer
import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/**
* [MotionLayout] that avoids remeasuring with the same inputs in the same frame.
diff --git a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
index afd2360..de92318 100644
--- a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt
@@ -13,7 +13,7 @@
import android.graphics.drawable.Drawable
import android.util.Log
import androidx.annotation.Px
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
class DrawableSize {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 5396bca..81737c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -7,8 +7,8 @@
import com.android.systemui.dagger.qualifiers.Tracing
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.tracing.TraceUtils.Companion.coroutineTracingIsEnabled
-import com.android.systemui.tracing.TraceContextElement
+import com.android.app.tracing.TraceUtils.Companion.coroutineTracingIsEnabled
+import com.android.app.tracing.TraceContextElement
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
index 2e355bd..eefd849 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/LottieViewWrapper.kt
@@ -18,7 +18,7 @@
import android.content.Context
import android.util.AttributeSet
import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
/** LottieAnimationView that traces each call to invalidate. */
open class LottieViewWrapper : LottieAnimationView {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
index 8f320a3..059ff56 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt
@@ -21,7 +21,7 @@
import com.android.internal.view.RotationPolicy
import com.android.internal.view.RotationPolicy.RotationPolicyListener
import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.tracing.traceSection
+import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 8e21f29..2f17b6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -86,4 +87,48 @@
val interactor = CommunalInteractor(communalRepository, widgetRepository)
assertThat(interactor.isCommunalEnabled).isFalse()
}
+
+ @Test
+ fun listensToSceneChange() =
+ testScope.runTest {
+ val interactor = CommunalInteractor(communalRepository, widgetRepository)
+ var desiredScene = collectLastValue(interactor.desiredScene)
+ runCurrent()
+ assertThat(desiredScene()).isEqualTo(CommunalSceneKey.Blank)
+
+ val targetScene = CommunalSceneKey.Communal
+ communalRepository.setDesiredScene(targetScene)
+ desiredScene = collectLastValue(interactor.desiredScene)
+ runCurrent()
+ assertThat(desiredScene()).isEqualTo(targetScene)
+ }
+
+ @Test
+ fun updatesScene() =
+ testScope.runTest {
+ val interactor = CommunalInteractor(communalRepository, widgetRepository)
+ val targetScene = CommunalSceneKey.Communal
+
+ interactor.onSceneChanged(targetScene)
+
+ val desiredScene = collectLastValue(communalRepository.desiredScene)
+ runCurrent()
+ assertThat(desiredScene()).isEqualTo(targetScene)
+ }
+
+ @Test
+ fun isCommunalShowing() =
+ testScope.runTest {
+ val interactor = CommunalInteractor(communalRepository, widgetRepository)
+
+ var isCommunalShowing = collectLastValue(interactor.isCommunalShowing)
+ runCurrent()
+ assertThat(isCommunalShowing()).isEqualTo(false)
+
+ interactor.onSceneChanged(CommunalSceneKey.Communal)
+
+ isCommunalShowing = collectLastValue(interactor.isCommunalShowing)
+ runCurrent()
+ assertThat(isCommunalShowing()).isEqualTo(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 5296f1a..5bfe569 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -25,6 +25,10 @@
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -46,6 +50,11 @@
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
@@ -63,6 +72,7 @@
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -71,6 +81,7 @@
@Mock private lateinit var lockHost: MediaHost
@Mock private lateinit var qsHost: MediaHost
@Mock private lateinit var qqsHost: MediaHost
+ @Mock private lateinit var hubModeHost: MediaHost
@Mock private lateinit var bypassController: KeyguardBypassController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@@ -93,10 +104,15 @@
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
+ private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
+ private val communalInteractor =
+ CommunalInteractor(communalRepository, FakeCommunalWidgetRepository())
private val notifPanelEvents = ShadeExpansionStateManager()
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
@Before
fun setup() {
@@ -117,11 +133,13 @@
mediaDataManager,
keyguardViewController,
dreamOverlayStateController,
+ communalInteractor,
configurationController,
wakefulnessLifecycle,
notifPanelEvents,
settings,
fakeHandler,
+ testScope.backgroundScope,
ResourcesSplitShadeStateController(),
logger,
)
@@ -131,6 +149,7 @@
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP)
setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
+ setupHost(hubModeHost, MediaHierarchyManager.LOCATION_COMMUNAL_HUB, COMMUNAL_TOP)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
whenever(mediaDataManager.hasActiveMedia()).thenReturn(true)
whenever(mediaCarouselController.mediaCarouselScrollHandler)
@@ -475,6 +494,33 @@
}
@Test
+ fun testCommunalLocation() =
+ testScope.runTest {
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ nullable(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+
+ communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+ runCurrent()
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QQS),
+ any(MediaHostState::class.java),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
fun testQsExpandedChanged_noQqsMedia() {
// When we are looking at QQS with active media
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
@@ -538,5 +584,6 @@
private const val QQS_TOP = 123
private const val QS_TOP = 456
private const val LOCKSCREEN_TOP = 789
+ private const val COMMUNAL_TOP = 111
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index ca8ea4e..b86f841 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -19,6 +19,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.byKey
import com.android.systemui.util.mockito.mock
@@ -40,9 +41,19 @@
@Test
fun setRenderedList_preservesOrdering() = runTest {
- val notifs by collectLastValue(notifsInteractor.notifications)
+ val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
val keys = (1..50).shuffled().map { "$it" }
- val entries = keys.map { mock<ListEntry> { whenever(key).thenReturn(it) } }
+ val entries =
+ keys.map {
+ mock<ListEntry> {
+ val mockRep = mock<NotificationEntry> {
+ whenever(key).thenReturn(it)
+ whenever(sbn).thenReturn(mock())
+ whenever(icons).thenReturn(mock())
+ }
+ whenever(representativeEntry).thenReturn(mockRep)
+ }
+ }
underTest.setRenderedList(entries)
assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index ec80e5f..f8252a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.notification.shared.byIsAmbient
@@ -77,7 +78,9 @@
fun setup() =
with(testComponent) {
activeNotificationListRepository.activeNotifications.value =
- testIcons.associateBy { it.key }
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
@@ -196,7 +199,9 @@
fun setup() =
with(testComponent) {
activeNotificationListRepository.activeNotifications.value =
- testIcons.associateBy { it.key }
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
@@ -318,7 +323,9 @@
fun setup() =
with(testComponent) {
activeNotificationListRepository.activeNotifications.value =
- testIcons.associateBy { it.key }
+ ActiveNotificationsStore.Builder()
+ .apply { testIcons.forEach(::addIndividualNotif) }
+ .build()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index ba68fbb..44acac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.phone.DozeParameters
@@ -342,14 +343,17 @@
val icon: Icon = mock()
shadeRepository.setLegacyShadeExpansion(0f)
activeNotificationsRepository.activeNotifications.value =
- listOf(
- activeNotificationModel(
- key = "notif1",
- groupKey = "group",
- statusBarIcon = icon
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
)
- )
- .associateBy { it.key }
+ }
+ .build()
val isolatedIcon by collectLastValue(underTest.isolatedIcon)
runCurrent()
@@ -368,14 +372,17 @@
val icon: Icon = mock()
shadeRepository.setLegacyShadeExpansion(.5f)
activeNotificationsRepository.activeNotifications.value =
- listOf(
- activeNotificationModel(
- key = "notif1",
- groupKey = "group",
- statusBarIcon = icon
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
)
- )
- .associateBy { it.key }
+ }
+ .build()
val isolatedIcon by collectLastValue(underTest.isolatedIcon)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
index 8fb5ff8..ba34ce6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
@@ -12,7 +12,7 @@
* permissions and limitations under the License.
*/
-package com.android.systemui.tracing
+package com.android.app.tracing
import android.os.Handler
import android.os.Looper
@@ -76,7 +76,7 @@
@Test
fun testLongTraceSection_doesNotThrow_whenUsingHelper() {
traceSection(SECTION_NAME_THATS_TOO_LONG) {
- Log.v(TAG, "com.android.systemui.tracing.traceSection() block.")
+ Log.v(TAG, "com.android.app.tracing.traceSection() block.")
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index e1c6dde..799bb40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -1,8 +1,17 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.shared.model.CommunalSceneKey
+import kotlinx.coroutines.flow.MutableStateFlow
+
/** Fake implementation of [CommunalRepository]. */
-class FakeCommunalRepository : CommunalRepository {
- override var isCommunalEnabled = false
+class FakeCommunalRepository(
+ override var isCommunalEnabled: Boolean = false,
+ override val desiredScene: MutableStateFlow<CommunalSceneKey> =
+ MutableStateFlow(CommunalSceneKey.Blank)
+) : CommunalRepository {
+ override fun setDesiredScene(desiredScene: CommunalSceneKey) {
+ this.desiredScene.value = desiredScene
+ }
fun setIsCommunalEnabled(value: Boolean) {
isCommunalEnabled = value
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 4380b42..1e4dd64 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -44,6 +44,7 @@
private static final int DIRECT_RECL_AND_THRASHING = 5;
private static final int LOW_MEM_AND_SWAP_UTIL = 6;
private static final int LOW_FILECACHE_AFTER_THRASHING = 7;
+ private static final int LOW_MEM = 8;
/**
* Processes the LMK_KILL_OCCURRED packet data
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 4538cad..0629e637 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -39,6 +39,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IAuthService;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
@@ -333,6 +334,33 @@
}
@Override
+ public long getLastAuthenticationTime(int userId,
+ @Authenticators.Types int authenticators) throws RemoteException {
+ // Only allow internal clients to call getLastAuthenticationTime with a different
+ // userId.
+ final int callingUserId = UserHandle.getCallingUserId();
+
+ if (userId != callingUserId) {
+ checkInternalPermission();
+ } else {
+ checkPermission();
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ // We can't do this above because we need the READ_DEVICE_CONFIG permission, which
+ // the calling user may not possess.
+ if (!Flags.lastAuthenticationTime()) {
+ throw new UnsupportedOperationException();
+ }
+
+ return mBiometricService.getLastAuthenticationTime(userId, authenticators);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public boolean hasEnrolledBiometrics(int userId, String opPackageName)
throws RemoteException {
checkInternalPermission();
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 1898b80..91a68ea 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -20,6 +20,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -39,6 +40,7 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -53,6 +55,7 @@
import android.hardware.camera2.CameraManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.security.keymint.HardwareAuthenticatorType;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -62,10 +65,16 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.security.Authorization;
+import android.security.GateKeeper;
import android.security.KeyStore;
+import android.security.authorization.IKeystoreAuthorization;
+import android.security.authorization.ResponseCode;
+import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Pair;
@@ -79,6 +88,7 @@
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -116,6 +126,10 @@
KeyStore mKeyStore;
@VisibleForTesting
ITrustManager mTrustManager;
+ @VisibleForTesting
+ IKeystoreAuthorization mKeystoreAuthorization;
+ @VisibleForTesting
+ IGateKeeperService mGateKeeper;
// Get and cache the available biometric authenticators and their associated info.
final ArrayList<BiometricSensor> mSensors = new ArrayList<>();
@@ -616,6 +630,64 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override // Binder call
+ public long getLastAuthenticationTime(
+ int userId, @Authenticators.Types int authenticators) {
+ super.getLastAuthenticationTime_enforcePermission();
+
+ if (!Flags.lastAuthenticationTime()) {
+ throw new UnsupportedOperationException();
+ }
+
+ Slogf.d(TAG, "getLastAuthenticationTime(userId=%d, authenticators=0x%x)",
+ userId, authenticators);
+
+ final long secureUserId;
+ try {
+ secureUserId = mGateKeeper.getSecureUserId(userId);
+ } catch (RemoteException e) {
+ Slogf.w(TAG, "Failed to get secure user id for " + userId, e);
+ return BIOMETRIC_NO_AUTHENTICATION;
+ }
+
+ if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) {
+ Slogf.w(TAG, "No secure user id for " + userId);
+ return BIOMETRIC_NO_AUTHENTICATION;
+ }
+
+ ArrayList<Integer> hardwareAuthenticators = new ArrayList<>(2);
+
+ if ((authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) {
+ hardwareAuthenticators.add(HardwareAuthenticatorType.PASSWORD);
+ }
+
+ if ((authenticators & Authenticators.BIOMETRIC_STRONG) != 0) {
+ hardwareAuthenticators.add(HardwareAuthenticatorType.FINGERPRINT);
+ }
+
+ if (hardwareAuthenticators.isEmpty()) {
+ throw new IllegalArgumentException("authenticators must not be empty");
+ }
+
+ int[] authTypesArray = hardwareAuthenticators.stream()
+ .mapToInt(Integer::intValue)
+ .toArray();
+ try {
+ return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error getting last auth time: " + e);
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ } catch (ServiceSpecificException e) {
+ // This is returned when the feature flag test fails in keystore2
+ if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
+ throw new UnsupportedOperationException();
+ }
+
+ return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
@@ -937,6 +1009,14 @@
return ActivityManager.getService();
}
+ public IKeystoreAuthorization getKeystoreAuthorizationService() {
+ return Authorization.getService();
+ }
+
+ public IGateKeeperService getGateKeeperService() {
+ return GateKeeper.getService();
+ }
+
public ITrustManager getTrustManager() {
return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE));
}
@@ -1050,6 +1130,8 @@
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
mBiometricCameraManager = injector.getBiometricCameraManager(context);
+ mKeystoreAuthorization = injector.getKeystoreAuthorizationService();
+ mGateKeeper = injector.getGateKeeperService();
try {
injector.getActivityManagerService().registerUserSwitchObserver(
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4579cc1..9f3e162 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -291,6 +291,10 @@
return name + "[debugOnly]";
}
+ private boolean callerIsRealCaller() {
+ return mCallingUid == mRealCallingUid;
+ }
+
private String dump(BalVerdict resultIfPiCreatorAllowsBal,
BalVerdict resultIfPiSenderAllowsBal) {
return " [callingPackage: " + getDebugPackageName(mCallingPackage, mCallingUid)
@@ -422,7 +426,7 @@
}
BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
- BalVerdict resultForRealCaller = callingUid == realCallingUid && resultForCaller.allows()
+ BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
? resultForCaller // no need to calculate again
// otherwise we might need to recalculate because the logic is not the same
: checkBackgroundActivityStartAllowedBySender(state, checkedOptions);
@@ -636,16 +640,14 @@
BalVerdict checkBackgroundActivityStartAllowedBySender(
BalState state,
ActivityOptions checkedOptions) {
- int realCallingUid = state.mRealCallingUid;
- BackgroundStartPrivileges backgroundStartPrivileges = state.mBackgroundStartPrivileges;
if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions)
&& ActivityManager.checkComponentPermission(
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
- realCallingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+ state.mRealCallingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
/*background*/ false,
- "realCallingUid has BAL permission. realCallingUid: " + realCallingUid);
+ "realCallingUid has BAL permission.");
}
// don't abort if the realCallingUid has a visible window
@@ -653,26 +655,23 @@
if (state.mRealCallingUidHasAnyVisibleWindow) {
return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
/*background*/ false,
- "realCallingUid has visible (non-toast) window. realCallingUid: "
- + realCallingUid);
+ "realCallingUid has visible (non-toast) window.");
}
// if the realCallingUid is a persistent system process, abort if the IntentSender
// wasn't allowed to start an activity
if (state.mIsRealCallingUidPersistentSystemProcess
- && backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
+ && state.mBackgroundStartPrivileges.allowsBackgroundActivityStarts()) {
return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
/*background*/ false,
"realCallingUid is persistent system process AND intent "
- + "sender allowed (allowBackgroundActivityStart = true). "
- + "realCallingUid: " + realCallingUid);
+ + "sender allowed (allowBackgroundActivityStart = true).");
}
// don't abort if the realCallingUid is an associated companion app
if (mService.isAssociatedCompanionApp(
- UserHandle.getUserId(realCallingUid), realCallingUid)) {
+ UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
return new BalVerdict(BAL_ALLOW_PENDING_INTENT,
/*background*/ false,
- "realCallingUid is a companion app. "
- + "realCallingUid: " + realCallingUid);
+ "realCallingUid is a companion app.");
}
// don't abort if the callerApp or other processes of that uid are allowed in any way
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3497353..95e2515 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1144,16 +1144,12 @@
if (pipTask == null) {
break;
}
- ActivityRecord[] pipActivity = new ActivityRecord[1];
- pipTask.forAllActivities((activity) -> {
- if (activity.pictureInPictureArgs != null) {
- pipActivity[0] = activity;
- }
- });
+ ActivityRecord pipActivity = pipTask.getActivity(
+ (activity) -> activity.pictureInPictureArgs != null);
Rect entryBounds = hop.getBounds();
mService.mRootWindowContainer.moveActivityToPinnedRootTask(
- pipActivity[0], null /* launchIntoPipHostActivity */,
+ pipActivity, null /* launchIntoPipHostActivity */,
"moveActivityToPinnedRootTask", null /* transition */, entryBounds);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
break;
diff --git a/services/midi/OWNERS b/services/midi/OWNERS
index f4d51f9..683cae1 100644
--- a/services/midi/OWNERS
+++ b/services/midi/OWNERS
@@ -1 +1,3 @@
philburk@google.com
+robertwu@google.com
+elaurent@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index f88afe7..a78f2dc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -41,6 +41,8 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -53,6 +55,7 @@
import android.os.Binder;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -84,6 +87,8 @@
@Rule
public MockitoRule mockitorule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private Context mContext;
@Mock
@@ -418,6 +423,37 @@
eq(callback));
}
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetLastAuthenticationTime_flaggedOff_throwsUnsupportedOperationException()
+ throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ mAuthService.mImpl.getLastAuthenticationTime(0,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void testGetLastAuthenticationTime_flaggedOn_callsBiometricService()
+ throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ final int userId = 0;
+ final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
+
+ mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators);
+
+ waitForIdle();
+ verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators));
+ }
+
private static void setInternalAndTestBiometricPermissions(
Context context, boolean hasPermission) {
for (String p : List.of(TEST_BIOMETRIC, MANAGE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 0230d77..408442b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -62,6 +62,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -71,12 +72,17 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.keymaster.HardwareAuthenticatorType;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.security.GateKeeper;
import android.security.KeyStore;
+import android.security.authorization.IKeystoreAuthorization;
+import android.service.gatekeeper.IGateKeeperService;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
@@ -92,6 +98,7 @@
import com.android.server.biometrics.sensors.LockoutTracker;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
@@ -105,6 +112,9 @@
@SmallTest
public class BiometricServiceTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final String TEST_PACKAGE_NAME = "test_package";
private static final long TEST_REQUEST_ID = 44;
@@ -162,10 +172,16 @@
@Mock
private BiometricCameraManager mBiometricCameraManager;
+ @Mock
+ private IKeystoreAuthorization mKeystoreAuthService;
+
+ @Mock
+ private IGateKeeperService mGateKeeperService;
+
BiometricContextProvider mBiometricContextProvider;
@Before
- public void setUp() {
+ public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
resetReceivers();
@@ -215,6 +231,9 @@
mStatusBarService, null /* handler */,
mAuthSessionCoordinator);
when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
+ when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService);
+ when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
+ when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
@@ -1751,6 +1770,44 @@
verifyNoMoreInteractions(callback);
}
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetLastAuthenticationTime_flagOff_throwsUnsupportedOperationException()
+ throws RemoteException {
+ mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG);
+ }
+
+ @Test
+ public void testGetLastAuthenticationTime_flagOn_callsKeystoreAuthorization()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+
+ final int[] hardwareAuthenticators = new int[] {
+ HardwareAuthenticatorType.PASSWORD,
+ HardwareAuthenticatorType.FINGERPRINT
+ };
+
+ final int userId = 0;
+ final long secureUserId = mGateKeeperService.getSecureUserId(userId);
+
+ assertNotEquals(GateKeeper.INVALID_SECURE_USER_ID, secureUserId);
+
+ final long expectedResult = 31337L;
+
+ when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
+ .thenReturn(expectedResult);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+
+ final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId,
+ Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
+
+ assertEquals(expectedResult, result);
+ verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators));
+ }
+
// Helper methods
private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index cb37821..59dc689 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -313,7 +313,8 @@
@Test
public void testBuilderAndGettersSafeModeDisabled() {
- final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build();
+ final VcnGatewayConnectionConfig config =
+ newBuilderMinimal().setSafeModeEnabled(false).build();
assertFalse(config.isSafeModeEnabled());
}
@@ -335,7 +336,8 @@
@Test
public void testPersistableBundleSafeModeDisabled() {
- final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build();
+ final VcnGatewayConnectionConfig config =
+ newBuilderMinimal().setSafeModeEnabled(false).build();
assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
}
@@ -456,7 +458,7 @@
assertEquals(config.isSafeModeEnabled(), configEqual.isSafeModeEnabled());
final VcnGatewayConnectionConfig configNotEqual =
- newBuilderMinimal().enableSafeMode(false).build();
+ newBuilderMinimal().setSafeModeEnabled(false).build();
assertEquals(config, configEqual);
assertNotEquals(config, configNotEqual);
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index bf73198..f846164 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -661,7 +661,7 @@
throws Exception {
final VcnGatewayConnectionConfig config =
VcnGatewayConnectionConfigTest.newTestBuilderMinimal()
- .enableSafeMode(safeModeEnabledByCaller)
+ .setSafeModeEnabled(safeModeEnabledByCaller)
.build();
final VcnGatewayConnection.Dependencies deps =
mock(VcnGatewayConnection.Dependencies.class);