Merge "Remove redundant CREATE_VIRTUAL_DEVICE permission enforcement" into main
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index e5389b4..11c5b51 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -75,3 +75,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enforce_quota_policy_to_fgs_jobs"
+ namespace: "backstage_power"
+ description: "Applies the normal quota policy to FGS jobs"
+ bug: "341201311"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index a1c72fb..03a3a0d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -99,10 +99,10 @@
* the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
* not be allowed to run more than 20 jobs within the past 10 minutes.
*
- * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
- * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state do not count
- * towards any quota and are not restricted regardless of the app's state change.
+ * Jobs are throttled while an app is not in a TOP or BOUND_TOP state. All jobs are allowed to run
+ * freely when an app enters the TOP or BOUND_TOP state and are restricted when the app leaves those
+ * states. However, jobs that are started while the app is in the TOP state do not count towards any
+ * quota and are not restricted regardless of the app's state change.
*
* Jobs will not be throttled when the device is charging. The device is considered to be charging
* once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
@@ -567,6 +567,11 @@
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+ if (Flags.enforceQuotaPolicyToFgsJobs()) {
+ ActivityManager.getService().registerUidObserver(new QcUidObserver(),
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_BOUND_TOP, null);
+ }
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, null);
@@ -2706,6 +2711,12 @@
}
}
+ @VisibleForTesting
+ int getProcessStateQuotaFreeThreshold() {
+ return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP :
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
private class QcHandler extends Handler {
QcHandler(Looper looper) {
@@ -2832,15 +2843,15 @@
mTopAppCache.put(uid, true);
mTopAppGraceCache.delete(uid);
if (mForegroundUids.get(uid)) {
- // Went from FGS to TOP. We don't need to reprocess timers or
- // jobs.
+ // Went from a process state with quota free to TOP. We don't
+ // need to reprocess timers or jobs.
break;
}
mForegroundUids.put(uid, true);
isQuotaFree = true;
} else {
final boolean reprocess;
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
reprocess = !mForegroundUids.get(uid);
mForegroundUids.put(uid, true);
isQuotaFree = true;
diff --git a/core/java/android/app/AppOps.md b/core/java/android/app/AppOps.md
index 7b11a03..535d62c 100644
--- a/core/java/android/app/AppOps.md
+++ b/core/java/android/app/AppOps.md
@@ -119,20 +119,20 @@
In addition to proc state, the `AppOpsService` also receives process capability update from the
`ActivityManagerService`. Proc capability specifies what while-in-use(`MODE_FOREGROUND`) operations
the proc is allowed to perform in its current proc state. There are three proc capabilities
- defined so far:
+ defined so far:
`PROCESS_CAPABILITY_FOREGROUND_LOCATION`, `PROCESS_CAPABILITY_FOREGROUND_CAMERA` and
`PROCESS_CAPABILITY_FOREGROUND_MICROPHONE`, they correspond to the while-in-use operation of
location, camera and microphone (microphone is `RECORD_AUDIO`).
In `ActivityManagerService`, `PROCESS_STATE_TOP` and `PROCESS_STATE_PERSISTENT` have all
three capabilities, `PROCESS_STATE_FOREGROUND_SERVICE` has capabilities defined by
- `foregroundServiceType` that is specified in foreground service's manifest file. A client process
+ `foregroundServiceType` that is specified in foreground service's manifest file. A client process
can pass its capabilities to service using `BIND_INCLUDE_CAPABILITIES` flag.
The proc state and capability are used for two use cases: Firstly, Tracking remembers the proc state
for each tracked event. Secondly, `noteOp`/`checkOp` calls for app-op that are set to
`MODE_FOREGROUND` are translated using the `AppOpsService.UidState.evalMode` method into
- `MODE_ALLOWED` when the app has the capability and `MODE_IGNORED` when the app does not have the
+ `MODE_ALLOWED` when the app has the capability and `MODE_IGNORED` when the app does not have the
capability. `checkOpRaw` calls are not affected.
The current proc state and capability for an app can be read from `dumpsys appops`.
@@ -284,7 +284,7 @@
##### Self data accesses
This is similar to the [synchronous data access](#synchronous-data-accesses) case only that the data
-provider and client are in the same process. In this case Android's RPC code is no involved and
+provider and client are in the same process. In this case Android's RPC code is not involved and
`AppOpsManager.noteOp` directly triggers `OnOpNotedCallback.onSelfNoted`. This should be a uncommon
case as it is uncommon for an app to provide data, esp. to itself.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index dcf82bf..ff0a3dd 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -47,13 +47,6 @@
}
flag {
- name: "start_user_before_scheduled_alarms"
- namespace: "multiuser"
- description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
- bug: "314907186"
-}
-
-flag {
name: "add_ui_for_sounds_from_background_users"
namespace: "multiuser"
description: "Allow foreground user to dismiss sounds that are coming from background users"
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6c1aa90..75ffcc3 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -461,6 +461,16 @@
public abstract void stylusGestureStarted(long eventTime);
/**
+ * Called by {@link com.android.server.wm.ContentRecorder} to verify whether
+ * the display is allowed to mirror primary display's content.
+ * @param displayId the id of the display where we mirror to.
+ * @return true if the mirroring dialog is confirmed (display is enabled), or
+ * {@link com.android.server.display.ExternalDisplayPolicy#ENABLE_ON_CONNECT}
+ * system property is enabled.
+ */
+ public abstract boolean isDisplayReadyForMirroring(int displayId);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 177ee6f..897ce4a 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -24,6 +24,8 @@
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
+import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
+import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
@@ -363,6 +365,22 @@
}
/**
+ * Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
+ * @hide
+ */
+ public static boolean isMouseReverseVerticalScrollingFeatureFlagEnabled() {
+ return mouseReverseVerticalScrolling();
+ }
+
+ /**
+ * Returns true if the feature flag for mouse swap primary button is enabled.
+ * @hide
+ */
+ public static boolean isMouseSwapPrimaryButtonFeatureFlagEnabled() {
+ return mouseSwapPrimaryButton();
+ }
+
+ /**
* Returns true if the touchpad visualizer is allowed to appear.
*
* @param context The application context.
@@ -501,6 +519,86 @@
}
/**
+ * Whether mouse vertical scrolling is enabled, this applies only to connected mice.
+ *
+ * @param context The application context.
+ * @return Whether the mouse will have its vertical scrolling reversed
+ * (scroll down to move up).
+ *
+ * @hide
+ */
+ public static boolean isMouseReverseVerticalScrollingEnabled(@NonNull Context context) {
+ if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, 0, UserHandle.USER_CURRENT)
+ != 0;
+ }
+
+ /**
+ * Sets whether the connected mouse will have its vertical scrolling reversed.
+ *
+ * @param context The application context.
+ * @param reverseScrolling Whether reverse scrolling is enabled.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setMouseReverseVerticalScrolling(@NonNull Context context,
+ boolean reverseScrolling) {
+ if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) {
+ return;
+ }
+
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, reverseScrolling ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Whether the primary mouse button is swapped on connected mice.
+ *
+ * @param context The application context.
+ * @return Whether mice will have their primary buttons swapped, so that left clicking will
+ * perform the secondary action (e.g. show menu) and right clicking will perform the primary
+ * action.
+ *
+ * @hide
+ */
+ public static boolean isMouseSwapPrimaryButtonEnabled(@NonNull Context context) {
+ if (!isMouseSwapPrimaryButtonFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, 0, UserHandle.USER_CURRENT)
+ != 0;
+ }
+
+ /**
+ * Sets whether mice will have their primary buttons swapped between left and right
+ * clicks.
+ *
+ * @param context The application context.
+ * @param swapPrimaryButton Whether swapping the primary button is enabled.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setMouseSwapPrimaryButton(@NonNull Context context,
+ boolean swapPrimaryButton) {
+ if (!isMouseSwapPrimaryButtonFeatureFlagEnabled()) {
+ return;
+ }
+
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, swapPrimaryButton ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Whether Accessibility bounce keys feature is enabled.
*
* <p>
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3ae9511..f1964e7 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6368,8 +6368,12 @@
Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
}
- /** @hide */
- public static final void invalidateUserSerialNumberCache() {
+
+ /**
+ * This method is used to invalidate caches, when user was added or removed.
+ * @hide
+ */
+ public static final void invalidateCacheOnUserListChange() {
UserManagerCache.invalidateUserSerialNumber();
}
@@ -6382,7 +6386,7 @@
* @hide
*/
@UnsupportedAppUsage
- @CachedProperty(modsFlagOnOrNone = {})
+ @CachedProperty(modsFlagOnOrNone = {}, api = "user_manager_users")
public int getUserSerialNumber(@UserIdInt int userId) {
// Read only flag should is to fix early access to this API
// cacheUserSerialNumber to be removed after the
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index a1bfe39..81987907 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -232,3 +232,19 @@
bug: "361329788"
is_exported: true
}
+
+flag {
+ name: "enable_angle_allow_list"
+ namespace: "gpu"
+ description: "Whether to read from angle allowlist to determine if app should use ANGLE"
+ is_fixed_read_only: true
+ bug: "370845648"
+}
+
+flag {
+ name: "api_for_backported_fixes"
+ namespace: "media_reliability"
+ description: "Public API app developers use to check if a known issue is fixed on a device."
+ bug: "308461809"
+ is_exported: true
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 271970b..1d8fcec 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -261,7 +261,7 @@
is_fixed_read_only: true
namespace: "permissions"
description: "If proc state is decreasing over the restriction threshold and capability is changed, delay if no new capabilities are added"
- bug: "308573169"
+ bug: "347891382"
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1a15d09..594005c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2351,6 +2351,11 @@
/**
* Activity Action: Show the permission screen for allowing apps to post promoted notifications.
+ * Properly formatted priority notifications are elevated in appearance. For example they may be
+ * able to use colors, have richer progress bars, show as chips in the status bar, and/or
+ * permanently appear on always-on-displays. This functionality is intended to be reserved for
+ * user initiated ongoing activities like navigation, phone calls, and ride sharing.
+ *
* <p>
* Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
* <p>
@@ -6205,6 +6210,25 @@
public static final String TOUCHPAD_RIGHT_CLICK_ZONE = "touchpad_right_click_zone";
/**
+ * Whether to enable reversed vertical scrolling for connected mice.
+ *
+ * When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
+ * @hide
+ */
+ public static final String MOUSE_REVERSE_VERTICAL_SCROLLING =
+ "mouse_reverse_vertical_scrolling";
+
+ /**
+ * Whether to enable swapping the primary button for connected mice.
+ *
+ * When enabled, right clicking will be the primary button and left clicking will be the
+ * secondary button (e.g. show menu).
+ * @hide
+ */
+ public static final String MOUSE_SWAP_PRIMARY_BUTTON =
+ "mouse_swap_primary_button";
+
+ /**
* Pointer fill style, specified by
* {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants.
*
@@ -6442,6 +6466,8 @@
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
+ PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING);
+ PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON);
}
/**
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index e795e809..9779dc0e 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -220,6 +220,15 @@
}
optional Touchpad touchpad = 36;
+ message Mouse {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto reverse_vertical_scrolling = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+
+ optional Mouse mouse = 38;
+
optional SettingProto tty_mode = 31 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Vibrate {
@@ -277,5 +286,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 38;
+ // Next tag = 39;
}
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d850f86..85ff846 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -60,7 +60,6 @@
public void testProcState() throws Exception {
// For the moment mostly want to confirm we don't crash
assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
- assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index b972882..cd52421 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -111,12 +111,6 @@
assertEquals(config.reqKeyboardType, vconfig.keyboard);
assertEquals(config.reqTouchScreen, vconfig.touchscreen);
assertEquals(config.reqNavigation, vconfig.navigation);
- if (vconfig.navigation == Configuration.NAVIGATION_NONAV) {
- assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV);
- }
- if (vconfig.keyboard != Configuration.KEYBOARD_UNDEFINED) {
- assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD);
- }
}
@SmallTest
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 4428ade..24e14e6 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -63,7 +63,7 @@
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
- method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int, int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling();
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 1eae3c6..8535e4a 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -54,5 +54,5 @@
void setAutoChangeStatus(boolean state);
boolean isAutoChangeEnabled();
List<String> getRoutingStatus();
- void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech);
+ void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index fb63b5c..bc410c7 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -647,24 +647,29 @@
* {@link ProtocolAndTechnologyRoute}
* @param emptyAid Zero-length AID route destination, where the possible inputs are defined in
* {@link ProtocolAndTechnologyRoute}
+ * @param systemCode System Code route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}
*/
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
public void overwriteRoutingTable(
@CardEmulation.ProtocolAndTechnologyRoute int protocol,
@CardEmulation.ProtocolAndTechnologyRoute int technology,
- @CardEmulation.ProtocolAndTechnologyRoute int emptyAid) {
+ @CardEmulation.ProtocolAndTechnologyRoute int emptyAid,
+ @CardEmulation.ProtocolAndTechnologyRoute int systemCode) {
String protocolRoute = routeIntToString(protocol);
String technologyRoute = routeIntToString(technology);
String emptyAidRoute = routeIntToString(emptyAid);
+ String systemCodeRoute = routeIntToString(systemCode);
NfcAdapter.callService(() ->
NfcAdapter.sCardEmulationService.overwriteRoutingTable(
mContext.getUser().getIdentifier(),
emptyAidRoute,
protocolRoute,
- technologyRoute
+ technologyRoute,
+ systemCodeRoute
));
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index c9f9d1b..a4c5a00d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -42,6 +42,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
/**
* LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app and the
@@ -63,6 +64,7 @@
private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
private BluetoothLeBroadcastMetadata.Builder mBuilder;
private boolean mIsProfileReady;
+ private Executor mExecutor;
// Cached assistant callbacks being register before service is connected.
private final Map<BluetoothLeBroadcastAssistant.Callback, Executor> mCachedCallbackExecutorMap =
new ConcurrentHashMap<>();
@@ -98,15 +100,19 @@
}
mProfileManager.callServiceConnectedListeners();
- mIsProfileReady = true;
- if (DEBUG) {
- Log.d(
- TAG,
- "onServiceConnected, register mCachedCallbackExecutorMap = "
- + mCachedCallbackExecutorMap);
+ if (!mIsProfileReady) {
+ mIsProfileReady = true;
+ registerServiceCallBack(mExecutor, mAssistantCallback);
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onServiceConnected, register mCachedCallbackExecutorMap = "
+ + mCachedCallbackExecutorMap);
+ }
+ mCachedCallbackExecutorMap.forEach(
+ (callback, executor) -> registerServiceCallBack(executor,
+ callback));
}
- mCachedCallbackExecutorMap.forEach(
- (callback, executor) -> registerServiceCallBack(executor, callback));
}
@Override
@@ -119,17 +125,71 @@
Log.d(TAG, "Bluetooth service disconnected");
}
mProfileManager.callServiceDisconnectedListeners();
- mIsProfileReady = false;
- mCachedCallbackExecutorMap.clear();
+ if (mIsProfileReady) {
+ mIsProfileReady = false;
+ unregisterServiceCallBack(mAssistantCallback);
+ mCachedCallbackExecutorMap.clear();
+ }
}
};
+ private final BluetoothLeBroadcastAssistant.Callback mAssistantCallback =
+ new BluetoothLeBroadcastAssistant.Callback() {
+ @Override
+ public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+ }
+
+ @Override
+ public void onSearchStarted(int reason) {}
+
+ @Override
+ public void onSearchStartFailed(int reason) {}
+
+ @Override
+ public void onSearchStopped(int reason) {}
+
+ @Override
+ public void onSearchStopFailed(int reason) {}
+
+ @Override
+ public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
+
+ @Override
+ public void onSourceAddFailed(
+ @NonNull BluetoothDevice sink,
+ @NonNull BluetoothLeBroadcastMetadata source,
+ int reason) {}
+
+ @Override
+ public void onSourceModified(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceModifyFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoved(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoveFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onReceiveStateChanged(
+ @NonNull BluetoothDevice sink,
+ int sourceId,
+ @NonNull BluetoothLeBroadcastReceiveState state) {}
+ };
+
public LocalBluetoothLeBroadcastAssistant(
Context context,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mProfileManager = profileManager;
mDeviceManager = deviceManager;
+ mExecutor = Executors.newSingleThreadExecutor();
BluetoothAdapter.getDefaultAdapter()
.getProfileProxy(
context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
index 0bcf7fe..07abb6b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
@@ -68,3 +68,44 @@
awaitClose { unregisterServiceCallBack(listener) }
}
.buffer(capacity = Channel.CONFLATED)
+
+/** [Flow] for [BluetoothLeBroadcast.Callback] onPlaybackStarted event */
+val LocalBluetoothLeBroadcast.onPlaybackStarted: Flow<Unit>
+ get() =
+ callbackFlow {
+ val listener =
+ object : BluetoothLeBroadcast.Callback {
+ override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastStartFailed(reason: Int) {
+ }
+
+ override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
+ }
+
+ override fun onBroadcastStopFailed(reason: Int) {
+ }
+
+ override fun onPlaybackStarted(reason: Int, broadcastId: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onPlaybackStopped(reason: Int, broadcastId: Int) {
+ }
+
+ override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastMetadataChanged(
+ broadcastId: Int,
+ metadata: BluetoothLeBroadcastMetadata
+ ) {}
+ }
+ registerServiceCallBack(
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ listener,
+ )
+ awaitClose { unregisterServiceCallBack(listener) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 727662b..4f315a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -22,6 +22,7 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
+import android.media.AudioDeviceInfo.AudioDeviceType;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.os.Handler;
@@ -63,7 +64,7 @@
@VisibleForTesting final List<MediaDevice> mInputMediaDevices = new CopyOnWriteArrayList<>();
- private MediaDevice mSelectedInputDevice;
+ private @AudioDeviceType int mSelectedInputDeviceType;
private final Collection<InputDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
private final Object mCallbackLock = new Object();
@@ -73,12 +74,12 @@
new AudioDeviceCallback() {
@Override
public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
- dispatchInputDeviceListUpdate();
+ applyDefaultSelectedTypeToAllPresets();
}
@Override
public void onAudioDevicesRemoved(@NonNull AudioDeviceInfo[] removedDevices) {
- dispatchInputDeviceListUpdate();
+ applyDefaultSelectedTypeToAllPresets();
}
};
@@ -92,9 +93,12 @@
mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener(
new HandlerExecutor(handler),
this::onPreferredDevicesForCapturePresetChangedListener);
+
+ applyDefaultSelectedTypeToAllPresets();
}
- private void onPreferredDevicesForCapturePresetChangedListener(
+ @VisibleForTesting
+ void onPreferredDevicesForCapturePresetChangedListener(
@MediaRecorder.SystemSource int capturePreset,
@NonNull List<AudioDeviceAttributes> devices) {
if (capturePreset == MediaRecorder.AudioSource.MIC) {
@@ -117,12 +121,30 @@
}
}
+ // TODO(b/355684672): handle edge case where there are two devices with the same type. Only
+ // using a single mSelectedInputDeviceType might not be enough to recognize the correct device.
public @Nullable MediaDevice getSelectedInputDevice() {
- return mSelectedInputDevice;
+ for (MediaDevice device : mInputMediaDevices) {
+ if (((InputMediaDevice) device).getAudioDeviceInfoType() == mSelectedInputDeviceType) {
+ return device;
+ }
+ }
+ return null;
}
- private void dispatchInputDeviceListUpdate() {
- // Get selected input device.
+ private void applyDefaultSelectedTypeToAllPresets() {
+ mSelectedInputDeviceType = retrieveDefaultSelectedDeviceType();
+ AudioDeviceAttributes deviceAttributes =
+ createInputDeviceAttributes(mSelectedInputDeviceType);
+ setPreferredDeviceForAllPresets(deviceAttributes);
+ }
+
+ private AudioDeviceAttributes createInputDeviceAttributes(@AudioDeviceType int type) {
+ // Address is not used.
+ return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_INPUT, type, /* address= */ "");
+ }
+
+ private @AudioDeviceType int retrieveDefaultSelectedDeviceType() {
List<AudioDeviceAttributes> attributesOfSelectedInputDevices =
mAudioManager.getDevicesForAttributes(INPUT_ATTRIBUTES);
int selectedInputDeviceAttributesType;
@@ -138,7 +160,10 @@
}
selectedInputDeviceAttributesType = attributesOfSelectedInputDevices.get(0).getType();
}
+ return selectedInputDeviceAttributesType;
+ }
+ private void dispatchInputDeviceListUpdate() {
// Get all input devices.
AudioDeviceInfo[] audioDeviceInfos =
mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
@@ -154,9 +179,8 @@
isInputGainFixed(),
getProductNameFromAudioDeviceInfo(info));
if (mediaDevice != null) {
- if (info.getType() == selectedInputDeviceAttributesType) {
+ if (info.getType() == mSelectedInputDeviceType) {
mediaDevice.setState(STATE_SELECTED);
- mSelectedInputDevice = mediaDevice;
}
mInputMediaDevices.add(mediaDevice);
}
@@ -190,12 +214,12 @@
}
public void selectDevice(@NonNull MediaDevice device) {
- if (!(device instanceof InputMediaDevice)) {
+ if (!(device instanceof InputMediaDevice inputMediaDevice)) {
Slog.w(TAG, "This device is not an InputMediaDevice: " + device.getName());
return;
}
- if (device.equals(mSelectedInputDevice)) {
+ if (inputMediaDevice.getAudioDeviceInfoType() == mSelectedInputDeviceType) {
Slog.w(TAG, "This device is already selected: " + device.getName());
return;
}
@@ -206,12 +230,11 @@
return;
}
- // TODO(b/355684672): apply address for BT devices.
+ // Update mSelectedInputDeviceType directly based on user action.
+ mSelectedInputDeviceType = inputMediaDevice.getAudioDeviceInfoType();
+
AudioDeviceAttributes deviceAttributes =
- new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_INPUT,
- ((InputMediaDevice) device).getAudioDeviceInfoType(),
- /* address= */ "");
+ createInputDeviceAttributes(inputMediaDevice.getAudioDeviceInfoType());
try {
setPreferredDeviceForAllPresets(deviceAttributes);
} catch (IllegalArgumentException e) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index 2f8105a..b41e970 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -74,6 +74,8 @@
/** The headset groupId to volume map during audio sharing. */
val volumeMap: StateFlow<GroupIdToVolumes>
+ suspend fun audioSharingAvailable(): Boolean
+
/** Set the volume of secondary headset during audio sharing. */
suspend fun setSecondaryVolume(
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
@@ -216,6 +218,12 @@
}
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
+ override suspend fun audioSharingAvailable(): Boolean {
+ return withContext(backgroundCoroutineContext) {
+ BluetoothUtils.isAudioSharingEnabled()
+ }
+ }
+
override suspend fun setSecondaryVolume(
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
volume: Int
@@ -262,6 +270,8 @@
MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
override val volumeMap: StateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
+ override suspend fun audioSharingAvailable(): Boolean = false
+
override suspend fun setSecondaryVolume(
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
volume: Int
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index f63bfc7..782cee2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -55,6 +56,7 @@
private static final int INPUT_USB_DEVICE_ID = 3;
private static final int INPUT_USB_HEADSET_ID = 4;
private static final int INPUT_USB_ACCESSORY_ID = 5;
+ private static final int HDMI_ID = 6;
private static final int MAX_VOLUME = 1;
private static final int CURRENT_VOLUME = 0;
private static final boolean VOLUME_FIXED_TRUE = true;
@@ -63,10 +65,86 @@
private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset";
private static final String PRODUCT_NAME_USB_DEVICE = "My USB Device";
private static final String PRODUCT_NAME_USB_ACCESSORY = "My USB Accessory";
+ private static final String PRODUCT_NAME_HDMI_DEVICE = "HDMI device";
private final Context mContext = spy(RuntimeEnvironment.application);
private InputRouteManager mInputRouteManager;
+ private AudioDeviceInfo mockBuiltinMicInfo() {
+ final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
+ when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ when(info.getId()).thenReturn(BUILTIN_MIC_ID);
+ when(info.getAddress()).thenReturn("");
+ when(info.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
+ return info;
+ }
+
+ private AudioDeviceInfo mockWiredHeadsetInfo() {
+ final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
+ when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(info.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ when(info.getAddress()).thenReturn("");
+ when(info.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
+ return info;
+ }
+
+ private AudioDeviceInfo mockUsbDeviceInfo() {
+ final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
+ when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_DEVICE);
+ when(info.getId()).thenReturn(INPUT_USB_DEVICE_ID);
+ when(info.getAddress()).thenReturn("");
+ when(info.getProductName()).thenReturn(PRODUCT_NAME_USB_DEVICE);
+ return info;
+ }
+
+ private AudioDeviceInfo mockUsbHeadsetInfo() {
+ final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
+ when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_HEADSET);
+ when(info.getId()).thenReturn(INPUT_USB_HEADSET_ID);
+ when(info.getAddress()).thenReturn("");
+ when(info.getProductName()).thenReturn(PRODUCT_NAME_USB_HEADSET);
+ return info;
+ }
+
+ private AudioDeviceInfo mockUsbAccessoryInfo() {
+ final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
+ when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_ACCESSORY);
+ when(info.getId()).thenReturn(INPUT_USB_ACCESSORY_ID);
+ when(info.getAddress()).thenReturn("");
+ when(info.getProductName()).thenReturn(PRODUCT_NAME_USB_ACCESSORY);
+ return info;
+ }
+
+ private AudioDeviceInfo mockHdmiInfo() {
+ final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
+ when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_HDMI);
+ when(info.getId()).thenReturn(HDMI_ID);
+ when(info.getAddress()).thenReturn("");
+ when(info.getProductName()).thenReturn(PRODUCT_NAME_HDMI_DEVICE);
+ return info;
+ }
+
+ private AudioDeviceAttributes getBuiltinMicDeviceAttributes() {
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT,
+ AudioDeviceInfo.TYPE_BUILTIN_MIC,
+ /* address= */ "");
+ }
+
+ private AudioDeviceAttributes getWiredHeadsetDeviceAttributes() {
+ return new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT,
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ /* address= */ "");
+ }
+
+ private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) {
+ final List<AudioDeviceAttributes> audioDeviceAttributesList =
+ new ArrayList<AudioDeviceAttributes>();
+ inputRouteManager.onPreferredDevicesForCapturePresetChangedListener(
+ MediaRecorder.AudioSource.MIC, audioDeviceAttributesList);
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -77,37 +155,15 @@
@Test
public void onAudioDevicesAdded_shouldUpdateInputMediaDevice() {
- final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
- when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
- when(info1.getId()).thenReturn(BUILTIN_MIC_ID);
- when(info1.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
-
- final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
- when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(info2.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
- when(info2.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
-
- final AudioDeviceInfo info3 = mock(AudioDeviceInfo.class);
- when(info3.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_DEVICE);
- when(info3.getId()).thenReturn(INPUT_USB_DEVICE_ID);
- when(info3.getProductName()).thenReturn(PRODUCT_NAME_USB_DEVICE);
-
- final AudioDeviceInfo info4 = mock(AudioDeviceInfo.class);
- when(info4.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_HEADSET);
- when(info4.getId()).thenReturn(INPUT_USB_HEADSET_ID);
- when(info4.getProductName()).thenReturn(PRODUCT_NAME_USB_HEADSET);
-
- final AudioDeviceInfo info5 = mock(AudioDeviceInfo.class);
- when(info5.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_ACCESSORY);
- when(info5.getId()).thenReturn(INPUT_USB_ACCESSORY_ID);
- when(info5.getProductName()).thenReturn(PRODUCT_NAME_USB_ACCESSORY);
-
- final AudioDeviceInfo unsupportedInfo = mock(AudioDeviceInfo.class);
- when(unsupportedInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HDMI);
- when(unsupportedInfo.getProductName()).thenReturn("HDMI device");
-
final AudioManager audioManager = mock(AudioManager.class);
- AudioDeviceInfo[] devices = {info1, info2, info3, info4, info5, unsupportedInfo};
+ AudioDeviceInfo[] devices = {
+ mockBuiltinMicInfo(),
+ mockWiredHeadsetInfo(),
+ mockUsbDeviceInfo(),
+ mockUsbHeadsetInfo(),
+ mockUsbAccessoryInfo(),
+ mockHdmiInfo()
+ };
when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
@@ -115,8 +171,9 @@
assertThat(inputRouteManager.mInputMediaDevices).isEmpty();
inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+ onPreferredDevicesForCapturePresetChanged(inputRouteManager);
- // The unsupported info should be filtered out.
+ // The unsupported (hdmi) info should be filtered out.
assertThat(inputRouteManager.mInputMediaDevices).hasSize(devices.length - 1);
assertThat(inputRouteManager.mInputMediaDevices.get(0).getId())
.isEqualTo(String.valueOf(BUILTIN_MIC_ID));
@@ -141,36 +198,25 @@
final MediaDevice device = mock(MediaDevice.class);
inputRouteManager.mInputMediaDevices.add(device);
- final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
- when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- inputRouteManager.mAudioDeviceCallback.onAudioDevicesRemoved(new AudioDeviceInfo[] {info});
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesRemoved(
+ new AudioDeviceInfo[] {mockWiredHeadsetInfo()});
+ onPreferredDevicesForCapturePresetChanged(inputRouteManager);
assertThat(inputRouteManager.mInputMediaDevices).isEmpty();
}
@Test
public void getSelectedInputDevice_returnOneFromAudioManager() {
- final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
- when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
- when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
-
- final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
- when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
- when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
- when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
-
final AudioManager audioManager = mock(AudioManager.class);
- AudioDeviceInfo[] devices = {info1, info2};
+ AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockBuiltinMicInfo()};
when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
// Mock audioManager.getDevicesForAttributes returns exactly one audioDeviceAttributes.
- AudioDeviceAttributes audioDeviceAttributes = new AudioDeviceAttributes(info1);
when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
- .thenReturn(Collections.singletonList(audioDeviceAttributes));
+ .thenReturn(Collections.singletonList(getWiredHeadsetDeviceAttributes()));
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
- inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+ onPreferredDevicesForCapturePresetChanged(inputRouteManager);
// The selected input device has the same type as the one returned from AudioManager.
InputMediaDevice selectedInputDevice =
@@ -181,31 +227,19 @@
@Test
public void getSelectedInputDevice_returnMoreThanOneFromAudioManager() {
- final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
- when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
- when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
-
- final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
- when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
- when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
- when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
-
final AudioManager audioManager = mock(AudioManager.class);
- AudioDeviceInfo[] devices = {info1, info2};
+ AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockBuiltinMicInfo()};
when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
// Mock audioManager.getDevicesForAttributes returns more than one audioDeviceAttributes.
- AudioDeviceAttributes audioDeviceAttributes1 = new AudioDeviceAttributes(info1);
- AudioDeviceAttributes audioDeviceAttributes2 = new AudioDeviceAttributes(info2);
List<AudioDeviceAttributes> attributesOfSelectedInputDevices = new ArrayList<>();
- attributesOfSelectedInputDevices.add(audioDeviceAttributes1);
- attributesOfSelectedInputDevices.add(audioDeviceAttributes2);
+ attributesOfSelectedInputDevices.add(getWiredHeadsetDeviceAttributes());
+ attributesOfSelectedInputDevices.add(getBuiltinMicDeviceAttributes());
when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
.thenReturn(attributesOfSelectedInputDevices);
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
- inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+ onPreferredDevicesForCapturePresetChanged(inputRouteManager);
// The selected input device has the same type as the first one returned from AudioManager.
InputMediaDevice selectedInputDevice =
@@ -216,27 +250,17 @@
@Test
public void getSelectedInputDevice_returnEmptyFromAudioManager() {
- final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
- when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
- when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
-
- final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
- when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
- when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
- when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
-
final AudioManager audioManager = mock(AudioManager.class);
- AudioDeviceInfo[] devices = {info1, info2};
+ AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockBuiltinMicInfo()};
when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
// Mock audioManager.getDevicesForAttributes returns empty list of audioDeviceAttributes.
- List<AudioDeviceAttributes> attributesOfSelectedInputDevices = new ArrayList<>();
+ List<AudioDeviceAttributes> emptyAttributesOfSelectedInputDevices = new ArrayList<>();
when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
- .thenReturn(attributesOfSelectedInputDevices);
+ .thenReturn(emptyAttributesOfSelectedInputDevices);
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
- inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+ onPreferredDevicesForCapturePresetChanged(inputRouteManager);
// The selected input device has default type AudioDeviceInfo.TYPE_BUILTIN_MIC.
InputMediaDevice selectedInputDevice =
@@ -249,7 +273,7 @@
public void selectDevice() {
final AudioManager audioManager = mock(AudioManager.class);
InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
- final MediaDevice inputMediaDevice =
+ final MediaDevice builtinMicDevice =
InputMediaDevice.create(
mContext,
String.valueOf(BUILTIN_MIC_ID),
@@ -258,16 +282,57 @@
CURRENT_VOLUME,
VOLUME_FIXED_TRUE,
PRODUCT_NAME_BUILTIN_MIC);
- inputRouteManager.selectDevice(inputMediaDevice);
+ inputRouteManager.selectDevice(builtinMicDevice);
- AudioDeviceAttributes deviceAttributes =
- new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_INPUT,
- AudioDeviceInfo.TYPE_BUILTIN_MIC,
- /* address= */ "");
for (@MediaRecorder.Source int preset : PRESETS) {
verify(audioManager, atLeastOnce())
- .setPreferredDeviceForCapturePreset(preset, deviceAttributes);
+ .setPreferredDeviceForCapturePreset(preset, getBuiltinMicDeviceAttributes());
+ }
+ }
+
+ @Test
+ public void onInitiation_shouldApplyDefaultSelectedDeviceToAllPresets() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ new InputRouteManager(mContext, audioManager);
+
+ verify(audioManager, atLeastOnce()).getDevicesForAttributes(INPUT_ATTRIBUTES);
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, atLeastOnce())
+ .setPreferredDeviceForCapturePreset(preset, getBuiltinMicDeviceAttributes());
+ }
+ }
+
+ @Test
+ public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes();
+ when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
+ .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes));
+
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ // Called twice, one after initiation, the other after onAudioDevicesAdded call.
+ verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, atLeast(2))
+ .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes);
+ }
+ }
+
+ @Test
+ public void onAudioDevicesRemoved_shouldApplyDefaultSelectedDeviceToAllPresets() {
+ final AudioManager audioManager = mock(AudioManager.class);
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+ AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesRemoved(devices);
+
+ // Called twice, one after initiation, the other after onAudioDevicesRemoved call.
+ verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
+ for (@MediaRecorder.Source int preset : PRESETS) {
+ verify(audioManager, atLeast(2))
+ .setPreferredDeviceForCapturePreset(preset, getBuiltinMicDeviceAttributes());
}
}
@@ -288,27 +353,25 @@
@Test
public void onAudioDevicesAdded_shouldSetProductNameCorrectly() {
- final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
- when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ final AudioDeviceInfo info1 = mockWiredHeadsetInfo();
String firstProductName = "My first headset";
when(info1.getProductName()).thenReturn(firstProductName);
+ InputMediaDevice inputMediaDevice1 = createInputMediaDeviceFromDeviceInfo(info1);
- final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
- when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(info2.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ final AudioDeviceInfo info2 = mockWiredHeadsetInfo();
String secondProductName = "My second headset";
when(info2.getProductName()).thenReturn(secondProductName);
+ InputMediaDevice inputMediaDevice2 = createInputMediaDeviceFromDeviceInfo(info2);
- final AudioDeviceInfo infoWithNullProductName = mock(AudioDeviceInfo.class);
- when(infoWithNullProductName.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(infoWithNullProductName.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ final AudioDeviceInfo infoWithNullProductName = mockWiredHeadsetInfo();
when(infoWithNullProductName.getProductName()).thenReturn(null);
+ InputMediaDevice inputMediaDevice3 =
+ createInputMediaDeviceFromDeviceInfo(infoWithNullProductName);
- final AudioDeviceInfo infoWithBlankProductName = mock(AudioDeviceInfo.class);
- when(infoWithBlankProductName.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
- when(infoWithBlankProductName.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ final AudioDeviceInfo infoWithBlankProductName = mockWiredHeadsetInfo();
when(infoWithBlankProductName.getProductName()).thenReturn("");
+ InputMediaDevice inputMediaDevice4 =
+ createInputMediaDeviceFromDeviceInfo(infoWithBlankProductName);
final AudioManager audioManager = mock(AudioManager.class);
AudioDeviceInfo[] devices = {
@@ -321,15 +384,22 @@
assertThat(inputRouteManager.mInputMediaDevices).isEmpty();
inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+ onPreferredDevicesForCapturePresetChanged(inputRouteManager);
- assertThat(getProductNameAtIndex(inputRouteManager, 1)).isEqualTo(firstProductName);
- assertThat(getProductNameAtIndex(inputRouteManager, 2)).isEqualTo(secondProductName);
- assertThat(getProductNameAtIndex(inputRouteManager, 3)).isNull();
- assertThat(getProductNameAtIndex(inputRouteManager, 4)).isNull();
+ assertThat(inputRouteManager.mInputMediaDevices)
+ .containsExactly(
+ inputMediaDevice1, inputMediaDevice2, inputMediaDevice3, inputMediaDevice4)
+ .inOrder();
}
- private String getProductNameAtIndex(InputRouteManager inputRouteManager, int index) {
- return ((InputMediaDevice) inputRouteManager.mInputMediaDevices.get(index))
- .getProductName();
+ private InputMediaDevice createInputMediaDeviceFromDeviceInfo(AudioDeviceInfo info) {
+ return InputMediaDevice.create(
+ mContext,
+ String.valueOf(info.getId()),
+ info.getType(),
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ VOLUME_FIXED_TRUE,
+ info.getProductName() == null ? null : info.getProductName().toString());
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 5293011..d8b6707 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -207,20 +207,6 @@
}
@Test
- public void getHotspotIconResource_deviceTypeExists_shouldNotNull() {
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_PHONE))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_TABLET))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_LAPTOP))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_WATCH))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_AUTO))
- .isNotNull();
- }
-
- @Test
public void testInternetIconInjector_getIcon_returnsCorrectValues() {
WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 2cdd0ae..3530e0f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -106,6 +106,8 @@
Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR,
Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS,
Settings.System.LOCALE_PREFERENCES,
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING,
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON,
Settings.System.TOUCHPAD_POINTER_SPEED,
Settings.System.TOUCHPAD_NATURAL_SCROLLING,
Settings.System.TOUCHPAD_TAP_TO_CLICK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 2823277..509b88b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -221,6 +221,8 @@
POINTER_ICON_VECTOR_STYLE_STROKE_END));
VALIDATORS.put(System.POINTER_SCALE,
new InclusiveFloatRangeValidator(DEFAULT_POINTER_SCALE, LARGE_POINTER_SCALE));
+ VALIDATORS.put(System.MOUSE_REVERSE_VERTICAL_SCROLLING, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 1f10ead..c4a45d0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -425,8 +425,8 @@
"tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
"tests/src/**/systemui/shared/system/RemoteTransitionTest.java",
"tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java",
- "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt",
- "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt",
"tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt",
"tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt",
"tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 07a1e63..380344a 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -148,5 +148,10 @@
{
"name": "SystemUIGoogleRobo2RNGTests"
}
+ ],
+ "imports": [
+ {
+ "path": "cts/tests/tests/multiuser"
+ }
]
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 179ba22..cecc11e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -19,7 +19,6 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.ActivityNotFoundException
import android.content.ComponentName
-import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.provider.Settings
@@ -27,7 +26,6 @@
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.accessibilityManager
import android.widget.RemoteViews
-import androidx.activity.result.ActivityResultLauncher
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
@@ -88,7 +86,6 @@
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var packageManager: PackageManager
- @Mock private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -117,10 +114,7 @@
communalSceneInteractor = kosmos.communalSceneInteractor
communalInteractor = spy(kosmos.communalInteractor)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
- kosmos.fakeUserTracker.set(
- userInfos = listOf(MAIN_USER_INFO),
- selectedUserIndex = 0,
- )
+ kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
accessibilityManager = kosmos.accessibilityManager
@@ -257,10 +251,13 @@
@Test
fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
testScope.runTest {
+ var activityStarted = false
val success =
- underTest.onOpenWidgetPicker(testableResources.resources, activityResultLauncher)
+ underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
+ run { activityStarted = true }
+ }
- verify(activityResultLauncher).launch(any())
+ assertTrue(activityStarted)
assertTrue(success)
}
}
@@ -268,14 +265,10 @@
@Test
fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
testScope.runTest {
- whenever(activityResultLauncher.launch(any()))
- .thenThrow(ActivityNotFoundException::class.java)
-
val success =
- underTest.onOpenWidgetPicker(
- testableResources.resources,
- activityResultLauncher,
- )
+ underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
+ run { throw ActivityNotFoundException() }
+ }
assertFalse(success)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt
deleted file mode 100644
index fd1f52b..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.data.repository
-
-import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class QSColumnsRepositoryTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private lateinit var underTest: QSColumnsRepository
-
- @Before
- fun setUp() {
- underTest = with(kosmos) { qsColumnsRepository }
- }
-
- @Test
- fun configChanges_triggerColumnsUpdate() =
- with(kosmos) {
- testScope.runTest {
- val latest by collectLastValue(underTest.columns)
-
- setColumnsInConfig(4)
- assertThat(latest).isEqualTo(4)
-
- setColumnsInConfig(8)
- assertThat(latest).isEqualTo(8)
- }
- }
-
- @Test
- @EnableFlags(DualShade.FLAG_NAME)
- fun withDualShade_returnsCorrectValue() =
- with(kosmos) {
- testScope.runTest {
- val latest by collectLastValue(underTest.columns)
- assertThat(latest).isEqualTo(4)
-
- setColumnsInConfig(8, id = R.integer.quick_settings_dual_shade_num_columns)
- // Asserts config changes are ignored
- assertThat(latest).isEqualTo(4)
- }
- }
-
- private fun setColumnsInConfig(
- columns: Int,
- id: Int = R.integer.quick_settings_infinite_grid_num_columns,
- ) =
- with(kosmos) {
- testCase.context.orCreateTestableResources.addOverride(id, columns)
- fakeConfigurationRepository.onConfigurationChange()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
index 9331c8d..0bbf47c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
@@ -16,13 +16,16 @@
package com.android.systemui.screenrecord.data.model
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
import com.google.common.truth.Truth.assertThat
+import org.junit.runner.RunWith
import kotlin.test.Test
@SmallTest
+@RunWith(AndroidJUnit4::class)
class ScreenRecordModelTest : SysuiTestCase() {
@Test
fun countdownSeconds_millis0_is0() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 0e9ef06..0454317 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -22,6 +22,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -36,10 +40,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import android.animation.Animator;
import android.annotation.IdRes;
import android.content.ContentResolver;
@@ -201,6 +201,12 @@
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import dagger.Lazy;
+
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.channels.BufferOverflow;
+import kotlinx.coroutines.test.TestScope;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -215,11 +221,6 @@
import java.util.List;
import java.util.Optional;
-import dagger.Lazy;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.channels.BufferOverflow;
-import kotlinx.coroutines.test.TestScope;
-
public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
protected static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
@@ -461,7 +462,8 @@
() -> mKosmos.getSceneInteractor(),
() -> mKosmos.getSceneContainerOcclusionInteractor(),
() -> mKosmos.getKeyguardClockInteractor(),
- () -> mKosmos.getSceneBackInteractor());
+ () -> mKosmos.getSceneBackInteractor(),
+ () -> mKosmos.getAlternateBouncerInteractor());
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -622,7 +624,8 @@
() -> mKosmos.getSceneInteractor(),
() -> mKosmos.getSceneContainerOcclusionInteractor(),
() -> mKosmos.getKeyguardClockInteractor(),
- () -> mKosmos.getSceneBackInteractor()),
+ () -> mKosmos.getSceneBackInteractor(),
+ () -> mKosmos.getAlternateBouncerInteractor()),
mKeyguardBypassController,
mDozeParameters,
mScreenOffAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index db274cc..f8720b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.givenCanShowAlternateBouncer
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.DisableSceneContainer
@@ -83,8 +85,9 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
private val mockDarkAnimator = mock<ObjectAnimator>()
private lateinit var underTest: StatusBarStateControllerImpl
@@ -121,6 +124,7 @@
{ kosmos.sceneContainerOcclusionInteractor },
{ kosmos.keyguardClockInteractor },
{ kosmos.sceneBackInteractor },
+ { kosmos.alternateBouncerInteractor },
) {
override fun createDarkAnimator(): ObjectAnimator {
return mockDarkAnimator
@@ -299,6 +303,52 @@
@Test
@EnableSceneContainer
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun start_hydratesStatusBarState_withAlternateBouncer() =
+ testScope.runTest {
+ var statusBarState = underTest.state
+ val listener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ statusBarState = newState
+ }
+ }
+ underTest.addCallback(listener)
+
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+ val alternateBouncerIsVisible by collectLastValue(alternateBouncerInteractor.isVisible)
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus!!.isUnlocked).isTrue()
+
+ sceneInteractor.changeScene(toScene = Scenes.Lockscreen, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+ kosmos.givenCanShowAlternateBouncer()
+ alternateBouncerInteractor.forceShow()
+ runCurrent()
+ assertThat(alternateBouncerIsVisible).isTrue()
+
+ // Call start to begin hydrating based on the scene framework:
+ underTest.start()
+
+ sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+ }
+
+ @Test
+ @EnableSceneContainer
@EnableFlags(DualShade.FLAG_NAME)
fun start_hydratesStatusBarState_dualShade_whileLocked() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
index ba9fa92..cd18925 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,10 +35,12 @@
private var gestureState: GestureState = NotStarted
private val gestureMonitor =
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- )
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun triggersGestureFinishedForThreeFingerGestureRight() {
@@ -82,7 +85,7 @@
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
index a83ed56..3f1633a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
@@ -36,10 +36,7 @@
private var triggered = false
private val handler =
TouchpadGestureHandler(
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = {},
- ),
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()),
EasterEggGestureMonitor(callback = { triggered = true }),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
index 59cc026..edf0e56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,10 +35,12 @@
private var gestureState: GestureState = GestureState.NotStarted
private val gestureMonitor =
- HomeGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- )
+ HomeGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun triggersGestureFinishedForThreeFingerGestureUp() {
@@ -78,7 +81,7 @@
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
index 7eac6bb..f68e773 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
@@ -44,7 +45,7 @@
}
private var gestureState: GestureState = GestureState.NotStarted
- private val velocityTracker =
+ private val velocityTracker1D =
mock<VelocityTracker1D> {
// by default return correct speed for the gesture - as if pointer is slowing down
on { calculateVelocity() } doReturn SLOW
@@ -52,11 +53,15 @@
private val gestureMonitor =
RecentAppsGestureMonitor(
gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
- velocityTracker = velocityTracker,
+ velocityTracker = VerticalVelocityTracker(velocityTracker1D),
)
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
+
@Test
fun triggersGestureFinishedForThreeFingerGestureUp() {
assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = Finished)
@@ -64,7 +69,7 @@
@Test
fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() {
- whenever(velocityTracker.calculateVelocity()).thenReturn(FAST)
+ whenever(velocityTracker1D.calculateVelocity()).thenReturn(FAST)
assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
}
@@ -102,7 +107,7 @@
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index 4d26366..9f7ea679 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,14 +36,14 @@
class TouchpadGestureHandlerTest : SysuiTestCase() {
private var gestureState: GestureState = GestureState.NotStarted
- private val handler =
- TouchpadGestureHandler(
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- ),
- EasterEggGestureMonitor {},
- )
+ private val gestureMonitor =
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+ private val handler = TouchpadGestureHandler(gestureMonitor, EasterEggGestureMonitor {})
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun handlesEventsFromTouchpad() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
diff --git a/packages/SystemUI/res/layout/audio_sharing_dialog.xml b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
new file mode 100644
index 0000000..7534e15
--- /dev/null
+++ b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/root"
+ style="@style/Widget.SliceView.Panel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView android:id="@+id/icon"
+ android:layout_width="28dp"
+ android:layout_height="28dp"
+ android:src="@drawable/ic_bt_le_audio_sharing"
+ android:layout_marginTop="5dp"
+ android:layout_marginBottom="20dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/title"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="20dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:text="@string/quick_settings_bluetooth_audio_sharing_dialog_title"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textSize="24sp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/subtitle"
+ app:layout_constraintTop_toBottomOf="@id/icon" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="20dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:textFontWeight="500"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/message"
+ app:layout_constraintTop_toBottomOf="@id/title" />
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="20dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:text="@string/quick_settings_bluetooth_audio_sharing_dialog_message"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/share_audio_button"
+ app:layout_constraintTop_toBottomOf="@id/subtitle" />
+
+ <Button
+ android:id="@+id/share_audio_button"
+ style="@style/SettingsLibActionButton"
+ android:textColor="?androidprv:attr/textColorOnAccent"
+ android:background="@drawable/audio_sharing_rounded_bg_ripple"
+ android:layout_marginBottom="4dp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="64dp"
+ android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/message"
+ app:layout_constraintBottom_toTopOf="@+id/switch_active_button"
+ android:text="@string/quick_settings_bluetooth_audio_sharing_button"
+ android:maxLines="2" />
+
+ <Button
+ android:id="@+id/switch_active_button"
+ style="@style/SettingsLibActionButton"
+ android:textColor="?androidprv:attr/textColorOnAccent"
+ android:background="@drawable/audio_sharing_rounded_bg_ripple"
+ android:layout_marginBottom="20dp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="64dp"
+ android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/share_audio_button"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:maxLines="2" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index b5efeb5..db526b1 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -25,9 +25,6 @@
<integer name="quick_settings_num_columns">4</integer>
- <!-- The number of columns in the infinite grid QuickSettings -->
- <integer name="quick_settings_infinite_grid_num_columns">8</integer>
-
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<integer name="quick_settings_user_time_settings_tile_span">2</integer>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index 7daad1a..857e162 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -24,9 +24,6 @@
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">3</integer>
- <!-- The number of columns in the infinite grid QuickSettings -->
- <integer name="quick_settings_infinite_grid_num_columns">6</integer>
-
<integer name="power_menu_lite_max_columns">2</integer>
<integer name="power_menu_lite_max_rows">3</integer>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6f94f9e..38ef0e9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -73,9 +73,6 @@
<!-- The number of columns in the infinite grid QuickSettings -->
<integer name="quick_settings_infinite_grid_num_columns">4</integer>
- <!-- The number of columns in the Dual Shade QuickSettings -->
- <integer name="quick_settings_dual_shade_num_columns">4</integer>
-
<!-- Override column number for quick settings.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
TODO (b/293252410) - change this comment/resource when flag is enabled -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7225061..2c5fb56 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -765,6 +765,14 @@
<string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing audio</string>
<!-- QuickSettings: Bluetooth dialog audio sharing button text accessibility label. Used as part of the string "Double tap to enter audio sharing settings". [CHAR LIMIT=50]-->
<string name="quick_settings_bluetooth_audio_sharing_button_accessibility">enter audio sharing settings</string>
+ <!-- QuickSettings: Bluetooth audio sharing dialog message. [CHAR LIMIT=NONE]-->
+ <string name="quick_settings_bluetooth_audio_sharing_dialog_message">This device\'s music and videos will play on both pairs of headphones</string>
+ <!-- QuickSettings: Bluetooth audio sharing dialog title. [CHAR LIMIT=NONE]-->
+ <string name="quick_settings_bluetooth_audio_sharing_dialog_title">Share your audio</string>
+ <!-- QuickSettings: Bluetooth audio sharing dialog subtitle. [CHAR LIMIT=NONE]-->
+ <string name="quick_settings_bluetooth_audio_sharing_dialog_subtitle"><xliff:g id="available_device_name" example="device 1">%1$s</xliff:g> and <xliff:g id="active_device_name" example="device 2">%2$s</xliff:g></string>
+ <!-- QuickSettings: Bluetooth audio sharing dialog button text. [CHAR LIMIT=NONE]-->
+ <string name="quick_settings_bluetooth_audio_sharing_dialog_switch_to_button">Switch to <xliff:g id="available_device_name" example="device 1">%1$s</xliff:g></string>
<!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
<string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModel.kt
new file mode 100644
index 0000000..a6fb150
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModel.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import androidx.annotation.StringRes
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.res.R
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+
+sealed class AudioSharingButtonState {
+ object Gone : AudioSharingButtonState()
+
+ data class Visible(@StringRes val resId: Int, val isActive: Boolean) :
+ AudioSharingButtonState()
+}
+
+class AudioSharingButtonViewModel
+@AssistedInject
+constructor(
+ private val localBluetoothManager: LocalBluetoothManager?,
+ private val audioSharingInteractor: AudioSharingInteractor,
+ private val bluetoothStateInteractor: BluetoothStateInteractor,
+ private val deviceItemInteractor: DeviceItemInteractor,
+) : ExclusiveActivatable() {
+
+ private val mutableButtonState =
+ MutableStateFlow<AudioSharingButtonState>(AudioSharingButtonState.Gone)
+ /** Flow representing the update of AudioSharingButtonState. */
+ val audioSharingButtonStateUpdate: StateFlow<AudioSharingButtonState> =
+ mutableButtonState.asStateFlow()
+
+ override suspend fun onActivated(): Nothing {
+ combine(
+ bluetoothStateInteractor.bluetoothStateUpdate,
+ deviceItemInteractor.deviceItemUpdate,
+ audioSharingInteractor.isAudioSharingOn
+ ) { bluetoothState, deviceItem, audioSharingOn ->
+ getButtonState(bluetoothState, deviceItem, audioSharingOn)
+ }
+ .collect { mutableButtonState.value = it }
+ awaitCancellation()
+ }
+
+ private fun getButtonState(
+ bluetoothState: Boolean,
+ deviceItem: List<DeviceItem>,
+ audioSharingOn: Boolean
+ ): AudioSharingButtonState {
+ return when {
+ // Don't show button when bluetooth is off
+ !bluetoothState -> AudioSharingButtonState.Gone
+ // Show sharing audio when broadcasting
+ audioSharingOn ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing,
+ isActive = true
+ )
+ // When not broadcasting, don't show button if there's connected source in any device
+ deviceItem.any {
+ BluetoothUtils.hasConnectedBroadcastSource(
+ it.cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ } -> AudioSharingButtonState.Gone
+ // Show audio sharing when there's a connected LE audio device
+ deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button,
+ isActive = false
+ )
+ else -> AudioSharingButtonState.Gone
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): AudioSharingButtonViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
new file mode 100644
index 0000000..692a78b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bluetooth.qsdialog
+
+import android.bluetooth.BluetoothDevice
+import android.content.Intent
+import android.os.Bundle
+import android.provider.Settings
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.A2dpProfile
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.HeadsetProfile
+import com.android.settingslib.bluetooth.HearingAidProfile
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class AudioSharingDeviceItemActionInteractorImpl
+@Inject
+constructor(
+ private val activityStarter: ActivityStarter,
+ private val audioSharingInteractor: AudioSharingInteractor,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val localBluetoothManager: LocalBluetoothManager?,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val logger: BluetoothTileDialogLogger,
+ private val uiEventLogger: UiEventLogger,
+ private val delegateFactory: AudioSharingDialogDelegate.Factory,
+ private val deviceItemActionInteractorImpl: DeviceItemActionInteractorImpl,
+) : DeviceItemActionInteractor {
+
+ override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
+ withContext(backgroundDispatcher) {
+ if (!audioSharingInteractor.audioSharingAvailable()) {
+ return@withContext deviceItemActionInteractorImpl.onClick(deviceItem, dialog)
+ }
+ val inAudioSharing = BluetoothUtils.isBroadcasting(localBluetoothManager)
+ logger.logDeviceClickInAudioSharingWhenEnabled(inAudioSharing)
+
+ when {
+ deviceItem.type ==
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ if (audioSharingQsDialogImprovement()) {
+ withContext(mainDispatcher) {
+ delegateFactory
+ .create(deviceItem.cachedBluetoothDevice)
+ .createDialog()
+ .let { dialogTransitionAnimator.showFromDialog(it, dialog) }
+ }
+ } else {
+ launchSettings(deviceItem.cachedBluetoothDevice.device, dialog)
+ logger.logLaunchSettingsCriteriaMatched(
+ "AvailableAudioSharingDeviceClicked",
+ deviceItem,
+ )
+ }
+ uiEventLogger.log(
+ BluetoothTileDialogUiEvent.AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED
+ )
+ }
+ inSharingAndDeviceNoSource(inAudioSharing, deviceItem) -> {
+ launchSettings(deviceItem.cachedBluetoothDevice.device, dialog)
+ logger.logLaunchSettingsCriteriaMatched("InSharingClickedNoSource", deviceItem)
+ uiEventLogger.log(
+ if (deviceItem.isLeAudioSupported)
+ BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_IN_SHARING_LE_DEVICE_CLICKED
+ else
+ BluetoothTileDialogUiEvent
+ .LAUNCH_SETTINGS_IN_SHARING_NON_LE_DEVICE_CLICKED
+ )
+ }
+ else -> {
+ deviceItemActionInteractorImpl.onClick(deviceItem, dialog)
+ }
+ }
+ }
+ }
+
+ private fun inSharingAndDeviceNoSource(
+ inAudioSharing: Boolean,
+ deviceItem: DeviceItem,
+ ): Boolean {
+ return inAudioSharing &&
+ deviceItem.isMediaDevice &&
+ !BluetoothUtils.hasConnectedBroadcastSource(
+ deviceItem.cachedBluetoothDevice,
+ localBluetoothManager,
+ )
+ }
+
+ private fun launchSettings(device: BluetoothDevice, dialog: SystemUIDialog) {
+ val intent =
+ Intent(Settings.ACTION_BLUETOOTH_SETTINGS).apply {
+ putExtra(
+ EXTRA_SHOW_FRAGMENT_ARGUMENTS,
+ Bundle().apply {
+ putParcelable(LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE, device)
+ },
+ )
+ }
+ intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ dialogTransitionAnimator.createActivityTransitionController(dialog),
+ )
+ }
+
+ private companion object {
+ const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
+
+ val DeviceItem.isLeAudioSupported: Boolean
+ get() =
+ cachedBluetoothDevice.profiles.any { profile ->
+ profile is LeAudioProfile && profile.isEnabled(cachedBluetoothDevice.device)
+ }
+
+ val DeviceItem.isMediaDevice: Boolean
+ get() =
+ cachedBluetoothDevice.uiAccessibleProfiles.any {
+ it is A2dpProfile ||
+ it is HearingAidProfile ||
+ it is LeAudioProfile ||
+ it is HeadsetProfile
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegate.kt
new file mode 100644
index 0000000..3ac942b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegate.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.os.Bundle
+import android.widget.Button
+import android.widget.TextView
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class AudioSharingDialogDelegate
+@AssistedInject
+constructor(
+ @Assisted private val cachedBluetoothDevice: CachedBluetoothDevice,
+ @Application private val coroutineScope: CoroutineScope,
+ private val viewModelFactory: AudioSharingDialogViewModel.Factory,
+ private val sysuiDialogFactory: SystemUIDialog.Factory,
+ private val uiEventLogger: UiEventLogger,
+) : SystemUIDialog.Delegate {
+
+ override fun createDialog(): SystemUIDialog = sysuiDialogFactory.create(this)
+
+ override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ with(dialog.layoutInflater.inflate(R.layout.audio_sharing_dialog, null)) {
+ dialog.setView(this)
+ val subtitleTextView = requireViewById<TextView>(R.id.subtitle)
+ val shareAudioButton = requireViewById<TextView>(R.id.share_audio_button)
+ val switchActiveButton = requireViewById<Button>(R.id.switch_active_button)
+ val job =
+ coroutineScope.launch {
+ val viewModel = viewModelFactory.create(cachedBluetoothDevice, this)
+ viewModel.dialogState.collect {
+ when (it) {
+ is AudioSharingDialogState.Hide -> dialog.dismiss()
+ is AudioSharingDialogState.Show -> {
+ subtitleTextView.text = it.subtitle
+ switchActiveButton.text = it.switchButtonText
+ switchActiveButton.setOnClickListener {
+ viewModel.switchActiveClicked()
+ uiEventLogger.log(
+ BluetoothTileDialogUiEvent
+ .AUDIO_SHARING_DIALOG_SWITCH_ACTIVE_CLICKED
+ )
+ dialog.dismiss()
+ }
+ shareAudioButton.setOnClickListener {
+ viewModel.shareAudioClicked()
+ uiEventLogger.log(
+ BluetoothTileDialogUiEvent
+ .AUDIO_SHARING_DIALOG_SHARE_AUDIO_CLICKED
+ )
+ dialog.dismiss()
+ }
+ }
+ }
+ }
+ }
+ SystemUIDialog.registerDismissListener(dialog) { job.cancel() }
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(cachedBluetoothDevice: CachedBluetoothDevice): AudioSharingDialogDelegate
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModel.kt
new file mode 100644
index 0000000..dc970aea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModel.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.content.Context
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+
+sealed class AudioSharingDialogState {
+ data object Hide : AudioSharingDialogState()
+
+ data class Show(val subtitle: String, val switchButtonText: String) : AudioSharingDialogState()
+}
+
+class AudioSharingDialogViewModel
+@AssistedInject
+constructor(
+ deviceItemInteractor: DeviceItemInteractor,
+ private val audioSharingInteractor: AudioSharingInteractor,
+ private val context: Context,
+ private val localBluetoothManager: LocalBluetoothManager?,
+ @Assisted private val cachedBluetoothDevice: CachedBluetoothDevice,
+ @Assisted private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ val dialogState: Flow<AudioSharingDialogState> =
+ deviceItemInteractor.deviceItemUpdateRequest
+ .map {
+ if (
+ audioSharingInteractor.isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice
+ )
+ ) {
+ createShowState(cachedBluetoothDevice)
+ } else {
+ AudioSharingDialogState.Hide
+ }
+ }
+ .onStart { emit(createShowState(cachedBluetoothDevice)) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+
+ fun switchActiveClicked() {
+ coroutineScope.launch { audioSharingInteractor.switchActive(cachedBluetoothDevice) }
+ }
+
+ fun shareAudioClicked() {
+ coroutineScope.launch { audioSharingInteractor.startAudioSharing() }
+ }
+
+ private fun createShowState(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): AudioSharingDialogState {
+ val activeDeviceName =
+ localBluetoothManager
+ ?.profileManager
+ ?.leAudioProfile
+ ?.activeDevices
+ ?.firstOrNull()
+ ?.let { localBluetoothManager.cachedDeviceManager?.findDevice(it)?.name } ?: ""
+ val availableDeviceName = cachedBluetoothDevice.name
+ return AudioSharingDialogState.Show(
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ availableDeviceName,
+ activeDeviceName
+ ),
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ availableDeviceName
+ )
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ cachedBluetoothDevice: CachedBluetoothDevice,
+ coroutineScope: CoroutineScope
+ ): AudioSharingDialogViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index 817f2d7..65f1105 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -16,82 +16,148 @@
package com.android.systemui.bluetooth.qsdialog
-import androidx.annotation.StringRes
import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.onPlaybackStarted
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.stateIn
-
-internal sealed class AudioSharingButtonState {
- object Gone : AudioSharingButtonState()
-
- data class Visible(@StringRes val resId: Int, val isActive: Boolean) :
- AudioSharingButtonState()
-}
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.withContext
/** Holds business logic for the audio sharing state. */
+interface AudioSharingInteractor {
+ val isAudioSharingOn: Flow<Boolean>
+
+ val audioSourceStateUpdate: Flow<Unit>
+
+ suspend fun handleAudioSourceWhenReady()
+
+ suspend fun isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): Boolean
+
+ suspend fun switchActive(cachedBluetoothDevice: CachedBluetoothDevice)
+
+ suspend fun startAudioSharing()
+
+ suspend fun audioSharingAvailable(): Boolean
+}
+
@SysUISingleton
-internal class AudioSharingInteractor
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingInteractorImpl
@Inject
constructor(
private val localBluetoothManager: LocalBluetoothManager?,
- bluetoothStateInteractor: BluetoothStateInteractor,
- deviceItemInteractor: DeviceItemInteractor,
- @Application private val coroutineScope: CoroutineScope,
+ private val audioSharingRepository: AudioSharingRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
-) {
- /** Flow representing the update of AudioSharingButtonState. */
- internal val audioSharingButtonStateUpdate: Flow<AudioSharingButtonState> =
- combine(
- bluetoothStateInteractor.bluetoothStateUpdate,
- deviceItemInteractor.deviceItemUpdate
- ) { bluetoothState, deviceItem ->
- getButtonState(bluetoothState, deviceItem)
+) : AudioSharingInteractor {
+
+ override val isAudioSharingOn: Flow<Boolean> =
+ flow { emit(audioSharingAvailable()) }
+ .flatMapLatest { isEnabled ->
+ if (isEnabled) {
+ audioSharingRepository.inAudioSharing
+ } else {
+ flowOf(false)
+ }
}
.flowOn(backgroundDispatcher)
- .stateIn(
- coroutineScope,
- SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
- initialValue = AudioSharingButtonState.Gone
- )
- private fun getButtonState(
- bluetoothState: Boolean,
- deviceItem: List<DeviceItem>
- ): AudioSharingButtonState {
- return when {
- // Don't show button when bluetooth is off
- !bluetoothState -> AudioSharingButtonState.Gone
- // Show sharing audio when broadcasting
- BluetoothUtils.isBroadcasting(localBluetoothManager) ->
- AudioSharingButtonState.Visible(
- R.string.quick_settings_bluetooth_audio_sharing_button_sharing,
- isActive = true
- )
- // When not broadcasting, don't show button if there's connected source in any device
- deviceItem.any {
- BluetoothUtils.hasConnectedBroadcastSource(
- it.cachedBluetoothDevice,
- localBluetoothManager
- )
- } -> AudioSharingButtonState.Gone
- // Show audio sharing when there's a connected LE audio device
- deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
- AudioSharingButtonState.Visible(
- R.string.quick_settings_bluetooth_audio_sharing_button,
- isActive = false
- )
- else -> AudioSharingButtonState.Gone
+ override val audioSourceStateUpdate =
+ isAudioSharingOn
+ .flatMapLatest {
+ if (it) {
+ audioSharingRepository.audioSourceStateUpdate
+ } else {
+ emptyFlow()
+ }
+ }
+ .flowOn(backgroundDispatcher)
+
+ override suspend fun handleAudioSourceWhenReady() {
+ withContext(backgroundDispatcher) {
+ if (audioSharingAvailable()) {
+ audioSharingRepository.leAudioBroadcastProfile?.let { profile ->
+ isAudioSharingOn
+ .mapNotNull { audioSharingOn ->
+ if (audioSharingOn) {
+ // onPlaybackStarted could emit multiple times during one
+ // audio sharing session, we only perform add source on the
+ // first time
+ profile.onPlaybackStarted.firstOrNull()
+ } else {
+ null
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .collect { audioSharingRepository.addSource() }
+ }
+ }
}
}
+
+ override suspend fun isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): Boolean {
+ return withContext(backgroundDispatcher) {
+ if (audioSharingAvailable()) {
+ BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice,
+ localBluetoothManager,
+ )
+ } else {
+ false
+ }
+ }
+ }
+
+ override suspend fun switchActive(cachedBluetoothDevice: CachedBluetoothDevice) {
+ if (!audioSharingAvailable()) {
+ return
+ }
+ audioSharingRepository.setActive(cachedBluetoothDevice)
+ }
+
+ override suspend fun startAudioSharing() {
+ if (!audioSharingAvailable()) {
+ return
+ }
+ audioSharingRepository.startAudioSharing()
+ }
+
+ // TODO(b/367965193): Move this after flags rollout
+ override suspend fun audioSharingAvailable(): Boolean {
+ return audioSharingRepository.audioSharingAvailable()
+ }
+}
+
+@SysUISingleton
+class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
+ override val isAudioSharingOn: Flow<Boolean> = flowOf(false)
+
+ override val audioSourceStateUpdate: Flow<Unit> = emptyFlow()
+
+ override suspend fun handleAudioSourceWhenReady() {}
+
+ override suspend fun isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ) = false
+
+ override suspend fun switchActive(cachedBluetoothDevice: CachedBluetoothDevice) {}
+
+ override suspend fun startAudioSharing() {}
+
+ override suspend fun audioSharingAvailable(): Boolean = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
new file mode 100644
index 0000000..b9b8d36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
+import com.android.settingslib.volume.data.repository.AudioSharingRepository as SettingsLibAudioSharingRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.withContext
+
+interface AudioSharingRepository {
+ val leAudioBroadcastProfile: LocalBluetoothLeBroadcast?
+
+ val audioSourceStateUpdate: Flow<Unit>
+
+ val inAudioSharing: StateFlow<Boolean>
+
+ suspend fun audioSharingAvailable(): Boolean
+
+ suspend fun addSource()
+
+ suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice)
+
+ suspend fun startAudioSharing()
+}
+
+@SysUISingleton
+class AudioSharingRepositoryImpl(
+ private val localBluetoothManager: LocalBluetoothManager,
+ private val settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : AudioSharingRepository {
+
+ override val leAudioBroadcastProfile: LocalBluetoothLeBroadcast?
+ get() = localBluetoothManager.profileManager?.leAudioBroadcastProfile
+
+ private val leAudioBroadcastAssistantProfile: LocalBluetoothLeBroadcastAssistant?
+ get() = localBluetoothManager.profileManager?.leAudioBroadcastAssistantProfile
+
+ override val audioSourceStateUpdate: Flow<Unit> =
+ leAudioBroadcastAssistantProfile?.onSourceConnectedOrRemoved ?: emptyFlow()
+
+ override val inAudioSharing: StateFlow<Boolean> =
+ settingsLibAudioSharingRepository.inAudioSharing
+
+ override suspend fun audioSharingAvailable(): Boolean {
+ return settingsLibAudioSharingRepository.audioSharingAvailable()
+ }
+
+ override suspend fun addSource() {
+ withContext(backgroundDispatcher) {
+ if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
+ return@withContext
+ }
+ leAudioBroadcastProfile?.latestBluetoothLeBroadcastMetadata?.let { metadata ->
+ leAudioBroadcastAssistantProfile?.let {
+ it.allConnectedDevices.forEach { sink -> it.addSource(sink, metadata, false) }
+ }
+ }
+ }
+ }
+
+ override suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice) {
+ withContext(backgroundDispatcher) {
+ if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
+ return@withContext
+ }
+ cachedBluetoothDevice.setActive()
+ }
+ }
+
+ override suspend fun startAudioSharing() {
+ withContext(backgroundDispatcher) {
+ if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
+ return@withContext
+ }
+ leAudioBroadcastProfile?.startPrivateBroadcast()
+ }
+ }
+}
+
+@SysUISingleton
+class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
+ override val leAudioBroadcastProfile: LocalBluetoothLeBroadcast? = null
+
+ override val audioSourceStateUpdate: Flow<Unit> = emptyFlow()
+
+ override val inAudioSharing: StateFlow<Boolean> = MutableStateFlow(false)
+
+ override suspend fun audioSharingAvailable(): Boolean = false
+
+ override suspend fun addSource() {}
+
+ override suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice) {}
+
+ override suspend fun startAudioSharing() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 17f9e63..55d4d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -39,7 +39,7 @@
/** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
@SysUISingleton
-internal class BluetoothStateInteractor
+class BluetoothStateInteractor
@Inject
constructor(
private val localBluetoothManager: LocalBluetoothManager?,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 7deea73..a9c5c69 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -300,7 +300,7 @@
}
private fun getProgressBarBackground(dialog: SystemUIDialog): View {
- return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+ return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_background)
}
private fun getScrollViewContent(dialog: SystemUIDialog): View {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index bdd4c16..aad233f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -42,6 +42,7 @@
LAUNCH_SETTINGS_IN_SHARING_LE_DEVICE_CLICKED(1717),
@UiEvent(doc = "Currently broadcasting and a non-LE audio supported device is clicked")
LAUNCH_SETTINGS_IN_SHARING_NON_LE_DEVICE_CLICKED(1718),
+ @Deprecated("Use case no longer needed")
@UiEvent(
doc = "Not broadcasting, having one connected, another saved LE audio device is clicked"
)
@@ -52,8 +53,13 @@
)
@UiEvent(doc = "Not broadcasting, one of the two connected LE audio devices is clicked")
LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720),
+ @Deprecated("Use case no longer needed")
@UiEvent(doc = "Not broadcasting, having two connected, the active LE audio devices is clicked")
- LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED(1881);
+ LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED(1881),
+ @UiEvent(doc = "Clicked on switch active button on audio sharing dialog")
+ AUDIO_SHARING_DIALOG_SWITCH_ACTIVE_CLICKED(1890),
+ @UiEvent(doc = "Clicked on share audio button on audio sharing dialog")
+ AUDIO_SHARING_DIALOG_SHARE_AUDIO_CLICKED(1891);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index a8f7fc3..5c35c52 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -28,8 +28,8 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
@@ -51,6 +51,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge
@@ -68,10 +69,12 @@
private val bluetoothStateInteractor: BluetoothStateInteractor,
private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
private val audioSharingInteractor: AudioSharingInteractor,
+ private val audioSharingButtonViewModelFactory: AudioSharingButtonViewModel.Factory,
private val bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
private val uiEventLogger: UiEventLogger,
+ private val logger: BluetoothTileDialogLogger,
@Application private val coroutineScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@@ -102,7 +105,7 @@
expandable?.dialogTransitionController(
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
+ INTERACTION_JANK_TAG,
)
)
controller?.let {
@@ -117,7 +120,7 @@
// stop the progress bar.
combine(
deviceItemInteractor.deviceItemUpdate,
- deviceItemInteractor.showSeeAllUpdate
+ deviceItemInteractor.showSeeAllUpdate,
) { deviceItem, showSeeAll ->
updateDialogUiJob?.cancel()
updateDialogUiJob = launch {
@@ -127,7 +130,7 @@
deviceItem,
showSeeAll,
showPairNewDevice =
- bluetoothStateInteractor.isBluetoothEnabled()
+ bluetoothStateInteractor.isBluetoothEnabled(),
)
animateProgressBar(dialog, false)
}
@@ -139,7 +142,15 @@
// the device item list and animate the progress bar.
merge(
deviceItemInteractor.deviceItemUpdateRequest,
- bluetoothDeviceMetadataInteractor.metadataUpdate
+ bluetoothDeviceMetadataInteractor.metadataUpdate,
+ if (
+ audioSharingInteractor.audioSharingAvailable() &&
+ audioSharingQsDialogImprovement()
+ ) {
+ audioSharingInteractor.audioSourceStateUpdate
+ } else {
+ emptyFlow()
+ },
)
.onEach {
dialogDelegate.animateProgressBar(dialog, true)
@@ -147,35 +158,42 @@
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(
context,
- DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED
+ DeviceFetchTrigger.BLUETOOTH_CALLBACK_RECEIVED,
)
}
}
.launchIn(this)
- if (BluetoothUtils.isAudioSharingEnabled()) {
- audioSharingInteractor.audioSharingButtonStateUpdate
- .onEach {
- when (it) {
- is AudioSharingButtonState.Visible -> {
- dialogDelegate.onAudioSharingButtonUpdated(
- dialog,
- VISIBLE,
- context.getString(it.resId),
- it.isActive
- )
- }
- is AudioSharingButtonState.Gone -> {
- dialogDelegate.onAudioSharingButtonUpdated(
- dialog,
- GONE,
- label = null,
- isActive = false
- )
+ if (audioSharingInteractor.audioSharingAvailable()) {
+ if (audioSharingQsDialogImprovement()) {
+ launch { audioSharingInteractor.handleAudioSourceWhenReady() }
+ }
+
+ audioSharingButtonViewModelFactory.create().run {
+ audioSharingButtonStateUpdate
+ .onEach {
+ when (it) {
+ is AudioSharingButtonState.Visible -> {
+ dialogDelegate.onAudioSharingButtonUpdated(
+ dialog,
+ VISIBLE,
+ context.getString(it.resId),
+ it.isActive,
+ )
+ }
+ is AudioSharingButtonState.Gone -> {
+ dialogDelegate.onAudioSharingButtonUpdated(
+ dialog,
+ GONE,
+ label = null,
+ isActive = false,
+ )
+ }
}
}
- }
- .launchIn(this)
+ .launchIn(this@launch)
+ launch { activate() }
+ }
}
// bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
@@ -185,13 +203,13 @@
dialogDelegate.onBluetoothStateUpdated(
dialog,
it,
- UiProperties.build(it, isAutoOnToggleFeatureAvailable())
+ UiProperties.build(it, isAutoOnToggleFeatureAvailable()),
)
updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(
context,
- DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED
+ DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED,
)
}
}
@@ -209,7 +227,10 @@
// deviceItemClick is emitted when user clicked on a device item.
dialogDelegate.deviceItemClick
- .onEach { deviceItemActionInteractor.onClick(it, dialog) }
+ .onEach {
+ deviceItemActionInteractor.onClick(it, dialog)
+ logger.logDeviceClick(it.cachedBluetoothDevice.address, it.type)
+ }
.launchIn(this)
// contentHeight is emitted when the dialog is dismissed.
@@ -230,7 +251,7 @@
dialog,
it,
if (it) R.string.turn_on_bluetooth_auto_info_enabled
- else R.string.turn_on_bluetooth_auto_info_disabled
+ else R.string.turn_on_bluetooth_auto_info_disabled,
)
}
.launchIn(this)
@@ -252,18 +273,18 @@
withContext(backgroundDispatcher) {
sharedPreferences.getInt(
CONTENT_HEIGHT_PREF_KEY,
- ViewGroup.LayoutParams.WRAP_CONTENT
+ ViewGroup.LayoutParams.WRAP_CONTENT,
)
}
return bluetoothDialogDelegateFactory.create(
UiProperties.build(
bluetoothStateInteractor.isBluetoothEnabled(),
- isAutoOnToggleFeatureAvailable()
+ isAutoOnToggleFeatureAvailable(),
),
cachedContentHeight,
this@BluetoothTileDialogViewModel,
- { cancelJob() }
+ { cancelJob() },
)
}
@@ -275,7 +296,7 @@
EXTRA_SHOW_FRAGMENT_ARGUMENTS,
Bundle().apply {
putString("device_address", deviceItem.cachedBluetoothDevice.address)
- }
+ },
)
}
startSettingsActivity(intent, view)
@@ -299,7 +320,7 @@
EXTRA_SHOW_FRAGMENT_ARGUMENTS,
Bundle().apply {
putBoolean(LocalBluetoothLeBroadcast.EXTRA_START_LE_AUDIO_SHARING, true)
- }
+ },
)
}
startSettingsActivity(intent, view)
@@ -345,7 +366,7 @@
companion object {
internal fun build(
isBluetoothEnabled: Boolean,
- isAutoOnToggleFeatureAvailable: Boolean
+ isAutoOnToggleFeatureAvailable: Boolean,
) =
UiProperties(
subTitleResId = getSubtitleResId(isBluetoothEnabled),
@@ -355,7 +376,7 @@
scrollViewMinHeightResId =
if (isAutoOnToggleFeatureAvailable)
R.dimen.bluetooth_dialog_scroll_view_min_height_with_auto_on
- else R.dimen.bluetooth_dialog_scroll_view_min_height
+ else R.dimen.bluetooth_dialog_scroll_view_min_height,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index f1894d3..cf0f19f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -16,87 +16,28 @@
package com.android.systemui.bluetooth.qsdialog
-import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothProfile
-import android.content.Intent
-import android.os.Bundle
-import android.provider.Settings
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.A2dpProfile
-import com.android.settingslib.bluetooth.BluetoothUtils
-import com.android.settingslib.bluetooth.HeadsetProfile
-import com.android.settingslib.bluetooth.HearingAidProfile
-import com.android.settingslib.bluetooth.LeAudioProfile
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractor.LaunchSettingsCriteria.Companion.getCurrentConnectedLeByGroupId
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
+interface DeviceItemActionInteractor {
+ suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {}
+}
+
@SysUISingleton
-class DeviceItemActionInteractor
+class DeviceItemActionInteractorImpl
@Inject
constructor(
- private val activityStarter: ActivityStarter,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val localBluetoothManager: LocalBluetoothManager?,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val logger: BluetoothTileDialogLogger,
private val uiEventLogger: UiEventLogger,
-) {
- private val leAudioProfile: LeAudioProfile?
- get() = localBluetoothManager?.profileManager?.leAudioProfile
+) : DeviceItemActionInteractor {
- private val assistantProfile: LocalBluetoothLeBroadcastAssistant?
- get() = localBluetoothManager?.profileManager?.leAudioBroadcastAssistantProfile
-
- private val launchSettingsCriteriaList: List<LaunchSettingsCriteria>
- get() =
- listOf(
- InSharingClickedNoSource(localBluetoothManager, backgroundDispatcher, logger),
- NotSharingClickedNonConnect(
- leAudioProfile,
- assistantProfile,
- backgroundDispatcher,
- logger
- ),
- NotSharingClickedActive(
- leAudioProfile,
- assistantProfile,
- backgroundDispatcher,
- logger
- )
- )
-
- suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
+ override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
withContext(backgroundDispatcher) {
- logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
- if (
- BluetoothUtils.isAudioSharingEnabled() &&
- localBluetoothManager != null &&
- leAudioProfile != null &&
- assistantProfile != null
- ) {
- val inAudioSharing = BluetoothUtils.isBroadcasting(localBluetoothManager)
- logger.logDeviceClickInAudioSharingWhenEnabled(inAudioSharing)
-
- val criteriaMatched =
- launchSettingsCriteriaList.firstOrNull {
- it.matched(inAudioSharing, deviceItem)
- }
- if (criteriaMatched != null) {
- uiEventLogger.log(criteriaMatched.getClickUiEvent(deviceItem))
- launchSettings(deviceItem.cachedBluetoothDevice.device, dialog)
- return@withContext
- }
- }
deviceItem.cachedBluetoothDevice.apply {
when (deviceItem.type) {
DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
@@ -106,12 +47,6 @@
DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
}
- DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
- // TODO(b/360759048): pop up dialog
- uiEventLogger.log(
- BluetoothTileDialogUiEvent.AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED
- )
- }
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
setActive()
uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
@@ -126,186 +61,12 @@
connect()
uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
}
- }
- }
- }
- }
-
- private fun launchSettings(device: BluetoothDevice, dialog: SystemUIDialog) {
- val intent =
- Intent(Settings.ACTION_BLUETOOTH_SETTINGS).apply {
- putExtra(
- EXTRA_SHOW_FRAGMENT_ARGUMENTS,
- Bundle().apply {
- putParcelable(LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE, device)
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ // Do nothing. Should already be handled in
+ // AudioSharingDeviceItemActionInteractor.
}
- )
- }
- intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0,
- dialogTransitionAnimator.createActivityTransitionController(dialog)
- )
- }
-
- private interface LaunchSettingsCriteria {
- suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean
-
- suspend fun getClickUiEvent(deviceItem: DeviceItem): BluetoothTileDialogUiEvent
-
- companion object {
- suspend fun getCurrentConnectedLeByGroupId(
- leAudioProfile: LeAudioProfile,
- assistantProfile: LocalBluetoothLeBroadcastAssistant,
- @Background backgroundDispatcher: CoroutineDispatcher,
- logger: BluetoothTileDialogLogger,
- ): Map<Int, List<BluetoothDevice>> {
- return withContext(backgroundDispatcher) {
- assistantProfile
- .getDevicesMatchingConnectionStates(
- intArrayOf(BluetoothProfile.STATE_CONNECTED)
- )
- ?.filterNotNull()
- ?.groupBy { leAudioProfile.getGroupId(it) }
- ?.also { logger.logConnectedLeByGroupId(it) } ?: emptyMap()
}
}
}
}
-
- private class InSharingClickedNoSource(
- private val localBluetoothManager: LocalBluetoothManager?,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val logger: BluetoothTileDialogLogger,
- ) : LaunchSettingsCriteria {
- // If currently broadcasting and the clicked device is not connected to the source
- override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
- return withContext(backgroundDispatcher) {
- val matched =
- inAudioSharing &&
- deviceItem.isMediaDevice &&
- !BluetoothUtils.hasConnectedBroadcastSource(
- deviceItem.cachedBluetoothDevice,
- localBluetoothManager
- )
-
- if (matched) {
- logger.logLaunchSettingsCriteriaMatched("InSharingClickedNoSource", deviceItem)
- }
-
- matched
- }
- }
-
- override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
- if (deviceItem.isLeAudioSupported)
- BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_IN_SHARING_LE_DEVICE_CLICKED
- else BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_IN_SHARING_NON_LE_DEVICE_CLICKED
- }
-
- private class NotSharingClickedNonConnect(
- private val leAudioProfile: LeAudioProfile?,
- private val assistantProfile: LocalBluetoothLeBroadcastAssistant?,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val logger: BluetoothTileDialogLogger,
- ) : LaunchSettingsCriteria {
- // If not broadcasting, having one device connected, and clicked on a not yet connected LE
- // audio device
- override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
- return withContext(backgroundDispatcher) {
- val matched =
- leAudioProfile?.let { leAudio ->
- assistantProfile?.let { assistant ->
- !inAudioSharing &&
- getCurrentConnectedLeByGroupId(
- leAudio,
- assistant,
- backgroundDispatcher,
- logger
- )
- .size == 1 &&
- deviceItem.isNotConnectedLeAudioSupported
- }
- } ?: false
-
- if (matched) {
- logger.logLaunchSettingsCriteriaMatched(
- "NotSharingClickedNonConnect",
- deviceItem
- )
- }
-
- matched
- }
- }
-
- override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
- BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED
- }
-
- private class NotSharingClickedActive(
- private val leAudioProfile: LeAudioProfile?,
- private val assistantProfile: LocalBluetoothLeBroadcastAssistant?,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val logger: BluetoothTileDialogLogger,
- ) : LaunchSettingsCriteria {
- // If not broadcasting, having two device connected, clicked on the active LE audio
- // device
- override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
- return withContext(backgroundDispatcher) {
- val matched =
- leAudioProfile?.let { leAudio ->
- assistantProfile?.let { assistant ->
- !inAudioSharing &&
- getCurrentConnectedLeByGroupId(
- leAudio,
- assistant,
- backgroundDispatcher,
- logger
- )
- .size == 2 &&
- deviceItem.isActiveLeAudioSupported
- }
- } ?: false
-
- if (matched) {
- logger.logLaunchSettingsCriteriaMatched(
- "NotSharingClickedConnected",
- deviceItem
- )
- }
-
- matched
- }
- }
-
- override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
- BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED
- }
-
- private companion object {
- const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"
-
- val DeviceItem.isLeAudioSupported: Boolean
- get() =
- cachedBluetoothDevice.profiles.any { profile ->
- profile is LeAudioProfile && profile.isEnabled(cachedBluetoothDevice.device)
- }
-
- val DeviceItem.isNotConnectedLeAudioSupported: Boolean
- get() = type == DeviceItemType.SAVED_BLUETOOTH_DEVICE && isLeAudioSupported
-
- val DeviceItem.isActiveLeAudioSupported: Boolean
- get() = type == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE && isLeAudioSupported
-
- val DeviceItem.isMediaDevice: Boolean
- get() =
- cachedBluetoothDevice.uiAccessibleProfiles.any {
- it is A2dpProfile ||
- it is HearingAidProfile ||
- it is LeAudioProfile ||
- it is HeadsetProfile
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 7280489..7ed5629 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -23,7 +23,6 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.flags.Flags
-import com.android.settingslib.flags.Flags.enableLeAudioSharing
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
@@ -56,7 +55,7 @@
connectionSummary: String,
background: Int,
actionAccessibilityLabel: String,
- isActive: Boolean
+ isActive: Boolean,
): DeviceItem {
return DeviceItem(
type = type,
@@ -70,7 +69,7 @@
background = background,
isEnabled = !cachedDevice.isBusy,
actionAccessibilityLabel = actionAccessibilityLabel,
- isActive = isActive
+ isActive = isActive,
)
}
}
@@ -80,7 +79,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
@@ -94,20 +93,20 @@
cachedDevice.connectionSummary ?: "",
backgroundOn,
context.getString(actionAccessibilityLabelDisconnect),
- isActive = true
+ isActive = true,
)
}
}
internal class AudioSharingMediaDeviceItemFactory(
- private val localBluetoothManager: LocalBluetoothManager?
+ private val localBluetoothManager: LocalBluetoothManager
) : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
- return enableLeAudioSharing() &&
+ return BluetoothUtils.isAudioSharingEnabled() &&
BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
}
@@ -120,24 +119,24 @@
?: context.getString(audioSharing),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
"",
- isActive = !cachedDevice.isBusy
+ isActive = !cachedDevice.isBusy,
)
}
}
internal class AvailableAudioSharingMediaDeviceItemFactory(
- private val localBluetoothManager: LocalBluetoothManager?
+ private val localBluetoothManager: LocalBluetoothManager
) : AvailableMediaDeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return BluetoothUtils.isAudioSharingEnabled() &&
super.isFilterMatched(context, cachedDevice, audioManager) &&
BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
cachedDevice,
- localBluetoothManager
+ localBluetoothManager,
)
}
@@ -151,7 +150,7 @@
),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
"",
- isActive = false
+ isActive = false,
)
}
}
@@ -160,7 +159,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -171,7 +170,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
@@ -186,7 +185,7 @@
?: context.getString(connected),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
context.getString(actionAccessibilityLabelActivate),
- isActive = false
+ isActive = false,
)
}
}
@@ -195,7 +194,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -206,7 +205,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
!BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
@@ -225,7 +224,7 @@
?: context.getString(connected),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
context.getString(actionAccessibilityLabelDisconnect),
- isActive = false
+ isActive = false,
)
}
}
@@ -234,7 +233,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
!BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
@@ -254,7 +253,7 @@
?: context.getString(saved),
if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
context.getString(actionAccessibilityLabelActivate),
- isActive = false
+ isActive = false,
)
}
}
@@ -263,12 +262,12 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager
+ audioManager: AudioManager,
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
!BluetoothUtils.isExclusivelyManagedBluetoothDevice(
context,
- cachedDevice.getDevice()
+ cachedDevice.getDevice(),
) &&
cachedDevice.isHearingAidDevice &&
cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 9114eca..0118e56 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -54,6 +54,8 @@
private val localBluetoothManager: LocalBluetoothManager?,
private val systemClock: SystemClock,
private val logger: BluetoothTileDialogLogger,
+ private val deviceItemFactoryList: List<@JvmSuppressWildcards DeviceItemFactory>,
+ private val deviceItemDisplayPriority: List<@JvmSuppressWildcards DeviceItemType>,
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
@@ -67,7 +69,7 @@
internal val showSeeAllUpdate
get() = mutableShowSeeAllUpdate.asStateFlow()
- internal val deviceItemUpdateRequest: SharedFlow<Unit> =
+ val deviceItemUpdateRequest: SharedFlow<Unit> =
conflatedCallbackFlow {
val listener =
object : BluetoothCallback {
@@ -114,26 +116,6 @@
}
.shareIn(coroutineScope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0))
- private var deviceItemFactoryList: List<DeviceItemFactory> =
- listOf(
- ActiveMediaDeviceItemFactory(),
- AudioSharingMediaDeviceItemFactory(localBluetoothManager),
- AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager),
- AvailableMediaDeviceItemFactory(),
- ConnectedDeviceItemFactory(),
- SavedDeviceItemFactory()
- )
-
- private var displayPriority: List<DeviceItemType> =
- listOf(
- DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
- DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- )
-
internal suspend fun updateDeviceItems(context: Context, trigger: DeviceFetchTrigger) {
withContext(backgroundDispatcher) {
val start = systemClock.elapsedRealtime()
@@ -144,7 +126,7 @@
.firstOrNull { it.isFilterMatched(context, cachedDevice, audioManager) }
?.create(context, cachedDevice)
}
- .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
+ .sort(deviceItemDisplayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
// Only emit when the job is not cancelled
if (isActive) {
mutableDeviceItemUpdate.tryEmit(deviceItems.take(MAX_DEVICE_ITEM_ENTRY))
@@ -176,14 +158,6 @@
)
}
- internal fun setDeviceItemFactoryListForTesting(list: List<DeviceItemFactory>) {
- deviceItemFactoryList = list
- }
-
- internal fun setDisplayPriorityForTesting(list: List<DeviceItemType>) {
- displayPriority = list
- }
-
companion object {
private const val TAG = "DeviceItemInteractor"
private const val MAX_DEVICE_ITEM_ENTRY = 3
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
new file mode 100644
index 0000000..50970a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog.dagger
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.flags.Flags
+import com.android.settingslib.volume.data.repository.AudioSharingRepository as SettingsLibAudioSharingRepository
+import com.android.systemui.bluetooth.qsdialog.ActiveMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.AudioSharingDeviceItemActionInteractorImpl
+import com.android.systemui.bluetooth.qsdialog.AudioSharingInteractor
+import com.android.systemui.bluetooth.qsdialog.AudioSharingInteractorEmptyImpl
+import com.android.systemui.bluetooth.qsdialog.AudioSharingInteractorImpl
+import com.android.systemui.bluetooth.qsdialog.AudioSharingMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.AudioSharingRepository
+import com.android.systemui.bluetooth.qsdialog.AudioSharingRepositoryEmptyImpl
+import com.android.systemui.bluetooth.qsdialog.AudioSharingRepositoryImpl
+import com.android.systemui.bluetooth.qsdialog.AvailableAudioSharingMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.AvailableMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractor
+import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractorImpl
+import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType
+import com.android.systemui.bluetooth.qsdialog.SavedDeviceItemFactory
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+
+/** Dagger module for audio sharing code for BT QS dialog */
+@Module
+interface AudioSharingModule {
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideAudioSharingRepository(
+ localBluetoothManager: LocalBluetoothManager?,
+ settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ ): AudioSharingRepository =
+ if (
+ Flags.enableLeAudioSharing() &&
+ Flags.audioSharingQsDialogImprovement() &&
+ localBluetoothManager != null
+ ) {
+ AudioSharingRepositoryImpl(
+ localBluetoothManager,
+ settingsLibAudioSharingRepository,
+ backgroundDispatcher,
+ )
+ } else {
+ AudioSharingRepositoryEmptyImpl()
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideAudioSharingInteractor(
+ localBluetoothManager: LocalBluetoothManager?,
+ impl: Lazy<AudioSharingInteractorImpl>,
+ emptyImpl: Lazy<AudioSharingInteractorEmptyImpl>,
+ ): AudioSharingInteractor =
+ if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ impl.get()
+ } else {
+ emptyImpl.get()
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideDeviceItemActionInteractor(
+ localBluetoothManager: LocalBluetoothManager?,
+ audioSharingImpl: Lazy<AudioSharingDeviceItemActionInteractorImpl>,
+ impl: Lazy<DeviceItemActionInteractorImpl>,
+ ): DeviceItemActionInteractor =
+ if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ audioSharingImpl.get()
+ } else {
+ impl.get()
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideDeviceItemFactoryList(
+ localBluetoothManager: LocalBluetoothManager?
+ ): List<DeviceItemFactory> = buildList {
+ add(ActiveMediaDeviceItemFactory())
+ if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ add(AudioSharingMediaDeviceItemFactory(localBluetoothManager))
+ add(AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager))
+ }
+ add(AvailableMediaDeviceItemFactory())
+ add(ConnectedDeviceItemFactory())
+ add(SavedDeviceItemFactory())
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideDeviceItemDisplayPriority(
+ localBluetoothManager: LocalBluetoothManager?
+ ): List<DeviceItemType> = buildList {
+ add(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+ if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ add(DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
+ add(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
+ }
+ add(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+ add(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+ add(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 3ae9250..6508e4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -27,7 +27,6 @@
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
-import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.dagger.CommunalModule.Companion.LAUNCHER_PACKAGE
import com.android.systemui.communal.data.model.CommunalWidgetCategories
@@ -184,10 +183,10 @@
val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal
- /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
+ /** Launch the widget picker activity using the given startActivity method. */
suspend fun onOpenWidgetPicker(
resources: Resources,
- activityLauncher: ActivityResultLauncher<Intent>,
+ startActivity: (intent: Intent) -> Unit,
): Boolean =
withContext(backgroundDispatcher) {
val widgets = communalInteractor.widgetContent.first()
@@ -199,7 +198,7 @@
}
getWidgetPickerActivityIntent(resources, excludeList)?.let {
try {
- activityLauncher.launch(it)
+ startActivity(it)
return@withContext true
} catch (e: Exception) {
Log.e(TAG, "Failed to launch widget picker activity", e)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 6228ac5..8c14d63 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -27,8 +27,6 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -51,6 +49,7 @@
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -64,12 +63,15 @@
private val uiEventLogger: UiEventLogger,
private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
private val widgetSection: CommunalAppWidgetSection,
+ private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
private const val TAG = "EditWidgetsActivity"
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
+
+ private const val REQUEST_CODE_WIDGET_PICKER = 200
}
/**
@@ -110,7 +112,7 @@
object : ActivityLifecycleCallbacks {
override fun onActivityCreated(
activity: Activity,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
) {
waitingForResult =
savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT)
@@ -172,41 +174,6 @@
if (communalEditWidgetsActivityFinishFix()) ActivityControllerImpl(this)
else NopActivityController()
- private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
- registerForActivityResult(StartActivityForResult()) { result ->
- when (result.resultCode) {
- RESULT_OK -> {
- uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN)
-
- result.data?.let { intent ->
- val isPendingWidgetDrag =
- intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
- // Nothing to do when a widget is being dragged & dropped. The drop
- // target in the communal grid will receive the widget to be added (if
- // the user drops it over).
- if (!isPendingWidgetDrag) {
- val (componentName, user) = getWidgetExtraFromIntent(intent)
- if (componentName != null && user != null) {
- // Add widget at the end.
- communalViewModel.onAddWidget(
- componentName,
- user,
- configurator = widgetConfigurator,
- )
- } else {
- run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
- }
- }
- } ?: run { Log.w(TAG, "No data in result.") }
- }
- else ->
- Log.w(
- TAG,
- "Failed to receive result from widget picker, code=${result.resultCode}"
- )
- }
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -226,8 +193,7 @@
PlatformTheme {
Box(
modifier =
- Modifier.fillMaxSize()
- .background(MaterialTheme.colorScheme.surfaceDim),
+ Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surfaceDim)
) {
CommunalHub(
viewModel = communalViewModel,
@@ -274,7 +240,13 @@
private fun onOpenWidgetPicker() {
lifecycleScope.launch {
- communalViewModel.onOpenWidgetPicker(resources, addWidgetActivityLauncher)
+ communalViewModel.onOpenWidgetPicker(resources) { intent: Intent ->
+ startActivityForResultAsUser(
+ intent,
+ REQUEST_CODE_WIDGET_PICKER,
+ userTracker.userHandle,
+ )
+ }
}
}
@@ -285,7 +257,7 @@
communalViewModel.changeScene(
scene = CommunalScenes.Communal,
loggingReason = "edit mode closing",
- transitionKey = CommunalTransitionKeys.FromEditMode
+ transitionKey = CommunalTransitionKeys.FromEditMode,
)
// Wait for the current scene to be idle on communal.
@@ -309,7 +281,7 @@
flagsMask: Int,
flagsValues: Int,
extraFlags: Int,
- options: Bundle?
+ options: Bundle?,
) {
activityController.onWaitingForResult(true)
super.startIntentSenderForResult(
@@ -319,15 +291,46 @@
flagsMask,
flagsValues,
extraFlags,
- options
+ options,
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
activityController.onWaitingForResult(false)
super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
- widgetConfigurator.setConfigurationResult(resultCode)
+
+ when (requestCode) {
+ WidgetConfigurationController.REQUEST_CODE ->
+ widgetConfigurator.setConfigurationResult(resultCode)
+ REQUEST_CODE_WIDGET_PICKER -> {
+ if (resultCode != RESULT_OK) {
+ Log.w(TAG, "Failed to receive result from widget picker, code=$resultCode")
+ return
+ }
+
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN)
+
+ data?.let { intent ->
+ val isPendingWidgetDrag =
+ intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
+ // Nothing to do when a widget is being dragged & dropped. The drop
+ // target in the communal grid will receive the widget to be added (if
+ // the user drops it over).
+ if (!isPendingWidgetDrag) {
+ val (componentName, user) = getWidgetExtraFromIntent(intent)
+ if (componentName != null && user != null) {
+ // Add widget at the end.
+ communalViewModel.onAddWidget(
+ componentName,
+ user,
+ configurator = widgetConfigurator,
+ )
+ } else {
+ run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ }
+ }
+ } ?: run { Log.w(TAG, "No data in result.") }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 31e867e..1fe54e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -31,12 +31,12 @@
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl
import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModelImpl
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl
-import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsSizeViewModelImpl
-import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -55,7 +55,7 @@
@Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
- @Binds fun bindQSColumnsViewModel(impl: QSColumnsSizeViewModelImpl): QSColumnsViewModel
+ @Binds fun bindGridSizeViewModel(impl: FixedColumnsSizeViewModelImpl): FixedColumnsSizeViewModel
@Binds
fun bindIconLabelVisibilityViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepository.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepository.kt
index 9b45c56..32ce973 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepository.kt
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.qs.panels.data.repository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
@SysUISingleton
-class QSColumnsInteractor @Inject constructor(repo: QSColumnsRepository) {
- val columns: StateFlow<Int> = repo.columns
+class FixedColumnsRepository @Inject constructor() {
+ // Number of columns in the narrowest state for consistency
+ private val _columns = MutableStateFlow(4)
+ val columns: StateFlow<Int> = _columns.asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
deleted file mode 100644
index 082f622..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.panels.data.repository
-
-import android.content.res.Resources
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.res.R
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.util.kotlin.emitOnStart
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class QSColumnsRepository
-@Inject
-constructor(
- @Application scope: CoroutineScope,
- @Main private val resources: Resources,
- configurationRepository: ConfigurationRepository,
-) {
- val columns: StateFlow<Int> =
- if (DualShade.isEnabled) {
- flowOf(resources.getInteger(R.integer.quick_settings_dual_shade_num_columns))
- } else {
- configurationRepository.onConfigurationChange.emitOnStart().mapLatest {
- resources.getInteger(R.integer.quick_settings_infinite_grid_num_columns)
- }
- }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- resources.getInteger(R.integer.quick_settings_infinite_grid_num_columns),
- )
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractor.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractor.kt
index 9b45c56..9591002 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractor.kt
@@ -17,11 +17,11 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
+import com.android.systemui.qs.panels.data.repository.FixedColumnsRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
@SysUISingleton
-class QSColumnsInteractor @Inject constructor(repo: QSColumnsRepository) {
+class FixedColumnsSizeInteractor @Inject constructor(repo: FixedColumnsRepository) {
val columns: StateFlow<Int> = repo.columns
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 6920e49..3ba49ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -31,8 +31,8 @@
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.rememberEditListState
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileSquishinessViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -45,7 +45,7 @@
@Inject
constructor(
private val iconTilesViewModel: IconTilesViewModel,
- private val gridSizeViewModel: QSColumnsViewModel,
+ private val gridSizeViewModel: FixedColumnsSizeViewModel,
private val squishinessViewModel: TileSquishinessViewModel,
) : PaginatableGridLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModel.kt
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModel.kt
index 0f1c77e..865c86b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModel.kt
@@ -17,16 +17,16 @@
package com.android.systemui.qs.panels.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.QSColumnsInteractor
+import com.android.systemui.qs.panels.domain.interactor.FixedColumnsSizeInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
-interface QSColumnsViewModel {
+interface FixedColumnsSizeViewModel {
val columns: StateFlow<Int>
}
@SysUISingleton
-class QSColumnsSizeViewModelImpl @Inject constructor(interactor: QSColumnsInteractor) :
- QSColumnsViewModel {
+class FixedColumnsSizeViewModelImpl @Inject constructor(interactor: FixedColumnsSizeInteractor) :
+ FixedColumnsSizeViewModel {
override val columns: StateFlow<Int> = interactor.columns
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 78212b2..d4f8298 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -29,13 +29,13 @@
@Inject
constructor(
iconTilesViewModel: IconTilesViewModel,
- gridSizeViewModel: QSColumnsViewModel,
+ gridSizeViewModel: FixedColumnsSizeViewModel,
iconLabelVisibilityViewModel: IconLabelVisibilityViewModel,
paginatedGridInteractor: PaginatedGridInteractor,
@Application applicationScope: CoroutineScope,
) :
IconTilesViewModel by iconTilesViewModel,
- QSColumnsViewModel by gridSizeViewModel,
+ FixedColumnsSizeViewModel by gridSizeViewModel,
IconLabelVisibilityViewModel by iconLabelVisibilityViewModel {
val rows =
paginatedGridInteractor.rows.stateIn(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt
index 0f1c77e..2049edb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt
@@ -17,16 +17,16 @@
package com.android.systemui.qs.panels.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.QSColumnsInteractor
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
-
-interface QSColumnsViewModel {
- val columns: StateFlow<Int>
-}
@SysUISingleton
-class QSColumnsSizeViewModelImpl @Inject constructor(interactor: QSColumnsInteractor) :
- QSColumnsViewModel {
- override val columns: StateFlow<Int> = interactor.columns
-}
+class PartitionedGridViewModel
+@Inject
+constructor(
+ iconTilesViewModel: IconTilesViewModel,
+ gridSizeViewModel: FixedColumnsSizeViewModel,
+ iconLabelVisibilityViewModel: IconLabelVisibilityViewModel,
+) :
+ IconTilesViewModel by iconTilesViewModel,
+ FixedColumnsSizeViewModel by gridSizeViewModel,
+ IconLabelVisibilityViewModel by iconLabelVisibilityViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index 72b586a..88e3019 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -40,14 +40,14 @@
@Inject
constructor(
tilesInteractor: CurrentTilesInteractor,
- qsColumnsViewModel: QSColumnsViewModel,
+ fixedColumnsSizeViewModel: FixedColumnsSizeViewModel,
quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
val squishinessViewModel: TileSquishinessViewModel,
private val iconTilesViewModel: IconTilesViewModel,
@Application private val applicationScope: CoroutineScope,
) {
- val columns = qsColumnsViewModel.columns
+ val columns = fixedColumnsSizeViewModel.columns
private val rows =
quickQuickSettingsRowInteractor.rows.stateIn(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 73ad0e5..da04f6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -46,6 +46,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
@@ -123,6 +124,7 @@
private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractorLazy;
private final Lazy<KeyguardClockInteractor> mKeyguardClockInteractorLazy;
private final Lazy<SceneBackInteractor> mSceneBackInteractorLazy;
+ private final Lazy<AlternateBouncerInteractor> mAlternateBouncerInteractorLazy;
private int mState;
private int mLastState;
private int mUpcomingState;
@@ -193,7 +195,8 @@
Lazy<SceneInteractor> sceneInteractorLazy,
Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor,
Lazy<KeyguardClockInteractor> keyguardClockInteractorLazy,
- Lazy<SceneBackInteractor> sceneBackInteractorLazy) {
+ Lazy<SceneBackInteractor> sceneBackInteractorLazy,
+ Lazy<AlternateBouncerInteractor> alternateBouncerInteractorLazy) {
mUiEventLogger = uiEventLogger;
mInteractionJankMonitorLazy = interactionJankMonitorLazy;
mJavaAdapter = javaAdapter;
@@ -205,6 +208,7 @@
mSceneContainerOcclusionInteractorLazy = sceneContainerOcclusionInteractor;
mKeyguardClockInteractorLazy = keyguardClockInteractorLazy;
mSceneBackInteractorLazy = sceneBackInteractorLazy;
+ mAlternateBouncerInteractorLazy = alternateBouncerInteractorLazy;
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistoricalRecords[i] = new HistoricalState();
}
@@ -233,6 +237,7 @@
mSceneInteractorLazy.get().getCurrentOverlays(),
mSceneBackInteractorLazy.get().getBackStack(),
mSceneContainerOcclusionInteractorLazy.get().getInvisibleDueToOcclusion(),
+ mAlternateBouncerInteractorLazy.get().isVisible(),
this::calculateStateFromSceneFramework),
this::onStatusBarStateChanged);
@@ -693,7 +698,8 @@
SceneKey currentScene,
Set<OverlayKey> currentOverlays,
SceneStack backStack,
- boolean isOccluded) {
+ boolean isOccluded,
+ boolean alternateBouncerIsVisible) {
SceneContainerFlag.isUnexpectedlyInLegacyMode();
final boolean onBouncer = currentScene.equals(Scenes.Bouncer);
@@ -714,7 +720,8 @@
final String inputLogString = "currentScene=" + currentScene.getTestTag()
+ " currentOverlays=" + currentOverlays + " backStack=" + backStack
- + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded;
+ + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded
+ + " alternateBouncerIsVisible=" + alternateBouncerIsVisible;
int newState;
@@ -722,6 +729,7 @@
// 1. deviceUnlockStatus.isUnlocked changes from false to true.
// 2. Lockscreen changes to Gone, either in currentScene or in backStack.
// 3. Bouncer is removed from currentScene or backStack, if it was present.
+ // 4. the alternate bouncer is hidden, if it was visible.
//
// From this function's perspective, though, deviceUnlockStatus, currentScene, and backStack
// each update separately, and the relative order of those updates is not well-defined. This
@@ -733,6 +741,7 @@
// 1. deviceUnlockStatus.isUnlocked is false.
// 2. currentScene is a keyguardish scene (Lockscreen, Bouncer, or Communal).
// 3. backStack contains a keyguardish scene (Lockscreen or Communal).
+ // 4. the alternate bouncer is visible.
final boolean onKeyguardish = onLockscreen || onBouncer || onCommunal;
final boolean overKeyguardish = overLockscreen || overCommunal;
@@ -741,7 +750,7 @@
// Occlusion is special; even though the device is still technically on the lockscreen,
// the UI behaves as if it is unlocked.
newState = StatusBarState.SHADE;
- } else if (onKeyguardish || overKeyguardish) {
+ } else if (onKeyguardish || overKeyguardish || alternateBouncerIsVisible) {
// We get here if we are on or over a keyguardish scene, even if isUnlocked is true; we
// want to return SHADE_LOCKED or KEYGUARD until we are also neither on nor over a
// keyguardish scene.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index dac0102..10090283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.connectivity
import android.os.UserManager
+import com.android.systemui.bluetooth.qsdialog.dagger.AudioSharingModule
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION
import com.android.systemui.qs.QsEventLogger
@@ -56,7 +57,7 @@
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
-@Module
+@Module(includes = [AudioSharingModule::class])
interface ConnectivityModule {
/** Inject BluetoothTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index d67947d..4e26ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -90,7 +90,7 @@
notification = notification,
isGroupConversation = isGroupConversation,
builder = builder,
- systemUiContext = systemUiContext
+ systemUiContext = systemUiContext,
)
val conversationData =
@@ -98,7 +98,7 @@
// We don't show the sender's name for one-to-one conversation
conversationSenderName =
if (isGroupConversation) conversationTextData?.senderName else null,
- avatar = conversationAvatar
+ avatar = conversationAvatar,
)
return SingleLineViewModel(
@@ -111,7 +111,7 @@
@JvmStatic
fun inflateRedactedSingleLineViewModel(
context: Context,
- isConversation: Boolean = false
+ isConversation: Boolean = false,
): SingleLineViewModel {
val conversationData =
if (isConversation) {
@@ -122,7 +122,7 @@
com.android.systemui.res.R.drawable
.ic_redacted_notification_single_line_icon
)
- )
+ ),
)
} else {
null
@@ -134,7 +134,7 @@
context.getString(
com.android.systemui.res.R.string.redacted_notification_single_line_text
),
- conversationData
+ conversationData,
)
}
@@ -159,11 +159,13 @@
}
// load the sender's name to display
- val name = lastMessage.senderPerson?.name
+ // null senderPerson means the current user.
+ val name = lastMessage.senderPerson?.name ?: user.name
+
val senderName =
systemUiContext.resources.getString(
R.string.conversation_single_line_name_display,
- if (Flags.cleanUpSpansAndNewLines()) name?.toString() else name
+ if (Flags.cleanUpSpansAndNewLines()) name?.toString() else name,
)
// We need to find back-up values for those texts if they are needed and empty
@@ -333,7 +335,7 @@
sender.icon
?: builder.getDefaultAvatar(
name = sender.name,
- uniqueNames = uniqueNames
+ uniqueNames = uniqueNames,
)
lastKey = senderKey
} else {
@@ -341,7 +343,7 @@
sender.icon
?: builder.getDefaultAvatar(
name = sender.name,
- uniqueNames = uniqueNames
+ uniqueNames = uniqueNames,
)
break
}
@@ -424,7 +426,7 @@
private fun Notification.Builder.getDefaultAvatar(
name: CharSequence?,
- uniqueNames: PeopleHelper.NameToPrefixMap? = null
+ uniqueNames: PeopleHelper.NameToPrefixMap? = null,
): Icon {
val layoutColor = getSmallIconColor(/* isHeader= */ false)
if (!name.isNullOrEmpty()) {
@@ -432,7 +434,7 @@
return peopleHelper.createAvatarSymbol(
/* name = */ name,
/* symbol = */ symbol,
- /* layoutColor = */ layoutColor
+ /* layoutColor = */ layoutColor,
)
}
// If name is null, create default avatar with background color
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index f5cfc8c..e0bf00f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -26,6 +26,7 @@
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
+import android.net.vcn.VcnUtils
import android.net.wifi.WifiInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.annotation.ArrayRes
@@ -161,7 +162,9 @@
defaultNetworkCapabilities
.map { networkCapabilities ->
networkCapabilities?.run {
- val subId = (transportInfo as? VcnTransportInfo)?.subId
+ val subId =
+ VcnUtils.getSubIdFromVcnCaps(connectivityManager, networkCapabilities)
+
// Never return an INVALID_SUBSCRIPTION_ID (-1)
if (subId != INVALID_SUBSCRIPTION_ID) {
subId
@@ -245,9 +248,9 @@
* info.
*/
fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(
- connectivityManager: ConnectivityManager,
+ connectivityManager: ConnectivityManager
): WifiInfo? {
- val mainWifiInfo = this.getMainWifiInfo()
+ val mainWifiInfo = this.getMainWifiInfo(connectivityManager)
if (mainWifiInfo != null) {
return mainWifiInfo
}
@@ -264,7 +267,9 @@
// eventually traced to a wifi or carrier merged connection. So, check those underlying
// networks for possible wifi information as well. See b/225902574.
return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork ->
- connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo()
+ connectivityManager
+ .getNetworkCapabilities(underlyingNetwork)
+ ?.getMainWifiInfo(connectivityManager)
}
}
@@ -272,7 +277,9 @@
* Checks the network capabilities for wifi info, but does *not* check the underlying
* networks. See [getMainOrUnderlyingWifiInfo].
*/
- private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? {
+ private fun NetworkCapabilities.getMainWifiInfo(
+ connectivityManager: ConnectivityManager
+ ): WifiInfo? {
// Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
// virtual networks like VCN.
val canHaveWifiInfo =
@@ -286,7 +293,7 @@
// [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
// re-used because it makes the logic here clearer, and because the method will be
// removed once this pipeline is fully launched.
- is VcnTransportInfo -> currentTransportInfo.wifiInfo
+ is VcnTransportInfo -> VcnUtils.getWifiInfoFromVcnCaps(connectivityManager, this)
is WifiInfo -> currentTransportInfo
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index bfc5429..6879a34 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -27,10 +27,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor
@Composable
-fun BackGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -39,18 +36,20 @@
titleResId = R.string.touchpad_back_gesture_action_title,
bodyResId = R.string.touchpad_back_gesture_guidance,
titleSuccessResId = R.string.touchpad_back_gesture_success_title,
- bodySuccessResId = R.string.touchpad_back_gesture_success_body
+ bodySuccessResId = R.string.touchpad_back_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_back_edu,
- successResId = R.raw.trackpad_back_success
- )
+ successResId = R.raw.trackpad_back_success,
+ ),
)
val gestureMonitorProvider =
DistanceBasedGestureMonitorProvider(
monitorFactory = { distanceThresholdPx, gestureStateCallback ->
- BackGestureMonitor(distanceThresholdPx, gestureStateCallback)
+ BackGestureMonitor(distanceThresholdPx).also {
+ it.addGestureStateCallback(gestureStateCallback)
+ }
}
)
GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
@@ -67,7 +66,7 @@
rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed),
rememberColorFilterProperty(".onTertiary", onTertiary),
- rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
+ rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index f2fec5f..a55fa44 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -26,10 +26,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor
@Composable
-fun HomeGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -38,18 +35,20 @@
titleResId = R.string.touchpad_home_gesture_action_title,
bodyResId = R.string.touchpad_home_gesture_guidance,
titleSuccessResId = R.string.touchpad_home_gesture_success_title,
- bodySuccessResId = R.string.touchpad_home_gesture_success_body
+ bodySuccessResId = R.string.touchpad_home_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_home_edu,
- successResId = R.raw.trackpad_home_success
- )
+ successResId = R.raw.trackpad_home_success,
+ ),
)
val gestureMonitorProvider =
DistanceBasedGestureMonitorProvider(
monitorFactory = { distanceThresholdPx, gestureStateCallback ->
- HomeGestureMonitor(distanceThresholdPx, gestureStateCallback)
+ HomeGestureMonitor(distanceThresholdPx).also {
+ it.addGestureStateCallback(gestureStateCallback)
+ }
}
)
GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
@@ -64,7 +63,7 @@
rememberLottieDynamicProperties(
rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
rememberColorFilterProperty(".onPrimaryFixed", onPrimaryFixed),
- rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
+ rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index b2fb6cd..6ee15aa 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -29,10 +29,7 @@
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
@Composable
-fun RecentAppsGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -41,20 +38,20 @@
titleResId = R.string.touchpad_recent_apps_gesture_action_title,
bodyResId = R.string.touchpad_recent_apps_gesture_guidance,
titleSuccessResId = R.string.touchpad_recent_apps_gesture_success_title,
- bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body
+ bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_recent_apps_edu,
- successResId = R.raw.trackpad_recent_apps_success
- )
+ successResId = R.raw.trackpad_recent_apps_success,
+ ),
)
val gestureMonitorProvider =
object : GestureMonitorProvider {
@Composable
override fun rememberGestureMonitor(
resources: Resources,
- gestureStateChangedCallback: (GestureState) -> Unit
+ gestureStateChangedCallback: (GestureState) -> Unit,
): TouchpadGestureMonitor {
val distanceThresholdPx =
resources.getDimensionPixelSize(
@@ -63,11 +60,9 @@
val velocityThresholdPxPerMs =
resources.getDimension(R.dimen.touchpad_recent_apps_gesture_velocity_threshold)
return remember(distanceThresholdPx, velocityThresholdPxPerMs) {
- RecentAppsGestureMonitor(
- distanceThresholdPx,
- gestureStateChangedCallback,
- velocityThresholdPxPerMs
- )
+ RecentAppsGestureMonitor(distanceThresholdPx, velocityThresholdPxPerMs).also {
+ it.addGestureStateCallback(gestureStateChangedCallback)
+ }
}
}
}
@@ -83,7 +78,7 @@
rememberLottieDynamicProperties(
rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim),
rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed),
- rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
+ rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
index ecb5574..490f04d 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
@@ -20,18 +20,21 @@
import kotlin.math.abs
/** Monitors for touchpad back gesture, that is three fingers swiping left or right */
-class BackGestureMonitor(
- private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
-) : TouchpadGestureMonitor {
- private val distanceTracker = DistanceTracker()
+class BackGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor {
- override fun processTouchpadEvent(event: MotionEvent) {
+ private val distanceTracker = DistanceTracker()
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
+
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
+
+ override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
- val distanceState = distanceTracker.processEvent(event)
- updateGestureStateBasedOnDistance(
+ val gestureState = distanceTracker.processEvent(event)
+ updateGestureState(
gestureStateChangedCallback,
- distanceState,
+ gestureState,
isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx },
progress = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
index 70d9366..d482358 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
@@ -38,10 +38,10 @@
}
}
-sealed interface DistanceGestureState
+sealed class DistanceGestureState(val deltaX: Float, val deltaY: Float)
-class Started(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Started(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
-class Moving(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Moving(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
-class Finished(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Finished(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
index c1caeb3..f194677 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
@@ -16,11 +16,8 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
-/**
- * Helper function for gesture recognizers to have common state triggering logic based on distance
- * only.
- */
-inline fun updateGestureStateBasedOnDistance(
+/** Helper function for gesture recognizers to have common state triggering logic */
+inline fun updateGestureState(
gestureStateChangedCallback: (GestureState) -> Unit,
gestureState: DistanceGestureState?,
isFinished: (Finished) -> Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
index fdcf9de..83d4f56 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
@@ -19,18 +19,21 @@
import android.view.MotionEvent
/** Monitors for touchpad home gesture, that is three fingers swiping up */
-class HomeGestureMonitor(
- private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
-) : TouchpadGestureMonitor {
- private val distanceTracker = DistanceTracker()
+class HomeGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor {
- override fun processTouchpadEvent(event: MotionEvent) {
+ private val distanceTracker = DistanceTracker()
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
+
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
+
+ override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
- val distanceState = distanceTracker.processEvent(event)
- updateGestureStateBasedOnDistance(
+ val gestureState = distanceTracker.processEvent(event)
+ updateGestureState(
gestureStateChangedCallback,
- distanceState,
+ gestureState,
isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
progress = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
index dd31ce3..1731bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
-import androidx.compose.ui.input.pointer.util.VelocityTracker1D
import kotlin.math.abs
/**
@@ -27,45 +26,30 @@
*/
class RecentAppsGestureMonitor(
private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
private val velocityThresholdPxPerMs: Float,
- private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false),
+ private val distanceTracker: DistanceTracker = DistanceTracker(),
+ private val velocityTracker: VerticalVelocityTracker = VerticalVelocityTracker(),
) : TouchpadGestureMonitor {
- private var xStart = 0f
- private var yStart = 0f
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
- override fun processTouchpadEvent(event: MotionEvent) {
- val action = event.actionMasked
- velocityTracker.addDataPoint(event.eventTime, event.y)
- when (action) {
- MotionEvent.ACTION_DOWN -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- xStart = event.x
- yStart = event.y
- gestureStateChangedCallback(GestureState.InProgress())
- }
- }
- MotionEvent.ACTION_UP -> {
- if (isThreeFingerTouchpadSwipe(event) && isRecentAppsGesture(event)) {
- gestureStateChangedCallback(GestureState.Finished)
- } else {
- gestureStateChangedCallback(GestureState.NotStarted)
- }
- velocityTracker.resetTracking()
- }
- MotionEvent.ACTION_CANCEL -> {
- velocityTracker.resetTracking()
- }
- }
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
}
- private fun isRecentAppsGesture(event: MotionEvent): Boolean {
- // below is trying to mirror behavior of TriggerSwipeUpTouchTracker#onGestureEnd.
- // We're diving velocity by 1000, to have the same unit of measure: pixels/ms.
- val swipeDistance = yStart - event.y
- val velocity = velocityTracker.calculateVelocity() / 1000
- return swipeDistance >= gestureDistanceThresholdPx &&
- abs(velocity) <= velocityThresholdPxPerMs
+ override fun accept(event: MotionEvent) {
+ if (!isThreeFingerTouchpadSwipe(event)) return
+ val gestureState = distanceTracker.processEvent(event)
+ velocityTracker.accept(event)
+
+ updateGestureState(
+ gestureStateChangedCallback,
+ gestureState,
+ isFinished = { state ->
+ -state.deltaY >= gestureDistanceThresholdPx &&
+ abs(velocityTracker.calculateVelocity().value) <= velocityThresholdPxPerMs
+ },
+ progress = { 0f },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
index 88671d4..4b82ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
@@ -18,13 +18,14 @@
import android.view.InputDevice
import android.view.MotionEvent
+import java.util.function.Consumer
/**
* Allows listening to touchpadGesture and calling onDone when gesture was triggered. Can have all
* motion events passed to [onMotionEvent] and will filter touchpad events accordingly
*/
class TouchpadGestureHandler(
- private val gestureMonitor: TouchpadGestureMonitor,
+ private val gestureMonitor: Consumer<MotionEvent>,
private val easterEggGestureMonitor: EasterEggGestureMonitor,
) {
@@ -40,7 +41,7 @@
if (isTwoFingerSwipe(event)) {
easterEggGestureMonitor.processTouchpadEvent(event)
} else {
- gestureMonitor.processTouchpadEvent(event)
+ gestureMonitor.accept(event)
}
true
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
index 4655c98..9216821 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
@@ -17,15 +17,11 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
+import java.util.function.Consumer
-/**
- * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState]
- * changes. All tracked motion events should be passed to [processTouchpadEvent]
- */
-interface TouchpadGestureMonitor {
- val gestureStateChangedCallback: (GestureState) -> Unit
-
- fun processTouchpadEvent(event: MotionEvent)
+/** Monitor for touchpad gestures that can notify callback when [GestureState] changes. */
+interface TouchpadGestureMonitor : Consumer<MotionEvent> {
+ fun addGestureStateCallback(callback: (GestureState) -> Unit)
}
fun isThreeFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 3)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt
new file mode 100644
index 0000000..9b38eca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+import androidx.compose.ui.input.pointer.util.VelocityTracker1D
+import java.util.function.Consumer
+
+/** Velocity in pixels/ms. */
+@JvmInline value class Velocity(val value: Float)
+
+/**
+ * Tracks velocity for processed MotionEvents. Useful for recognizing gestures based on velocity.
+ */
+interface VelocityTracker : Consumer<MotionEvent> {
+
+ fun calculateVelocity(): Velocity
+}
+
+class VerticalVelocityTracker(
+ private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false)
+) : VelocityTracker {
+
+ override fun accept(event: MotionEvent) {
+ val action = event.actionMasked
+ if (action == MotionEvent.ACTION_DOWN) {
+ velocityTracker.resetTracking()
+ }
+ velocityTracker.addDataPoint(event.eventTime, event.y)
+ }
+
+ /**
+ * Calculates velocity on demand - this calculation can be expensive so shouldn't be called
+ * after every event.
+ */
+ override fun calculateVelocity() = Velocity(velocityTracker.calculateVelocity() / 1000)
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
new file mode 100644
index 0000000..655b2cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AudioSharingButtonViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val bluetoothState = MutableStateFlow(false)
+ private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
+ @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+ @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
+ @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
+ @Mock private lateinit var deviceItem: DeviceItem
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var audioSharingButtonViewModel: AudioSharingButtonViewModel
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+ whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
+ whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
+ audioSharingButtonViewModel =
+ AudioSharingButtonViewModel(
+ localBluetoothManager,
+ kosmos.audioSharingInteractor,
+ bluetoothStateInteractor,
+ deviceItemInteractor,
+ )
+ audioSharingButtonViewModel.activateIn(testScope)
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun testButtonStateUpdate_bluetoothOff_returnGone() {
+ testScope.runTest {
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
+ kosmos.bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_noDevice_returnGone() {
+ testScope.runTest {
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
+ kosmos.bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothState.value = true
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_isBroadcasting_returnSharingAudio() {
+ testScope.runTest {
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
+ kosmos.bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothState.value = true
+ runCurrent()
+ deviceItemUpdate.emit(listOf())
+ runCurrent()
+ kosmos.bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing,
+ isActive = true,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasSource_returnGone() {
+ testScope.runTest {
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
+ kosmos.bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager,
+ )
+ )
+ .thenReturn(true)
+ bluetoothState.value = true
+ runCurrent()
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasActiveDevice_returnAudioSharing() {
+ testScope.runTest {
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
+ kosmos.bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager,
+ )
+ )
+ .thenReturn(false)
+ whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
+ bluetoothState.value = true
+ runCurrent()
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button,
+ isActive = false,
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
new file mode 100644
index 0000000..ce37eee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bluetooth.qsdialog
+
+import android.bluetooth.BluetoothDevice
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.flags.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+ private lateinit var actionInteractorImpl: DeviceItemActionInteractor
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var connectedAudioSharingMediaDeviceItem: DeviceItem
+ private lateinit var connectedMediaDeviceItem: DeviceItem
+ @Mock private lateinit var dialog: SystemUIDialog
+ @Mock private lateinit var leAudioProfile: LeAudioProfile
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+ connectedMediaDeviceItem =
+ DeviceItem(
+ type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = null,
+ background = null,
+ )
+ connectedAudioSharingMediaDeviceItem =
+ DeviceItem(
+ type = DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = null,
+ background = null,
+ )
+ actionInteractorImpl = kosmos.audioSharingDeviceItemActionInteractorImpl
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
+ fun testOnClick_connectedAudioSharingMediaDevice_flagOn_createDialog() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+ verify(dialogTransitionAnimator)
+ .showFromDialog(any(), any(), eq(null), anyBoolean())
+ }
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
+ fun testOnClick_connectedAudioSharingMediaDevice_flagOff_shouldLaunchSettings() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
+ actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ )
+ verify(dialogTransitionAnimator, never())
+ .showFromDialog(any(), any(), eq(null), anyBoolean())
+ }
+ }
+ }
+
+ @Test
+ fun testOnClick_inAudioSharing_clickedDeviceHasSource_shouldNotLaunchSettings() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+ .thenReturn(listOf(leAudioProfile))
+ whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any(),
+ )
+ )
+ .thenReturn(true)
+
+ actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
+ verify(activityStarter, Mockito.never())
+ .postStartActivityDismissingKeyguard(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testOnClick_inAudioSharing_clickedDeviceNoSource_shouldLaunchSettings() {
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
+ whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+ .thenReturn(listOf(leAudioProfile))
+
+ whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any(),
+ )
+ )
+ .thenReturn(false)
+
+ actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ )
+ }
+ }
+ }
+
+ private companion object {
+ const val DEVICE_NAME = "device"
+ const val DEVICE_CONNECTION_SUMMARY = "active"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
new file mode 100644
index 0000000..25b85b5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.Button
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingDialogDelegateTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
+ private val updateFlow = MutableSharedFlow<Unit>()
+ private lateinit var underTest: AudioSharingDialogDelegate
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ // TODO(b/364515243): use real object instead of mock
+ whenever(deviceItemInteractor.deviceItemUpdateRequest).thenReturn(updateFlow)
+ whenever(deviceItemInteractor.deviceItemUpdate)
+ .thenReturn(MutableStateFlow(emptyList()))
+ underTest = audioSharingDialogDelegate
+ }
+ }
+
+ @Test
+ fun testCreateDialog() =
+ kosmos.testScope.runTest {
+ val dialog = underTest.createDialog()
+ assertThat(dialog).isInstanceOf(SystemUIDialog::class.java)
+ }
+
+ @Test
+ fun testCreateDialog_showState() =
+ with(kosmos) {
+ testScope.runTest {
+ val availableDeviceName = "name"
+ whenever(cachedBluetoothDevice.name).thenReturn(availableDeviceName)
+ val dialog = spy(underTest.createDialog())
+ dialog.show()
+ runCurrent()
+ val subtitleTextView = dialog.findViewById<TextView>(R.id.subtitle)
+ val switchActiveButton = dialog.findViewById<Button>(R.id.switch_active_button)
+ val shareAudioButton = dialog.findViewById<Button>(R.id.share_audio_button)
+ val subtitle =
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ availableDeviceName,
+ ""
+ )
+ val switchButtonText =
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ availableDeviceName
+ )
+ assertThat(subtitleTextView.text).isEqualTo(subtitle)
+ assertThat(switchActiveButton.text).isEqualTo(switchButtonText)
+ assertThat(switchActiveButton.hasOnClickListeners()).isTrue()
+ assertThat(shareAudioButton.hasOnClickListeners()).isTrue()
+
+ switchActiveButton.performClick()
+ verify(dialog).dismiss()
+ }
+ }
+
+ @Test
+ fun testCreateDialog_hideState() =
+ with(kosmos) {
+ testScope.runTest {
+ val dialog = spy(underTest.createDialog())
+ dialog.show()
+ runCurrent()
+ updateFlow.emit(Unit)
+ runCurrent()
+ verify(dialog).dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
new file mode 100644
index 0000000..beb816c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.bluetooth.BluetoothDevice
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.cachedBluetoothDeviceManager
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingDialogViewModelTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+ @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+ @Mock private lateinit var leAudioProfile: LeAudioProfile
+ private val updateFlow = MutableSharedFlow<Unit>()
+ private lateinit var underTest: AudioSharingDialogViewModel
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ // TODO(b/364515243): use real object instead of mock
+ whenever(deviceItemInteractor.deviceItemUpdateRequest).thenReturn(updateFlow)
+ whenever(deviceItemInteractor.deviceItemUpdate)
+ .thenReturn(MutableStateFlow(emptyList()))
+ underTest = audioSharingDialogViewModel
+ }
+ }
+
+ @Test
+ fun testDialogState_show() =
+ with(kosmos) {
+ testScope.runTest {
+ val deviceName = "name"
+ whenever(cachedBluetoothDevice.name).thenReturn(deviceName)
+ val actual by collectLastValue(underTest.dialogState)
+ runCurrent()
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingDialogState.Show(
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ deviceName,
+ ""
+ ),
+ context.getString(
+ R.string
+ .quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ deviceName
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testDialogState_showWithActiveDeviceName() =
+ with(kosmos) {
+ testScope.runTest {
+ val deviceName = "name"
+ whenever(cachedBluetoothDevice.name).thenReturn(deviceName)
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(localBluetoothManager.cachedDeviceManager)
+ .thenReturn(cachedBluetoothDeviceManager)
+ whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
+ whenever(leAudioProfile.activeDevices).thenReturn(listOf(mock<BluetoothDevice>()))
+ whenever(cachedBluetoothDeviceManager.findDevice(any()))
+ .thenReturn(cachedBluetoothDevice)
+ val actual by collectLastValue(underTest.dialogState)
+ runCurrent()
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingDialogState.Show(
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ deviceName,
+ deviceName
+ ),
+ context.getString(
+ R.string
+ .quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ deviceName
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testDialogState_hide() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual by collectLastValue(underTest.dialogState)
+ runCurrent()
+ updateFlow.emit(Unit)
+ assertThat(actual).isEqualTo(AudioSharingDialogState.Hide)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index 2c53fd6..25f9565 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -16,158 +16,197 @@
package com.android.systemui.bluetooth.qsdialog
+import android.bluetooth.BluetoothLeBroadcast
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.android.dx.mockito.inline.extended.StaticMockitoSession
-import com.android.settingslib.bluetooth.BluetoothUtils
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.res.R
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
-@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingInteractorTest : SysuiTestCase() {
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
- private val bluetoothState = MutableStateFlow(false)
- private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
- @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
- @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
- @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
- @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
- @Mock private lateinit var deviceItem: DeviceItem
- private lateinit var mockitoSession: StaticMockitoSession
- private lateinit var audioSharingInteractor: AudioSharingInteractor
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
+ @Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
+ private lateinit var underTest: AudioSharingInteractor
@Before
fun setUp() {
- mockitoSession =
- mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
- whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
- whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
- audioSharingInteractor =
- AudioSharingInteractor(
- localBluetoothManager,
- bluetoothStateInteractor,
- deviceItemInteractor,
- testScope.backgroundScope,
- testDispatcher,
- )
- }
-
- @After
- fun tearDown() {
- mockitoSession.finishMocking()
+ with(kosmos) { underTest = audioSharingInteractor }
}
@Test
- fun testButtonStateUpdate_bluetoothOff_returnGone() {
- testScope.runTest {
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ fun testIsAudioSharingOn_flagOff_false() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(false)
+ val value by collectLastValue(underTest.isAudioSharingOn)
+ runCurrent()
- assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ assertThat(value).isFalse()
+ }
}
- }
@Test
- fun testButtonStateUpdate_noDevice_returnGone() {
- testScope.runTest {
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
- bluetoothState.value = true
- runCurrent()
+ fun testIsAudioSharingOn_flagOn_notInAudioSharing_false() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
+ val value by collectLastValue(underTest.isAudioSharingOn)
+ runCurrent()
- assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ assertThat(value).isFalse()
+ }
}
- }
@Test
- fun testButtonStateUpdate_isBroadcasting_returnSharingAudio() {
- testScope.runTest {
- whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(true)
+ fun testIsAudioSharingOn_flagOn_inAudioSharing_true() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ val value by collectLastValue(underTest.isAudioSharingOn)
+ runCurrent()
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
- bluetoothState.value = true
- deviceItemUpdate.emit(listOf())
- runCurrent()
+ assertThat(value).isTrue()
+ }
+ }
- assertThat(actual)
- .isEqualTo(
- AudioSharingButtonState.Visible(
- R.string.quick_settings_bluetooth_audio_sharing_button_sharing,
- isActive = true
- )
+ @Test
+ fun testAudioSourceStateUpdate_notInAudioSharing_returnEmpty() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
+ val value by collectLastValue(underTest.audioSourceStateUpdate)
+ runCurrent()
+
+ assertThat(value).isNull()
+ }
+ }
+
+ @Test
+ fun testAudioSourceStateUpdate_inAudioSharing_returnUnit() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ val value by collectLastValue(underTest.audioSourceStateUpdate)
+ runCurrent()
+ bluetoothTileDialogAudioSharingRepository.emitAudioSourceStateUpdate()
+ runCurrent()
+
+ assertThat(value).isNull()
+ }
+ }
+
+ @Test
+ fun testHandleAudioSourceWhenReady_flagOff_sourceNotAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(false)
+ val job = launch { underTest.handleAudioSourceWhenReady() }
+ runCurrent()
+
+ assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
+ job.cancel()
+ }
+ }
+
+ @Test
+ fun testHandleAudioSourceWhenReady_noProfile_sourceNotAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(null)
+ val job = launch { underTest.handleAudioSourceWhenReady() }
+ runCurrent()
+
+ assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
+ job.cancel()
+ }
+ }
+
+ @Test
+ fun testHandleAudioSourceWhenReady_hasProfileButAudioSharingOff_sourceNotAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
+ localBluetoothLeBroadcast
)
+ val job = launch { underTest.handleAudioSourceWhenReady() }
+ runCurrent()
+
+ assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
+ job.cancel()
+ }
}
- }
@Test
- fun testButtonStateUpdate_hasSource_returnGone() {
- testScope.runTest {
- whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
- whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
- whenever(
- BluetoothUtils.hasConnectedBroadcastSource(
- cachedBluetoothDevice,
- localBluetoothManager
- )
+ fun testHandleAudioSourceWhenReady_audioSharingOnButNoPlayback_sourceNotAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
+ localBluetoothLeBroadcast
)
- .thenReturn(true)
+ val job = launch { underTest.handleAudioSourceWhenReady() }
+ runCurrent()
+ verify(localBluetoothLeBroadcast)
+ .registerServiceCallBack(any(), callbackCaptor.capture())
+ runCurrent()
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
- bluetoothState.value = true
- deviceItemUpdate.emit(listOf(deviceItem))
- runCurrent()
-
- assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
+ job.cancel()
+ }
}
- }
@Test
- fun testButtonStateUpdate_hasActiveDevice_returnAudioSharing() {
- testScope.runTest {
- whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
- whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
- whenever(
- BluetoothUtils.hasConnectedBroadcastSource(
- cachedBluetoothDevice,
- localBluetoothManager
- )
+ fun testHandleAudioSourceWhenReady_audioSharingOnAndPlaybackStarts_sourceAdded() =
+ with(kosmos) {
+ testScope.runTest {
+ bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
+ localBluetoothLeBroadcast
)
- .thenReturn(false)
- whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
+ val job = launch { underTest.handleAudioSourceWhenReady() }
+ runCurrent()
+ verify(localBluetoothLeBroadcast)
+ .registerServiceCallBack(any(), callbackCaptor.capture())
+ runCurrent()
+ callbackCaptor.value.onPlaybackStarted(0, 0)
+ runCurrent()
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
- bluetoothState.value = true
- deviceItemUpdate.emit(listOf(deviceItem))
- runCurrent()
-
- assertThat(actual)
- .isEqualTo(
- AudioSharingButtonState.Visible(
- R.string.quick_settings_bluetooth_audio_sharing_button,
- isActive = false
- )
- )
+ assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isTrue()
+ job.cancel()
+ }
}
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
new file mode 100644
index 0000000..c9e8813
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bluetooth.qsdialog
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AudioSharingRepositoryTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+ @Mock private lateinit var leAudioBroadcastProfile: LocalBluetoothLeBroadcast
+ @Mock private lateinit var leAudioBroadcastAssistant: LocalBluetoothLeBroadcastAssistant
+ @Mock private lateinit var metadata: BluetoothLeBroadcastMetadata
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+ private val kosmos = testKosmos()
+ private lateinit var underTest: AudioSharingRepository
+
+ @Before
+ fun setUp() {
+ underTest =
+ AudioSharingRepositoryImpl(
+ kosmos.localBluetoothManager,
+ kosmos.audioSharingRepository,
+ kosmos.testDispatcher,
+ )
+ }
+
+ @Test
+ fun testSwitchActive() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setAudioSharingAvailable(true)
+ underTest.setActive(cachedBluetoothDevice)
+ verify(cachedBluetoothDevice).setActive()
+ }
+ }
+
+ @Test
+ fun testSwitchActive_flagOff_doNothing() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setAudioSharingAvailable(false)
+ underTest.setActive(cachedBluetoothDevice)
+ verify(cachedBluetoothDevice, never()).setActive()
+ }
+ }
+
+ @Test
+ fun testStartAudioSharing() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
+ audioSharingRepository.setAudioSharingAvailable(true)
+ underTest.startAudioSharing()
+ verify(leAudioBroadcastProfile).startPrivateBroadcast()
+ }
+ }
+
+ @Test
+ fun testStartAudioSharing_flagOff_doNothing() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setAudioSharingAvailable(false)
+ underTest.startAudioSharing()
+ verify(leAudioBroadcastProfile, never()).startPrivateBroadcast()
+ }
+ }
+
+ @Test
+ fun testAddSource_flagOff_doesNothing() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setAudioSharingAvailable(false)
+
+ underTest.addSource()
+ runCurrent()
+
+ verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+ }
+ }
+
+ @Test
+ fun testAddSource_noMetadata_doesNothing() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
+ audioSharingRepository.setAudioSharingAvailable(true)
+ whenever(leAudioBroadcastProfile.latestBluetoothLeBroadcastMetadata)
+ .thenReturn(null)
+
+ underTest.addSource()
+ runCurrent()
+
+ verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+ }
+ }
+
+ @Test
+ fun testAddSource_noConnectedDevice_doesNothing() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
+ whenever(profileManager.leAudioBroadcastAssistantProfile)
+ .thenReturn(leAudioBroadcastAssistant)
+ audioSharingRepository.setAudioSharingAvailable(true)
+ whenever(leAudioBroadcastProfile.latestBluetoothLeBroadcastMetadata)
+ .thenReturn(metadata)
+ whenever(leAudioBroadcastAssistant.allConnectedDevices).thenReturn(emptyList())
+
+ underTest.addSource()
+ runCurrent()
+
+ verify(leAudioBroadcastAssistant, never()).addSource(any(), any(), anyBoolean())
+ }
+ }
+
+ @Test
+ fun testAddSource_hasConnectedDeviceAndMetadata_addSource() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
+ whenever(profileManager.leAudioBroadcastAssistantProfile)
+ .thenReturn(leAudioBroadcastAssistant)
+ audioSharingRepository.setAudioSharingAvailable(true)
+ whenever(leAudioBroadcastProfile.latestBluetoothLeBroadcastMetadata)
+ .thenReturn(metadata)
+ whenever(leAudioBroadcastAssistant.allConnectedDevices)
+ .thenReturn(listOf(bluetoothDevice))
+
+ underTest.addSource()
+ runCurrent()
+
+ verify(leAudioBroadcastAssistant).addSource(bluetoothDevice, metadata, false)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index d7bea66..a56c2cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -31,8 +31,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.kotlin.getMutableStateFlow
@@ -42,12 +45,12 @@
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
@@ -64,10 +67,12 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
private val fakeSystemClock = FakeSystemClock()
private val backgroundExecutor = FakeExecutor(fakeSystemClock)
@@ -75,8 +80,6 @@
@Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
- @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
-
@Mock private lateinit var bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
@@ -111,15 +114,15 @@
private val sharedPreferences = FakeSharedPreferences()
- private lateinit var scheduler: TestCoroutineScheduler
private lateinit var dispatcher: CoroutineDispatcher
private lateinit var testScope: TestScope
@Before
fun setUp() {
- scheduler = TestCoroutineScheduler()
- dispatcher = UnconfinedTestDispatcher(scheduler)
- testScope = TestScope(dispatcher)
+ dispatcher = kosmos.testDispatcher
+ testScope = kosmos.testScope
+ // TODO(b/364515243): use real object instead of mock
+ whenever(kosmos.deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
bluetoothTileDialogViewModel =
BluetoothTileDialogViewModel(
deviceItemInteractor,
@@ -139,11 +142,13 @@
dispatcher
)
),
- audioSharingInteractor,
+ kosmos.audioSharingInteractor,
+ kosmos.audioSharingButtonViewModelFactory,
bluetoothDeviceMetadataInteractor,
mDialogTransitionAnimator,
activityStarter,
uiEventLogger,
+ bluetoothTileDialogLogger,
testScope.backgroundScope,
dispatcher,
dispatcher,
@@ -161,13 +166,10 @@
whenever(sysuiDialog.context).thenReturn(mContext)
whenever(bluetoothTileDialogDelegate.bluetoothStateToggle)
.thenReturn(getMutableStateFlow(false))
- whenever(bluetoothTileDialogDelegate.deviceItemClick)
- .thenReturn(getMutableStateFlow(deviceItem))
+ whenever(bluetoothTileDialogDelegate.deviceItemClick).thenReturn(MutableSharedFlow())
whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
.thenReturn(getMutableStateFlow(false))
- whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
- .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
}
@@ -175,6 +177,7 @@
fun testShowDialog_noAnimation() {
testScope.runTest {
bluetoothTileDialogViewModel.showDialog(null)
+ runCurrent()
verify(mDialogTransitionAnimator, never()).show(any(), any(), any())
}
@@ -184,6 +187,7 @@
fun testShowDialog_animated() {
testScope.runTest {
bluetoothTileDialogViewModel.showDialog(expandable)
+ runCurrent()
verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
}
@@ -194,6 +198,7 @@
testScope.runTest {
backgroundExecutor.execute {
bluetoothTileDialogViewModel.showDialog(expandable)
+ runCurrent()
verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
}
@@ -204,6 +209,7 @@
fun testShowDialog_fetchDeviceItem() {
testScope.runTest {
bluetoothTileDialogViewModel.showDialog(null)
+ runCurrent()
verify(deviceItemInteractor).deviceItemUpdate
}
@@ -214,6 +220,7 @@
testScope.runTest {
whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
bluetoothTileDialogViewModel.showDialog(null)
+ runCurrent()
val clickedView = View(context)
bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 681ea75..9c427c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -15,34 +15,22 @@
*/
package com.android.systemui.bluetooth.qsdialog
-import android.bluetooth.BluetoothDevice
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.android.dx.mockito.inline.extended.StaticMockitoSession
-import com.android.settingslib.bluetooth.BluetoothUtils
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.settingslib.bluetooth.LeAudioProfile
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -56,28 +44,18 @@
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
private lateinit var actionInteractorImpl: DeviceItemActionInteractor
- private lateinit var mockitoSession: StaticMockitoSession
private lateinit var activeMediaDeviceItem: DeviceItem
private lateinit var notConnectedDeviceItem: DeviceItem
- private lateinit var connectedAudioSharingMediaDeviceItem: DeviceItem
private lateinit var connectedMediaDeviceItem: DeviceItem
private lateinit var connectedOtherDeviceItem: DeviceItem
@Mock private lateinit var dialog: SystemUIDialog
- @Mock private lateinit var profileManager: LocalBluetoothProfileManager
- @Mock private lateinit var leAudioProfile: LeAudioProfile
- @Mock private lateinit var assistantProfile: LocalBluetoothLeBroadcastAssistant
- @Mock private lateinit var bluetoothDevice: BluetoothDevice
- @Mock private lateinit var bluetoothDeviceGroupId2: BluetoothDevice
- @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
@Before
fun setUp() {
- mockitoSession =
- mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
activeMediaDeviceItem =
DeviceItem(
type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
@@ -86,7 +64,7 @@
notConnectedDeviceItem =
DeviceItem(
type = DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
@@ -95,16 +73,7 @@
connectedMediaDeviceItem =
DeviceItem(
type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
- deviceName = DEVICE_NAME,
- connectionSummary = DEVICE_CONNECTION_SUMMARY,
- iconWithDescription = null,
- background = null
- )
- connectedAudioSharingMediaDeviceItem =
- DeviceItem(
- type = DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
@@ -113,18 +82,13 @@
connectedOtherDeviceItem =
DeviceItem(
type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
background = null
)
- actionInteractorImpl = kosmos.deviceItemActionInteractor
- }
-
- @After
- fun tearDown() {
- mockitoSession.finishMocking()
+ actionInteractorImpl = kosmos.deviceItemActionInteractorImpl
}
@Test
@@ -132,14 +96,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
verify(cachedBluetoothDevice).setActive()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE
- )
}
}
}
@@ -149,14 +107,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(activeMediaDeviceItem, dialog)
verify(cachedBluetoothDevice).disconnect()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE
- )
}
}
}
@@ -166,14 +118,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(connectedOtherDeviceItem, dialog)
verify(cachedBluetoothDevice).disconnect()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.CONNECTED_BLUETOOTH_DEVICE
- )
}
}
}
@@ -183,293 +129,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
verify(cachedBluetoothDevice).connect()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.SAVED_BLUETOOTH_DEVICE
- )
- }
- }
- }
-
- @Test
- fun testOnClick_connectedAudioSharingMediaDevice_logClick() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE
- )
- }
- }
- }
-
- @Test
- fun testOnClick_audioSharingDisabled_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_inAudioSharing_clickedDeviceHasSource_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.uiAccessibleProfiles)
- .thenReturn(listOf(leAudioProfile))
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
- whenever(
- BluetoothUtils.hasConnectedBroadcastSource(
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- )
- .thenReturn(true)
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_inAudioSharing_clickedDeviceNoSource_shouldLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
- whenever(cachedBluetoothDevice.uiAccessibleProfiles)
- .thenReturn(listOf(leAudioProfile))
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
- whenever(
- BluetoothUtils.hasConnectedBroadcastSource(
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- )
- .thenReturn(false)
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_noConnectedLeDevice_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasOneConnectedLeDevice_clickedNonLe_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice))
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasOneConnectedLeDevice_clickedLe_shouldLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.profiles).thenReturn(listOf(leAudioProfile))
- whenever(leAudioProfile.isEnabled(ArgumentMatchers.any())).thenReturn(true)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice))
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasOneConnectedLeDevice_clickedConnectedLe_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice))
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasTwoConnectedLeDevice_clickedNotConnectedLe_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice, bluetoothDeviceGroupId2))
- whenever(leAudioProfile.getGroupId(ArgumentMatchers.any())).thenAnswer {
- val device = it.arguments.first() as BluetoothDevice
- if (device == bluetoothDevice) GROUP_ID_1 else GROUP_ID_2
- }
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasTwoConnectedLeDevice_clickedActiveLe_shouldLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.profiles).thenReturn(listOf(leAudioProfile))
- whenever(leAudioProfile.isEnabled(ArgumentMatchers.any())).thenReturn(true)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice, bluetoothDeviceGroupId2))
- whenever(leAudioProfile.getGroupId(ArgumentMatchers.any())).thenAnswer {
- val device = it.arguments.first() as BluetoothDevice
- if (device == bluetoothDevice) GROUP_ID_1 else GROUP_ID_2
- }
-
- actionInteractorImpl.onClick(activeMediaDeviceItem, dialog)
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
}
}
}
@@ -478,7 +139,5 @@
const val DEVICE_NAME = "device"
const val DEVICE_CONNECTION_SUMMARY = "active"
const val DEVICE_ADDRESS = "address"
- const val GROUP_ID_1 = 1
- const val GROUP_ID_2 = 2
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index ef441c1..10c3457 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -133,8 +133,8 @@
@Test
fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_flagOff_returnsFalse() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING off or the device doesn't support broadcast
- // source or assistant.
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING off or the device doesn't support broadcast
+ // source or assistant.
`when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
assertThat(
@@ -145,9 +145,9 @@
}
@Test
- fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isActiveDevice_returnsFalse() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
- // assistant.
+ fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isActiveDevice_false() {
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+ // assistant.
`when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
`when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(true)
@@ -159,9 +159,9 @@
}
@Test
- fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isNotAvailable_returnsFalse() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
- // assistant.
+ fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isNotAvailable_false() {
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+ // assistant.
`when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
`when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
`when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
@@ -177,8 +177,8 @@
@Test
fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_returnsTrue() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
- // assistant.
+ // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
+ // assistant.
`when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
`when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
`when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index 194590c..c39b9a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -83,18 +83,6 @@
fun setUp() {
dispatcher = UnconfinedTestDispatcher()
testScope = TestScope(dispatcher)
- interactor =
- DeviceItemInteractor(
- bluetoothTileDialogRepository,
- audioManager,
- adapter,
- localBluetoothManager,
- fakeSystemClock,
- logger,
- testScope.backgroundScope,
- dispatcher
- )
-
`when`(deviceItem1.cachedBluetoothDevice).thenReturn(cachedDevice1)
`when`(deviceItem2.cachedBluetoothDevice).thenReturn(cachedDevice2)
`when`(cachedDevice1.address).thenReturn("ADDRESS")
@@ -108,9 +96,19 @@
fun testUpdateDeviceItems_noCachedDevice_returnEmpty() {
testScope.runTest {
`when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(emptyList())
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ true }, deviceItem1))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ true }, deviceItem1)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -125,9 +123,19 @@
fun testUpdateDeviceItems_hasCachedDevice_filterNotMatch_returnEmpty() {
testScope.runTest {
`when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ false }, deviceItem1))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ false }, deviceItem1)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -142,9 +150,19 @@
fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnDeviceItem() {
testScope.runTest {
`when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ true }, deviceItem1))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ true }, deviceItem1)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -159,9 +177,22 @@
fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnMultipleDeviceItem() {
testScope.runTest {
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ false }, deviceItem1), createFactory({ true }, deviceItem2))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(
+ createFactory({ false }, deviceItem1),
+ createFactory({ true }, deviceItem2)
+ ),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -176,18 +207,31 @@
fun testUpdateDeviceItems_sortByDisplayPriority() {
testScope.runTest {
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
- interactor.setDeviceItemFactoryListForTesting(
- listOf(
- createFactory({ cachedDevice -> cachedDevice.device == device1 }, deviceItem1),
- createFactory({ cachedDevice -> cachedDevice.device == device2 }, deviceItem2)
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device1 },
+ deviceItem1
+ ),
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device2 },
+ deviceItem2
+ )
+ ),
+ listOf(
+ DeviceItemType.SAVED_BLUETOOTH_DEVICE,
+ DeviceItemType.CONNECTED_BLUETOOTH_DEVICE
+ ),
+ testScope.backgroundScope,
+ dispatcher
)
- )
- interactor.setDisplayPriorityForTesting(
- listOf(
- DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- DeviceItemType.CONNECTED_BLUETOOTH_DEVICE
- )
- )
`when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
`when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -204,15 +248,28 @@
fun testUpdateDeviceItems_sameType_sortByRecentlyConnected() {
testScope.runTest {
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(listOf(device2, device1))
- interactor.setDeviceItemFactoryListForTesting(
- listOf(
- createFactory({ cachedDevice -> cachedDevice.device == device1 }, deviceItem1),
- createFactory({ cachedDevice -> cachedDevice.device == device2 }, deviceItem2)
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device1 },
+ deviceItem1
+ ),
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device2 },
+ deviceItem2
+ )
+ ),
+ listOf(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE),
+ testScope.backgroundScope,
+ dispatcher
)
- )
- interactor.setDisplayPriorityForTesting(
- listOf(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
- )
`when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
`when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -231,10 +288,19 @@
`when`(bluetoothTileDialogRepository.cachedDevices)
.thenReturn(listOf(cachedDevice2, cachedDevice2, cachedDevice2, cachedDevice2))
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ true }, deviceItem2))
- )
-
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ true }, deviceItem2)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
index 85e8ab4..5741d64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -122,6 +122,7 @@
@Test
@Throws(IOException::class)
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
fun test_imageClipData_loadFailure() {
whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
whenever(mMockContext.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 6febb91..7a579ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -58,7 +58,7 @@
private static final int MIN_RSSI = -100;
private static final int MAX_RSSI = -55;
private WifiInfo mWifiInfo = mock(WifiInfo.class);
- private VcnTransportInfo mVcnTransportInfo = mock(VcnTransportInfo.class);
+ private VcnTransportInfo mVcnTransportInfo = new VcnTransportInfo.Builder().build();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 59fc0d1..87cda64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -591,8 +591,8 @@
ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
verify(mStackScroller).setFooterView(captor.capture());
- assertNotNull(captor.getValue().findViewById(R.id.manage_text).hasOnClickListeners());
- assertNotNull(captor.getValue().findViewById(R.id.dismiss_text).hasOnClickListeners());
+ assertNotNull(captor.getValue().findViewById(R.id.manage_text));
+ assertNotNull(captor.getValue().findViewById(R.id.dismiss_text));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 328d310..c48898a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -136,6 +136,7 @@
private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
private val wifiPickerTrackerCallback =
argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -1003,6 +1004,18 @@
assertThat(latest).isTrue()
}
+ private fun newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
/** Regression test for b/272586234. */
@Test
fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
@@ -1012,10 +1025,12 @@
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
@@ -1034,10 +1049,12 @@
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
@@ -1094,10 +1111,15 @@
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 0945742..88f262b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -23,6 +23,7 @@
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.TelephonyNetworkSpecifier
import android.net.VpnTransportInfo
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
@@ -74,6 +75,8 @@
private val testScope = kosmos.testScope
private val tunerService = mock<TunerService>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+
@Before
fun setUp() {
createAndSetRepo()
@@ -343,6 +346,30 @@
assertThat(latest!!.wifi.isDefault).isTrue()
}
+ private fun newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
+ private fun newCellNetwork(subId: Int): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.networkSpecifier).thenReturn(TelephonyNetworkSpecifier(subId))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
@Test
fun defaultConnections_carrierMergedViaWifiWithVcnTransport_wifiAndCarrierMergedDefault() =
testScope.runTest {
@@ -350,10 +377,12 @@
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
}
@@ -373,10 +402,12 @@
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
@@ -561,10 +592,12 @@
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
@@ -645,14 +678,15 @@
@Test
fun vcnSubId_tracksVcnTransportInfo() =
testScope.runTest {
- val vcnInfo = VcnTransportInfo(SUB_1_ID)
+ val underlyingCell = newCellNetwork(SUB_1_ID)
val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -663,14 +697,15 @@
@Test
fun vcnSubId_filersOutInvalid() =
testScope.runTest {
- val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID)
+ val underlyingCell = newCellNetwork(INVALID_SUBSCRIPTION_ID)
val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -703,11 +738,12 @@
val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
- val vcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -721,14 +757,15 @@
val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
- val wifiVcnInfo = VcnTransportInfo(wifiInfo)
- val sub1VcnInfo = VcnTransportInfo(SUB_1_ID)
- val sub2VcnInfo = VcnTransportInfo(SUB_2_ID)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
+ val underlyingCell1 = newCellNetwork(SUB_1_ID)
+ val underlyingCell2 = newCellNetwork(SUB_2_ID)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(wifiVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
// WIFI VCN info
@@ -738,14 +775,16 @@
// Cellular VCN info with subId 1
whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true)
- whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo)
+ whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell1))
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isEqualTo(SUB_1_ID)
// Cellular VCN info with subId 2
- whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo)
+ whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell2))
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -776,11 +815,12 @@
@Test
fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() {
val wifiInfo = mock<WifiInfo>()
- val vcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
@@ -860,11 +900,15 @@
fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() {
val wifiInfo = mock<WifiInfo>()
val underlyingNetwork = mock<Network>()
- val underlyingVcnInfo = VcnTransportInfo(wifiInfo)
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(wifiInfo)
+
val underlyingWifiCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
.thenReturn(underlyingWifiCapabilities)
@@ -887,11 +931,15 @@
@DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
val underlyingNetwork = mock<Network>()
- val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(mock<WifiInfo>())
+
val underlyingWifiCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
.thenReturn(underlyingWifiCapabilities)
@@ -917,10 +965,15 @@
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelKosmos.kt
new file mode 100644
index 0000000..cac4ff3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.audioSharingButtonViewModel: AudioSharingButtonViewModel by
+ Kosmos.Fixture {
+ AudioSharingButtonViewModel(
+ localBluetoothManager,
+ audioSharingInteractor,
+ bluetoothStateInteractor,
+ deviceItemInteractor,
+ )
+ }
+
+val Kosmos.audioSharingButtonViewModelFactory: AudioSharingButtonViewModel.Factory by
+ Kosmos.Fixture {
+ object : AudioSharingButtonViewModel.Factory {
+ override fun create(): AudioSharingButtonViewModel {
+ return audioSharingButtonViewModel
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorKosmos.kt
new file mode 100644
index 0000000..8019efc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import com.android.internal.logging.uiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.audioSharingDeviceItemActionInteractorImpl: AudioSharingDeviceItemActionInteractorImpl by
+ Kosmos.Fixture {
+ AudioSharingDeviceItemActionInteractorImpl(
+ activityStarter,
+ audioSharingInteractor,
+ dialogTransitionAnimator,
+ localBluetoothManager,
+ testDispatcher,
+ testDispatcher,
+ bluetoothTileDialogLogger,
+ uiEventLogger,
+ audioSharingDialogDelegateFactory,
+ deviceItemActionInteractorImpl,
+ )
+ }
+
+val Kosmos.audioSharingDialogDelegateFactory: AudioSharingDialogDelegate.Factory by
+ Kosmos.Fixture {
+ object : AudioSharingDialogDelegate.Factory {
+ override fun create(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): AudioSharingDialogDelegate {
+ return audioSharingDialogDelegate
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelKosmos.kt
new file mode 100644
index 0000000..b8899de8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelKosmos.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import android.content.applicationContext
+import com.android.internal.logging.uiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.phone.systemUIDialogDotFactory
+import kotlinx.coroutines.CoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.cachedBluetoothDevice: CachedBluetoothDevice by Kosmos.Fixture { mock {} }
+
+val Kosmos.audioSharingDialogViewModel: AudioSharingDialogViewModel by
+ Kosmos.Fixture {
+ AudioSharingDialogViewModel(
+ deviceItemInteractor,
+ audioSharingInteractor,
+ applicationContext,
+ localBluetoothManager,
+ cachedBluetoothDevice,
+ testScope.backgroundScope,
+ testDispatcher
+ )
+ }
+
+val Kosmos.audioSharingDialogViewModelFactory: AudioSharingDialogViewModel.Factory by
+ Kosmos.Fixture {
+ object : AudioSharingDialogViewModel.Factory {
+ override fun create(
+ cachedBluetoothDevice: CachedBluetoothDevice,
+ coroutineScope: CoroutineScope
+ ): AudioSharingDialogViewModel {
+ return audioSharingDialogViewModel
+ }
+ }
+ }
+
+val Kosmos.audioSharingDialogDelegate: AudioSharingDialogDelegate by
+ Kosmos.Fixture {
+ AudioSharingDialogDelegate(
+ cachedBluetoothDevice,
+ testScope.backgroundScope,
+ audioSharingDialogViewModelFactory,
+ systemUIDialogDotFactory,
+ uiEventLogger
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
index 02ed264..4f4d1da 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
@@ -14,9 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.bluetooth.qsdialog
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
+import com.android.systemui.kosmos.testDispatcher
-val Kosmos.qsColumnsInteractor by Kosmos.Fixture { QSColumnsInteractor(qsColumnsRepository) }
+val Kosmos.audioSharingInteractor: AudioSharingInteractor by
+ Kosmos.Fixture {
+ AudioSharingInteractorImpl(
+ localBluetoothManager,
+ bluetoothTileDialogAudioSharingRepository,
+ testDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryKosmos.kt
similarity index 74%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryKosmos.kt
index 02ed264..d15d0e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryKosmos.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.bluetooth.qsdialog
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
-val Kosmos.qsColumnsInteractor by Kosmos.Fixture { QSColumnsInteractor(qsColumnsRepository) }
+val Kosmos.bluetoothTileDialogAudioSharingRepository by
+ Kosmos.Fixture { FakeAudioSharingRepository() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorKosmos.kt
similarity index 63%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorKosmos.kt
index 0ca025f..aaa918c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorKosmos.kt
@@ -14,14 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.data.repository
+package com.android.systemui.bluetooth.qsdialog
-import android.content.res.mainResources
-import com.android.systemui.common.ui.data.repository.configurationRepository
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
-val Kosmos.qsColumnsRepository by
+val Kosmos.bluetoothStateInteractor: BluetoothStateInteractor by
Kosmos.Fixture {
- QSColumnsRepository(applicationCoroutineScope, mainResources, configurationRepository)
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ bluetoothTileDialogLogger,
+ testScope.backgroundScope,
+ testDispatcher
+ )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
similarity index 79%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
index 5ff4634..b5b2f5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
@@ -20,8 +20,7 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.util.mockito.mock
+import org.mockito.kotlin.mock
val Kosmos.bluetoothTileDialogLogger: BluetoothTileDialogLogger by Kosmos.Fixture { mock {} }
@@ -29,14 +28,10 @@
val Kosmos.dialogTransitionAnimator: DialogTransitionAnimator by Kosmos.Fixture { mock {} }
-val Kosmos.deviceItemActionInteractor: DeviceItemActionInteractor by
+val Kosmos.deviceItemActionInteractorImpl: DeviceItemActionInteractorImpl by
Kosmos.Fixture {
- DeviceItemActionInteractor(
- activityStarter,
- dialogTransitionAnimator,
- localBluetoothManager,
+ DeviceItemActionInteractorImpl(
testDispatcher,
- bluetoothTileDialogLogger,
uiEventLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
new file mode 100644
index 0000000..a839f17
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeAudioSharingRepository : AudioSharingRepository {
+ private var mutableAvailable: Boolean = false
+
+ private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ private val mutableAudioSourceStateUpdate = MutableSharedFlow<Unit>()
+
+ var sourceAdded: Boolean = false
+ private set
+
+ private var profile: LocalBluetoothLeBroadcast? = null
+
+ override val leAudioBroadcastProfile: LocalBluetoothLeBroadcast?
+ get() = profile
+
+ override val audioSourceStateUpdate: Flow<Unit> = mutableAudioSourceStateUpdate
+
+ override val inAudioSharing: StateFlow<Boolean> = mutableInAudioSharing
+
+ override suspend fun audioSharingAvailable(): Boolean = mutableAvailable
+
+ override suspend fun addSource() {
+ sourceAdded = true
+ }
+
+ override suspend fun setActive(cachedBluetoothDevice: CachedBluetoothDevice) {}
+
+ override suspend fun startAudioSharing() {}
+
+ fun setAudioSharingAvailable(available: Boolean) {
+ mutableAvailable = available
+ }
+
+ fun setInAudioSharing(state: Boolean) {
+ mutableInAudioSharing.value = state
+ }
+
+ fun setLeAudioBroadcastProfile(leAudioBroadcastProfile: LocalBluetoothLeBroadcast?) {
+ profile = leAudioBroadcastProfile
+ }
+
+ fun emitAudioSourceStateUpdate() {
+ mutableAudioSourceStateUpdate.tryEmit(Unit)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index f97f303..522c387 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.bouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.classifier.falsingCollector
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -152,6 +153,7 @@
val wifiInteractor by lazy { kosmos.wifiInteractor }
val fakeWifiRepository by lazy { kosmos.fakeWifiRepository }
val volumeDialogInteractor by lazy { kosmos.volumeDialogInteractor }
+ val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel }
val scrimController by lazy { kosmos.scrimController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index cfc31c7..10b073e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.plugins.statusbar
import com.android.internal.logging.uiEventLogger
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
@@ -45,5 +46,6 @@
{ sceneContainerOcclusionInteractor },
{ keyguardClockInteractor },
{ sceneBackInteractor },
+ { alternateBouncerInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepositoryKosmos.kt
similarity index 74%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepositoryKosmos.kt
index 02ed264..2f5daaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/FixedColumnsRepositoryKosmos.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.qs.panels.data.repository
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
-val Kosmos.qsColumnsInteractor by Kosmos.Fixture { QSColumnsInteractor(qsColumnsRepository) }
+val Kosmos.fixedColumnsRepository by Kosmos.Fixture { FixedColumnsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractorKosmos.kt
similarity index 78%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractorKosmos.kt
index 02ed264..f4d281d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/FixedColumnsSizeInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
+import com.android.systemui.qs.panels.data.repository.fixedColumnsRepository
-val Kosmos.qsColumnsInteractor by Kosmos.Fixture { QSColumnsInteractor(qsColumnsRepository) }
+val Kosmos.fixedColumnsSizeInteractor by
+ Kosmos.Fixture { FixedColumnsSizeInteractor(fixedColumnsRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index b4317ad..546129f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -18,11 +18,11 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.qsColumnsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.tileSquishinessViewModel
val Kosmos.infiniteGridLayout by
Kosmos.Fixture {
- InfiniteGridLayout(iconTilesViewModel, qsColumnsViewModel, tileSquishinessViewModel)
+ InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel, tileSquishinessViewModel)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModelKosmos.kt
similarity index 77%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModelKosmos.kt
index 16b2f54..feadc91 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/FixedColumnsSizeViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.domain.interactor.qsColumnsInteractor
+import com.android.systemui.qs.panels.domain.interactor.fixedColumnsSizeInteractor
-val Kosmos.qsColumnsViewModel by Kosmos.Fixture { QSColumnsSizeViewModelImpl(qsColumnsInteractor) }
+val Kosmos.fixedColumnsSizeViewModel by
+ Kosmos.Fixture { FixedColumnsSizeViewModelImpl(fixedColumnsSizeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 10d8e1e..85e9265 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -24,7 +24,7 @@
Kosmos.Fixture {
PaginatedGridViewModel(
iconTilesViewModel,
- qsColumnsViewModel,
+ fixedColumnsSizeViewModel,
iconLabelVisibilityViewModel,
paginatedGridInteractor,
applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt
similarity index 76%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt
index 16b2f54..fde174d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt
@@ -17,6 +17,12 @@
package com.android.systemui.qs.panels.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.domain.interactor.qsColumnsInteractor
-val Kosmos.qsColumnsViewModel by Kosmos.Fixture { QSColumnsSizeViewModelImpl(qsColumnsInteractor) }
+val Kosmos.partitionedGridViewModel by
+ Kosmos.Fixture {
+ PartitionedGridViewModel(
+ iconTilesViewModel,
+ fixedColumnsSizeViewModel,
+ iconLabelVisibilityViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 67d9e0e..babbd50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -25,7 +25,7 @@
Kosmos.Fixture {
QuickQuickSettingsViewModel(
currentTilesInteractor,
- qsColumnsViewModel,
+ fixedColumnsSizeViewModel,
quickQuickSettingsRowInteractor,
tileSquishinessViewModel,
iconTilesViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index a4719e5..5da6ee9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -22,6 +22,7 @@
import kotlinx.coroutines.flow.StateFlow
class FakeAudioSharingRepository : AudioSharingRepository {
+ private var mutableAvailable: Boolean = false
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
private val mutablePrimaryGroupId: MutableStateFlow<Int> =
MutableStateFlow(TEST_GROUP_ID_INVALID)
@@ -34,8 +35,14 @@
override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
+ override suspend fun audioSharingAvailable(): Boolean = mutableAvailable
+
override suspend fun setSecondaryVolume(volume: Int) {}
+ fun setAudioSharingAvailable(available: Boolean) {
+ mutableAvailable = available
+ }
+
fun setInAudioSharing(state: Boolean) {
mutableInAudioSharing.value = state
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index c5fef19..5d57408 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -65,8 +65,6 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService.TargetUser;
-import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
-import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -233,6 +231,9 @@
"Caller does not have permission to execute the"
+ " appfunction",
/* extras= */ null));
+ throw new SecurityException(
+ "Caller does not have permission to execute the"
+ + " appfunction");
}
})
.thenCompose(
@@ -380,7 +381,8 @@
runtimeMetadataSearchSession));
AppFunctionRuntimeMetadata newMetadata =
new AppFunctionRuntimeMetadata.Builder(existingMetadata)
- .setEnabled(enabledState).build();
+ .setEnabled(enabledState)
+ .build();
AppSearchBatchResult<String, Void> putDocumentBatchResult =
runtimeMetadataSearchSession
.put(
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2485626..5236b03 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3629,42 +3629,68 @@
}
@GuardedBy({"mService", "mProcLock"})
- private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index,
- int lruSeq, String what, Object obj, ProcessRecord srcApp) {
+ private int offerLruProcessInternalLSP(ProcessRecord app, long now, String what, Object obj,
+ ProcessRecord srcApp) {
app.setLastActivityTime(now);
if (app.hasActivitiesOrRecentTasks()) {
// Don't want to touch dependent processes that are hosting activities.
- return index;
+ return -1;
}
- int lrui = mLruProcesses.lastIndexOf(app);
+ final int lrui = mLruProcesses.lastIndexOf(app);
if (lrui < 0) {
Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+ what + " " + obj + " from " + srcApp);
- return index;
}
+ return lrui;
+ }
- if (lrui >= index) {
- // Don't want to cause this to move dependent processes *back* in the
- // list as if they were less frequently used.
- return index;
- }
+ /**
+ * This method is called after the indices array is populated by the indices offered by
+ * {@link #offerLruProcessInternalLSP} to actually move the processes to the desired locations
+ * in the LRU list. Since the indices array is a SparseBooleanArray, the indices are sorted
+ * and this allows us to preserve the previous order of the processes relative to each other.
+ * Key of the indices array holds the current index of the process in the LRU list and the value
+ * is a boolean indicating whether the process is an activity process or not. Activity processes
+ * are moved to the nextActivityIndex and non-activity processes are moved to the nextIndex
+ * positions, which are provided by the caller.
+ *
+ * @param indices The indices of the processes to move.
+ * @param nextActivityIndex The next index to insert an activity process.
+ * @param nextIndex The next index to insert a non-activity process.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private void completeLruProcessInternalLSP(SparseBooleanArray indices, int nextActivityIndex,
+ int nextIndex) {
+ for (int i = indices.size() - 1; i >= 0; i--) {
+ final int lrui = indices.keyAt(i);
+ if (lrui < 0) {
+ // Rest of the indices are invalid, we can return early.
+ return;
+ }
+ final boolean isActivity = indices.valueAt(i);
+ int index = isActivity ? nextActivityIndex : nextIndex;
- if (lrui >= mLruProcessActivityStart && index < mLruProcessActivityStart) {
- // Don't want to touch dependent processes that are hosting activities.
- return index;
- }
+ if (lrui >= index) {
+ // Don't want to cause this to move dependent processes *back* in the
+ // list as if they were less frequently used.
+ continue;
+ }
- mLruProcesses.remove(lrui);
- if (index > 0) {
+ final ProcessRecord app = mLruProcesses.remove(lrui);
index--;
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
+ + " in LRU list: " + app);
+ mLruProcesses.add(index, app);
+ app.setLruSeq(mLruSeq);
+
+ if (isActivity) {
+ nextActivityIndex = index;
+ } else {
+ nextIndex = index;
+ }
}
- if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
- + " in LRU list: " + app);
- mLruProcesses.add(index, app);
- app.setLruSeq(lruSeq);
- return index;
}
/**
@@ -4058,6 +4084,15 @@
app.setLruSeq(mLruSeq);
+ // Key of the indices array holds the current index of the process in the LRU list and the
+ // value is a boolean indicating whether the process is an activity process or not.
+ // Activity processes will be moved to the nextActivityIndex and non-activity processes will
+ // be moved to the nextIndex positions when completeLruProcessInternalLSP is called.
+ // Since SparseBooleanArray's keys are sorted, we'll be able to keep the existing order of
+ // the processes relative to each other after the move.
+ final SparseBooleanArray indices = new SparseBooleanArray(psr.numberOfConnections()
+ + app.mProviders.numberOfProviderConnections());
+
// If the app is currently using a content provider or service,
// bump those processes as well.
for (int j = psr.numberOfConnections() - 1; j >= 0; j--) {
@@ -4069,16 +4104,12 @@
&& !cr.binding.service.app.isPersistent()) {
if (cr.binding.service.app.mServices.hasClientActivities()) {
if (nextActivityIndex >= 0) {
- nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextActivityIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), true);
}
} else {
- nextIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), false);
}
}
}
@@ -4086,10 +4117,11 @@
for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) {
ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider;
if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) {
- nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq,
- "provider reference", cpr, app);
+ indices.append(offerLruProcessInternalLSP(cpr.proc, now,
+ "provider reference", cpr, app), false);
}
}
+ completeLruProcessInternalLSP(indices, nextActivityIndex, nextIndex);
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d206b20..fdf7dec 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -286,7 +286,6 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -8049,7 +8048,14 @@
}
synchronized (mAbsoluteVolumeDeviceInfoMapLock) {
if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) {
- return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior;
+ final AbsoluteVolumeDeviceInfo deviceInfo = mAbsoluteVolumeDeviceInfoMap.get(
+ audioSystemDeviceOut);
+ if (deviceInfo != null) {
+ return deviceInfo.mDeviceVolumeBehavior;
+ }
+
+ Log.e(TAG,
+ "Null absolute volume device info stored for key " + audioSystemDeviceOut);
}
}
@@ -15043,6 +15049,11 @@
private void addAudioSystemDeviceOutToAbsVolumeDevices(int audioSystemDeviceOut,
AbsoluteVolumeDeviceInfo info) {
+ if (info == null) {
+ Log.e(TAG, "Cannot add null absolute volume info for audioSystemDeviceOut "
+ + audioSystemDeviceOut);
+ return;
+ }
if (DEBUG_VOL) {
Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut)
+ " to mAbsoluteVolumeDeviceInfoMap with behavior "
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index a53b8df..c7a70fa 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -5666,6 +5666,11 @@
displayPowerController.stylusGestureStarted(eventTime);
}
}
+
+ @Override
+ public boolean isDisplayReadyForMirroring(int displayId) {
+ return mExternalDisplayPolicy.isDisplayReadyForMirroring(displayId);
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 28a0b28..f34d2cc 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -375,6 +375,54 @@
}
}
+ boolean isDisplayReadyForMirroring(int displayId) {
+ if (!mFlags.isWaitingConfirmationBeforeMirroringEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring CONFIRMED - "
+ + " flag 'waiting for confirmation before mirroring' is disabled");
+ }
+ return true;
+ }
+
+ synchronized (mSyncRoot) {
+ if (!mIsBootCompleted) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "boot is in progress");
+ }
+ return false;
+ }
+
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is null");
+ }
+ return false;
+ }
+
+ if (!isExternalDisplayLocked(logicalDisplay)) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay" + logicalDisplay.getDisplayIdLocked()
+ + " type is " + logicalDisplay.getDisplayInfoLocked().type);
+ }
+ return false;
+ }
+
+ if (!logicalDisplay.isEnabledLocked()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is disabled");
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
@Override
public void notifyThrottling(@NonNull final Temperature temp) {
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 99ced7f..b2e98bc 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -217,6 +217,11 @@
Flags::enableUserRefreshRateForExternalDisplay
);
+ private final FlagState mEnableWaitingConfirmationBeforeMirroring = new FlagState(
+ Flags.FLAG_ENABLE_WAITING_CONFIRMATION_BEFORE_MIRRORING,
+ Flags::enableWaitingConfirmationBeforeMirroring
+ );
+
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
Flags::enableBatteryStatsForAllDisplays
@@ -445,6 +450,14 @@
}
/**
+ * @return {@code true} if mirroring won't be enabled until boot completes and the user enables
+ * the display.
+ */
+ public boolean isWaitingConfirmationBeforeMirroringEnabled() {
+ return mEnableWaitingConfirmationBeforeMirroring.isEnabled();
+ }
+
+ /**
* @return {@code true} if battery stats is enabled for all displays, not just the primary
* display.
*/
@@ -511,6 +524,7 @@
pw.println(" " + mVirtualDisplayLimit);
pw.println(" " + mNormalBrightnessForDozeParameter);
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
+ pw.println(" " + mEnableWaitingConfirmationBeforeMirroring);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 2f04d9e..df62638 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -367,6 +367,17 @@
}
flag {
+ name: "enable_waiting_confirmation_before_mirroring"
+ namespace: "display_manager"
+ description: "Allow ContentRecorder checking whether user confirmed mirroring after boot"
+ bug: "361698995"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_battery_stats_for_all_displays"
namespace: "display_manager"
description: "Flag to enable battery stats for all displays."
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d70bd8b..d1a6d3b 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -63,6 +63,12 @@
mObservers = Map.ofEntries(
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
(reason) -> updateMousePointerSpeed()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING),
+ (reason) -> updateMouseReverseVerticalScrolling()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON),
+ (reason) -> updateMouseSwapPrimaryButton()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
(reason) -> updateTouchpadPointerSpeed()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
@@ -163,6 +169,16 @@
mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
}
+ private void updateMouseReverseVerticalScrolling() {
+ mNative.setMouseReverseVerticalScrollingEnabled(
+ InputSettings.isMouseReverseVerticalScrollingEnabled(mContext));
+ }
+
+ private void updateMouseSwapPrimaryButton() {
+ mNative.setMouseSwapPrimaryButtonEnabled(
+ InputSettings.isMouseSwapPrimaryButtonEnabled(mContext));
+ }
+
private void updateTouchpadPointerSpeed() {
mNative.setTouchpadPointerSpeed(
constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4404d63..21e8bcc 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -127,6 +127,10 @@
void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
+ void setMouseSwapPrimaryButtonEnabled(boolean enabled);
+
void setTouchpadPointerSpeed(int speed);
void setTouchpadNaturalScrollingEnabled(boolean enabled);
@@ -388,6 +392,12 @@
public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@Override
+ public native void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
+ @Override
+ public native void setMouseSwapPrimaryButtonEnabled(boolean enabled);
+
+ @Override
public native void setTouchpadPointerSpeed(int speed);
@Override
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
index c02b103..404c841 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.os.Environment;
import android.security.keystore.recovery.KeyChainSnapshot;
-import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -29,9 +28,11 @@
import com.android.server.locksettings.recoverablekeystore.serialization
.KeyChainSnapshotParserException;
import com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSerializer;
+import com.android.server.utils.Slogf;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
@@ -81,12 +82,14 @@
public synchronized void put(int uid, KeyChainSnapshot snapshot) {
mSnapshotByUid.put(uid, snapshot);
- try {
- writeToDisk(uid, snapshot);
+ File snapshotFile = getSnapshotFile(uid);
+ try (FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)) {
+ KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream);
} catch (IOException | CertificateEncodingException e) {
- Log.e(TAG,
- String.format(Locale.US, "Error persisting snapshot for %d to disk", uid),
- e);
+ // If we fail to write the latest snapshot, we should delete any older snapshot that
+ // happens to be around. Otherwise snapshot syncs might end up going 'back in time'.
+ snapshotFile.delete();
+ Slogf.e(TAG, e, "Error persisting snapshot for %d to disk", uid);
}
}
@@ -100,10 +103,17 @@
return snapshot;
}
- try {
- return readFromDisk(uid);
+ File snapshotFile = getSnapshotFile(uid);
+ try (FileInputStream fileInputStream = new FileInputStream(snapshotFile)) {
+ return KeyChainSnapshotDeserializer.deserialize(fileInputStream);
+ } catch (FileNotFoundException e) {
+ Slogf.i(TAG, "Snapshot for uid %d not found", uid);
+ return null;
} catch (IOException | KeyChainSnapshotParserException e) {
- Log.e(TAG, String.format(Locale.US, "Error reading snapshot for %d from disk", uid), e);
+ // If we fail to read the latest snapshot, we should delete it in case it is in some way
+ // corrupted. We can regenerate snapshots anyway.
+ snapshotFile.delete();
+ Slogf.e(TAG, e, "Error reading snapshot for %d from disk", uid);
return null;
}
}
@@ -116,50 +126,6 @@
getSnapshotFile(uid).delete();
}
- /**
- * Writes the snapshot for recovery agent {@code uid} to disk.
- *
- * @throws IOException if an IO error occurs writing to disk.
- */
- private void writeToDisk(int uid, KeyChainSnapshot snapshot)
- throws IOException, CertificateEncodingException {
- File snapshotFile = getSnapshotFile(uid);
-
- try (
- FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)
- ) {
- KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream);
- } catch (IOException | CertificateEncodingException e) {
- // If we fail to write the latest snapshot, we should delete any older snapshot that
- // happens to be around. Otherwise snapshot syncs might end up going 'back in time'.
- snapshotFile.delete();
- throw e;
- }
- }
-
- /**
- * Reads the last snapshot for recovery agent {@code uid} from disk.
- *
- * @return The snapshot, or null if none existed.
- * @throws IOException if an IO error occurs reading from disk.
- */
- @Nullable
- private KeyChainSnapshot readFromDisk(int uid)
- throws IOException, KeyChainSnapshotParserException {
- File snapshotFile = getSnapshotFile(uid);
-
- try (
- FileInputStream fileInputStream = new FileInputStream(snapshotFile)
- ) {
- return KeyChainSnapshotDeserializer.deserialize(fileInputStream);
- } catch (IOException | KeyChainSnapshotParserException e) {
- // If we fail to read the latest snapshot, we should delete it in case it is in some way
- // corrupted. We can regenerate snapshots anyway.
- snapshotFile.delete();
- throw e;
- }
- }
-
private File getSnapshotFile(int uid) {
File folder = getStorageFolder();
String fileName = getSnapshotFileName(uid);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 1f79ac0..089bbb7 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.content.pm.Flags.improveInstallFreeze;
import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
@@ -1050,13 +1049,13 @@
}
public void onFreezeStarted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
public void onFreezeCompleted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8bab9de..708e067 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1101,7 +1101,7 @@
if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
UserManager.invalidateIsUserUnlockedCache();
UserManager.invalidateQuietModeEnabledCache();
- UserManager.invalidateUserSerialNumberCache();
+ UserManager.invalidateCacheOnUserListChange();
}
}
@@ -4448,7 +4448,7 @@
if (userData != null) {
synchronized (mUsersLock) {
- mUsers.put(userData.info.id, userData);
+ addUserDataLU(userData);
if (mNextSerialNumber < 0
|| mNextSerialNumber <= userData.info.id) {
mNextSerialNumber = userData.info.id + 1;
@@ -5724,7 +5724,7 @@
userData.info = userInfo;
userData.userProperties = new UserProperties(
userTypeDetails.getDefaultUserPropertiesReference());
- mUsers.put(userId, userData);
+ addUserDataLU(userData);
}
writeUserLP(userData);
writeUserListLP();
@@ -6138,7 +6138,7 @@
final UserData userData = new UserData();
userData.info = userInfo;
synchronized (mUsersLock) {
- mUsers.put(userInfo.id, userData);
+ addUserDataLU(userData);
}
updateUserIds();
return userData;
@@ -6148,8 +6148,7 @@
@VisibleForTesting
void removeUserInfo(@UserIdInt int userId) {
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
}
}
@@ -6579,8 +6578,7 @@
// Remove this user from the list
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
mIsUserManaged.delete(userId);
}
synchronized (mUserStates) {
@@ -6969,6 +6967,26 @@
}
/**
+ * Adding user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void addUserDataLU(UserData userData) {
+ if (android.multiuser.Flags.invalidateCacheOnUsersChangedReadOnly()) {
+ UserManager.invalidateCacheOnUserListChange();
+ }
+ mUsers.put(userData.info.id, userData);
+ }
+
+ /**
+ * Removing user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void removeUserDataLU(@UserIdInt int userId) {
+ UserManager.invalidateCacheOnUserListChange();
+ mUsers.remove(userId);
+ }
+
+ /**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
private void updateUserIds() {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 670a61d..05dcbb7 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -25,6 +25,7 @@
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
@@ -268,7 +269,16 @@
}
final DisplayPolicy.DecorInsets.Info decor =
displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
- outAppBounds.intersectUnchecked(decor.mOverrideNonDecorFrame);
+ if (!outAppBounds.intersect(decor.mOverrideNonDecorFrame)) {
+ // TODO (b/364883053): When a split screen is requested from an app intent for a new
+ // task, the bounds is not the final bounds, and this is also not a bounds change
+ // event handled correctly with the offset. Revert back to legacy method for this
+ // case.
+ if (inOutConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_MULTI_WINDOW) {
+ outAppBounds.inset(decor.mOverrideNonDecorInsets);
+ }
+ }
if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets);
}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index bc33946..0b5872b 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -285,6 +285,11 @@
}
}
+ private boolean isDisplayReadyForMirroring() {
+ return mDisplayContent.getDisplayInfo().type != Display.TYPE_EXTERNAL
+ || mDisplayContent.mWmService.mDisplayManagerInternal.isDisplayReadyForMirroring(
+ mDisplayContent.getDisplayId());
+ }
/**
* Ensure recording does not fall back to the display stack; ensure the recording is stopped
@@ -335,7 +340,7 @@
return;
}
- if (mContentRecordingSession.isWaitingForConsent()) {
+ if (mContentRecordingSession.isWaitingForConsent() || !isDisplayReadyForMirroring()) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do "
+ "nothing");
return;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index efca902..248ed1a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,6 +337,8 @@
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
+ void setMouseReverseVerticalScrollingEnabled(bool enabled);
+ void setMouseSwapPrimaryButtonEnabled(bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
@@ -482,6 +484,12 @@
// True if stylus button reporting through motion events is enabled.
bool stylusButtonMotionEventsEnabled{true};
+ // True if mouse vertical scrolling is reversed.
+ bool mouseReverseVerticalScrollingEnabled{false};
+
+ // True if the mouse primary button is swapped (left/right buttons).
+ bool mouseSwapPrimaryButtonEnabled{false};
+
// The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest).
int32_t touchpadPointerSpeed{0};
@@ -762,6 +770,10 @@
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
+ outConfig->mouseReverseVerticalScrollingEnabled =
+ mLocked.mouseReverseVerticalScrollingEnabled;
+ outConfig->mouseSwapPrimaryButtonEnabled = mLocked.mouseSwapPrimaryButtonEnabled;
+
outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed;
outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
@@ -1317,6 +1329,36 @@
return mLocked.pointerSpeed;
}
+void NativeInputManager::setMouseReverseVerticalScrollingEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseReverseVerticalScrollingEnabled == enabled) {
+ return;
+ }
+
+ mLocked.mouseReverseVerticalScrollingEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::MOUSE_SETTINGS);
+}
+
+void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseSwapPrimaryButtonEnabled == enabled) {
+ return;
+ }
+
+ mLocked.mouseSwapPrimaryButtonEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::MOUSE_SETTINGS);
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -3002,6 +3044,18 @@
return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId());
}
+static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj,
+ bool enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseReverseVerticalScrollingEnabled(enabled);
+}
+
+static void nativeSetMouseSwapPrimaryButtonEnabled(JNIEnv* env, jobject nativeImplObj,
+ bool enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseSwapPrimaryButtonEnabled(enabled);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -3048,6 +3102,9 @@
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
{"setMousePointerAccelerationEnabled", "(IZ)V",
(void*)nativeSetMousePointerAccelerationEnabled},
+ {"setMouseReverseVerticalScrollingEnabled", "(Z)V",
+ (void*)nativeSetMouseReverseVerticalScrollingEnabled},
+ {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled},
{"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
{"setTouchpadNaturalScrollingEnabled", "(Z)V",
(void*)nativeSetTouchpadNaturalScrollingEnabled},
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 2836d46..2add5b0 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -349,7 +349,7 @@
return nullptr;
}
- // Return the currently watched pids. The lock must be held.
+ // Return the currently watched pids as a comma-separated list. The lock must be held.
std::string watchedPidsLocked() const {
if (watched_.size() == 0) return "none";
bool first = true;
@@ -357,6 +357,7 @@
for (auto i = watched_.cbegin(); i != watched_.cend(); i++) {
if (first) {
result += StringPrintf("%d", *i);
+ first = false;
} else {
result += StringPrintf(",%d", *i);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index f728168..782262d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -18,6 +18,7 @@
import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED;
import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
import static com.google.common.truth.Truth.assertThat;
@@ -36,6 +37,7 @@
import android.os.IThermalService;
import android.os.RemoteException;
import android.os.Temperature;
+import android.view.Display;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
@@ -97,6 +99,8 @@
@Mock
private LogicalDisplay mMockedLogicalDisplay;
@Mock
+ private LogicalDisplay mMockedDefaultDisplay;
+ @Mock
private DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock
private ExternalDisplayStatsService mMockedExternalDisplayStatsService;
@@ -141,6 +145,15 @@
when(mMockedLogicalDisplay.getDisplayInfoLocked()).thenReturn(mockedLogicalDisplayInfo);
when(mMockedLogicalDisplayMapper.getDisplayLocked(EXTERNAL_DISPLAY_ID)).thenReturn(
mMockedLogicalDisplay);
+
+ // Initialize default logical display
+ when(mMockedDefaultDisplay.getDisplayIdLocked()).thenReturn(Display.DEFAULT_DISPLAY);
+ when(mMockedDefaultDisplay.isEnabledLocked()).thenReturn(true);
+ final var mockedDefaultDisplayInfo = new DisplayInfo();
+ mockedDefaultDisplayInfo.type = TYPE_INTERNAL;
+ when(mMockedDefaultDisplay.getDisplayInfoLocked()).thenReturn(mockedDefaultDisplayInfo);
+ when(mMockedLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)).thenReturn(
+ mMockedDefaultDisplay);
}
@Test
@@ -293,6 +306,52 @@
verify(mMockedLogicalDisplayMapper, never()).forEachLocked(any());
}
+ @Test
+ public void testMirroringAlwaysConfirmedByUser_flagDisabled() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testMirroringConfirmed_afterBootForEnabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isTrue();
+ }
+
+ @Test
+ public void testMirroringNotConfirmed_afterBootForDisabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExternalDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.DEFAULT_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExistingDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.INVALID_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_duringBoot() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
private void setTemperature(final IThermalEventListener thermalEventListener,
final List<Temperature> temperature) throws RemoteException {
for (var t : temperature) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
index 5676a38..6d14065 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -459,7 +459,6 @@
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
newInjector.setReadStream(bais);
newDataStore.loadIfNeeded();
- assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 584fd62..40b9c61 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
+import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -303,6 +304,12 @@
}
}
+ private int getProcessStateQuotaFreeThreshold() {
+ synchronized (mQuotaController.mLock) {
+ return mQuotaController.getProcessStateQuotaFreeThreshold();
+ }
+ }
+
private void setProcessState(int procState) {
setProcessState(procState, mSourceUid);
}
@@ -315,7 +322,7 @@
final boolean contained = foregroundUids.get(uid);
mUidObserver.onUidStateChanged(uid, procState, 0,
ActivityManager.PROCESS_CAPABILITY_NONE);
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
if (!contained) {
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
.put(eq(uid), eq(true));
@@ -1371,7 +1378,7 @@
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
@@ -1473,7 +1480,7 @@
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -1505,7 +1512,7 @@
createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
timeUsedMs, 5), true);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -4126,7 +4133,7 @@
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
// Change to a state that should still be considered foreground.
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
advanceElapsedClock(5 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
@@ -4134,6 +4141,36 @@
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ /** Tests that Timers count FOREGROUND_SERVICE jobs. */
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
+ public void testTimerTracking_Fgs() {
+ setDischarging();
+
+ JobStatus jobStatus = createJobStatus("testTimerTracking_Fgs", 1);
+ setProcessState(ActivityManager.PROCESS_STATE_BOUND_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ // Change to FOREGROUND_SERVICE state that should count.
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
+ }
+ List<TimingSession> expected = new ArrayList<>();
+ expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that Timers properly track sessions when switching between foreground and background
* states.
@@ -4180,7 +4217,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4213,7 +4250,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4262,7 +4299,7 @@
}
assertEquals(0, stats.jobCountInRateLimitingWindow);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4412,7 +4449,7 @@
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4625,7 +4662,7 @@
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -5901,7 +5938,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -5935,7 +5972,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -6056,7 +6093,7 @@
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -6534,7 +6571,7 @@
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop2.isExpeditedQuotaApproved());
assertTrue(jobFg.isExpeditedQuotaApproved());
assertTrue(jobBg.isExpeditedQuotaApproved());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index e443696..c51261f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -52,6 +52,7 @@
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.SurfaceControl;
@@ -93,9 +94,11 @@
private boolean mHandleAnisotropicDisplayMirroring = false;
@Before public void setUp() {
+ mDisplayInfo.type = Display.TYPE_VIRTUAL;
MockitoAnnotations.initMocks(this);
doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+ doReturn(false).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
// Skip unnecessary operations of relayout.
spyOn(mWm.mWindowPlacerLocked);
@@ -163,6 +166,25 @@
}
@Test
+ public void testUpdateRecording_externalDisplayWithoutUserConfirmation() {
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+ }
+
+ @Test
+ public void testUpdateRecording_externalDisplayWithUserConfirmation() {
+ doReturn(true).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
public void testUpdateRecording_display_invalidDisplayIdToMirror() {
defaultInit();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index 5e8f347..c8fc482 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -26,7 +26,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertFalse;
@@ -73,7 +72,6 @@
when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(0);
when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState);
- spy(mDisplayContent);
doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity();
when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 49ca6f3..44de65a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1965,13 +1965,14 @@
}
/**
- * Inform whether the device is aligned with the satellite for demo mode.
+ * Inform whether the device is aligned with the satellite in both real and demo mode.
*
- * Framework can send datagram to modem only when device is aligned with the satellite.
- * This method helps framework to simulate the experience of sending datagram over satellite.
+ * In demo mode, framework will send datagram to modem only when device is aligned with
+ * the satellite. This method helps framework to simulate the experience of sending datagram
+ * over satellite.
*
- * @param isAligned {@true} Device is aligned with the satellite for demo mode
- * {@false} Device is not aligned with the satellite for demo mode
+ * @param isAligned {code @true} Device is aligned with the satellite
+ * {code @false} Device is not aligned with the satellite
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 61f0146..231c8f5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2977,10 +2977,10 @@
void requestTimeForNextSatelliteVisibility(in ResultReceiver receiver);
/**
- * Inform whether the device is aligned with the satellite within in margin for demo mode.
+ * Inform whether the device is aligned with the satellite in both real and demo mode.
*
- * @param isAligned {@true} Device is aligned with the satellite for demo mode
- * {@false} Device is not aligned with the satellite for demo mode
+ * @param isAligned {@true} Device is aligned with the satellite.
+ * {@false} Device is not aligned with the satellite.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")