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

Commit 1b8d8cad authored by Menghan Li's avatar Menghan Li
Browse files

feat(EDT): Add the getForceInvertState API to UiModeManager

Part of the move of the API from ViewRootImpl to UiModeManager.
The logic remains unchained. a new AIDL, IUiModeManagerCallback, was
created to let the service notify all UiModeManagers about force invert
changes, similarly to what was done in ViewRootImpl.

Bug: 368721320
Flag: android.view.accessibility.force_invert_color
Test: atest ViewRootImplTest
Change-Id: Id92a51f2569de325ae1ad0d21dcea934f954331a
parent edd78961
Loading
Loading
Loading
Loading
+31 −23
Original line number Diff line number Diff line
@@ -215,7 +215,15 @@ interface IUiModeManager {
    int getActiveProjectionTypes();

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


    /**
     * Returns the force invert state.
     *
     * @hide
     */
    int getForceInvertState();
}
+1 −0
Original line number Diff line number Diff line
@@ -24,4 +24,5 @@ package android.app;
*/
oneway interface IUiModeManagerCallback {
  void notifyContrastChanged(float contrast);
  void notifyForceInvertStateChanged(int forceInvertState);
}
+137 −0
Original line number Diff line number Diff line
@@ -120,6 +120,24 @@ public class UiModeManager {
        void onContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float contrast);
    }

    /**
     * Listener for the force invert state. To listen for changes to
     * the force invert state on the device, implement this interface and
     * register it with the system by calling {@link #addForceInvertStateChangeListener}.
     *
     * @hide
     */
    public interface ForceInvertStateChangeListener {

        /**
         * Called when the force invert state changes.
         *
         * @param forceInvertState The force invert state in {@link #getForceInvertState}
         * @hide
         */
        void onForceInvertStateChanged(@ForceInvertType int forceInvertState);
    }

    /**
     * Broadcast sent when the device's UI has switched to car mode, either
     * by being placed in a car dock or explicit action of the user.  After
@@ -374,6 +392,36 @@ public class UiModeManager {
    @SystemApi
    public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1;

    /** @hide */
    @IntDef(prefix = {"Force_Invert_Type_"}, value = {
            FORCE_INVERT_TYPE_OFF,
            FORCE_INVERT_TYPE_DARK,
            FORCE_INVERT_TYPE_LIGHT,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ForceInvertType {}

    /**
     * Constant for {@link #getForceInvertState()}: Do not force invert.
     *
     * @hide
     */
    public static final int FORCE_INVERT_TYPE_OFF = 0;

    /**
     * Constant for {@link #getForceInvertState()}: Force apps to be dark.
     *
     * @hide
     */
    public static final int FORCE_INVERT_TYPE_DARK = 1;

    /**
     * Constant for {@link #getForceInvertState()}: Force apps to be light.
     *
     * @hide
     */
    public static final int FORCE_INVERT_TYPE_LIGHT = 2;

    private static Globals sGlobals;

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

        @ForceInvertType
        private int mForceInvertState = FORCE_INVERT_TYPE_OFF;
        private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;

        /**
@@ -414,16 +464,63 @@ public class UiModeManager {
        private final ArrayMap<ContrastChangeListener, Executor>
                mContrastChangeListeners = new ArrayMap<>();

        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) {
                return mForceInvertState;
            }
        }

        private void addForceInvertStateChangeListener(ForceInvertStateChangeListener listener,
                Executor executor) {
            synchronized (mGlobalsLock) {
                mForceInvertStateChangeListeners.put(listener, executor);
            }
        }

        private void removeForceInvertStateChangeListener(ForceInvertStateChangeListener listener) {
            synchronized (mGlobalsLock) {
                mForceInvertStateChangeListeners.remove(listener);
            }
        }

        @Override
        public void notifyForceInvertStateChanged(@ForceInvertType int forceInvertState) {
            final Map<ForceInvertStateChangeListener, Executor> listeners = new ArrayMap<>();
            synchronized (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);
                }
            });
        }

        private float getContrast() {
            synchronized (mGlobalsLock) {
                return mContrast;
@@ -1453,4 +1550,44 @@ public class UiModeManager {
        Objects.requireNonNull(listener);
        sGlobals.removeContrastChangeListener(listener);
    }

    /**
     * Returns the force invert state for the user.
     *
     * @hide
     */
    @ForceInvertType
    public int getForceInvertState() {
        return sGlobals.getForceInvertState();
    }

    /**
     * Registers a {@link ForceInvertStateChangeListener} for the current user.
     *
     * @param executor The executor on which the listener should be called back.
     * @param listener The listener.
     *
     * @hide
     */
    public void addForceInvertStateChangeListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull ForceInvertStateChangeListener listener) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);
        sGlobals.addForceInvertStateChangeListener(listener, executor);
    }

    /**
     * Unregisters a {@link ForceInvertStateChangeListener} for the current user.
     * If the listener was not registered, does nothing and returns.
     *
     * @param listener The listener to unregister.
     *
     * @hide
     */
    public void removeForceInvertStateChangeListener(
            @NonNull ForceInvertStateChangeListener listener) {
        Objects.requireNonNull(listener);
        sGlobals.removeForceInvertStateChangeListener(listener);
    }
}
+118 −1
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.FORCE_INVERT_TYPE_DARK;
import static android.app.UiModeManager.FORCE_INVERT_TYPE_OFF;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
@@ -33,6 +35,7 @@ import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserHandle.getCallingUserId;
import static android.provider.Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED;
import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.util.TimeUtils.isTimeBetween;

@@ -45,6 +48,7 @@ import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.AlarmManager;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
@@ -56,6 +60,7 @@ import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
import android.app.UiModeManager.AttentionModeThemeOverlayType;
import android.app.UiModeManager.ForceInvertType;
import android.app.UiModeManager.NightModeCustomReturnType;
import android.app.UiModeManager.NightModeCustomType;
import android.content.BroadcastReceiver;
@@ -93,6 +98,7 @@ import android.service.vr.IVrStateCallbacks;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -256,6 +262,9 @@ final class UiModeManagerService extends SystemService {
    @GuardedBy("mLock")
    private final SparseArray<Float> mContrasts = new SparseArray<>();

    @GuardedBy("mLock")
    private final SparseIntArray mForceInvertStates = new SparseIntArray();

    public UiModeManagerService(Context context) {
        this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
    }
@@ -407,9 +416,33 @@ final class UiModeManagerService extends SystemService {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            updateSystemProperties();
            updateForceInvertStates();
        }
    };

    private final ContentObserver mForceInvertStateObserver = new ContentObserver(mHandler) {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            updateForceInvertStates();
        }
    };

    private void updateForceInvertStates() {
        if (!android.view.accessibility.Flags.forceInvertColor()) {
            return;
        }

        synchronized (mLock) {
            if (updateForceInvertStateLocked()) {
                int forceInvertState = getForceInvertStateLocked();
                mUiModeManagerCallbacks.get(mCurrentUser, new RemoteCallbackList<>())
                        .broadcast(ignoreRemoteException(
                                callback ->
                                        callback.notifyForceInvertStateChanged(forceInvertState)));
            }
        }
    }

    private final ContentObserver mContrastObserver = new ContentObserver(mHandler) {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
@@ -485,6 +518,12 @@ final class UiModeManagerService extends SystemService {
                context.getContentResolver()
                        .registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
                                false, mDarkThemeObserver, 0);
                if (android.view.accessibility.Flags.forceInvertColor()) {
                    context.getContentResolver()
                            .registerContentObserver(
                                    Secure.getUriFor(ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED),
                                    false, mForceInvertStateObserver, UserHandle.USER_ALL);
                }
                context.getContentResolver().registerContentObserver(
                        Secure.getUriFor(Secure.CONTRAST_LEVEL), false,
                        mContrastObserver, UserHandle.USER_ALL);
@@ -1275,6 +1314,14 @@ final class UiModeManagerService extends SystemService {
                return getContrastLocked();
            }
        }

        @Override
        @ForceInvertType
        public int getForceInvertState() {
            synchronized (mLock) {
                return getForceInvertStateLocked();
            }
        }
    };

    private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
@@ -1357,6 +1404,76 @@ final class UiModeManagerService extends SystemService {
        }
    }

    /**
     * Return the force invert for the current user. If not cached, fetch it from the settings.
     */
    @GuardedBy("mLock")
    @ForceInvertType
    private int getForceInvertStateLocked() {
        if (mForceInvertStates.indexOfKey(mCurrentUser) < 0) {
            updateForceInvertStateLocked();
        }
        return mForceInvertStates.get(mCurrentUser);
    }

    /**
     * Read the force invert setting for the current user and update {@link #mForceInvertStates}
     * if the contrast changed. Returns true if {@link #mForceInvertStates} was updated.
     */
    @GuardedBy("mLock")
    private boolean updateForceInvertStateLocked() {
        int forceInvertState = getForceInvertStateInternal();
        if (mForceInvertStates.get(mCurrentUser) != forceInvertState) {
            mForceInvertStates.put(mCurrentUser, forceInvertState);
            return true;
        }
        return false;
    }

    /**
     * Returns the current state of force invert, which modifies the display colors to
     * enhance visibility based on the system's dark theme settings and app-specific configurations.
     *
     * <p>This method is for informational purposes only. The application does not need to
     * implement any special handling for force invert; the system applies it automatically.
     * If you want to prevent force invert from affecting your app, ensure you have defined
     * both light and dark themes. Force invert is not applied to apps that already adapt
     * to the user's system theme preference.</p>
     *
     * @return The current force invert state, represented by a {@code ForceDarkType} constant.
     *
     * @hide
     */
    private int getForceInvertStateInternal() {
        if (!android.view.accessibility.Flags.forceInvertColor()) {
            return FORCE_INVERT_TYPE_OFF;
        }

        if (!isSystemInDarkTheme()) {
            return FORCE_INVERT_TYPE_OFF;
        }

        if (!isForceInvert()) {
            return FORCE_INVERT_TYPE_OFF;
        }

        return FORCE_INVERT_TYPE_DARK;
    }

    private boolean isSystemInDarkTheme() {
        Context sysUiContext = ActivityThread.currentActivityThread().getSystemUiContext();
        int sysUiNightMode = sysUiContext.getResources().getConfiguration().uiMode
                & Configuration.UI_MODE_NIGHT_MASK;
        return sysUiNightMode == Configuration.UI_MODE_NIGHT_YES;
    }

    private boolean isForceInvert() {
        return Settings.Secure.getIntForUser(
                getContext().getContentResolver(),
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
                /* def= */ 0, mCurrentUser) == 1;
    }

    /**
     * Return the contrast for the current user. If not cached, fetch it from the settings.
     */