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

Commit 1471eace authored by Aurélien Pomini's avatar Aurélien Pomini Committed by Android (Google) Code Review
Browse files

Merge "Fix UiModeManager contrast + force invert for HSUM" into main

parents 49411748 fedf9129
Loading
Loading
Loading
Loading
+11 −6
Original line number Diff line number Diff line
@@ -24,10 +24,16 @@ import android.app.IUiModeManagerCallback;
 * @hide
 */
interface IUiModeManager {

    /**
     * @hide
     */
    void addCallback(IUiModeManagerCallback callback, int userId);

    /**
     * @hide
     */
    void addCallback(IUiModeManagerCallback callback);
    void removeCallback(IUiModeManagerCallback callback, int userId);

    /**
     * Enables the car mode. Only the system can do this.
@@ -215,15 +221,14 @@ interface IUiModeManager {
    int getActiveProjectionTypes();

    /**
     * Returns the contrast for the current user.
     * Returns the contrast for the given user.
     */
    float getContrast();

    float getContrast(int userId);

    /**
     * Returns the force invert state.
     * Returns the force invert state for the given user.
     *
     * @hide
     */
    int getForceInvertState();
    int getForceInvertState(int userId);
}
+224 −17
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.app;

import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
import static android.app.Flags.fixContrastAndForceInvertStateForMultiUser;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -38,10 +39,12 @@ import android.os.IpcDataCache;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -86,7 +89,6 @@ public class UiModeManager {

    private static final String TAG = "UiModeManager";


    /**
     * A listener with a single method that is invoked whenever the packages projecting using the
     * {@link ProjectionType}s for which it is registered change.
@@ -427,7 +429,7 @@ public class UiModeManager {
     * Context required for getting the opPackageName of API caller; maybe be {@code null} if the
     * old constructor marked with UnSupportedAppUsage is used.
     */
    private @Nullable Context mContext;
    private final @Nullable Context mContext;

    private final Object mLock = new Object();
    /**
@@ -452,6 +454,8 @@ public class UiModeManager {
        private final IUiModeManager mService;
        private final Object mGlobalsLock = new Object();

        // ============= Legacy values and methods ============= //
        // TODO(b/362682063) remove when cleaning up the flag
        @ForceInvertType
        private int mForceInvertState = FORCE_INVERT_TYPE_OFF;
        private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;
@@ -466,17 +470,6 @@ public class UiModeManager {
        private final ArrayMap<ForceInvertStateChangeListener, Executor>
                mForceInvertStateChangeListeners = new ArrayMap<>();

        Globals(IUiModeManager service) {
            mService = service;
            try {
                mService.addCallback(this);
                mContrast = mService.getContrast();
                mForceInvertState = mService.getForceInvertState();
            } catch (RemoteException e) {
                Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
            }
        }

        @ForceInvertType
        private int getForceInvertState() {
            synchronized (mGlobalsLock) {
@@ -548,6 +541,190 @@ public class UiModeManager {
                        () -> listener.onContrastChanged(contrast)));
            }
        }

        // ============= End legacy values and methods ============= //

        /**
         * Map of {@link UserCallback} per user id. This will only contain one value for the current
         * user, unless the process using this service interacts across users.
         */
        private final SparseArray<UserCallback> mUserCallbacks = new SparseArray<>();

        Globals(IUiModeManager service) {
            mService = service;
            if (fixContrastAndForceInvertStateForMultiUser()) return;
            try {
                mService.addCallback(this, UserHandle.USER_NULL);
                mContrast = mService.getContrast(UserHandle.USER_NULL);
                mForceInvertState = mService.getForceInvertState(UserHandle.USER_NULL);
            } catch (RemoteException e) {
                Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
            }
        }

        private UserCallback getUserCallbackOrCreate(int userId) {
            UserCallback userCallback = mUserCallbacks.get(userId);
            if (userCallback == null) {
                try {
                    userCallback = new UserCallback(userId);
                    mService.addCallback(userCallback, userId);
                    mUserCallbacks.put(userId, userCallback);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return userCallback;
        }

        private void removeCallbackIfUnusedLocked(int userId) {
            UserCallback userCallback = mUserCallbacks.get(userId);
            if (userCallback == null
                    || !userCallback.mContrastChangeListeners.isEmpty()
                    || !userCallback.mForceInvertStateChangeListeners.isEmpty()) {
                return;
            }
            try {
                mService.removeCallback(userCallback, userId);
                mUserCallbacks.remove(userId);
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to remove UiModeManagerCallback", e);
            }
        }

        @ForceInvertType
        private int getForceInvertState(int userId) {
            synchronized (mGlobalsLock) {
                UserCallback userCallback = mUserCallbacks.get(userId);
                if (userCallback != null) {
                    return userCallback.mForceInvertState;
                }
                try {
                    return mService.getForceInvertState(userId);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }

        private void addForceInvertStateChangeListener(ForceInvertStateChangeListener listener,
                Executor executor, int userId) {
            synchronized (mGlobalsLock) {
                UserCallback userCallback = getUserCallbackOrCreate(userId);
                userCallback.mForceInvertStateChangeListeners.put(listener, executor);
            }
        }

        private void removeForceInvertStateChangeListener(ForceInvertStateChangeListener listener,
                int userId) {
            synchronized (mGlobalsLock) {
                UserCallback userCallback = mUserCallbacks.get(userId);
                if (userCallback != null) {
                    userCallback.mForceInvertStateChangeListeners.remove(listener);
                    removeCallbackIfUnusedLocked(userId);
                }
            }
        }

        private float getContrast(int userId) {
            synchronized (mGlobalsLock) {
                UserCallback userCallback = mUserCallbacks.get(userId);
                if (userCallback != null) {
                    return userCallback.mContrast;
                }
                try {
                    return mService.getContrast(userId);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }

        private void addContrastChangeListener(ContrastChangeListener listener, Executor executor,
                int userId) {
            synchronized (mGlobalsLock) {
                UserCallback userCallback = getUserCallbackOrCreate(userId);
                userCallback.mContrastChangeListeners.put(listener, executor);
            }
        }

        private void removeContrastChangeListener(ContrastChangeListener listener, int userId) {
            synchronized (mGlobalsLock) {
                UserCallback userCallback = mUserCallbacks.get(userId);
                if (userCallback != null) {
                    userCallback.mContrastChangeListeners.remove(listener);
                    removeCallbackIfUnusedLocked(userId);
                }
            }
        }
    }

    /** Global class storing all listeners and cached values for a specific user id. */
    private static class UserCallback extends  IUiModeManagerCallback.Stub {

        private UserCallback(int userId) {
            try {
                sGlobals.mService.addCallback(this, userId);
                mContrast = sGlobals.mService.getContrast(userId);
                mForceInvertState = sGlobals.mService.getForceInvertState(userId);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /** Cached contrast value */
        private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;

        /** Cached force invert state value */
        @ForceInvertType
        private int mForceInvertState = FORCE_INVERT_TYPE_OFF;

        /**
         * Map that stores user provided {@link ContrastChangeListener} callbacks,
         * and the executors on which these callbacks should be called.
         */
        private final ArrayMap<ContrastChangeListener, Executor>
                mContrastChangeListeners = new ArrayMap<>();

        private final ArrayMap<ForceInvertStateChangeListener, Executor>
                mForceInvertStateChangeListeners = new ArrayMap<>();

        @Override
        public void notifyContrastChanged(float contrast) throws RemoteException {
            synchronized (sGlobals.mGlobalsLock) {
                // if value changed in the settings, update the cached value and notify listeners
                Float previousContrast = mContrast;
                if (Math.abs(previousContrast - contrast) < 1e-10) {
                    return;
                }
                mContrast = contrast;
                mContrastChangeListeners.forEach((listener, executor) ->
                        executor.execute(() -> listener.onContrastChanged(contrast)));
            }
        }

        @Override
        public void notifyForceInvertStateChanged(@ForceInvertType int forceInvertState)
                throws RemoteException {
            final Map<ForceInvertStateChangeListener, Executor> listeners = new ArrayMap<>();
            synchronized (sGlobals.mGlobalsLock) {
                // if value changed in the settings, update the cached value and notify listeners
                if (mForceInvertState == forceInvertState) {
                    return;
                }

                mForceInvertState = forceInvertState;
                listeners.putAll(mForceInvertStateChangeListeners);
            }

            listeners.forEach((listener, executor) -> {
                final long token = Binder.clearCallingIdentity();
                try {
                    executor.execute(() -> listener.onForceInvertStateChanged(forceInvertState));
                } finally {
                    Binder.restoreCallingIdentity(token);
                }
            });
        }
    }

    /**
@@ -1520,11 +1697,14 @@ public class UiModeManager {
     */
    @FloatRange(from = -1.0f, to = 1.0f)
    public float getContrast() {
        if (fixContrastAndForceInvertStateForMultiUser()) {
            return sGlobals.getContrast(getUserId());
        }
        return sGlobals.getContrast();
    }

    /**
     * Registers a {@link ContrastChangeListener} for the current user.
     * Registers a {@link ContrastChangeListener} for the user.
     *
     * @param executor The executor on which the listener should be called back.
     * @param listener The listener.
@@ -1534,17 +1714,25 @@ public class UiModeManager {
            @NonNull ContrastChangeListener listener) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);
        if (fixContrastAndForceInvertStateForMultiUser()) {
            sGlobals.addContrastChangeListener(listener, executor, getUserId());
            return;
        }
        sGlobals.addContrastChangeListener(listener, executor);
    }

    /**
     * Unregisters a {@link ContrastChangeListener} for the current user.
     * Unregisters a {@link ContrastChangeListener} for the user.
     * If the listener was not registered, does nothing and returns.
     *
     * @param listener The listener to unregister.
     */
    public void removeContrastChangeListener(@NonNull ContrastChangeListener listener) {
        Objects.requireNonNull(listener);
        if (fixContrastAndForceInvertStateForMultiUser()) {
            sGlobals.removeContrastChangeListener(listener, getUserId());
            return;
        }
        sGlobals.removeContrastChangeListener(listener);
    }

@@ -1557,6 +1745,9 @@ public class UiModeManager {
    @FlaggedApi(android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR)
    @ForceInvertType
    public int getForceInvertState() {
        if (fixContrastAndForceInvertStateForMultiUser()) {
            return sGlobals.getForceInvertState(getUserId());
        }
        return sGlobals.getForceInvertState();
    }

@@ -1577,7 +1768,7 @@ public class UiModeManager {
    }

    /**
     * Registers a {@link ForceInvertStateChangeListener} for the current user.
     * Registers a {@link ForceInvertStateChangeListener} for the user.
     *
     * @param executor The executor on which the listener should be called back.
     * @param listener The listener.
@@ -1589,11 +1780,15 @@ public class UiModeManager {
            @NonNull ForceInvertStateChangeListener listener) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);
        if (fixContrastAndForceInvertStateForMultiUser()) {
            sGlobals.addForceInvertStateChangeListener(listener, executor, getUserId());
            return;
        }
        sGlobals.addForceInvertStateChangeListener(listener, executor);
    }

    /**
     * Unregisters a {@link ForceInvertStateChangeListener} for the current user.
     * Unregisters a {@link ForceInvertStateChangeListener} for the user.
     * If the listener was not registered, does nothing and returns.
     *
     * @param listener The listener to unregister.
@@ -1603,6 +1798,18 @@ public class UiModeManager {
    public void removeForceInvertStateChangeListener(
            @NonNull ForceInvertStateChangeListener listener) {
        Objects.requireNonNull(listener);
        if (fixContrastAndForceInvertStateForMultiUser()) {
            sGlobals.removeForceInvertStateChangeListener(listener, getUserId());
            return;
        }
        sGlobals.removeForceInvertStateChangeListener(listener);
    }

    /**
     * Return the context user id. If this class was built with the UnsupportedAppUsage constructor
     * and the context is null, return the user id of this process instead.
     */
    private int getUserId() {
        return mContext != null ? mContext.getUserId() : UserHandle.myUserId();
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -21,3 +21,13 @@ flag {
         purpose: PURPOSE_BUGFIX
     }
}

flag {
     namespace: "systemui"
     name: "fix_contrast_and_force_invert_state_for_multi_user"
     description: "Fixes contrast and force invert state APIs for multi user and HSUM cases."
     bug: "362682063"
     metadata {
         purpose: PURPOSE_BUGFIX
     }
}
 No newline at end of file
+147 −16

File changed.

Preview size limit exceeded, changes collapsed.

+65 −7
Original line number Diff line number Diff line
@@ -91,6 +91,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
@@ -405,9 +406,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {

    @Test
    public void setNightModeActivated_permissionToChangeOtherUsers() throws RemoteException {
        SystemService.TargetUser user = mock(SystemService.TargetUser.class);
        doReturn(9).when(user).getUserIdentifier();
        mUiManagerService.onUserSwitching(user, user);
        switchUser(9);
        when(mContext.checkCallingOrSelfPermission(
                eq(Manifest.permission.INTERACT_ACROSS_USERS)))
                .thenReturn(PackageManager.PERMISSION_DENIED);
@@ -1575,16 +1574,31 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {

    @Test
    @EnableFlags(android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR)
    public void getForceInvertState_nightModeFalse_returnsOff() throws RemoteException {
    @DisableFlags(android.app.Flags.FLAG_FIX_CONTRAST_AND_FORCE_INVERT_STATE_FOR_MULTI_USER)
    public void getForceInvertState_nightModeFalse_returnsOff_legacy() throws RemoteException {
        mService.setNightModeActivated(false);

        assertThat(mUiManagerService.getForceInvertStateInternal())
                .isEqualTo(FORCE_INVERT_TYPE_OFF);
    }

    @Test
    @EnableFlags({ android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR,
            android.app.Flags.FLAG_FIX_CONTRAST_AND_FORCE_INVERT_STATE_FOR_MULTI_USER })
    public void getForceInvertState_nightModeFalse_returnsOff() throws RemoteException {
        int testUserId = 9;
        switchUser(testUserId);

        mService.setNightModeActivated(false);

        assertThat(mUiManagerService.getForceInvertStateInternal(testUserId))
                .isEqualTo(FORCE_INVERT_TYPE_OFF);
    }

    @Test
    @EnableFlags(android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR)
    public void getForceInvertState_nightModeTrueAndForceInvertOff_returnsOff()
    @DisableFlags(android.app.Flags.FLAG_FIX_CONTRAST_AND_FORCE_INVERT_STATE_FOR_MULTI_USER)
    public void getForceInvertState_nightModeTrueAndForceInvertOff_returnsOff_legacy()
            throws RemoteException {
        mService.setNightModeActivated(true);

@@ -1598,10 +1612,30 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
    }

    @Test
    @EnableFlags(android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR)
    public void getForceInvertState_nightModeTrueAndForceInvertOn_returnsDark() throws Exception {
    @EnableFlags({ android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR,
            android.app.Flags.FLAG_FIX_CONTRAST_AND_FORCE_INVERT_STATE_FOR_MULTI_USER })
    public void getForceInvertState_nightModeTrueAndForceInvertOff_returnsOff()
            throws RemoteException {
        int testUserId = 9;
        switchUser(testUserId);

        mService.setNightModeActivated(true);
        Settings.Secure.putIntForUser(
                mContentResolver,
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
                /* value= */ 0,
                /* userId= */ testUserId);

        assertThat(mUiManagerService.getForceInvertStateInternal(testUserId))
                .isEqualTo(FORCE_INVERT_TYPE_OFF);
    }

    @Test
    @EnableFlags(android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR)
    @DisableFlags(android.app.Flags.FLAG_FIX_CONTRAST_AND_FORCE_INVERT_STATE_FOR_MULTI_USER)
    public void getForceInvertState_nightModeTrueAndForceInvertOn_returnsDark_legacy()
            throws Exception {
        mService.setNightModeActivated(true);
        Settings.Secure.putInt(
                mContentResolver,
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
@@ -1611,6 +1645,30 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
                .isEqualTo(FORCE_INVERT_TYPE_DARK);
    }

    @Test
    @EnableFlags({ android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR,
            android.app.Flags.FLAG_FIX_CONTRAST_AND_FORCE_INVERT_STATE_FOR_MULTI_USER })
    public void getForceInvertState_nightModeTrueAndForceInvertOn_returnsDark() throws Exception {
        int testUserId = 9;
        switchUser(testUserId);

        mService.setNightModeActivated(true);
        Settings.Secure.putIntForUser(
                mContentResolver,
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
                /* value= */ 1,
                /* userId = */ testUserId);

        assertThat(mUiManagerService.getForceInvertStateInternal(testUserId))
                .isEqualTo(FORCE_INVERT_TYPE_DARK);
    }

    private void switchUser(int userId) {
        SystemService.TargetUser user = mock(SystemService.TargetUser.class);
        doReturn(userId).when(user).getUserIdentifier();
        mUiManagerService.onUserSwitching(user, user);
    }

    private void triggerDockIntent() {
        final Intent dockedIntent =
                new Intent(Intent.ACTION_DOCK_EVENT)