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

Commit bec27eaf authored by Felipe Leme's avatar Felipe Leme
Browse files

Fixed SSM.switchUser() so it doesn't crash the system.

SystemServiceManager (SSM) keeps a cache of the TargetUsers it uses
on user lifecycle events, and this cache is only valid from user
start to user stopped. But on user switch, the callback also needs
the TargetUser for the previous user, so if that user is removed,
currently it would crash.

Fixes: 169027200
Test: adb shell pm create-user ZedsDeadBaby && \
  adb shell am switch-user 10 && sleep 10s && \
  adb shell am switch-user 0 && adb shell pm remove-user 10

Change-Id: Ia5a3ac88040a89a9af5f99502846bb05b7ce680b
parent 3709d3ef
Loading
Loading
Loading
Loading
+52 −25
Original line number Diff line number Diff line
@@ -17,13 +17,13 @@
package com.android.server;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.util.ArrayMap;
import android.util.Slog;
@@ -84,6 +84,13 @@ public final class SystemServiceManager {
    @GuardedBy("mTargetUsers")
    private final SparseArray<TargetUser> mTargetUsers = new SparseArray<>();

    /**
     * Reference to the current user, it's used to set the {@link TargetUser} on
     * {@link #switchUser(int, int)} as the previous user might have been removed already.
     */
    @GuardedBy("mTargetUsers")
    private @Nullable TargetUser mCurrentUser;

    SystemServiceManager(Context context) {
        mContext = context;
    }
@@ -259,18 +266,22 @@ public final class SystemServiceManager {
        return targetUser;
    }

    private @NonNull TargetUser newTargetUser(@UserIdInt int userId) {
        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
        Preconditions.checkState(userInfo != null, "No UserInfo for " + userId);
        return new TargetUser(userInfo);
    }

    /**
     * Starts the given user.
     */
    public void startUser(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
        // Create cached TargetUser
        final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
        Preconditions.checkState(userInfo != null, "No UserInfo for " + userId);
        final TargetUser targetUser = newTargetUser(userId);
        synchronized (mTargetUsers) {
            mTargetUsers.put(userId, new TargetUser(userInfo));
            mTargetUsers.put(userId, targetUser);
        }

        onUser(t, START, userId);
        onUser(t, START, /* prevUser= */ null, targetUser);
    }

    /**
@@ -291,7 +302,26 @@ public final class SystemServiceManager {
     * Switches to the given user.
     */
    public void switchUser(@UserIdInt int from, @UserIdInt int to) {
        onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, to, from);
        final TargetUser curUser, prevUser;
        synchronized (mTargetUsers) {
            if (mCurrentUser == null) {
                if (DEBUG) {
                    Slog.d(TAG, "First user switch: from " + from + " to " + to);
                }
                prevUser = newTargetUser(from);
            } else {
                if (from != mCurrentUser.getUserIdentifier()) {
                    Slog.wtf(TAG, "switchUser(" + from + "," + to + "): mCurrentUser is "
                            + mCurrentUser + ", it should be " + from);
                }
                prevUser = mCurrentUser;
            }
            curUser = mCurrentUser = getTargetUser(to);
            if (DEBUG) {
                Slog.d(TAG, "Set mCurrentUser to " + mCurrentUser);
            }
        }
        onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, prevUser, curUser);
    }

    /**
@@ -314,21 +344,16 @@ public final class SystemServiceManager {
    }

    private void onUser(@NonNull String onWhat, @UserIdInt int userId) {
        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userId);
        onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null,
                getTargetUser(userId));
    }

    private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
            @UserIdInt int userId) {
        onUser(t, onWhat, userId, UserHandle.USER_NULL);
    }

    private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
            @UserIdInt int curUserId, @UserIdInt int prevUserId) {
            @Nullable TargetUser prevUser, @NonNull TargetUser curUser) {
        final int curUserId = curUser.getUserIdentifier();
        t.traceBegin("ssm." + onWhat + "User-" + curUserId);
        Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
        final TargetUser curUser = getTargetUser(curUserId);
        final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
                : getTargetUser(prevUserId);
        Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId
                + (prevUser != null ? " (from " + prevUser + ")" : ""));
        final int serviceLen = mServices.size();
        for (int i = 0; i < serviceLen; i++) {
            final SystemService service = mServices.get(i);
@@ -466,7 +491,8 @@ public final class SystemServiceManager {
                    .append(service.getClass().getSimpleName())
                    .append("\n");
        }

        synchronized (mTargetUsers) {
            builder.append("Current user: ").append(mCurrentUser).append('\n');
            builder.append("Target users: ");
            final int targetUsersSize = mTargetUsers.size();
            for (int i = 0; i < targetUsersSize; i++) {
@@ -474,6 +500,7 @@ public final class SystemServiceManager {
                if (i != targetUsersSize - 1) builder.append(',');
            }
            builder.append('\n');
        }

        Slog.e(TAG, builder.toString());
    }