uwb(multi-device-tests): Add test for verify 3p app usage restrictions
Add a shell command to simulate app moving to foreground/background to
help with CTS testing
Also, use setExact for the bg timer to ensure we stop bg sessions on
time.
Bug: 250619496
Test: atest CtsUwbMultiDeviceTestCase_FiraRangingTests
Test: atest ServiceUwbTests
Change-Id: I5f241f764c14cbedde3dabc5905a451ed6d94e98
diff --git a/service/java/com/android/server/uwb/UwbSessionManager.java b/service/java/com/android/server/uwb/UwbSessionManager.java
index f3d1d54..03bc49e 100644
--- a/service/java/com/android/server/uwb/UwbSessionManager.java
+++ b/service/java/com/android/server/uwb/UwbSessionManager.java
@@ -115,7 +115,8 @@
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
-public class UwbSessionManager implements INativeUwbManager.SessionNotification {
+public class UwbSessionManager implements INativeUwbManager.SessionNotification,
+ ActivityManager.OnUidImportanceListener {
private static final String TAG = "UwbSessionManager";
private static final byte OPERATION_TYPE_INIT_SESSION = 0;
@@ -194,34 +195,35 @@
return mIsRangeDataNtfConfigEnableDisableSupported;
}
+ @Override
+ public void onUidImportance(final int uid, final int importance) {
+ Handler handler = new Handler(mLooper);
+ handler.post(() -> {
+ List<UwbSession> uwbSessions = mNonPrivilegedUidToFiraSessionsTable.get(uid);
+ // Not a uid in the watch list
+ if (uwbSessions == null) return;
+ // Feature not supported on device.
+ if (!isRangeDataNtfConfigEnableDisableSupported()) return;
+ boolean newModeHasNonPrivilegedFgApp =
+ UwbInjector.isForegroundAppOrServiceImportance(importance);
+ for (UwbSession uwbSession : uwbSessions) {
+ // already at correct state.
+ if (newModeHasNonPrivilegedFgApp == uwbSession.hasNonPrivilegedFgApp()) {
+ continue;
+ }
+ uwbSession.setHasNonPrivilegedFgApp(newModeHasNonPrivilegedFgApp);
+ // Reconfigure the session based on the new fg/bg state.
+ Log.i(TAG, "App state change. IsFg: " + newModeHasNonPrivilegedFgApp
+ + ". Reconfiguring session ntf control");
+ uwbSession.reconfigureFiraSessionOnFgStateChange();
+ }
+ });
+ }
+
// Detect UIDs going foreground/background
private void registerUidImportanceTransitions() {
- Handler handler = new Handler(mLooper);
- mActivityManager.addOnUidImportanceListener(new ActivityManager.OnUidImportanceListener() {
- @Override
- public void onUidImportance(final int uid, final int importance) {
- handler.post(() -> {
- List<UwbSession> uwbSessions = mNonPrivilegedUidToFiraSessionsTable.get(uid);
- // Not a uid in the watch list
- if (uwbSessions == null) return;
- // Feature not supported on device.
- if (!isRangeDataNtfConfigEnableDisableSupported()) return;
- boolean newModeHasNonPrivilegedFgApp =
- UwbInjector.isForegroundAppOrServiceImportance(importance);
- for (UwbSession uwbSession : uwbSessions) {
- // already at correct state.
- if (newModeHasNonPrivilegedFgApp == uwbSession.hasNonPrivilegedFgApp()) {
- continue;
- }
- uwbSession.setHasNonPrivilegedFgApp(newModeHasNonPrivilegedFgApp);
- // Reconfigure the session based on the new fg/bg state.
- Log.i(TAG, "App state change. IsFg: " + newModeHasNonPrivilegedFgApp
- + ". Reconfiguring session ntf control");
- uwbSession.reconfigureFiraSessionOnFgStateChange();
- }
- });
- }
- }, IMPORTANCE_FOREGROUND_SERVICE);
+ mActivityManager.addOnUidImportanceListener(
+ UwbSessionManager.this, IMPORTANCE_FOREGROUND_SERVICE);
}
private static boolean hasAllRangingResultError(@NonNull UwbRangingData rangingData) {
@@ -435,7 +437,7 @@
AttributionSource nonPrivilegedAppAttrSource =
uwbSession.getAnyNonPrivilegedAppInAttributionSource();
if (nonPrivilegedAppAttrSource != null) {
- Log.d(TAG, "Found a non fg 3p app/service in the attribution source of request: "
+ Log.d(TAG, "Found a 3p app/service in the attribution source of request: "
+ nonPrivilegedAppAttrSource);
// TODO(b/211445008): Move this operation to uwb thread.
long identity = Binder.clearCallingIdentity();
@@ -1995,7 +1997,7 @@
+ " Stopping session");
stopRangingInternal(mSessionHandle, true /* triggeredBySystemPolicy */);
};
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mUwbInjector.getElapsedSinceBootMillis()
+ NON_PRIVILEGED_BG_APP_TIMEOUT_MS,
NON_PRIVILEGED_BG_APP_TIMER_TAG,
diff --git a/service/java/com/android/server/uwb/UwbShellCommand.java b/service/java/com/android/server/uwb/UwbShellCommand.java
index 72ad082..36cdb7a 100644
--- a/service/java/com/android/server/uwb/UwbShellCommand.java
+++ b/service/java/com/android/server/uwb/UwbShellCommand.java
@@ -16,6 +16,8 @@
package com.android.server.uwb;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.uwb.UwbAddress.SHORT_ADDRESS_BYTE_LENGTH;
import static com.google.uwb.support.ccc.CccParams.CHAPS_PER_SLOT_3;
@@ -54,6 +56,7 @@
import android.annotation.NonNull;
import android.content.AttributionSource;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -935,6 +938,21 @@
case "get-country-code":
pw.println("Uwb Country Code = " + mUwbCountryCode.getCountryCode());
return 0;
+ case "simulate-app-state-change": {
+ String appPackageName = getNextArgRequired();
+ boolean isFg = getNextArgRequiredTrueOrFalse("foreground", "background");
+ int uid = 0;
+ try {
+ uid = mContext.getPackageManager().getApplicationInfo(
+ appPackageName, 0).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Unable to find package name: " + appPackageName);
+ return -1;
+ }
+ mUwbInjector.getUwbSessionManager().onUidImportance(
+ uid, isFg ? IMPORTANCE_FOREGROUND : IMPORTANCE_BACKGROUND);
+ return 0;
+ }
case "set-log-mode": {
String logMode = getNextArgRequired();
if (!UciLogModeStore.isValid(logMode)) {
@@ -1167,6 +1185,8 @@
pw.println(" Disable vendor diagnostics notification");
pw.println(" take-bugreport");
pw.println(" take bugreport through betterBug or alternatively bugreport manager");
+ pw.println(" simulate-app-state-change <package-name> foreground|background");
+ pw.println(" Simulate app moving to foreground/background to test stack handling");
}
private void onHelpPrivileged(PrintWriter pw) {
diff --git a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java
index 3aef295..45e7480 100644
--- a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java
+++ b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java
@@ -1570,7 +1570,7 @@
// Verify the appropriate timer is setup.
ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
- verify(mAlarmManager).set(
+ verify(mAlarmManager).setExact(
anyInt(), anyLong(), eq(UwbSession.NON_PRIVILEGED_BG_APP_TIMER_TAG),
alarmListenerCaptor.capture(), any());
assertThat(alarmListenerCaptor.getValue()).isNotNull();
diff --git a/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_decorator.py b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_decorator.py
index 50711bb..8c2f137 100644
--- a/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_decorator.py
+++ b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_decorator.py
@@ -70,9 +70,9 @@
(ranging_event, round(time.time() - start_time, 2)))
self.clear_ranging_session_callback_events(session)
return
- except TimeoutError:
+ except errors.CallbackHandlerTimeoutError:
self.log.warn("Failed to receive 'RangingSessionCallback' event")
- raise TimeoutError("Failed to receive '%s' event" % ranging_event)
+ raise TimeoutError("Failed to receive '%s' event" % ranging_event)
def open_fira_ranging(self,
params: uwb_ranging_params.UwbRangingParams,
diff --git a/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py b/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py
index 9a2b806..af871f1 100644
--- a/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py
+++ b/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py
@@ -3,6 +3,8 @@
import logging
import random
import sys
+import time
+from threading import Thread
from typing import List
from mobly import asserts
@@ -33,6 +35,9 @@
"test_stop_responder_ranging_nearby_share_profile",
"test_ranging_device_tracker_profile_with_airplane_mode_toggle",
"test_ranging_nearby_share_profile_with_airplane_mode_toggle",
+ "test_ranging_default_params_move_to_bg_and_fg",
+ "test_ranging_default_params_move_to_bg_and_stay_there_stops_session",
+ "test_ranging_default_params_no_valid_reports_stops_session",
)
@@ -86,10 +91,17 @@
def teardown_test(self):
super().teardown_test()
- self.responder.stop_ranging()
- self.initiator.stop_ranging()
- self.responder.close_ranging()
- self.initiator.close_ranging()
+ try:
+ self.initiator.stop_ranging()
+ self.initiator.close_ranging()
+ except:
+ pass
+ try:
+ self.responder.stop_ranging()
+ self.responder.close_ranging()
+ except:
+ pass
+
### Helper Methods ###
@@ -280,6 +292,29 @@
initiator.reconfigure_fira_ranging(reconfigure_params)
uwb_test_utils.verify_peer_found(initiator, peer_addr)
+
+ @staticmethod
+ def _move_snippet_to_bg(device: uwb_ranging_decorator.UwbRangingDecorator):
+ """Simulate moving snippet app to background
+
+ Args:
+ device: The uwb device object.
+ """
+ device.ad.adb.shell(
+ ["cmd", "uwb", "simulate-app-state-change", "com.google.snippet.uwb", "background"])
+
+
+ @staticmethod
+ def _move_snippet_to_fg(device: uwb_ranging_decorator.UwbRangingDecorator):
+ """Simulate moving snippet app to foreground
+
+ Args:
+ device: The uwb device object.
+ """
+ device.ad.adb.shell(
+ ["cmd", "uwb", "simulate-app-state-change", "com.google.snippet.uwb", "foreground"])
+
+
### Test Cases ###
def test_ranging_default_params(self):
@@ -1065,6 +1100,142 @@
self.initiator, self.responder, initiator_params, responder_params,
self.responder_addr)
+ def test_ranging_default_params_move_to_bg_and_fg(self):
+ """
+ 1. Verifies ranging with default Fira parameters.
+ 2. Move app to background (turn screen off).
+ 3. Ensures the app does not receive range data notifications
+ 4. Move app to foreground (turn screen on).
+ 5. Ensures the app starts receiving range data notifications
+ """
+ initiator_params = uwb_ranging_params.UwbRangingParams(
+ device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR,
+ device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER,
+ device_address=self.initiator_addr,
+ destination_addresses=[self.responder_addr],
+ )
+ responder_params = uwb_ranging_params.UwbRangingParams(
+ device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER,
+ device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE,
+ device_address=self.responder_addr,
+ destination_addresses=[self.initiator_addr],
+ )
+ self._verify_one_to_one_ranging(self.initiator, self.responder,
+ initiator_params, responder_params,
+ self.responder_addr)
+
+ # Turn screen off to simulate app moving to background.
+ RangingTest._move_snippet_to_bg(self.initiator)
+ time.sleep(0.75)
+ self.initiator.clear_ranging_session_callback_events()
+ try:
+ self.initiator.verify_callback_received("ReportReceived")
+ except TimeoutError:
+ # Expect to get a timeout error
+ logging.info("Did not get any ranging reports as expected")
+ else:
+ asserts.fail("Should not receive ranging reports when the app is in background")
+
+ # Turn screen on to simulate app moving to foreground.
+ RangingTest._move_snippet_to_fg(self.initiator)
+ self.initiator.clear_ranging_session_callback_events()
+ try:
+ self.initiator.verify_callback_received("ReportReceived")
+ except TimeoutError:
+ asserts.fail("Should receive ranging reports when the app is in foreground")
+
+
+ def test_ranging_default_params_move_to_bg_and_stay_there_stops_session(self):
+ """
+ 1. Verifies ranging with default Fira parameters.
+ 2. Move app to background (turn screen off).
+ 3. Ensures the app does not receive range data notifications
+ 4. Remain in background.
+ 5. Ensures the session is stopped within 4 mins.
+ """
+ initiator_params = uwb_ranging_params.UwbRangingParams(
+ device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR,
+ device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER,
+ device_address=self.initiator_addr,
+ destination_addresses=[self.responder_addr],
+ )
+ responder_params = uwb_ranging_params.UwbRangingParams(
+ device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER,
+ device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE,
+ device_address=self.responder_addr,
+ destination_addresses=[self.initiator_addr],
+ )
+ self._verify_one_to_one_ranging(self.initiator, self.responder,
+ initiator_params, responder_params,
+ self.responder_addr)
+
+ # Turn screen off to simulate app moving to background.
+ RangingTest._move_snippet_to_bg(self.initiator)
+ time.sleep(0.75)
+ self.initiator.clear_ranging_session_callback_events()
+ try:
+ self.initiator.verify_callback_received("ReportReceived")
+ except TimeoutError:
+ # Expect to get a timeout error
+ logging.info("Did not get any ranging reports as expected")
+ else:
+ asserts.fail("Should not receive ranging reports when the app is in background")
+
+ # Wait for 4 mins
+ try:
+ self.initiator.verify_callback_received("Stopped", timeout=60*4)
+ except TimeoutError:
+ asserts.fail("Should receive ranging reports when the app is in foreground")
+
+
+ def test_ranging_default_params_no_valid_reports_stops_session(self):
+ """
+ 1. Verifies ranging with default Fira parameters.
+ 2. Reboot the initiator to abruptly terminate session and cause ranging report errors.
+ 3. Ensures the session is stopped within 2 mins.
+ """
+ initiator_params = uwb_ranging_params.UwbRangingParams(
+ device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR,
+ device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER,
+ device_address=self.initiator_addr,
+ destination_addresses=[self.responder_addr],
+ )
+ responder_params = uwb_ranging_params.UwbRangingParams(
+ device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER,
+ device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE,
+ device_address=self.responder_addr,
+ destination_addresses=[self.initiator_addr],
+ )
+ self._verify_one_to_one_ranging(self.initiator, self.responder,
+ initiator_params, responder_params,
+ self.responder_addr)
+
+ # Reboot responder and ensure peer is no longer seen in ranging reports
+ def reboot_responder():
+ self.responder.ad.reboot()
+ uwb_test_utils.initialize_uwb_country_code_if_not_set(self.responder.ad.adb)
+
+ # create a thread to reboot the responder and not block the main test.
+ thread = Thread(target=reboot_responder)
+ thread.start()
+
+ time.sleep(0.75)
+ self.initiator.clear_ranging_session_callback_events()
+ try:
+ uwb_test_utils.verify_peer_found(self.initiator, self.responder_addr)
+ asserts.fail("Peer found even though it was rebooted.")
+ except signals.TestFailure:
+ logging.info("Peer %s not found as expected", self.responder_addr)
+
+ # Wait for 2 mins to stop the session.
+ try:
+ self.initiator.verify_callback_received("Stopped", timeout=60*2)
+ except TimeoutError:
+ asserts.fail("Should receive ranging reports when the app is in foreground")
+ # Ensure the responder is back after reboot.
+ thread.join()
+
+
if __name__ == "__main__":
if "--" in sys.argv:
index = sys.argv.index("--")