Loading core/java/android/app/supervision/ISupervisionManager.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,6 @@ interface ISupervisionManager { oneway void setSupervisionRecoveryInfo(in SupervisionRecoveryInfo recoveryInfo); SupervisionRecoveryInfo getSupervisionRecoveryInfo(); boolean hasSupervisionCredentials(); oneway void registerSupervisionListener(in ISupervisionListener listener); oneway void registerSupervisionListener(int userId, in ISupervisionListener listener); oneway void unregisterSupervisionListener(in ISupervisionListener listener); } core/java/android/app/supervision/SupervisionManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -313,7 +313,7 @@ public class SupervisionManager { public void registerSupervisionListener(@NonNull SupervisionListener listener) { if (mService != null) { try { mService.registerSupervisionListener(listener.mListener); mService.registerSupervisionListener(mContext.getUserId(), listener.mListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading services/supervision/java/com/android/server/supervision/SupervisionService.java +138 −92 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.app.supervision.SupervisionRecoveryInfo; import android.app.supervision.flags.Flags; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; Loading @@ -47,14 +48,14 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import android.util.ArrayMap; import android.util.SparseArray; import com.android.internal.R; Loading @@ -69,6 +70,7 @@ import com.android.server.appbinding.AppBindingService; import com.android.server.appbinding.AppServiceConnection; import com.android.server.appbinding.finders.SupervisionAppServiceFinder; import com.android.server.pm.UserManagerInternal; import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; Loading @@ -95,19 +97,23 @@ public class SupervisionService extends ISupervisionManager.Stub { @GuardedBy("getLockObject()") private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); private final Context mContext; private final Injector mInjector; @VisibleForTesting final ArrayList<ISupervisionListener> mSupervisionListeners; final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); @GuardedBy("getLockObject()") final ArrayMap<IBinder, SupervisionListenerRecord> mSupervisionListeners = new ArrayMap<>(); @GuardedBy("getLockObject()") final SupervisionSettings mSupervisionSettings = SupervisionSettings.getInstance(); public SupervisionService(Context context) { mContext = context.createAttributionContext(SupervisionLog.TAG); mInjector = new Injector(context); this(new Injector(context.createAttributionContext(SupervisionLog.TAG))); } @VisibleForTesting SupervisionService(Injector injector) { mInjector = injector; mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); mSupervisionListeners = new ArrayList<>(); } /** Loading Loading @@ -193,7 +199,8 @@ public class SupervisionService extends ISupervisionManager.Stub { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } else { SupervisionRecoveryInfoStorage.getInstance(mContext).saveRecoveryInfo(recoveryInfo); SupervisionRecoveryInfoStorage.getInstance(mInjector.context) .saveRecoveryInfo(recoveryInfo); } } Loading @@ -203,7 +210,7 @@ public class SupervisionService extends ISupervisionManager.Stub { if (Flags.persistentSupervisionSettings()) { return mSupervisionSettings.getRecoveryInfo(); } return SupervisionRecoveryInfoStorage.getInstance(mContext).loadRecoveryInfo(); return SupervisionRecoveryInfoStorage.getInstance(mInjector.context).loadRecoveryInfo(); } @Override Loading Loading @@ -250,18 +257,38 @@ public class SupervisionService extends ISupervisionManager.Stub { } @Override public void registerSupervisionListener(@NonNull ISupervisionListener listener) { public void registerSupervisionListener( @UserIdInt int userId, @Nullable ISupervisionListener listener) { if (listener == null) { return; } if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { enforcePermission(INTERACT_ACROSS_USERS); } synchronized (getLockObject()) { if (!mSupervisionListeners.contains(listener)) { mSupervisionListeners.add(listener); SupervisionListenerRecord record = mSupervisionListeners.get(listener.asBinder()); if (record == null) { try { mSupervisionListeners.put(listener.asBinder(), new SupervisionListenerRecord(listener, userId)); } catch (RemoteException e) { // Binder died, ignore } } } } @Override public void unregisterSupervisionListener(@NonNull ISupervisionListener listener) { public void unregisterSupervisionListener(@Nullable ISupervisionListener listener) { if (listener == null) { return; } synchronized (getLockObject()) { mSupervisionListeners.remove(listener); SupervisionListenerRecord record = mSupervisionListeners.remove(listener.asBinder()); if (record != null) { record.unlinkToDeath(); } } } Loading Loading @@ -300,7 +327,9 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override protected void dump( @NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, SupervisionLog.TAG, printWriter)) return; if (!DumpUtils.checkDumpPermission(mInjector.context, SupervisionLog.TAG, printWriter)) { return; } try (var pw = new IndentingPrintWriter(printWriter, " ")) { pw.println("SupervisionService state:"); Loading Loading @@ -350,46 +379,64 @@ public class SupervisionService extends ISupervisionManager.Stub { mSupervisionSettings.saveUserData(); } } final long token = Binder.clearCallingIdentity(); try { Binder.withCleanCallingIdentity(() -> { updateWebContentFilters(userId, enabled); dispatchSupervisionEvent(userId, listener -> listener.onSetSupervisionEnabled(userId, enabled)); if (!enabled) { clearDevicePolicies(userId, supervisionAppPackage); } }); } if (Flags.enableSupervisionAppService()) { List<AppServiceConnection> connections = getSupervisionAppServiceConnections(userId); @NonNull private List<ISupervisionListener> getSupervisionAppServiceListeners(@UserIdInt int userId) { ArrayList<ISupervisionListener> listeners = new ArrayList<>(); if (!Flags.enableSupervisionAppService()) { return listeners; } List<AppServiceConnection> connections = getSupervisionAppServiceConnections(userId); for (AppServiceConnection conn : connections) { String targetPackage = conn.getFinder().getTargetPackage(userId); ISupervisionListener binder = (ISupervisionListener) conn.getServiceBinder(); if (binder == null) { Slog.d( SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. Binder is" + " null.", targetPackage)); Slogf.w(SupervisionLog.TAG, "Failed to bind to SupervisionAppService for %s", targetPackage); continue; } try { binder.onSetSupervisionEnabled(userId, enabled); } catch (RemoteException e) { Slog.d( SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. e = %s", targetPackage, e)); listeners.add(binder); } return listeners; } dispatchSupervisionListenerEvent( listener -> listener.onSetSupervisionEnabled(userId, enabled)); private void dispatchSupervisionEvent(@UserIdInt int userId, @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) { ArrayList<ISupervisionListener> listeners = new ArrayList<>(); // Add SupervisionAppServices listeners before the platform listeners. listeners.addAll(getSupervisionAppServiceListeners(userId)); synchronized (getLockObject()) { mSupervisionListeners.forEach((binder, record) -> { if (record.userId == userId || record.userId == UserHandle.USER_ALL) { listeners.add(record.listener); } }); } listeners.forEach(listener -> action.accept(listener)); } private void clearDevicePolicies( @UserIdInt int userId, @Nullable String supervisionAppPackage) { DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal(); if (Flags.enableRemovePoliciesOnSupervisionDisable() && !enabled && dpmi != null && supervisionAppPackage != null) { if (Flags.enableRemovePoliciesOnSupervisionDisable() && dpmi != null && supervisionAppPackage != null) { dpmi.removePoliciesForAdmins(supervisionAppPackage, userId); } } finally { Binder.restoreCallingIdentity(token); } } /** Loading @@ -400,31 +447,19 @@ public class SupervisionService extends ISupervisionManager.Stub { * supervision. (If the filter is already enabled when enabling supervision, do not disable it). */ private void updateWebContentFilters(@UserIdInt int userId, boolean enabled) { try { int browserValue = Settings.Secure.getIntForUser( mContext.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, userId); if (!enabled || browserValue != 1) { Settings.Secure.putIntForUser( mContext.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, browserValue * -1, userId); } } catch (Settings.SettingNotFoundException ignored) { // Ignore the exception and do not change the value as no value has been set. updateContentFilterSetting(userId, enabled, BROWSER_CONTENT_FILTERS_ENABLED); updateContentFilterSetting(userId, enabled, SEARCH_CONTENT_FILTERS_ENABLED); } try { int searchValue = Settings.Secure.getIntForUser( mContext.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED, userId); if (!enabled || searchValue != 1) { private void updateContentFilterSetting(@UserIdInt int userId, boolean enabled, String key) { try { final ContentResolver contentResolver = mInjector.context.getContentResolver(); final int value = Settings.Secure.getIntForUser(contentResolver, key, userId); if (!enabled || value != 1) { Settings.Secure.putIntForUser( mContext.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED, searchValue * -1, contentResolver, key, value * -1, userId); } } catch (Settings.SettingNotFoundException ignored) { Loading Loading @@ -461,46 +496,36 @@ public class SupervisionService extends ISupervisionManager.Stub { */ private ComponentName getSupervisionProfileOwnerComponent() { return ComponentName.unflattenFromString( mContext.getResources() mInjector.context.getResources() .getString(R.string.config_defaultSupervisionProfileOwnerComponent)); } /** Returns the package assigned to the {@code SYSTEM_SUPERVISION} role. */ private String getSystemSupervisionPackage() { return mContext.getResources().getString(R.string.config_systemSupervision); return mInjector.context.getResources().getString(R.string.config_systemSupervision); } /** Enforces that the caller has the given permission. */ private void enforcePermission(String permission) { checkCallAuthorization( mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED); mInjector.context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED); } /** Enforces that the caller has at least one of the given permission. */ private void enforceAnyPermission(String... permissions) { boolean authorized = false; for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { if (mInjector.context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { authorized = true; } } checkCallAuthorization(authorized); } private void dispatchSupervisionListenerEvent( @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) { List<ISupervisionListener> immutableListener; synchronized (getLockObject()) { immutableListener = List.copyOf(mSupervisionListeners); } for (ISupervisionListener listener : immutableListener) { Binder.withCleanCallingIdentity(() -> action.accept(listener)); } } /** Provides local services in a lazy manner. */ static class Injector { private final Context mContext; public Context context; private DevicePolicyManagerInternal mDpmInternal; private AppBindingService mAppBindingService; private KeyguardManager mKeyguardManager; Loading @@ -508,7 +533,7 @@ public class SupervisionService extends ISupervisionManager.Stub { private UserManagerInternal mUserManagerInternal; Injector(Context context) { mContext = context; this.context = context; } @Nullable Loading @@ -529,14 +554,14 @@ public class SupervisionService extends ISupervisionManager.Stub { KeyguardManager getKeyguardManager() { if (mKeyguardManager == null) { mKeyguardManager = mContext.getSystemService(KeyguardManager.class); mKeyguardManager = context.getSystemService(KeyguardManager.class); } return mKeyguardManager; } PackageManager getPackageManager() { if (mPackageManager == null) { mPackageManager = mContext.getPackageManager(); mPackageManager = context.getPackageManager(); } return mPackageManager; } Loading Loading @@ -667,4 +692,25 @@ public class SupervisionService extends ISupervisionManager.Stub { } } } private final class SupervisionListenerRecord implements DeathRecipient { public final ISupervisionListener listener; public final int userId; SupervisionListenerRecord(@NonNull ISupervisionListener listener, @UserIdInt int userId) throws RemoteException { this.listener = listener; this.userId = userId; listener.asBinder().linkToDeath(this, 0); } public void unlinkToDeath() { listener.asBinder().unlinkToDeath(this, 0); } @Override public void binderDied() { unregisterSupervisionListener(listener); } } } services/supervision/java/com/android/server/supervision/SupervisionSettings.java +1 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,7 @@ public class SupervisionSettings { recoveryInfoFile = new AtomicFile(new File(parent, "supervision_recovery_info.xml"), "supervision"); userDataFile = new AtomicFile(new File(parent, "supervision_settings.xml"), "supervision"); mUserData.clear(); } /** Gets data about a specific user. */ Loading services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +67 −13 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.content.IntentFilter import android.os.IBinder import android.content.pm.PackageManager import android.content.pm.UserInfo import android.content.pm.UserInfo.FLAG_FOR_TESTING Loading Loading @@ -66,7 +67,9 @@ import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.kotlin.any import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever Loading Loading @@ -103,13 +106,15 @@ class SupervisionServiceTest { LocalServices.removeServiceForTest(UserManagerInternal::class.java) LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal) // Creating a temporary folder to enable access to SupervisionSettings. SupervisionSettings.getInstance() .changeDirForTesting(Files.createTempDirectory("tempSupervisionFolder").toFile()) service = SupervisionService(context) lifecycle = SupervisionService.Lifecycle(context, service) lifecycle.registerProfileOwnerListener() // Creating a temporary folder to enable access to SupervisionSettings. SupervisionSettings.getInstance() .changeDirForTesting(Files.createTempDirectory("tempSupervisionFolder").toFile()) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() } @Test Loading Loading @@ -435,23 +440,70 @@ class SupervisionServiceTest { @Test @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE) fun setSupervisionEnabledForUser_notifiesSupervisionListener() { service.registerSupervisionListener(mockSupervisionListener) fun registerAndUnregisteSupervisionListener() { val (listener, binder) = registerSupervisionListenerForUser(USER_ID) unregisterSupervisionListener(listener, binder) } assertThat(service.mSupervisionListeners.size).isEqualTo(1) assertThat(service.mSupervisionListeners).containsExactly(mockSupervisionListener) @Test @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE) fun setSupervisionEnabledForUser_notifiesSupervisionListeners_multipleUsers() { val listeners = buildMap { userData.keys.forEach { userId -> put(userId, registerSupervisionListenerForUser(userId)) } } service.setSupervisionEnabledForUser(USER_ID, true) // Ensure that listeners registered for USER_ALL are notified val anyUserListeners = buildMap { put(UserHandle.USER_ALL, registerSupervisionListenerForUser(UserHandle.USER_ALL)) } verify(mockSupervisionListener).onSetSupervisionEnabled(eq(USER_ID), eq(true)) listeners.forEach { userId, (listener, binder) -> setSupervisionEnabledForUser(userId, true, listeners + anyUserListeners) setSupervisionEnabledForUser(userId, false, listeners + anyUserListeners) clearInvocations(listener) } service.setSupervisionEnabledForUser(USER_ID, false) listeners.forEach { userId, (listener, binder) -> unregisterSupervisionListener(listener, binder) } } verify(mockSupervisionListener).onSetSupervisionEnabled(eq(USER_ID), eq(false)) private fun registerSupervisionListenerForUser( userId: Int ): Pair<ISupervisionListener, IBinder> { val listener = mock<ISupervisionListener>() val binder = mock<IBinder>() service.unregisterSupervisionListener(mockSupervisionListener) whenever(listener.asBinder()).thenReturn(binder) assertThat(service.mSupervisionListeners).doesNotContainKey(binder) service.registerSupervisionListener(userId, listener) assertThat(service.mSupervisionListeners).containsKey(binder) return Pair(listener, binder) } assertThat(service.mSupervisionListeners.size).isEqualTo(0) private fun unregisterSupervisionListener(listener: ISupervisionListener, binder: IBinder) { service.unregisterSupervisionListener(listener) assertThat(service.mSupervisionListeners).doesNotContainKey(binder) } private fun setSupervisionEnabledForUser(expectedUserId: Int, enabled: Boolean, listeners: Map<Int, Pair<ISupervisionListener, IBinder>>) { service.setSupervisionEnabledForUser(expectedUserId, enabled) listeners.forEach { userId, (listener, binder) -> when (userId) { expectedUserId, UserHandle.USER_ALL -> { verify(listener).onSetSupervisionEnabled(eq(expectedUserId), eq(enabled)) } else -> { verify(listener, never()).onSetSupervisionEnabled(any(), any()) } } } } private val systemSupervisionPackage: String Loading Loading @@ -533,6 +585,8 @@ private class SupervisionContextWrapper( override fun getPackageManager() = pkgManager override fun createAttributionContext(attributionTag: String?) = this override fun registerReceiverForAllUsers( receiver: BroadcastReceiver?, filter: IntentFilter, Loading Loading
core/java/android/app/supervision/ISupervisionManager.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,6 @@ interface ISupervisionManager { oneway void setSupervisionRecoveryInfo(in SupervisionRecoveryInfo recoveryInfo); SupervisionRecoveryInfo getSupervisionRecoveryInfo(); boolean hasSupervisionCredentials(); oneway void registerSupervisionListener(in ISupervisionListener listener); oneway void registerSupervisionListener(int userId, in ISupervisionListener listener); oneway void unregisterSupervisionListener(in ISupervisionListener listener); }
core/java/android/app/supervision/SupervisionManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -313,7 +313,7 @@ public class SupervisionManager { public void registerSupervisionListener(@NonNull SupervisionListener listener) { if (mService != null) { try { mService.registerSupervisionListener(listener.mListener); mService.registerSupervisionListener(mContext.getUserId(), listener.mListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading
services/supervision/java/com/android/server/supervision/SupervisionService.java +138 −92 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.app.supervision.SupervisionRecoveryInfo; import android.app.supervision.flags.Flags; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; Loading @@ -47,14 +48,14 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import android.util.ArrayMap; import android.util.SparseArray; import com.android.internal.R; Loading @@ -69,6 +70,7 @@ import com.android.server.appbinding.AppBindingService; import com.android.server.appbinding.AppServiceConnection; import com.android.server.appbinding.finders.SupervisionAppServiceFinder; import com.android.server.pm.UserManagerInternal; import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; Loading @@ -95,19 +97,23 @@ public class SupervisionService extends ISupervisionManager.Stub { @GuardedBy("getLockObject()") private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>(); private final Context mContext; private final Injector mInjector; @VisibleForTesting final ArrayList<ISupervisionListener> mSupervisionListeners; final SupervisionManagerInternal mInternal = new SupervisionManagerInternalImpl(); @GuardedBy("getLockObject()") final ArrayMap<IBinder, SupervisionListenerRecord> mSupervisionListeners = new ArrayMap<>(); @GuardedBy("getLockObject()") final SupervisionSettings mSupervisionSettings = SupervisionSettings.getInstance(); public SupervisionService(Context context) { mContext = context.createAttributionContext(SupervisionLog.TAG); mInjector = new Injector(context); this(new Injector(context.createAttributionContext(SupervisionLog.TAG))); } @VisibleForTesting SupervisionService(Injector injector) { mInjector = injector; mInjector.getUserManagerInternal().addUserLifecycleListener(new UserLifecycleListener()); mSupervisionListeners = new ArrayList<>(); } /** Loading Loading @@ -193,7 +199,8 @@ public class SupervisionService extends ISupervisionManager.Stub { if (Flags.persistentSupervisionSettings()) { mSupervisionSettings.saveRecoveryInfo(recoveryInfo); } else { SupervisionRecoveryInfoStorage.getInstance(mContext).saveRecoveryInfo(recoveryInfo); SupervisionRecoveryInfoStorage.getInstance(mInjector.context) .saveRecoveryInfo(recoveryInfo); } } Loading @@ -203,7 +210,7 @@ public class SupervisionService extends ISupervisionManager.Stub { if (Flags.persistentSupervisionSettings()) { return mSupervisionSettings.getRecoveryInfo(); } return SupervisionRecoveryInfoStorage.getInstance(mContext).loadRecoveryInfo(); return SupervisionRecoveryInfoStorage.getInstance(mInjector.context).loadRecoveryInfo(); } @Override Loading Loading @@ -250,18 +257,38 @@ public class SupervisionService extends ISupervisionManager.Stub { } @Override public void registerSupervisionListener(@NonNull ISupervisionListener listener) { public void registerSupervisionListener( @UserIdInt int userId, @Nullable ISupervisionListener listener) { if (listener == null) { return; } if (UserHandle.getUserId(Binder.getCallingUid()) != userId) { enforcePermission(INTERACT_ACROSS_USERS); } synchronized (getLockObject()) { if (!mSupervisionListeners.contains(listener)) { mSupervisionListeners.add(listener); SupervisionListenerRecord record = mSupervisionListeners.get(listener.asBinder()); if (record == null) { try { mSupervisionListeners.put(listener.asBinder(), new SupervisionListenerRecord(listener, userId)); } catch (RemoteException e) { // Binder died, ignore } } } } @Override public void unregisterSupervisionListener(@NonNull ISupervisionListener listener) { public void unregisterSupervisionListener(@Nullable ISupervisionListener listener) { if (listener == null) { return; } synchronized (getLockObject()) { mSupervisionListeners.remove(listener); SupervisionListenerRecord record = mSupervisionListeners.remove(listener.asBinder()); if (record != null) { record.unlinkToDeath(); } } } Loading Loading @@ -300,7 +327,9 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override protected void dump( @NonNull FileDescriptor fd, @NonNull PrintWriter printWriter, @Nullable String[] args) { if (!DumpUtils.checkDumpPermission(mContext, SupervisionLog.TAG, printWriter)) return; if (!DumpUtils.checkDumpPermission(mInjector.context, SupervisionLog.TAG, printWriter)) { return; } try (var pw = new IndentingPrintWriter(printWriter, " ")) { pw.println("SupervisionService state:"); Loading Loading @@ -350,46 +379,64 @@ public class SupervisionService extends ISupervisionManager.Stub { mSupervisionSettings.saveUserData(); } } final long token = Binder.clearCallingIdentity(); try { Binder.withCleanCallingIdentity(() -> { updateWebContentFilters(userId, enabled); dispatchSupervisionEvent(userId, listener -> listener.onSetSupervisionEnabled(userId, enabled)); if (!enabled) { clearDevicePolicies(userId, supervisionAppPackage); } }); } if (Flags.enableSupervisionAppService()) { List<AppServiceConnection> connections = getSupervisionAppServiceConnections(userId); @NonNull private List<ISupervisionListener> getSupervisionAppServiceListeners(@UserIdInt int userId) { ArrayList<ISupervisionListener> listeners = new ArrayList<>(); if (!Flags.enableSupervisionAppService()) { return listeners; } List<AppServiceConnection> connections = getSupervisionAppServiceConnections(userId); for (AppServiceConnection conn : connections) { String targetPackage = conn.getFinder().getTargetPackage(userId); ISupervisionListener binder = (ISupervisionListener) conn.getServiceBinder(); if (binder == null) { Slog.d( SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. Binder is" + " null.", targetPackage)); Slogf.w(SupervisionLog.TAG, "Failed to bind to SupervisionAppService for %s", targetPackage); continue; } try { binder.onSetSupervisionEnabled(userId, enabled); } catch (RemoteException e) { Slog.d( SupervisionLog.TAG, TextUtils.formatSimple( "Unable to toggle supervision for package %s. e = %s", targetPackage, e)); listeners.add(binder); } return listeners; } dispatchSupervisionListenerEvent( listener -> listener.onSetSupervisionEnabled(userId, enabled)); private void dispatchSupervisionEvent(@UserIdInt int userId, @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) { ArrayList<ISupervisionListener> listeners = new ArrayList<>(); // Add SupervisionAppServices listeners before the platform listeners. listeners.addAll(getSupervisionAppServiceListeners(userId)); synchronized (getLockObject()) { mSupervisionListeners.forEach((binder, record) -> { if (record.userId == userId || record.userId == UserHandle.USER_ALL) { listeners.add(record.listener); } }); } listeners.forEach(listener -> action.accept(listener)); } private void clearDevicePolicies( @UserIdInt int userId, @Nullable String supervisionAppPackage) { DevicePolicyManagerInternal dpmi = mInjector.getDpmInternal(); if (Flags.enableRemovePoliciesOnSupervisionDisable() && !enabled && dpmi != null && supervisionAppPackage != null) { if (Flags.enableRemovePoliciesOnSupervisionDisable() && dpmi != null && supervisionAppPackage != null) { dpmi.removePoliciesForAdmins(supervisionAppPackage, userId); } } finally { Binder.restoreCallingIdentity(token); } } /** Loading @@ -400,31 +447,19 @@ public class SupervisionService extends ISupervisionManager.Stub { * supervision. (If the filter is already enabled when enabling supervision, do not disable it). */ private void updateWebContentFilters(@UserIdInt int userId, boolean enabled) { try { int browserValue = Settings.Secure.getIntForUser( mContext.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, userId); if (!enabled || browserValue != 1) { Settings.Secure.putIntForUser( mContext.getContentResolver(), BROWSER_CONTENT_FILTERS_ENABLED, browserValue * -1, userId); } } catch (Settings.SettingNotFoundException ignored) { // Ignore the exception and do not change the value as no value has been set. updateContentFilterSetting(userId, enabled, BROWSER_CONTENT_FILTERS_ENABLED); updateContentFilterSetting(userId, enabled, SEARCH_CONTENT_FILTERS_ENABLED); } try { int searchValue = Settings.Secure.getIntForUser( mContext.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED, userId); if (!enabled || searchValue != 1) { private void updateContentFilterSetting(@UserIdInt int userId, boolean enabled, String key) { try { final ContentResolver contentResolver = mInjector.context.getContentResolver(); final int value = Settings.Secure.getIntForUser(contentResolver, key, userId); if (!enabled || value != 1) { Settings.Secure.putIntForUser( mContext.getContentResolver(), SEARCH_CONTENT_FILTERS_ENABLED, searchValue * -1, contentResolver, key, value * -1, userId); } } catch (Settings.SettingNotFoundException ignored) { Loading Loading @@ -461,46 +496,36 @@ public class SupervisionService extends ISupervisionManager.Stub { */ private ComponentName getSupervisionProfileOwnerComponent() { return ComponentName.unflattenFromString( mContext.getResources() mInjector.context.getResources() .getString(R.string.config_defaultSupervisionProfileOwnerComponent)); } /** Returns the package assigned to the {@code SYSTEM_SUPERVISION} role. */ private String getSystemSupervisionPackage() { return mContext.getResources().getString(R.string.config_systemSupervision); return mInjector.context.getResources().getString(R.string.config_systemSupervision); } /** Enforces that the caller has the given permission. */ private void enforcePermission(String permission) { checkCallAuthorization( mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED); mInjector.context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED); } /** Enforces that the caller has at least one of the given permission. */ private void enforceAnyPermission(String... permissions) { boolean authorized = false; for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { if (mInjector.context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { authorized = true; } } checkCallAuthorization(authorized); } private void dispatchSupervisionListenerEvent( @NonNull RemoteExceptionIgnoringConsumer<ISupervisionListener> action) { List<ISupervisionListener> immutableListener; synchronized (getLockObject()) { immutableListener = List.copyOf(mSupervisionListeners); } for (ISupervisionListener listener : immutableListener) { Binder.withCleanCallingIdentity(() -> action.accept(listener)); } } /** Provides local services in a lazy manner. */ static class Injector { private final Context mContext; public Context context; private DevicePolicyManagerInternal mDpmInternal; private AppBindingService mAppBindingService; private KeyguardManager mKeyguardManager; Loading @@ -508,7 +533,7 @@ public class SupervisionService extends ISupervisionManager.Stub { private UserManagerInternal mUserManagerInternal; Injector(Context context) { mContext = context; this.context = context; } @Nullable Loading @@ -529,14 +554,14 @@ public class SupervisionService extends ISupervisionManager.Stub { KeyguardManager getKeyguardManager() { if (mKeyguardManager == null) { mKeyguardManager = mContext.getSystemService(KeyguardManager.class); mKeyguardManager = context.getSystemService(KeyguardManager.class); } return mKeyguardManager; } PackageManager getPackageManager() { if (mPackageManager == null) { mPackageManager = mContext.getPackageManager(); mPackageManager = context.getPackageManager(); } return mPackageManager; } Loading Loading @@ -667,4 +692,25 @@ public class SupervisionService extends ISupervisionManager.Stub { } } } private final class SupervisionListenerRecord implements DeathRecipient { public final ISupervisionListener listener; public final int userId; SupervisionListenerRecord(@NonNull ISupervisionListener listener, @UserIdInt int userId) throws RemoteException { this.listener = listener; this.userId = userId; listener.asBinder().linkToDeath(this, 0); } public void unlinkToDeath() { listener.asBinder().unlinkToDeath(this, 0); } @Override public void binderDied() { unregisterSupervisionListener(listener); } } }
services/supervision/java/com/android/server/supervision/SupervisionSettings.java +1 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,7 @@ public class SupervisionSettings { recoveryInfoFile = new AtomicFile(new File(parent, "supervision_recovery_info.xml"), "supervision"); userDataFile = new AtomicFile(new File(parent, "supervision_settings.xml"), "supervision"); mUserData.clear(); } /** Gets data about a specific user. */ Loading
services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt +67 −13 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.content.IntentFilter import android.os.IBinder import android.content.pm.PackageManager import android.content.pm.UserInfo import android.content.pm.UserInfo.FLAG_FOR_TESTING Loading Loading @@ -66,7 +67,9 @@ import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.kotlin.any import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever Loading Loading @@ -103,13 +106,15 @@ class SupervisionServiceTest { LocalServices.removeServiceForTest(UserManagerInternal::class.java) LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal) // Creating a temporary folder to enable access to SupervisionSettings. SupervisionSettings.getInstance() .changeDirForTesting(Files.createTempDirectory("tempSupervisionFolder").toFile()) service = SupervisionService(context) lifecycle = SupervisionService.Lifecycle(context, service) lifecycle.registerProfileOwnerListener() // Creating a temporary folder to enable access to SupervisionSettings. SupervisionSettings.getInstance() .changeDirForTesting(Files.createTempDirectory("tempSupervisionFolder").toFile()) assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() } @Test Loading Loading @@ -435,23 +440,70 @@ class SupervisionServiceTest { @Test @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE) fun setSupervisionEnabledForUser_notifiesSupervisionListener() { service.registerSupervisionListener(mockSupervisionListener) fun registerAndUnregisteSupervisionListener() { val (listener, binder) = registerSupervisionListenerForUser(USER_ID) unregisterSupervisionListener(listener, binder) } assertThat(service.mSupervisionListeners.size).isEqualTo(1) assertThat(service.mSupervisionListeners).containsExactly(mockSupervisionListener) @Test @EnableFlags(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE) fun setSupervisionEnabledForUser_notifiesSupervisionListeners_multipleUsers() { val listeners = buildMap { userData.keys.forEach { userId -> put(userId, registerSupervisionListenerForUser(userId)) } } service.setSupervisionEnabledForUser(USER_ID, true) // Ensure that listeners registered for USER_ALL are notified val anyUserListeners = buildMap { put(UserHandle.USER_ALL, registerSupervisionListenerForUser(UserHandle.USER_ALL)) } verify(mockSupervisionListener).onSetSupervisionEnabled(eq(USER_ID), eq(true)) listeners.forEach { userId, (listener, binder) -> setSupervisionEnabledForUser(userId, true, listeners + anyUserListeners) setSupervisionEnabledForUser(userId, false, listeners + anyUserListeners) clearInvocations(listener) } service.setSupervisionEnabledForUser(USER_ID, false) listeners.forEach { userId, (listener, binder) -> unregisterSupervisionListener(listener, binder) } } verify(mockSupervisionListener).onSetSupervisionEnabled(eq(USER_ID), eq(false)) private fun registerSupervisionListenerForUser( userId: Int ): Pair<ISupervisionListener, IBinder> { val listener = mock<ISupervisionListener>() val binder = mock<IBinder>() service.unregisterSupervisionListener(mockSupervisionListener) whenever(listener.asBinder()).thenReturn(binder) assertThat(service.mSupervisionListeners).doesNotContainKey(binder) service.registerSupervisionListener(userId, listener) assertThat(service.mSupervisionListeners).containsKey(binder) return Pair(listener, binder) } assertThat(service.mSupervisionListeners.size).isEqualTo(0) private fun unregisterSupervisionListener(listener: ISupervisionListener, binder: IBinder) { service.unregisterSupervisionListener(listener) assertThat(service.mSupervisionListeners).doesNotContainKey(binder) } private fun setSupervisionEnabledForUser(expectedUserId: Int, enabled: Boolean, listeners: Map<Int, Pair<ISupervisionListener, IBinder>>) { service.setSupervisionEnabledForUser(expectedUserId, enabled) listeners.forEach { userId, (listener, binder) -> when (userId) { expectedUserId, UserHandle.USER_ALL -> { verify(listener).onSetSupervisionEnabled(eq(expectedUserId), eq(enabled)) } else -> { verify(listener, never()).onSetSupervisionEnabled(any(), any()) } } } } private val systemSupervisionPackage: String Loading Loading @@ -533,6 +585,8 @@ private class SupervisionContextWrapper( override fun getPackageManager() = pkgManager override fun createAttributionContext(attributionTag: String?) = this override fun registerReceiverForAllUsers( receiver: BroadcastReceiver?, filter: IntentFilter, Loading