Android O New Features System Theme

Posted by webmaster1 on Wed, 21 Aug 2019 09:41:17 +0200

I. Introduction to System Topics

Android P has been out for a long time. Today, in the process of using Android P, there is a Device Theme under Dislpay.

As follows

It can be seen that one has three options. After using it, he runs the user to set the mobile phone theme. Unlike app theme, the theme settings here can be queried by the system and all applications. There are two dark and bright states for the system theme. The three options in the above settings represent the following meanings:

  1. Automatic (base on wallpaper): The system checks whether the currently set wallpaper is dark or bright, and then decides more about the setting theme.
  2. Light: Set the bright theme
  3. Dark: Setting Dark Themes

Set the highlight theme:


Set dark themes:

From the source code, it is found that launche and SystemUI on the system monitor the status of the system theme, then display the theme of the system. If other app s need to monitor the change of the theme in real time according to the current theme of the system, they also need to monitor the change of the theme in real time.

2. How to realize the system theme

Personal app s listen to system topics, directly using Wallpaper Manager, addOnColors ChangedListener method, through the listener callback
The steps are as follows:

Step 1: Get Wallpaper Manager
            if (wallpaperManager == null) {
                wallpaperManager = WallpaperManager.getInstance(context.getApplicationContext());
            }

The invocation of Wallpaper Manager is actually getSystem Service

    public static WallpaperManager getInstance(Context context) {
        return (WallpaperManager)context.getSystemService(
                Context.WALLPAPER_SERVICE);
    }
Step 2: Monitoring Thematic Changes

This callback method will be triggered during the topic switch. Here is the method called after the trigger.

WallpaperManager.OnColorsChangedListener  onColorsChangedListener = new WallpaperManager.OnColorsChangedListener(){
    @Override
    public void onColorsChanged(WallpaperColors colors, int which) {//1
        if (WallpaperManager.FLAG_SYSTEM ==  which) {//2
                updateTheme(colors);
        }
     }
};
wallpaperManager.addOnColorsChangedListener(onColorsChangedListene, null);//3
WallpaperColors colorsSystem = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
updateTheme(colorsSystem);//4
  • 3. The second parameter is handler. If null is selected, the hander corresponding to the looper of the main thread is represented.
  • Returns two parameters Wallpaper Colors, which; Wallpaper Colors can determine whether it belongs to a bright or dark topic, and which indicates whether the lock screen (Wallpaper Manager. FLAG_LOCK) or the system (Wallpaper Manager. FLAG_SYSTEM), because the theme setting has an Automatic (base on wallpaper) option, and the lock screen wallpaper and desktop wallpaper can be different, so Wallpaper Manager. FLAG_SYSTEM is a configuration theme, such as 2.
  • Four, because theme callbacks occur only when the theme is switched, the app needs to go back to the theme when it starts running.

Because the system theme is only available on android O, if you want the lowest version of app to be below android O, please make a judgement.

 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) {
 }
Step 3: Judging whether the topic is bright or dark

Here's a code snippet for SystemUI to determine a topic

public void updateTheme(WallpaperColors colors) {
            final boolean useDarkTheme = colors != null
                    && (colors.getColorHints() & HINT_SUPPORTS_DARK_THEME) != 0;
}

Printing log s shows that colors.getColorHints() is 4 at light and 6 at dark, while HINT_SUPPORTS_DARK_THEME=== 2

Many methods of this class, WallpaperColors, are modified by hide, including the getColorHints method, which makes non-system calls impossible, as shown in the figure.

Android P then restricts the invocation of the system API through reflection. It's no use calling this method directly, but the toString method of this class prints this value out again.

We can intercept this value and then make a judgment, so we can modify the judgment above.

public void updateTheme(WallpaperColors colors) {
            final boolean useDarkTheme = colors != null
                    && (getColorHints(colors) & HINT_SUPPORTS_DARK_THEME) != 0;
}

private int getColorHints(WallpaperColors colors) {
        String str = colors.toString();
        int index = str.lastIndexOf("h: ");
        String val = str.substring(index + 3, str.length() - 1);
        if (TextUtils.isDigitsOnly(val)) {
            return Integer.valueOf(val);
        }
        Log.e(TAG, "can not get getColorHints!!");
        return 0;
    }

The reform is not elegant and can only be used.

Step 4: Exit listening
 wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener);

3. Introduce the WallpaperColors class

Wallpaper Colors Source Interpretation: Provides information about the colors of a wallpaper. This record wallpaper color
WallpaperColors has several interesting approaches

Method Return type Significance
getPrimaryColor() Color Get the most visually representative color of wallpaper.
getSecondaryColor() Color The second most outstanding color of wallpaper was obtained.
getTertiaryColor() Color The third most outstanding color of wallpaper was obtained.
getMainColors() List of Color A list of the most outstanding colors sorted by importance.
getColorHints() int Combination of wallpaper color tips. (dark or bright)
calculateDarkHints(Bitmap source) int Check if the image is bright enough based on the incoming Bitmap
fromBitmap(@NonNull Bitmap bitmap) WallpaperColors Based on the incoming Bitmap, the check image is returned to WallpaperColors
fromDrawable(Drawable drawable) WallpaperColors Based on the incoming Drawable, check that the image returns to WallpaperColors

4. Share an encapsulated class

import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public final class ColorExtractor {

    public interface ColorsChange {
        public void themeChange(boolean useDarkTheme);
    }

    private static ColorExtractor colorExtractor;
    private static final String TAG = "ColorExtractor";
    private WallpaperManager wallpaperManager;
    private static final int HINT_SUPPORTS_DARK_THEME = 1 << 1;
    private WallpaperManager.OnColorsChangedListener onColorsChangedListene;
    private List<ColorsChange> listens;

    private ColorExtractor() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) {
            onColorsChangedListene = new WallpaperManager.OnColorsChangedListener() {
                @Override
                public void onColorsChanged(WallpaperColors colors, int which) {
                    if (which == WallpaperManager.FLAG_SYSTEM) {
                        final boolean useDarkTheme = colors != null
                                && (getColorHints(colors) & HINT_SUPPORTS_DARK_THEME) != 0;
                        if (listens != null && listens.size() > 0) {
                            for (ColorsChange colorsChange : listens) {
                                colorsChange.themeChange(useDarkTheme);
                            }
                        }
                    }

                }
            };
        }
        listens = new ArrayList<>();
    }

    public static ColorExtractor getInstance() {
        if (colorExtractor == null) {
            colorExtractor = new ColorExtractor();
        }
        return colorExtractor;
    }

    public void startListen(Context context) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) {
            if (wallpaperManager == null) {
                wallpaperManager = WallpaperManager.getInstance(context.getApplicationContext());
            }
            WallpaperColors colorsSystem = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
            final boolean useDarkTheme = colorsSystem != null
                    && (getColorHints(colorsSystem) & HINT_SUPPORTS_DARK_THEME) != 0;
            if (listens != null && listens.size() > 0) {
                for (ColorsChange colorsChange : listens) {
                    colorsChange.themeChange(useDarkTheme);
                }
            }
            wallpaperManager.addOnColorsChangedListener(onColorsChangedListene, null);
        }
    }



    public void addThemeChange(ColorsChange colorsChange) {
        if (!listens.contains(colorsChange)) {
            listens.add(colorsChange);
        }
    }

    public void removeThemeChange(ColorsChange colorsChange) {
        if (listens.contains(colorsChange)) {
            listens.remove(colorsChange);
        }
    }


    public void stopListen() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) {
                wallpaperManager.removeOnColorsChangedListener(onColorsChangedListene);
            }
            if (listens != null) {
                listens.clear();
            }
        }

    }


    private int getColorHints(WallpaperColors colors) {
        String str = colors.toString();

        int index = str.lastIndexOf("h: ");
        String val = str.substring(index + 3, str.length() - 1);
        if (TextUtils.isDigitsOnly(val)) {
            return Integer.valueOf(val);
        }

        Log.e(TAG, "can not get getColorHints!!");
        return 0;
    }
}
Usage method:
//Singleton instantiation
ColorExtractor colorExtractor = ColorExtractor.getInstance();
//Register listener events, because Wallpaper Manager calls the initial theme when registering, so in order to get the initial value, the listener events are registered before addThemeChange begins.
colorExtractor.addThemeChange(new ColorExtractor.ColorsChange() {
    @Override
    public void themeChange(boolean useDarkTheme) {
        Log.i(TAG, "useDarkTheme[" + useDarkTheme + "]");
    }
});
//Start listening; generally in onStart (or onCreate) methods
colorExtractor.startListen(this);
//End listening, generally in onStop (or onDestory) method
colorExtractor.stopListen();

Topics: Android Java Mobile