Is there an API to detect which theme the OS is using - dark or light (or other)?

22,850

Solution 1

OK so I got to know how this usually works, on both newest version of Android (Q) and before.

It seems that when the OS creates the WallpaperColors , it also generates color-hints. In the function WallpaperColors.fromBitmap , there is a call to int hints = calculateDarkHints(bitmap); , and this is the code of calculateDarkHints :

/**
 * Checks if image is bright and clean enough to support light text.
 *
 * @param source What to read.
 * @return Whether image supports dark text or not.
 */
private static int calculateDarkHints(Bitmap source) {
    if (source == null) {
        return 0;
    }

    int[] pixels = new int[source.getWidth() * source.getHeight()];
    double totalLuminance = 0;
    final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA);
    int darkPixels = 0;
    source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */,
            source.getWidth(), source.getHeight());

    // This bitmap was already resized to fit the maximum allowed area.
    // Let's just loop through the pixels, no sweat!
    float[] tmpHsl = new float[3];
    for (int i = 0; i < pixels.length; i++) {
        ColorUtils.colorToHSL(pixels[i], tmpHsl);
        final float luminance = tmpHsl[2];
        final int alpha = Color.alpha(pixels[i]);
        // Make sure we don't have a dark pixel mass that will
        // make text illegible.
        if (luminance < DARK_PIXEL_LUMINANCE && alpha != 0) {
            darkPixels++;
        }
        totalLuminance += luminance;
    }

    int hints = 0;
    double meanLuminance = totalLuminance / pixels.length;
    if (meanLuminance > BRIGHT_IMAGE_MEAN_LUMINANCE && darkPixels < maxDarkPixels) {
        hints |= HINT_SUPPORTS_DARK_TEXT;
    }
    if (meanLuminance < DARK_THEME_MEAN_LUMINANCE) {
        hints |= HINT_SUPPORTS_DARK_THEME;
    }

    return hints;
}

Then searching for getColorHints that the WallpaperColors.java has, I've found updateTheme function in StatusBar.java :

    WallpaperColors systemColors = mColorExtractor
            .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
    final boolean useDarkTheme = systemColors != null
            && (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;

This would work only on Android 8.1 , because then the theme was based on the colors of the wallpaper alone. On Android 9.0 , the user can set it without any connection to the wallpaper.

Here's what I've made, according to what I've seen on Android :

enum class DarkThemeCheckResult {
    DEFAULT_BEFORE_THEMES, LIGHT, DARK, PROBABLY_DARK, PROBABLY_LIGHT, USER_CHOSEN
}

@JvmStatic
fun getIsOsDarkTheme(context: Context): DarkThemeCheckResult {
    when {
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.O -> return DarkThemeCheckResult.DEFAULT_BEFORE_THEMES
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> {
            val wallpaperManager = WallpaperManager.getInstance(context)
            val wallpaperColors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
                    ?: return DarkThemeCheckResult.UNKNOWN
            val primaryColor = wallpaperColors.primaryColor.toArgb()
            val secondaryColor = wallpaperColors.secondaryColor?.toArgb() ?: primaryColor
            val tertiaryColor = wallpaperColors.tertiaryColor?.toArgb() ?: secondaryColor
            val bitmap = generateBitmapFromColors(primaryColor, secondaryColor, tertiaryColor)
            val darkHints = calculateDarkHints(bitmap)
            //taken from StatusBar.java , in updateTheme :
            val HINT_SUPPORTS_DARK_THEME = 1 shl 1
            val useDarkTheme = darkHints and HINT_SUPPORTS_DARK_THEME != 0
            if (Build.VERSION.SDK_INT == VERSION_CODES.O_MR1)
                return if (useDarkTheme)
                    DarkThemeCheckResult.UNKNOWN_MAYBE_DARK
                else DarkThemeCheckResult.UNKNOWN_MAYBE_LIGHT
            return if (useDarkTheme)
                DarkThemeCheckResult.MOST_PROBABLY_DARK
            else DarkThemeCheckResult.MOST_PROBABLY_LIGHT
        }
        else -> {
            return when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
                Configuration.UI_MODE_NIGHT_YES -> DarkThemeCheckResult.DARK
                Configuration.UI_MODE_NIGHT_NO -> DarkThemeCheckResult.LIGHT
                else -> DarkThemeCheckResult.MOST_PROBABLY_LIGHT
            }
        }
    }
}

fun generateBitmapFromColors(@ColorInt primaryColor: Int, @ColorInt secondaryColor: Int, @ColorInt tertiaryColor: Int): Bitmap {
    val colors = intArrayOf(primaryColor, secondaryColor, tertiaryColor)
    val imageSize = 6
    val bitmap = Bitmap.createBitmap(imageSize, 1, Bitmap.Config.ARGB_8888)
    for (i in 0 until imageSize / 2)
        bitmap.setPixel(i, 0, colors[0])
    for (i in imageSize / 2 until imageSize / 2 + imageSize / 3)
        bitmap.setPixel(i, 0, colors[1])
    for (i in imageSize / 2 + imageSize / 3 until imageSize)
        bitmap.setPixel(i, 0, colors[2])
    return bitmap
}

I've set the various possible values, because in most of those cases nothing is guaranteed.

Solution 2

Google has just published the documentation on the dark theme at the end of I/O 2019, here.

In order to manage the dark theme, you must first use the latest version of the Material Components library: "com.google.android.material:material:1.1.0-alpha06".

Change the application theme according to the system theme

For the application to switch to the dark theme depending on the system, only one theme is required. To do this, the theme must have Theme.MaterialComponents.DayNight as a parent.

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">
    ...
</style>

Determine the current system theme

To know if the system is currently in dark theme or not, you can implement the following code:

switch (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) {
    case Configuration.UI_MODE_NIGHT_YES:
        …
        break;
    case Configuration.UI_MODE_NIGHT_NO:
        …
        break; 
}

Be notified of a change in the theme

I don't think it's possible to implement a callback to be notified whenever the theme changes, but that's not a problem. Indeed, when the system changes theme, the activity is automatically recreated. Placing the previous code at the beginning of the activity is then sufficient.

From which version of the Android SDK does it work?

I couldn't get this to work on Android Pie with version 28 of the Android SDK. So I assume that this only works from the next version of the SDK, which will be launched with Q, version 29.

Result

result

Solution 3

A simpler Kotlin approach to Charles Annic's answer:

fun Context.isDarkThemeOn(): Boolean {
    return resources.configuration.uiMode and 
            Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
}

Solution 4

I think Google is basing at the battery level for applying dark and light themes in Android Q.

Maybe DayNight theme?

You then need to enable the feature in your app. You do that by calling AppCompatDelegate.setDefaultNightMode(), which takes one of the follow values:

  • MODE_NIGHT_NO. Always use the day (light) theme.
  • MODE_NIGHT_YES. Always use the night (dark) theme.
  • MODE_NIGHT_FOLLOW_SYSTEM (default). This setting follows the system’s setting, which on Android Pie and above is a system setting (more on this below).
  • MODE_NIGHT_AUTO_BATTERY. Changes to dark when the device has its ‘Battery Saver’ feature enabled, light otherwise. ✨New in v1.1.0-alpha03.
  • MODE_NIGHT_AUTO_TIME & MODE_NIGHT_AUTO. Changes between day/night based on the time of day.

Solution 5

I wanted to add to Vitor Hugo Schwaab answer, you could break the code down further and use isNightModeActive.

resources.configuration.isNightModeActive

resources
configuration
isNightModeActive

Share:
22,850
android developer
Author by

android developer

Really like to develop Android apps &amp; libraries on my spare time. Github website: https://github.com/AndroidDeveloperLB/ My spare time apps: https://play.google.com/store/apps/developer?id=AndroidDeveloperLB

Updated on July 09, 2022

Comments

  • android developer
    android developer almost 2 years

    Background

    On recent Android versions, ever since Android 8.1, the OS got more and more support for themes. More specifically dark theme.

    The problem

    Even though there is a lot of talk about dark mode in the point-of-view for users, there is almost nothing that's written for developers.

    What I've found

    Starting from Android 8.1, Google provided some sort of dark theme . If the user chooses to have a dark wallpaper, some UI components of the OS would turn black (article here).

    In addition, if you developed a live wallpaper app, you could tell the OS which colors it has (3 types of colors), which affected the OS colors too (at least on Vanilla based ROMs and on Google devices). That's why I even made an app that lets you have any wallpaper while still be able to choose the colors (here). This is done by calling notifyColorsChanged and then provide them using onComputeColors

    Starting from Android 9.0, it is now possible to choose which theme to have: light, dark or automatic (based on wallpaper) :

    enter image description here

    And now on the near Android Q , it seems it went further, yet it's still unclear to what extent. Somehow a launcher called "Smart Launcher" has ridden on it, offering to use the theme right on itself (article here). So, if you enable dark mode (manually, as written here) , you get the app's settings screen as such:

    enter image description here

    The only thing I've found so far is the above articles, and that I'm following this kind of topic.

    I also know how to request the OS to change color using the live wallpaper, but this seems to be changing on Android Q, at least according to what I've seen when trying it (I think it's more based on time-of-day, but not sure).

    The questions

    1. Is there an API to get which colors the OS is set to use ?

    2. Is there any kind of API to get the theme of the OS ? From which version?

    3. Is the new API somehow related to night mode too? How do those work together?

    4. Is there a nice API for apps to handle the chosen theme? Meaning that if the OS is in certain theme, so will the current app?

  • android developer
    android developer about 5 years
    What about getting the colors? Is it possible? And why does the article say about Android Q? What's special about it? I've tested the code to check on which mode I am, and even though now it's night, it says "Night mode is not active, we're in day time" . Tested on Android 9 . How come ? Or maybe it's for Android Q ?
  • JavierSegoviaCordoba
    JavierSegoviaCordoba about 5 years
    @androiddeveloper the article really said nothing about Android Q (2016). It only helps to let you change the theme based on the device settings (like the behavior of Smart Launcher).
  • JavierSegoviaCordoba
    JavierSegoviaCordoba about 5 years
    Tomorrow I will try personally with multiple API versions.
  • android developer
    android developer about 5 years
    Actually something is weird on my device. The settings of the OS lets me to choose which theme to use, but all of them let it stay on dark theme, including when I choose light theme. I remember I've reported about this issue, but I thought it was fixed... Anyway, is there a way to also check the colors and not just dark vs light ?
  • JavierSegoviaCordoba
    JavierSegoviaCordoba about 5 years
    I am testing it and on my Android PIE emulator the daynight theme doesn't work (even trying to force to use it everytime) and Android Studio doesn't let me download AndroidQ. I am going to try Android Oreo...
  • JavierSegoviaCordoba
    JavierSegoviaCordoba about 5 years
    Really the emulator doesn't change the theme even in the android settings screen... I am going to try with my OnePlus 6
  • JavierSegoviaCordoba
    JavierSegoviaCordoba about 5 years
    I can't get it working. I tried both (App class and for each Activity). I tried to force the Dark mode with AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE‌​_NIGHT_YES) and delegate.setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES)‌​. I tried AppCompat and Material Components too.
  • android developer
    android developer about 5 years
    Maybe it works only on Android Q, then? Can you please share me the project you've tried? I have Android Q emulator working. I can also try on my Pixel 2 which currently has Android 9 (had Q on it before, but went back because of annoyances).
  • JavierSegoviaCordoba
    JavierSegoviaCordoba about 5 years
    Really I got nothing working. But it should works on Android Pie at least. I will try to download Android Q tomorrow. The project has really nothing interesting, I just followed the steps from the Chris Banes post.
  • android developer
    android developer about 5 years
    Why is it called "DayNight" ? Have they returned the feature that changes the theme according to time ? Have you tried this API ? Does it work ?
  • Charles Annic
    Charles Annic about 5 years
    I have no idea about the name, I find it weird too. I didn't find the setting to change the theme over time in the Android Q settings, but this is independent of your implementation in your application. If this feature is present in Q, your application will adapt to it with the solution I just gave. I have tried this API in the application I am currently developing and it works perfectly!
  • Charles Annic
    Charles Annic about 5 years
    Edit : I have just added an example of the result obtained.
  • android developer
    android developer about 5 years
    From which device did you take it? I never saw quick settings tiles being square shaped...
  • android developer
    android developer about 5 years
    And how come you said it will restart, but I see no transition of restarting an Activity? It changed color in an instant...
  • android developer
    android developer about 5 years
    I've found this one too: youtu.be/l1e1gHhci70?t=1214 . Is this what you meant to use?
  • Charles Annic
    Charles Annic about 5 years
    I took it from the Android Studio emulator with the Android Q beta 3. Since the first beta of Android Q, it is possible to modify some aesthetic aspects of the system, such as the shape of the tiles. See here: androidpolice.com/2019/04/04/…
  • Charles Annic
    Charles Annic about 5 years
    In the documentation I gave previously, Google provides the following link for the AppCompat DayNight Documentation: medium.com/androiddevelopers/…. This is where it is said that the activity is recreated with each change. I tested it, the activity is being recreated. Indeed, there is no animation because the transition is probably overriding by the system when changing themes. And finally, yes, that's what I am using. Have you tried my solution? Doesn't that work for you?
  • android developer
    android developer about 5 years
    About the icons shape, I thought it's just for the launcher. Cool (but a bit weird, haha) . About the theme check, works well. About the theme in styles file, it works, but what should be done for AppTheme.AppBarOverlay and AppTheme.PopupOverlay ? What should be their parents? And is there a way to get which colors the wallpaper or live wallpaper sends to the OS to be used, on Android 8.1 ?
  • Charles Annic
    Charles Annic about 5 years
    I agree, haha, I'll probably stick to the round icons! AppTheme.AppBarOverlay inherits ThemeOverlay.MaterialComponents.Dark.ActionBar and AppTheme.PopupOverlay inherits ThemeOverlay.MaterialComponents.Light. Is the WallpaperColors API what you are looking for? It allows you to get the primary, secondary and tertiary colors of the wallpaper. developer.android.com/reference/android/app/WallpaperColors
  • android developer
    android developer about 5 years
    This doesn't look well. When I click on the overflow menu item, I see the popup text to be black on dark background, so it's barely readable. See: imgur.com/a/kz914YM . About WallpaperColors, how do I get an instance of it? As far as I know, this class is used by the live wallpaper app. My question was if it's possible to get those colors that are set to be used by the OS (which are requested either by the live wallpaper or the normal one). I don't know if this API even exists.
  • Pedro Paulo Amorim
    Pedro Paulo Amorim almost 5 years
    AppCompatDelegate.MODE_NIGHT_YES is working on my One Plus 3T running Android 9 but AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM is not even with the night mode enabled.
  • Artem Mostyaev
    Artem Mostyaev over 3 years
    Kotlin is the best!
  • Yusuf Eka Sayogana
    Yusuf Eka Sayogana about 3 years
    what is the MAX_DARK_AREA,DARK_PIXEL_LUMINANCE, BRIGHT_IMAGE_MEAN_LUMINANCE, HINT_SUPPORTS_DARK_TEXT HINT_SUPPORTS_DARK_THEME values ?
  • android developer
    android developer about 3 years
    @YusufEkaSayogana I didn't copy all the code. It's not mine and it's irrelevant . If you wish you can read the entire code online somewhere, just as I did. It's also in the source code of Android, available on the SDK. Search for the code of "calculateDarkHints". I think it also changed over Android versions.
  • Ivan Mir
    Ivan Mir about 3 years
    @ArtemMostyaev C# is even shorter Resources.Configuration.UiMode.HasFlag(UiMode.NightYes) :P
  • Anders Gustafsson
    Anders Gustafsson over 2 years
    Please note that this method is only available in API Level 30 and higher.
  • eriknyk
    eriknyk over 2 years
    why this answer is accepted since it is not complete
  • android developer
    android developer over 2 years
    @eriknyk What's missing?
  • eriknyk
    eriknyk over 2 years
    @androiddeveloper inside calculateDarkHints() methods there are many known constants like: MAX_DARK_AREA, DARK_PIXEL_LUMINANCE, BRIGHT_IMAGE_MEAN_LUMINANCE, DARK_THEME_MEAN_LUMINANCE. And other uknown props in getIsOsDarkTheme() are: DarkThemeCheckResult.UNKNOWN, DarkThemeCheckResult.UNKNOWN_MAYBE_DARK, DarkThemeCheckResult.UNKNOWN_MAYBE_LIGHT, DarkThemeCheckResult.MOST_PROBABLY_DARK, else DarkThemeCheckResult.MOST_PROBABLY_LIGHT. in this secodn case we can just add them however not sure why we need all those variants, but in the first case I have not idea.
  • android developer
    android developer over 2 years
    @eriknyk But what would you do with these, that you think they are missing and important?
  • eriknyk
    eriknyk over 2 years
    @androiddeveloper think like you're another developer with another knowlege level than you and asnwer your question yourself
  • android developer
    android developer over 2 years
    @eriknyk Why would I talk about myself? I'm not the topic. I'm talking about what you wrote...