Minor improvements on AdServicesManagerService and related classes:
- Added @GuardeBy annotations.
- Made collections final.
- Implemented Dumpable.
- Removed redundant unit test.
Test: adb shell dumpsys system_server_dumper --name AdServices # on UDC
Test: adb shell dumpsys sdk_sandbox --AdServices # on TM
Test: atest AdServicesManagerServiceTest UserInstanceManagerTest com.android.server.adservices.tests.TopicsDaoTest TopicsDbHelperTest SdkSandboxManagerServiceUnitTest
Bug: 280677793
Change-Id: I2da6527977d0188f5cc0616505280720a841d160
(cherry picked from commit d106cb6bfe3a9cc1b792a685ab27b377101f19e1)
diff --git a/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java b/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java
index ffe862f..a4c3bf0 100644
--- a/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java
+++ b/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java
@@ -41,7 +41,9 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
+import android.util.Dumpable;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
@@ -49,7 +51,9 @@
import com.android.server.adservices.feature.PrivacySandboxFeatureType;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -88,16 +92,30 @@
private final Context mContext;
+ @GuardedBy("mRegisterReceiverLock")
private BroadcastReceiver mSystemServicePackageChangedReceiver;
+
+ @GuardedBy("mRegisterReceiverLock")
private BroadcastReceiver mSystemServiceUserActionReceiver;
+ @GuardedBy("mRegisterReceiverLock")
private HandlerThread mHandlerThread;
+
+ @GuardedBy("mRegisterReceiverLock")
private Handler mHandler;
+ @GuardedBy("mSetPackageVersionLock")
private int mAdServicesModuleVersion;
+
+ @GuardedBy("mSetPackageVersionLock")
private String mAdServicesModuleName;
- private Map<Integer, VersionedPackage> mAdServicesPackagesRolledBackFrom = new ArrayMap<>();
- private Map<Integer, VersionedPackage> mAdServicesPackagesRolledBackTo = new ArrayMap<>();
+
+ @GuardedBy("mRollbackCheckLock")
+ private final Map<Integer, VersionedPackage> mAdServicesPackagesRolledBackFrom =
+ new ArrayMap<>();
+
+ @GuardedBy("mRollbackCheckLock")
+ private final Map<Integer, VersionedPackage> mAdServicesPackagesRolledBackTo = new ArrayMap<>();
// This will be triggered when there is a flag change.
private final DeviceConfig.OnPropertiesChangedListener mOnFlagsChangedListener =
@@ -128,8 +146,8 @@
}
/** @hide */
- public static class Lifecycle extends SystemService {
- private AdServicesManagerService mService;
+ public static final class Lifecycle extends SystemService implements Dumpable {
+ private final AdServicesManagerService mService;
/** @hide */
public Lifecycle(Context context) {
@@ -138,6 +156,7 @@
mService =
new AdServicesManagerService(
context, new UserInstanceManager(topicsDao, ADSERVICES_BASE_DIR));
+ LogUtil.d("AdServicesManagerService constructed!");
}
/** @hide */
@@ -162,6 +181,17 @@
"SdkSandboxManagerLocal not found when registering AdServicesManager!");
}
}
+
+ @Override
+ public String getDumpableName() {
+ return "AdServices";
+ }
+
+ @Override
+ public void dump(PrintWriter writer, String[] args) {
+ // Usage: adb shell dumpsys system_server_dumper --name AdServices
+ mService.dump(/* fd= */ null, writer, args);
+ }
}
@Override
@@ -761,6 +791,25 @@
}
}
+ @Override
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingPermission(android.Manifest.permission.DUMP, /* message= */ null);
+
+ synchronized (mSetPackageVersionLock) {
+ pw.printf("mAdServicesModuleName: %s\n", mAdServicesModuleName);
+ pw.printf("mAdServicesModuleVersion: %d\n", mAdServicesModuleVersion);
+ }
+ synchronized (mRegisterReceiverLock) {
+ pw.printf("mHandlerThread: %s\n", mHandlerThread);
+ }
+ synchronized (mRollbackCheckLock) {
+ pw.printf("mAdServicesPackagesRolledBackFrom: %s\n", mAdServicesPackagesRolledBackFrom);
+ pw.printf("mAdServicesPackagesRolledBackTo: %s\n", mAdServicesPackagesRolledBackTo);
+ }
+ mUserInstanceManager.dump(pw, args);
+ }
+
@RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_MANAGER)
public void recordAdServicesDeletionOccurred(
@AdServicesManager.DeletionApiType int deletionType) {
@@ -939,16 +988,14 @@
synchronized (mRollbackCheckLock) {
if (!FlagsFactory.getFlags().getAdServicesSystemServiceEnabled()) {
LogUtil.d("AdServicesSystemServiceEnabled is FALSE.");
- mAdServicesPackagesRolledBackFrom = new ArrayMap<>();
- mAdServicesPackagesRolledBackTo = new ArrayMap<>();
+ resetRollbackArraysRCLocked();
return;
}
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
if (rollbackManager == null) {
LogUtil.d("Failed to get the RollbackManager service.");
- mAdServicesPackagesRolledBackFrom = new ArrayMap<>();
- mAdServicesPackagesRolledBackTo = new ArrayMap<>();
+ resetRollbackArraysRCLocked();
return;
}
List<RollbackInfo> recentlyCommittedRollbacks =
@@ -974,6 +1021,12 @@
}
}
+ @GuardedBy("mRollbackCheckLock")
+ private void resetRollbackArraysRCLocked() {
+ mAdServicesPackagesRolledBackFrom.clear();
+ mAdServicesPackagesRolledBackTo.clear();
+ }
+
@VisibleForTesting
Map<Integer, VersionedPackage> getAdServicesPackagesRolledBackFrom() {
return mAdServicesPackagesRolledBackFrom;
diff --git a/adservices/service/java/com/android/server/adservices/UserInstanceManager.java b/adservices/service/java/com/android/server/adservices/UserInstanceManager.java
index 6903efa..fe7d478 100644
--- a/adservices/service/java/com/android/server/adservices/UserInstanceManager.java
+++ b/adservices/service/java/com/android/server/adservices/UserInstanceManager.java
@@ -28,6 +28,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -153,6 +154,27 @@
}
}
+ void dump(PrintWriter writer, String[] args) {
+ writer.println("UserInstanceManager");
+ String prefix = " ";
+ writer.printf("%smAdServicesBaseDir: %s\n", prefix, mAdServicesBaseDir);
+ synchronized (mLock) {
+ writer.printf("%smConsentManagerMapLocked: %s\n", prefix, mConsentManagerMapLocked);
+ writer.printf(
+ "%smAppConsentManagerMapLocked: %s\n", prefix, mAppConsentManagerMapLocked);
+ writer.printf(
+ "%smRollbackHandlingManagerMapLocked: %s\n",
+ prefix, mRollbackHandlingManagerMapLocked);
+ }
+ synchronized (UserInstanceManager.class) {
+ writer.printf(
+ "%smBlockedTopicsManagerMapLocked=%s\n",
+ prefix, mBlockedTopicsManagerMapLocked);
+ }
+
+ mTopicsDao.dump(writer, prefix, args);
+ }
+
@VisibleForTesting
void tearDownForTesting() {
synchronized (mLock) {
diff --git a/adservices/service/java/com/android/server/adservices/data/topics/TopicsDao.java b/adservices/service/java/com/android/server/adservices/data/topics/TopicsDao.java
index 36ce77f..c6741e1 100644
--- a/adservices/service/java/com/android/server/adservices/data/topics/TopicsDao.java
+++ b/adservices/service/java/com/android/server/adservices/data/topics/TopicsDao.java
@@ -29,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.adservices.LogUtil;
+import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -204,4 +205,9 @@
LogUtil.e("Failed to remove all blocked topics." + e.getMessage());
}
}
+
+ /** Dumps its internal state. */
+ public void dump(PrintWriter writer, String prefix, String[] args) {
+ mTopicsDbHelper.dump(writer, prefix, args);
+ }
}
diff --git a/adservices/service/java/com/android/server/adservices/data/topics/TopicsDbHelper.java b/adservices/service/java/com/android/server/adservices/data/topics/TopicsDbHelper.java
index 25f9e44..174aa60 100644
--- a/adservices/service/java/com/android/server/adservices/data/topics/TopicsDbHelper.java
+++ b/adservices/service/java/com/android/server/adservices/data/topics/TopicsDbHelper.java
@@ -27,6 +27,7 @@
import com.android.server.adservices.LogUtil;
import java.io.File;
+import java.io.PrintWriter;
/**
* Helper to manage the Topics API system service database. Designed as a singleton to make sure
@@ -115,4 +116,13 @@
return null;
}
}
+
+ /** Dumps its internal state. */
+ public void dump(PrintWriter writer, String prefix, String[] args) {
+ writer.printf("%sTopicsDbHelper\n", prefix);
+ String prefix2 = prefix + " ";
+ writer.printf("%sCURRENT_DATABASE_VERSION: %d\n", prefix2, CURRENT_DATABASE_VERSION);
+ writer.printf("%smDbFile: %s\n", prefix2, mDbFile);
+ writer.printf("%smDbVersion: %d\n", prefix2, mDbVersion);
+ }
}
diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java
index f169982..de7adba 100644
--- a/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java
+++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java
@@ -19,11 +19,14 @@
import static com.android.server.adservices.PhFlags.KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -71,6 +74,8 @@
import org.mockito.MockitoAnnotations;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -108,6 +113,7 @@
private static final int TEST_ROLLED_BACK_FROM_MODULE_VERSION = 339990000;
private static final int TEST_ROLLED_BACK_TO_MODULE_VERSION = 330000000;
private static final int ROLLBACK_ID = 1768705420;
+ private static final String USER_INSTANCE_MANAGER_DUMP = "D'OHump!";
@Before
public void setup() {
@@ -119,7 +125,13 @@
TopicsDao topicsDao = new TopicsDao(mDBHelper);
mUserInstanceManager =
new UserInstanceManager(
- topicsDao, /* adservicesBaseDir */ context.getFilesDir().getAbsolutePath());
+ topicsDao,
+ /* adServicesBaseDir= */ context.getFilesDir().getAbsolutePath()) {
+ @Override
+ public void dump(PrintWriter writer, String[] args) {
+ writer.println(USER_INSTANCE_MANAGER_DUMP);
+ }
+ };
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
@@ -947,6 +959,36 @@
.isEqualTo(PrivacySandboxFeatureType.PRIVACY_SANDBOX_UNSUPPORTED.name());
}
+ @Test
+ public void testDump_noPermission() throws Exception {
+ mService = new AdServicesManagerService(mSpyContext, mUserInstanceManager);
+
+ assertThrows(
+ SecurityException.class,
+ () -> mService.dump(/* fd= */ null, /* pw= */ null, /* args= */ null));
+ }
+
+ @Test
+ public void testDump() throws Exception {
+ doNothing()
+ .when(mSpyContext)
+ .enforceCallingPermission(eq(android.Manifest.permission.DUMP), isNull());
+ mService = new AdServicesManagerService(mSpyContext, mUserInstanceManager);
+
+ String dump;
+ try (StringWriter sw = new StringWriter()) {
+ PrintWriter pw = new PrintWriter(sw);
+
+ mService.dump(/* fd= */ null, pw, /* args= */ null);
+
+ pw.flush();
+ dump = sw.toString();
+ }
+ // Content doesn't matter much, we just wanna make sure it doesn't crash (for example,
+ // by using the wrong %s / %d tokens) and that its components are dumped
+ assertWithMessage("content of dump()").that(dump).contains(USER_INSTANCE_MANAGER_DUMP);
+ }
+
// Mock the call to get the AdServices module version from the PackageManager.
private void setAdServicesModuleVersion(AdServicesManagerService service, int version) {
doReturn(version).when(service).getAdServicesApexVersion();
diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/UserInstanceManagerTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/UserInstanceManagerTest.java
index 14f9cb0..403afef 100644
--- a/adservices/tests/unittest/system-service/src/com/android/server/adservices/UserInstanceManagerTest.java
+++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/UserInstanceManagerTest.java
@@ -19,6 +19,7 @@
import static com.android.server.adservices.data.topics.TopicsTables.DUMMY_MODEL_VERSION;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.adservices.topics.Topic;
import android.app.adservices.topics.TopicParcel;
@@ -39,6 +40,8 @@
import org.junit.Test;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.List;
import java.util.Set;
@@ -47,6 +50,8 @@
private static final Context APPLICATION_CONTEXT = ApplicationProvider.getApplicationContext();
private static final String TEST_BASE_PATH =
APPLICATION_CONTEXT.getFilesDir().getAbsolutePath();
+ private static final String TOPICS_DAO_DUMP = "D'OHump!";
+
private final TopicsDbHelper mDBHelper = TopicsDbTestUtil.getDbHelperForTest();
private TopicsDao mTopicsDao;
@@ -229,4 +234,31 @@
assertThat(blockedTopics1).hasSize(1);
assertThat(blockedTopics1).containsExactly(topicToBlock1);
}
+
+ @Test
+ public void testDump() throws Exception {
+ TopicsDao topicsDao =
+ new TopicsDao(mDBHelper) {
+ @Override
+ public void dump(PrintWriter writer, String prefix, String[] args) {
+ writer.println(TOPICS_DAO_DUMP);
+ }
+ };
+ UserInstanceManager mgr = new UserInstanceManager(topicsDao, TEST_BASE_PATH);
+
+ String dump;
+ String[] args = new String[0];
+ try (StringWriter sw = new StringWriter()) {
+ PrintWriter pw = new PrintWriter(sw);
+
+ mgr.dump(pw, args);
+
+ pw.flush();
+ dump = sw.toString();
+ }
+
+ // Content doesn't matter much, we just wanna make sure it doesn't crash (for example,
+ // by using the wrong %s / %d tokens) and that its components are dumped
+ assertWithMessage("content of dump()").that(dump).contains(TOPICS_DAO_DUMP);
+ }
}
diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDaoTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDaoTest.java
index db25b32..d4acd60 100644
--- a/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDaoTest.java
+++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDaoTest.java
@@ -20,6 +20,9 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
import android.adservices.topics.Topic;
import org.junit.After;
@@ -27,6 +30,7 @@
import org.junit.Test;
import org.mockito.MockitoAnnotations;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Set;
@@ -176,4 +180,19 @@
assertThat(blockedTopics1).hasSize(1);
assertThat(blockedTopics1).containsExactly(topicToBlock1);
}
+
+ @Test
+ public void testDump() {
+ // Currently, TopicsDao just delegates to helper, so we're being pragmactic and just
+ // testing that
+ PrintWriter writer = mock(PrintWriter.class);
+ String prefix = "TOPICS DAO";
+ String[] args = new String[] {"Y", "U", "NO", "FAKE?"};
+ TopicsDbHelper mockTopicsDbHelper = mock(TopicsDbHelper.class);
+ TopicsDao topicsDao = new TopicsDao(mockTopicsDbHelper);
+
+ topicsDao.dump(writer, prefix, args);
+
+ verify(mockTopicsDbHelper).dump(writer, prefix, args);
+ }
}
diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDbHelperTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDbHelperTest.java
index 3700974..0532626 100644
--- a/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDbHelperTest.java
+++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/data/topics/TopicsDbHelperTest.java
@@ -18,13 +18,23 @@
import static com.android.server.adservices.data.topics.TopicsDbTestUtil.doesTableExistAndColumnCountMatch;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.database.sqlite.SQLiteDatabase;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import org.junit.Test;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
/** Unit test to test class {@link TopicsDbHelper} */
public class TopicsDbHelperTest {
@Test
@@ -33,4 +43,49 @@
assertNotNull(db);
assertTrue(doesTableExistAndColumnCountMatch(db, "blocked_topics", 4));
}
+
+ @Test
+ public void testDump() throws Exception {
+ String dump;
+ String prefix = "fixed, pre is:";
+ try (StringWriter sw = new StringWriter()) {
+ PrintWriter pw = new PrintWriter(sw);
+
+ TopicsDbHelper dao =
+ TopicsDbHelper.getInstance(
+ InstrumentationRegistry.getInstrumentation().getTargetContext());
+ dao.dump(pw, prefix, /* args= */ null);
+
+ pw.flush();
+ dump = sw.toString();
+ }
+
+ // Content doesn't matter much, we just wanna make sure it doesn't crash (for example,
+ // by using the wrong %s / %d tokens) and every line dumps the prefix
+ assertDumpHasPrefix(dump, prefix);
+ }
+
+ // TODO(b/280677793): move to common code?
+ static String[] assertDumpHasPrefix(String dump, String prefix) {
+ assertWithMessage("content of dump()").that(dump).isNotEmpty();
+
+ String[] lines = dump.split("\n");
+ List<Integer> violatedLineNumbers = new ArrayList<>();
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i];
+ if (!line.startsWith(prefix)) {
+ violatedLineNumbers.add(i);
+ }
+ }
+ if (!violatedLineNumbers.isEmpty()) {
+ fail(
+ "Every line should start with '"
+ + prefix
+ + "', but some ("
+ + violatedLineNumbers
+ + ") did not. Full dump(): \n"
+ + dump);
+ }
+ return lines;
+ }
}
diff --git a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
index 664879b..8f4e945 100644
--- a/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
+++ b/sdksandbox/service/java/com/android/server/sdksandbox/SdkSandboxManagerService.java
@@ -33,6 +33,7 @@
import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.WorkerThread;
import android.app.ActivityManager;
@@ -122,6 +123,8 @@
private static final String SANDBOX_NOT_AVAILABLE_MSG = "Sandbox is unavailable";
private static final String SANDBOX_DISABLED_MSG = "SDK sandbox is disabled";
+ private static final String DUMP_ARG_AD_SERVICES = "--AdServices";
+
private final Context mContext;
private final ActivityManager mActivityManager;
@@ -227,6 +230,13 @@
private static final String WEBVIEW_SAFE_MODE_CONTENT_PROVIDER = "SafeModeContentProvider";
+ // On UDC, AdServicesManagerService.Lifecycle implements dumpable so it's dumped as part of
+ // SystemServer.
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static final String POST_UDC_DUMP_AD_SERVICES_MESSAGE =
+ "Don't need to dump AdServices on UDC+ - use "
+ + "'dumpsys system_server_dumper --name AdServices instead'";
+
static class Injector {
private final Context mContext;
private SdkSandboxManagerLocal mLocalManager;
@@ -1163,6 +1173,11 @@
mContext.enforceCallingPermission(android.Manifest.permission.DUMP,
"Can't dump " + TAG);
+ if (args != null && args.length > 0 && args[0].equals(DUMP_ARG_AD_SERVICES)) {
+ dumpAdServices(fd, writer, args, /* quiet= */ false);
+ return;
+ }
+
// TODO(b/211575098): Use IndentingPrintWriter for better formatting
synchronized (mLock) {
writer.println("Checked Webview visibility patch exists: " + mCheckedVisibilityPatch);
@@ -1192,6 +1207,41 @@
writer.println("mServiceProvider:");
mServiceProvider.dump(writer);
writer.println();
+
+ dumpAdServices(fd, writer, args, /* quiet= */ true);
+ }
+
+ private void dumpAdServices(
+ @Nullable FileDescriptor fd, PrintWriter writer, String[] args, boolean quiet) {
+ if (SdkLevel.isAtLeastU()) {
+ // On UDC, AdServicesManagerService.Lifecycle implements dumpable so it's dumped as
+ // part of SystemServer, hence this special arg option is not needed
+ if (quiet) {
+ Log.d(TAG, POST_UDC_DUMP_AD_SERVICES_MESSAGE);
+ } else {
+ writer.println(POST_UDC_DUMP_AD_SERVICES_MESSAGE);
+ }
+ return;
+ }
+ writer.print("AdServices:");
+ IBinder adServicesManager = getAdServicesManager();
+ if (adServicesManager == null) {
+ // Should not happen on "real life", but it could on unit tests.
+ Log.e(TAG, "dumpAdServices(): mAdServicesManager not set");
+ writer.println(" N/A");
+ return;
+ }
+ writer.println();
+ writer.println();
+ writer.flush(); // must flush, other raw dump on fd below will be printed before it
+ try {
+ adServicesManager.dump(fd, args);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to dump AdServices", e);
+ // Shouldn't happen, but it doesn't hurt to catch
+ writer.printf("Failed to dump Adservices: %s\n", e);
+ }
+ writer.println();
}
@Override
@@ -1522,7 +1572,8 @@
}
}
- private void registerAdServicesManagerService(IBinder iBinder) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ void registerAdServicesManagerService(IBinder iBinder) {
synchronized (mLock) {
mAdServicesManager = iBinder;
}
diff --git a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
index 2087c02..15af3bd 100644
--- a/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
+++ b/sdksandbox/tests/unittest/src/com/android/server/sdksandbox/SdkSandboxManagerServiceUnitTest.java
@@ -16,6 +16,7 @@
package com.android.server.sdksandbox;
+import static android.Manifest.permission.DUMP;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.sdksandbox.ISharedPreferencesSyncCallback.PREFERENCES_SYNC_INTERNAL_ERROR;
import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
@@ -36,6 +37,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
@@ -76,6 +78,7 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -102,6 +105,7 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
@@ -120,10 +124,12 @@
*/
public class SdkSandboxManagerServiceUnitTest {
+ private static final String TAG = SdkSandboxManagerServiceUnitTest.class.getSimpleName();
+
private SdkSandboxManagerService mService;
private ActivityManager mAmSpy;
private FakeSdkSandboxService mSdkSandboxService;
- private MockitoSession mStaticMockSession = null;
+ private MockitoSession mStaticMockSession;
private Context mSpyContext;
private SdkSandboxManagerService.Injector mInjector;
private int mClientAppUid;
@@ -133,6 +139,8 @@
ArgumentCaptor.forClass(ActivityInterceptorCallback.class);
private SdkSandboxStorageManagerUtility mSdkSandboxStorageManagerUtility;
+ @Mock private IBinder mAdServicesManager;
+
private static FakeSdkSandboxProvider sProvider;
private static SdkSandboxPulledAtoms sSdkSandboxPulledAtoms;
@@ -185,7 +193,8 @@
ExtendedMockito.mockitoSession()
.mockStatic(LocalManagerRegistry.class)
.mockStatic(SdkSandboxStatsLog.class)
- .spyStatic(Process.class);
+ .spyStatic(Process.class)
+ .initMocks(this);
if (SdkLevel.isAtLeastU()) {
mockitoSessionBuilder =
mockitoSessionBuilder.mockStatic(ActivityInterceptorCallbackRegistry.class);
@@ -1000,11 +1009,6 @@
assertThat(lifecycleCallback2.getSdkSandboxDeathCount()).isEqualTo(1);
}
- @Test(expected = SecurityException.class)
- public void testDumpWithoutPermission() {
- mService.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]);
- }
-
@Test
public void testSdkSandboxServiceUnbindingWhenAppDied() throws Exception {
disableKillUid();
@@ -1713,15 +1717,100 @@
}
@Test
- public void testDump() throws Exception {
- Mockito.doNothing()
- .when(mSpyContext)
- .enforceCallingPermission(
- Mockito.eq("android.permission.DUMP"), Mockito.anyString());
+ public void testDump_preU() throws Exception {
+ requiresAtLeastU(false);
+ mockGrantedPermission(DUMP);
+ mService.registerAdServicesManagerService(mAdServicesManager);
- final StringWriter stringWriter = new StringWriter();
- mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), null);
- assertThat(stringWriter.toString()).contains("FakeDump");
+ String dump;
+ try (StringWriter stringWriter = new StringWriter()) {
+ // Mock call to mAdServicesManager.dump();
+ FileDescriptor fd = new FileDescriptor();
+ PrintWriter writer = new PrintWriter(stringWriter);
+ String[] args = new String[0];
+ Mockito.doAnswer(
+ (inv) -> {
+ writer.println("FakeAdServiceDump");
+ return null;
+ })
+ .when(mAdServicesManager)
+ .dump(fd, args);
+
+ mService.dump(fd, writer, args);
+
+ dump = stringWriter.toString();
+ }
+
+ assertThat(dump).contains("FakeDump");
+ assertThat(dump).contains("FakeAdServiceDump");
+ }
+
+ @Test
+ public void testDump_atLeastU() throws Exception {
+ requiresAtLeastU(true);
+ mockGrantedPermission(DUMP);
+ mService.registerAdServicesManagerService(mAdServicesManager);
+
+ String dump;
+ try (StringWriter stringWriter = new StringWriter()) {
+ mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+ dump = stringWriter.toString();
+ }
+
+ assertThat(dump).contains("FakeDump");
+
+ Mockito.verify(mAdServicesManager, Mockito.never())
+ .dump(ArgumentMatchers.any(), ArgumentMatchers.any());
+ }
+
+ @Test
+ public void testDump_adServices_preU() throws Exception {
+ requiresAtLeastU(false);
+ mockGrantedPermission(DUMP);
+ mService.registerAdServicesManagerService(mAdServicesManager);
+
+ String dump;
+ try (StringWriter stringWriter = new StringWriter()) {
+ // Mock call to mAdServicesManager.dump();
+ FileDescriptor fd = new FileDescriptor();
+ PrintWriter writer = new PrintWriter(stringWriter);
+ String[] args = new String[] {"--AdServices"};
+ Mockito.doAnswer(
+ (inv) -> {
+ writer.println("FakeAdServiceDump");
+ return null;
+ })
+ .when(mAdServicesManager)
+ .dump(fd, args);
+
+ mService.dump(fd, writer, args);
+
+ dump = stringWriter.toString();
+ }
+
+ assertThat(dump).isEqualTo("AdServices:\n\nFakeAdServiceDump\n\n");
+ }
+
+ @Test
+ public void testDump_adServices_atLeastU() throws Exception {
+ requiresAtLeastU(true);
+ mockGrantedPermission(DUMP);
+ mService.registerAdServicesManagerService(mAdServicesManager);
+
+ String dump;
+ try (StringWriter stringWriter = new StringWriter()) {
+ mService.dump(
+ new FileDescriptor(),
+ new PrintWriter(stringWriter),
+ new String[] {"--AdServices"});
+ dump = stringWriter.toString();
+ }
+
+ assertThat(dump)
+ .isEqualTo(SdkSandboxManagerService.POST_UDC_DUMP_AD_SERVICES_MESSAGE + "\n");
+
+ Mockito.verify(mAdServicesManager, Mockito.never())
+ .dump(ArgumentMatchers.any(), ArgumentMatchers.any());
}
@Test(expected = SecurityException.class)
@@ -3356,6 +3445,29 @@
return mSpyContext.getPackageManager().getSdkSandboxPackageName();
}
+ private void mockGrantedPermission(String permission) {
+ Log.d(TAG, "mockGrantedPermission(" + permission + ")");
+ Mockito.doNothing()
+ .when(mSpyContext)
+ .enforceCallingPermission(Mockito.eq(permission), Mockito.anyString());
+ }
+
+ private void requiresAtLeastU(boolean required) {
+ Log.d(
+ TAG,
+ "requireAtLeastU("
+ + required
+ + "): SdkLevel.isAtLeastU()="
+ + SdkLevel.isAtLeastU());
+ // TODO(b/280677793): rather than assuming it's the given version, mock it:
+ // ExtendedMockito.doReturn(required).when(() -> SdkLevel.isAtLeastU());
+ if (required) {
+ assumeTrue("Device must be at least U", SdkLevel.isAtLeastU());
+ } else {
+ assumeFalse("Device must be less than U", SdkLevel.isAtLeastU());
+ }
+ }
+
/** Fake service provider that returns local instance of {@link SdkSandboxServiceProvider} */
private static class FakeSdkSandboxProvider implements SdkSandboxServiceProvider {
private FakeSdkSandboxService mSdkSandboxService;