/** * Copyright (c) 2018, The LineageOS 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 lineageos.style; import android.content.Context; import android.graphics.Bitmap; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import lineageos.app.LineageContextConstants; import java.util.ArrayList; import java.util.List; /** * Interface used to customize the System colors. It's capable of setting a global * light and dark mode and custom color accents. */ public class StyleInterface { /** * Global style: automatic (based on wallpaper) mode * * @see #setGlobalStyle */ public static final int STYLE_GLOBAL_AUTO_WALLPAPER = 0; /** * Global style: automatic (based on day time) mode * * @see #setGlobalStyle */ public static final int STYLE_GLOBAL_AUTO_DAYTIME = 1; /** * Global style: light * * @see #setGlobalStyle */ public static final int STYLE_GLOBAL_LIGHT = 2; /** * Global style: dark * * @see #setGlobalStyle */ public static final int STYLE_GLOBAL_DARK = 3; /** * Default accent name. * It can also be used to remove any active accent * * @see #setAccent */ public static final String ACCENT_DEFAULT = "lineageos"; /** * Dark style: default * * @see #setDarkOverlay * @hide */ public static final String OVERLAY_DARK_DEFAULT = "org.lineageos.overlay.dark"; /** * Dark style: black * * @see #setDarkOverlay * @hide */ public static final String OVERLAY_DARK_BLACK = "org.lineageos.overlay.black"; /** * Allows an application to change system style. * This is a dangerous permission, your app must request * it at runtime as any other dangerous permission */ public static final String CHANGE_STYLE_SETTINGS_PERMISSION = "lineageos.permission.CHANGE_STYLE"; private static final String TAG = "StyleInterface"; private static IStyleInterface sService; private static StyleInterface sInstance; private Context mContext; private StyleInterface(Context context) { Context appContext = context.getApplicationContext(); if (appContext != null) { mContext = appContext; } else { mContext = context; } sService = getService(); if (context.getPackageManager().hasSystemFeature( LineageContextConstants.Features.STYLES) && sService == null) { throw new RuntimeException("Unable to get StyleInterfaceService. The service" + " either crashed, was not started, or the interface has been called to early" + " in SystemServer init"); } } /** * Get or create an instance of the {@link lineageos.style.StyleInterface} * * @param context Used to get the service * @return {@link StyleInterface} */ public static StyleInterface getInstance(Context context) { if (sInstance == null) { sInstance = new StyleInterface(context); } return sInstance; } /** @hide **/ public static IStyleInterface getService() { if (sService != null) { return sService; } IBinder b = ServiceManager.getService(LineageContextConstants.LINEAGE_STYLE_INTERFACE); sService = IStyleInterface.Stub.asInterface(b); if (b != null) { sService = IStyleInterface.Stub.asInterface(b); return sService; } else { Log.e(TAG, "null service. SAD!"); return null; } } /** * Set global style. * * You will need {@link #CHANGE_STYLE_SETTINGS_PERMISSION} * to utilize this functionality. * * @see #STYLE_GLOBAL_AUTO_WALLPAPER * @see #STYLE_GLOBAL_AUTO_DAYTIME * @see #STYLE_GLOBAL_LIGHT * @see #STYLE_GLOBAL_DARK * @param style The style mode to set the device to. * One of {@link #STYLE_GLOBAL_AUTO_WALLPAPER}, * {@link #STYLE_GLOBAL_AUTO_DAYTIME}, * {@link #STYLE_GLOBAL_LIGHT} or * {@link #STYLE_GLOBAL_DARK} * @param pkgName The package name of the calling application * * @return Whether the process failed */ public boolean setGlobalStyle(int style, String pkgName) { if (sService == null) { return false; } try { return sService.setGlobalStyle(style, pkgName); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return false; } /** * Get the current global style. * * @return One of {@link #STYLE_GLOBAL_AUTO_WALLPAPER}, * {@link #STYLE_GLOBAL_AUTO_DAYTIME}, * {@link #STYLE_GLOBAL_LIGHT} or * {@link #STYLE_GLOBAL_DARK} */ public int getGlobalStyle() { if (sService == null) { return STYLE_GLOBAL_AUTO_WALLPAPER; } try { return sService.getGlobalStyle(); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return STYLE_GLOBAL_AUTO_WALLPAPER; } /** * Set color accent package. * * You will need {@link #CHANGE_STYLE_SETTINGS_PERMISSION} * to utilize this functionality. * * @param pkgName The package name of the accent * * @return Whether the process failed */ public boolean setAccent(String pkgName) { if (sService == null) { return false; } try { return sService.setAccent(pkgName); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return false; } /** * Get the current accent package. * * @return The current accent package name. Defaults to {#ACCENT_DEFAULT} */ public String getAccent() { if (sService == null) { return ACCENT_DEFAULT; } try { return sService.getAccent(); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return ACCENT_DEFAULT; } /** * Get a list of trusted accents that are included in the build. * * The first element (if any) is the default accent. * * @see #setAccent * * @return A list of accents package names that can be used with {#setAccent}. */ public List getTrustedAccents() { if (sService == null) { return new ArrayList<>(); } try { return sService.getTrustedAccents(); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return new ArrayList<>(); } /** * Get the best color that suites a bitmap object and the appropriate global style * * @param source The object you want the suggested color to be matched with * @param colors A list of colors that the selection will be made from * * @return A {@link lineageos.style.Suggestion} which holds the best style + accent combination */ public Suggestion getSuggestion(Bitmap source, int[] colors) { if (colors.length == 0) { throw new IllegalArgumentException( "The colors array argument must contain at least one element"); } Suggestion fallback = new Suggestion(STYLE_GLOBAL_LIGHT, colors[0]); if (sService == null) { return fallback; } try { return sService.getSuggestion(source, colors); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return fallback; } /** * Determine whether the dark style is being used right now, * regardless of the current global style mode. * * @return Returns true if dark style is enabled */ public boolean isDarkNow() { if (sService == null) { return false; } try { return sService.isDarkNow(); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return false; } /** * Set dark overlay package. * * You will need {@link #CHANGE_STYLE_SETTINGS_PERMISSION} * to utilize this functionality. * * @see #OVERLAY_DARK_DEFAULT * @see #OVERLAY_DARK_BLACK * @param overlayName The package name of the overlay. * One of {@link #OVERLAY_DARK_DEFAULT} or * {@link #OVERLAY_DARK_BLACK}, * * @return Whether the process failed * @hide */ public boolean setDarkOverlay(String overlayName) { if (sService == null) { return false; } try { return sService.setDarkOverlay(overlayName); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return false; } /** * Get current dark overlay package name. * * @see #OVERLAY_DARK_DEFAULT * @see #OVERLAY_DARK_BLACK * @@return {@link #OVERLAY_DARK_DEFAULT} or {@link #OVERLAY_DARK_BLACK} * @hide */ public String getDarkOverlay() { if (sService == null) { return OVERLAY_DARK_DEFAULT; } try { return sService.getDarkOverlay(); } catch (RemoteException e) { Log.e(TAG, e.getLocalizedMessage(), e); } return OVERLAY_DARK_DEFAULT; } }