Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 676b7257 authored by Jason Parks's avatar Jason Parks
Browse files

Refactor setSupervisionEnabledForUserInternal.

Bug: 417532929
Bug: 422188703
Test: atest FrameworksServicesTests_supervision
Flag: EXEMPT: Refactor
Change-Id: I4d4f2f94c5ed556b2ffe32fc9724e8e19b5458ee
parent d987f495
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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);
}
+1 −1
Original line number Diff line number Diff line
@@ -309,7 +309,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();
            }
+138 −92
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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<>();
    }

    /**
@@ -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);
        }
    }

@@ -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
@@ -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();
            }
        }
    }

@@ -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:");
@@ -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);
        }
    }

    /**
@@ -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) {
@@ -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;
@@ -508,7 +533,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
        private UserManagerInternal mUserManagerInternal;

        Injector(Context context) {
            mContext = context;
            this.context = context;
        }

        @Nullable
@@ -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;
        }
@@ -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);
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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. */
+67 −13
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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
@@ -533,6 +585,8 @@ private class SupervisionContextWrapper(

    override fun getPackageManager() = pkgManager

    override fun createAttributionContext(attributionTag: String?) = this

    override fun registerReceiverForAllUsers(
        receiver: BroadcastReceiver?,
        filter: IntentFilter,