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

Commit ae718856 authored by Jason Parks's avatar Jason Parks
Browse files

Add the ability to enable and disable supervision

- Removed the is-enabled command as it was not multi-user aware
- Added enable and disable commands
- Updated dump() to iterate over all known users and dump their supervision state
- Added a UserLifecycleListener to clear data for removed users
- Added unit tests

Test: atest SupervisionServiceTest
Bug: 361098041
Flag: android.app.supervision.flags.supervision_api
Change-Id: Iebe39246be1380b3c8eb23c8668f656f11446ad3
parent 27c2d996
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -21,5 +21,5 @@ package android.app.supervision;
 * {@hide}
 */
interface ISupervisionManager {
    boolean isSupervisionEnabled();
    boolean isSupervisionEnabledForUser(int userId);
}
+3 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app.supervision;

import android.annotation.SystemService;
import android.annotation.UserHandleAware;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.RemoteException;
@@ -45,13 +46,12 @@ public class SupervisionManager {
     *
     * @hide
     */
    @UserHandleAware
    public boolean isSupervisionEnabled() {
        try {
            return mService.isSupervisionEnabled();
            return mService.isSupervisionEnabledForUser(mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }


}
+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.server.supervision;

import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.Bundle;

/**
 * Local system service interface for {@link SupervisionService}.
 *
 * @hide Only for use within Android OS.
 */
public abstract class SupervisionManagerInternal {
    /**
     * Returns whether supervision is enabled for the specified user
     *
     * @param userId The user to retrieve the supervision state for
     * @return whether the user is supervised
     */
    public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);

    /**
     * Sets whether the supervision lock screen should be shown for the specified user
     *
     * @param userId The user set the superivision state for
     * @param enabled Whether or not the superivision lock screen needs to be shown
     * @param options Optional configuration parameters for the supervision lock screen
     */
    public abstract void setSupervisionLockscreenEnabledForUser(
            @UserIdInt int userId, boolean enabled, @Nullable Bundle options);
}
+88 −7
Original line number Diff line number Diff line
@@ -18,14 +18,22 @@ package com.android.server.supervision;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.supervision.ISupervisionManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -38,13 +46,25 @@ public class SupervisionService extends ISupervisionManager.Stub {

    private final Context mContext;

    // TODO(b/362756788): Does this need to be a LockGuard lock?
    private final Object mLockDoNoUseDirectly = new Object();

    @GuardedBy("getLockObject()")
    private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();

    private final UserManagerInternal mUserManagerInternal;

    public SupervisionService(Context context) {
        mContext = context.createAttributionContext("SupervisionService");
        mContext = context.createAttributionContext(LOG_TAG);
        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
        mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
    }

    @Override
    public boolean isSupervisionEnabled() {
        return false;
    public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
        synchronized (getLockObject()) {
            return getUserDataLocked(userId).supervisionEnabled;
        }
    }

    @Override
@@ -60,11 +80,44 @@ public class SupervisionService extends ISupervisionManager.Stub {
    }

    @Override
    protected void dump(
            @NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return;
    protected void dump(@NonNull FileDescriptor fd,
            @NonNull PrintWriter printWriter, @Nullable String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return;

        try (var pw = new IndentingPrintWriter(printWriter, "  ")) {
            pw.println("SupervisionService state:");
            pw.increaseIndent();

            var users = mUserManagerInternal.getUsers(false);
            synchronized (getLockObject()) {
                for (var user : users) {
                    getUserDataLocked(user.id).dump(pw);
                    pw.println();
                }
            }
        }
    }

    private Object getLockObject() {
        return mLockDoNoUseDirectly;
    }

    @NonNull
    @GuardedBy("getLockObject()")
    SupervisionUserData getUserDataLocked(@UserIdInt int userId) {
        SupervisionUserData data = mUserData.get(userId);
        if (data == null) {
            // TODO(b/362790738): Do not create user data for nonexistent users.
            data = new SupervisionUserData(userId);
            mUserData.append(userId, data);
        }
        return data;
    }

        fout.println("Supervision enabled: " + isSupervisionEnabled());
    void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) {
        synchronized (getLockObject()) {
            getUserDataLocked(userId).supervisionEnabled = enabled;
        }
    }

    public static class Lifecycle extends SystemService {
@@ -77,7 +130,35 @@ public class SupervisionService extends ISupervisionManager.Stub {

        @Override
        public void onStart() {
            publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal);
            publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService);
        }
    }

    final SupervisionManagerInternal mInternal = new SupervisionManagerInternal() {
        public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
            synchronized (getLockObject()) {
                return getUserDataLocked(userId).supervisionEnabled;
            }
        }

        @Override
        public void setSupervisionLockscreenEnabledForUser(
                @UserIdInt int userId, boolean enabled, @Nullable Bundle options) {
            synchronized (getLockObject()) {
                SupervisionUserData data = getUserDataLocked(userId);
                data.supervisionLockScreenEnabled = enabled;
                data.supervisionLockScreenOptions = options;
            }
        }
    };

    private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
        @Override
        public void onUserRemoved(UserInfo user) {
            synchronized (getLockObject()) {
                mUserData.remove(user.id);
            }
        }
    }
}
+15 −17
Original line number Diff line number Diff line
@@ -17,8 +17,7 @@
package com.android.server.supervision;

import android.os.ShellCommand;

import java.io.PrintWriter;
import android.os.UserHandle;

public class SupervisionServiceShellCommand extends ShellCommand {
    private final SupervisionService mService;
@@ -32,30 +31,29 @@ public class SupervisionServiceShellCommand extends ShellCommand {
        if (cmd == null) {
            return handleDefaultCommands(null);
        }
        final PrintWriter pw = getOutPrintWriter();
        switch (cmd) {
            case "help": return help(pw);
            case "is-enabled": return isEnabled(pw);
            case "enable": return setEnabled(true);
            case "disable": return setEnabled(false);
            default: return handleDefaultCommands(cmd);
        }
    }

    private int help(PrintWriter pw) {
        pw.println("Supervision service commands:");
        pw.println("  help");
        pw.println("      Prints this help text");
        pw.println("  is-enabled");
        pw.println("      Is supervision enabled");
        return 0;
    }

    private int isEnabled(PrintWriter pw) {
        pw.println(mService.isSupervisionEnabled());
    private int setEnabled(boolean enabled) {
        final var pw = getOutPrintWriter();
        final var userId = UserHandle.parseUserArg(getNextArgRequired());
        mService.setSupervisionEnabledForUser(userId, enabled);
        return 0;
    }

    @Override
    public void onHelp() {
        help(getOutPrintWriter());
        final var pw = getOutPrintWriter();
        pw.println("Supervision service (supervision) commands:");
        pw.println("  help");
        pw.println("      Prints this help text");
        pw.println("  enable <USER_ID>");
        pw.println("      Enables supervision for the given user.");
        pw.println("  disable <USER_ID>");
        pw.println("      Disables supervision for the given user.");
    }
}
Loading