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

Commit 4a0ebdcb authored by Marcelo Arteiro's avatar Marcelo Arteiro
Browse files

Initial implementation of ThemeService

This CL introduces the initial API for the Theme Service, providing a structured and robust way for system services to manage user-specific theme settings.This new API is intended to replace the existing, error-prone method of direct JSON manipulation of the theme_customization_overlay_packages secure setting.

This new approach ensures a clean and type-safe method of managing themes. The service also listens for changes to this setting, ensuring compatibility with any existing code that modifies it directly.

The public API in ThemeManager includes:
- `registerThemeSettingsCallback(callback)`
- `unregisterThemeSettingsCallback(callback)`
- `updateThemeSettings(settings)`
- `getThemeSettings()`

Here's how to interact with the service:
```
// Get the ThemeManager instance
ThemeManager manager = context.getSystemService(ThemeManager.class);

// To update the theme, create a new ThemeSettings object using the
// builder. This example creates a theme from a preset color.
ThemeSettings newSettings = ThemeSettings.builder(
        /* colorIndex */ 0,
        /* themeStyle */ ThemeStyle.TONAL_SPOT
    ).buildFromPreset(
        Color.valueOf(0xFF1A73E8), // systemPalette
        Color.valueOf(0xFF1A73E8)  // accentColor
    );

// Pass the complete settings object to update the theme.
manager.updateThemeSettings(newSettings);

// To get the current settings:
ThemeSettings currentSettings = manager.getThemeSettings();
if (currentSettings != null) {
    if (currentSettings instanceof ThemeSettingsPreset) {
        // Handle preset-based theme
        ThemeSettingsPreset preset = (ThemeSettingsPreset) currentSettings;
        Color palette = preset.systemPalette();
        Color accent = preset.accentColor();
    } else if (currentSettings instanceof ThemeSettingsWallpaper) {
        // Handle wallpaper-based theme
        ThemeSettingsWallpaper wallpaper = (ThemeSettingsWallpaper) currentSettings;
        boolean useForLockscreen = wallpaper.colorBoth();
    }
}
```

This new API provides a solid foundation for more advanced theme customization features in the future.

Bug: 333694176
Test: atest FrameworksServicesTests_theme
Flag: android.server.enable_theme_service
Change-Id: I114b3ba1f63ceb5429c63e18a9a4a00a0b62df09
parent 710ca857
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -863,6 +863,14 @@ public final class SystemServiceRegistry {
                return new SystemVibrator(ctx);
            }});

        registerService(Context.THEME_SERVICE, ThemeManager.class,
            new CachedServiceFetcher<ThemeManager>() {
                @Override
                public ThemeManager createService(ContextImpl ctx) {
                    return new ThemeManager();
                }
            });

        registerService(Context.WALLPAPER_SERVICE, WallpaperManager.class,
                new CachedServiceFetcher<WallpaperManager>() {
            @Override
+165 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.app;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.theming.IThemeManager;
import android.content.theming.IThemeSettingsCallback;
import android.content.theming.ThemeSettings;
import android.os.RemoteException;
import android.os.ServiceManager;


/**
 * Provides access to the system Theme Service.
 *
 * <p>This class allows applications to interact with the system's Theme Service, enabling them to
 * register for theme settings change notifications, update theme settings, and retrieve the
 * current theme settings.
 *
 * <p>Theme Settings are managed on a per-user basis. This means that all operations performed
 * through this class are scoped to the user associated with the context from which this
 * {@link ThemeManager} instance was obtained.
 *
 * <p>To obtain an instance of this class, use {@link Context#getSystemService(Class)} with
 * {@link ThemeManager}.
 *
 * @hide
 */
@SystemService(Context.THEME_SERVICE)
@FlaggedApi(android.server.Flags.FLAG_ENABLE_THEME_SERVICE)
public class ThemeManager {
    private final IThemeManager mService;

    /**
     * @hide
     */
    public ThemeManager() {
        try {
            mService = IThemeManager.Stub.asInterface(
                    ServiceManager.getServiceOrThrow(Context.THEME_SERVICE));
        } catch (ServiceManager.ServiceNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Registers a callback to receive notifications of theme settings changes.
     *
     * <p>This method allows clients to register an {@link IThemeSettingsCallback}
     * to be notified whenever the theme settings for the current user are changed.
     *
     * @param callback The {@link IThemeSettingsCallback} to register.
     * @return {@code true} if the callback was successfully registered, {@code false} otherwise.
     * @hide
     */
    public boolean registerThemeSettingsCallback(@NonNull IThemeSettingsCallback callback) {
        try {
            return mService.registerThemeSettingsCallback(callback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Unregisters a previously registered theme settings change callback.
     *
     * <p>This method allows clients to unregister an {@link IThemeSettingsCallback}
     * that was previously registered using
     * {@link #registerThemeSettingsCallback(IThemeSettingsCallback)}.
     *
     * @param callback The {@link IThemeSettingsCallback} to unregister.
     * @return {@code true} if the callback was successfully unregistered, {@code false} otherwise.
     */
    public boolean unregisterThemeSettingsCallback(@NonNull IThemeSettingsCallback callback) {
        try {
            return mService.unregisterThemeSettingsCallback(callback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Updates the theme settings for the current user.
     *
     * <p>This method allows clients to update the theme settings for the current user.
     * The provided {@link ThemeSettings} object should contain the new theme settings.
     * Any settings not explicitly set in the {@link ThemeSettings} object will remain unchanged.
     * Specifically, any null properties within the provided {@link ThemeSettings} object
     * will be skipped, and the corresponding existing theme settings will be preserved.
     *
     * <p>It is recommended to use the {@link android.content.theming.ThemeSettings} to
     * construct
     * {@link ThemeSettings} objects, especially when only updating a subset of theme properties.
     * This ensures that only the intended properties are modified, and avoids accidentally
     * resetting other settings to default or null values.
     *
     * @param newSettings The {@link ThemeSettings} object containing the new theme settings.
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.UPDATE_THEME_SETTINGS)
    public boolean updateThemeSettings(@NonNull ThemeSettings newSettings) {
        try {
            return mService.updateThemeSettings(newSettings);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Retrieves the theme settings for the current user.
     *
     * <p>This method allows clients to retrieve the current theme settings the calling user.
     *
     * @return The {@link ThemeSettings} object containing the current theme settings,
     * or {@code null} if an error occurs or no settings are found.
     * @hide
     */
    @Nullable
    public ThemeSettings getThemeSettings() {
        try {
            return mService.getThemeSettings();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Retrieves the theme settings for the current user, or the system default if none is set.
     *
     * <p>This method allows clients to retrieve the current theme settings for the calling user.
     * If no theme has been explicitly set, it returns a system-generated default. This method
     * will never return {@code null}.
     *
     * @return The non-null {@link ThemeSettings} object containing the current or default theme
     * settings.
     * @hide
     */
    @NonNull
    public ThemeSettings getThemeSettingsOrDefault() {
        try {
            return mService.getThemeSettingsOrDefault();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -4402,6 +4402,7 @@ public abstract class Context {
                // @hide: POWER_WHITELIST_MANAGER,
                DEVICE_POLICY_SERVICE,
                UI_MODE_SERVICE,
                // @hide: THEME_SERVICE,
                DOWNLOAD_SERVICE,
                NFC_SERVICE,
                BLUETOOTH_SERVICE,
@@ -6399,6 +6400,18 @@ public abstract class Context {
     */
    public static final String OVERLAY_SERVICE = "overlay";

    /**
     * Use with {@link #getSystemService(String)} to retrieve a {@link ThemeManager} for theme
     * management.
     *
     * @see #getSystemService(String)
     * @see ThemeManager
     * @hide
     */
    @FlaggedApi(android.server.Flags.FLAG_ENABLE_THEME_SERVICE)
    public static final String THEME_SERVICE = "theme";


    /**
     * Use with {@link #getSystemService(String)} to manage resources.
     *
+39 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.content.theming;

import android.content.theming.ThemeSettings;
import android.content.theming.IThemeSettingsCallback;

/** {@hide} */
interface IThemeManager {
    /** {@hide} */
    boolean registerThemeSettingsCallback(in IThemeSettingsCallback callback);
    /** {@hide} */
    boolean unregisterThemeSettingsCallback(in IThemeSettingsCallback callback);

    /** {@hide} */
    @EnforcePermission("UPDATE_THEME_SETTINGS")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.UPDATE_THEME_SETTINGS)")
    boolean updateThemeSettings(in ThemeSettings newSettings);

    /** {@hide} */
    ThemeSettings getThemeSettings();

    /** {@hide} */
    ThemeSettings getThemeSettingsOrDefault();
}
 No newline at end of file
+24 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.content.theming;

import android.content.theming.ThemeSettings;

/** {@hide} */
interface IThemeSettingsCallback{
    void onSettingsChanged(in ThemeSettings oldSettings, in ThemeSettings newSettings);
}
Loading