diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp index 24257eb569..282ca412e4 100644 --- a/staticlibs/testutils/Android.bp +++ b/staticlibs/testutils/Android.bp @@ -27,12 +27,15 @@ java_library { "framework-connectivity-test-defaults", ], libs: [ + // TODO: is there a way to subclass MockContentResolver without depending on the impl? + "android.test.mock.impl", "androidx.annotation_annotation", "net-utils-device-common-bpf", // TestBpfMap extends IBpfMap. ], static_libs: [ "androidx.test.ext.junit", "collector-device-lib", + "frameworks-base-testutils", // For FakeSettingsProvider. "kotlin-reflect", "libnanohttpd", "mockito-target-minus-junit4", diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ContentResolverWithFakeSettingsProvider.kt b/staticlibs/testutils/devicetests/com/android/testutils/ContentResolverWithFakeSettingsProvider.kt new file mode 100644 index 0000000000..4eff04071c --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/ContentResolverWithFakeSettingsProvider.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2025 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.testutils + +import android.database.ContentObserver +import android.net.Uri +import android.os.Process +import android.os.UserHandle +import android.provider.Settings +import android.test.mock.MockContentResolver +import android.util.ArrayMap +import android.util.Log +import android.util.Pair +import com.android.internal.util.test.FakeSettingsProvider +import java.util.Collections + +private const val TAG = "CRWithFakeSettings" + +class ContentResolverWithFakeSettingsProvider : MockContentResolver() { + private val observers = ArrayMap>() + + init { + addProvider(Settings.AUTHORITY, FakeSettingsProvider({ userId: Int, uri: Uri -> + val p = observers.get(uri) + if (p == null) return@FakeSettingsProvider + val observer = p.first + val callbackUser = p.second + val user = UserHandle(userId) + val currentUser = UserHandle.getUserHandleForUid(Process.myUid()) + if (callbackUser == UserHandle.ALL || + UserHandle.CURRENT.equals(callbackUser) && currentUser.equals(user) || + callbackUser.equals(user) + ) { + Log.d(TAG, "Notifying change in $uri") + observer.onChange(false, Collections.singletonList(uri), 0, user) + } + })) + } + + /** + * Registers a ContentObserver that gets callbacks when the given URI changes. + * Note: this method does not override any actual ContentResolver methods - they are all final. + */ + fun registerContentObserver(uri: Uri, observer: ContentObserver) { + if (observers.containsKey(uri)) { + throw IllegalStateException("Duplicate ContentObserver for URI: $uri") + } + observers.put(uri, Pair(observer, UserHandle.CURRENT)) + } + + /** + * Registers a ContentObserver that gets callbacks when the given URI changes. + * Note: this method does not override any actual ContentResolver methods - they are all final. + */ + fun registerContentObserverAsUser(uri: Uri, observer: ContentObserver, userHandle: UserHandle) { + if (observers.containsKey(uri)) { + throw IllegalStateException("Duplicate ContentObserver for URI: $uri") + } + observers.put(uri, Pair(observer, userHandle)) + } +}