Loading src/com/android/settings/supervision/SupervisionHelper.kt +24 −1 Original line number Diff line number Diff line Loading @@ -16,14 +16,16 @@ package com.android.settings.supervision import android.app.KeyguardManager import android.app.role.RoleManager import android.content.Context import android.os.UserHandle import android.os.UserManager import android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING import android.util.Log import androidx.annotation.VisibleForTesting /** Convenience methods for interacting with the supervising user profile. */ open class SupervisionHelper private constructor(context: Context) { open class SupervisionHelper private constructor(private val context: Context) { private val mUserManager = context.getSystemService(UserManager::class.java) private val mKeyguardManager = context.getSystemService(KeyguardManager::class.java) Loading @@ -41,7 +43,28 @@ open class SupervisionHelper private constructor(context: Context) { return mKeyguardManager?.isDeviceSecure(supervisingUserId) ?: false } /** * Retrieves the package name of the system supervision app. * * @return The package name of the system supervision app, or null if not found. */ fun getSupervisionPackageName(): String? { val roleManager = context.getSystemService(RoleManager::class.java) if (roleManager == null) { Log.w(TAG, "RoleManager service not available.") return null } val roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) if (roleHolders.isEmpty()) Log.w(TAG, "No package holding the system supervision role.") // supervision role is exclusive, only one app may hold this role in a user return roleHolders.firstOrNull() } companion object { private const val TAG = "SupervisionSettings" @Volatile @VisibleForTesting var sInstance: SupervisionHelper? = null fun getInstance(context: Context): SupervisionHelper { Loading src/com/android/settings/supervision/ipc/SupervisionMessengerClient.kt +10 −9 Original line number Diff line number Diff line Loading @@ -18,9 +18,10 @@ package com.android.settings.supervision.ipc import android.content.Context import android.content.Intent import android.util.Log import com.android.internal.R import com.android.settings.supervision.PreferenceDataProvider import com.android.settings.supervision.SupervisionHelper import com.android.settingslib.ipc.MessengerServiceClient import com.android.settingslib.supervision.SupervisionLog /** * A specialized [MessengerServiceClient] for interacting with the system supervision service. Loading @@ -34,14 +35,13 @@ import com.android.settingslib.ipc.MessengerServiceClient class SupervisionMessengerClient(context: Context) : MessengerServiceClient(context), PreferenceDataProvider { // TODO(b/399497788): get the package name from RoleManager and do pre-check before calling the // messenger service private val supervisionPackageName: String = context.getResources().getString(R.string.config_systemSupervision) override val serviceIntentFactory: () -> Intent get() = { Intent(SUPERVISION_MESSENGER_SERVICE_BIND_ACTION) } private val supervisionPackageName: String? by lazy { SupervisionHelper.getInstance(context).getSupervisionPackageName() } /** * Retrieves preference data from the system supervision app. * Loading @@ -55,15 +55,16 @@ class SupervisionMessengerClient(context: Context) : */ override suspend fun getPreferenceData(keys: List<String>): Map<String, PreferenceData> = try { invoke(supervisionPackageName, PreferenceDataApi(), PreferenceDataRequest(keys = keys)) val targetPackageName = supervisionPackageName ?: return mapOf() invoke(targetPackageName, PreferenceDataApi(), PreferenceDataRequest(keys = keys)) .await() } catch (e: Exception) { Log.e(TAG, "Error fetching Preference data from supervision app", e) Log.e(SupervisionLog.TAG, "Error fetching Preference data from supervision app", e) mapOf() } companion object { private const val TAG = "SupervisionMessengerClient" private const val SUPERVISION_MESSENGER_SERVICE_BIND_ACTION = "android.app.supervision.action.SUPERVISION_MESSENGER_SERVICE" } Loading tests/robotests/src/com/android/settings/supervision/SupervisionHelperTest.kt 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * 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.settings.supervision import android.app.role.RoleManager import android.content.Context import android.content.ContextWrapper import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 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 import org.mockito.kotlin.mock import org.mockito.kotlin.stub import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class SupervisionHelperTest { private lateinit var context: Context private val mockRoleManager = mock<RoleManager>() private lateinit var supervisionHelper: SupervisionHelper @Before fun setUp() { context = contextOfRoleManager(mockRoleManager) SupervisionHelper.sInstance = null } @Test fun getSupervisionPackageName_roleManagerReturnsPackageName_shouldReturnPackageName() { val testPackageName = "com.android.supervision" mockRoleManager.stub { on { getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } doReturn listOf(testPackageName) } supervisionHelper = SupervisionHelper.getInstance(context) val result = supervisionHelper.getSupervisionPackageName() assertThat(result).isEqualTo(testPackageName) verify(mockRoleManager).getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } @Test fun getSupervisionPackageName_roleManagerReturnsEmptyList_shouldReturnNull() { mockRoleManager.stub { on { getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } doReturn emptyList() } supervisionHelper = SupervisionHelper.getInstance(context) val result = supervisionHelper.getSupervisionPackageName() assertThat(result).isNull() verify(mockRoleManager).getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } @Test fun getSupervisionPackageName_roleManagerReturnsNull_shouldReturnNull() { context = contextOfRoleManager(null) supervisionHelper = SupervisionHelper.getInstance(context) val result = supervisionHelper.getSupervisionPackageName() assertThat(result).isNull() } private fun contextOfRoleManager(roleManager: RoleManager?): Context { return object : ContextWrapper(ApplicationProvider.getApplicationContext()) { override fun getSystemService(name: String): Any? = when (name) { ROLE_SERVICE -> roleManager else -> super.getSystemService(name) } } } } Loading
src/com/android/settings/supervision/SupervisionHelper.kt +24 −1 Original line number Diff line number Diff line Loading @@ -16,14 +16,16 @@ package com.android.settings.supervision import android.app.KeyguardManager import android.app.role.RoleManager import android.content.Context import android.os.UserHandle import android.os.UserManager import android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING import android.util.Log import androidx.annotation.VisibleForTesting /** Convenience methods for interacting with the supervising user profile. */ open class SupervisionHelper private constructor(context: Context) { open class SupervisionHelper private constructor(private val context: Context) { private val mUserManager = context.getSystemService(UserManager::class.java) private val mKeyguardManager = context.getSystemService(KeyguardManager::class.java) Loading @@ -41,7 +43,28 @@ open class SupervisionHelper private constructor(context: Context) { return mKeyguardManager?.isDeviceSecure(supervisingUserId) ?: false } /** * Retrieves the package name of the system supervision app. * * @return The package name of the system supervision app, or null if not found. */ fun getSupervisionPackageName(): String? { val roleManager = context.getSystemService(RoleManager::class.java) if (roleManager == null) { Log.w(TAG, "RoleManager service not available.") return null } val roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) if (roleHolders.isEmpty()) Log.w(TAG, "No package holding the system supervision role.") // supervision role is exclusive, only one app may hold this role in a user return roleHolders.firstOrNull() } companion object { private const val TAG = "SupervisionSettings" @Volatile @VisibleForTesting var sInstance: SupervisionHelper? = null fun getInstance(context: Context): SupervisionHelper { Loading
src/com/android/settings/supervision/ipc/SupervisionMessengerClient.kt +10 −9 Original line number Diff line number Diff line Loading @@ -18,9 +18,10 @@ package com.android.settings.supervision.ipc import android.content.Context import android.content.Intent import android.util.Log import com.android.internal.R import com.android.settings.supervision.PreferenceDataProvider import com.android.settings.supervision.SupervisionHelper import com.android.settingslib.ipc.MessengerServiceClient import com.android.settingslib.supervision.SupervisionLog /** * A specialized [MessengerServiceClient] for interacting with the system supervision service. Loading @@ -34,14 +35,13 @@ import com.android.settingslib.ipc.MessengerServiceClient class SupervisionMessengerClient(context: Context) : MessengerServiceClient(context), PreferenceDataProvider { // TODO(b/399497788): get the package name from RoleManager and do pre-check before calling the // messenger service private val supervisionPackageName: String = context.getResources().getString(R.string.config_systemSupervision) override val serviceIntentFactory: () -> Intent get() = { Intent(SUPERVISION_MESSENGER_SERVICE_BIND_ACTION) } private val supervisionPackageName: String? by lazy { SupervisionHelper.getInstance(context).getSupervisionPackageName() } /** * Retrieves preference data from the system supervision app. * Loading @@ -55,15 +55,16 @@ class SupervisionMessengerClient(context: Context) : */ override suspend fun getPreferenceData(keys: List<String>): Map<String, PreferenceData> = try { invoke(supervisionPackageName, PreferenceDataApi(), PreferenceDataRequest(keys = keys)) val targetPackageName = supervisionPackageName ?: return mapOf() invoke(targetPackageName, PreferenceDataApi(), PreferenceDataRequest(keys = keys)) .await() } catch (e: Exception) { Log.e(TAG, "Error fetching Preference data from supervision app", e) Log.e(SupervisionLog.TAG, "Error fetching Preference data from supervision app", e) mapOf() } companion object { private const val TAG = "SupervisionMessengerClient" private const val SUPERVISION_MESSENGER_SERVICE_BIND_ACTION = "android.app.supervision.action.SUPERVISION_MESSENGER_SERVICE" } Loading
tests/robotests/src/com/android/settings/supervision/SupervisionHelperTest.kt 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * 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.settings.supervision import android.app.role.RoleManager import android.content.Context import android.content.ContextWrapper import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 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 import org.mockito.kotlin.mock import org.mockito.kotlin.stub import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class SupervisionHelperTest { private lateinit var context: Context private val mockRoleManager = mock<RoleManager>() private lateinit var supervisionHelper: SupervisionHelper @Before fun setUp() { context = contextOfRoleManager(mockRoleManager) SupervisionHelper.sInstance = null } @Test fun getSupervisionPackageName_roleManagerReturnsPackageName_shouldReturnPackageName() { val testPackageName = "com.android.supervision" mockRoleManager.stub { on { getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } doReturn listOf(testPackageName) } supervisionHelper = SupervisionHelper.getInstance(context) val result = supervisionHelper.getSupervisionPackageName() assertThat(result).isEqualTo(testPackageName) verify(mockRoleManager).getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } @Test fun getSupervisionPackageName_roleManagerReturnsEmptyList_shouldReturnNull() { mockRoleManager.stub { on { getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } doReturn emptyList() } supervisionHelper = SupervisionHelper.getInstance(context) val result = supervisionHelper.getSupervisionPackageName() assertThat(result).isNull() verify(mockRoleManager).getRoleHolders(RoleManager.ROLE_SYSTEM_SUPERVISION) } @Test fun getSupervisionPackageName_roleManagerReturnsNull_shouldReturnNull() { context = contextOfRoleManager(null) supervisionHelper = SupervisionHelper.getInstance(context) val result = supervisionHelper.getSupervisionPackageName() assertThat(result).isNull() } private fun contextOfRoleManager(roleManager: RoleManager?): Context { return object : ContextWrapper(ApplicationProvider.getApplicationContext()) { override fun getSystemService(name: String): Any? = when (name) { ROLE_SERVICE -> roleManager else -> super.getSystemService(name) } } } }