1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.dynamicui; 18 19 import android.app.WallpaperManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.SharedPreferences; 23 import android.graphics.Color; 24 import android.support.v4.graphics.ColorUtils; 25 import android.support.v7.graphics.Palette; 26 27 import com.android.launcher3.Utilities; 28 29 import java.lang.reflect.InvocationTargetException; 30 import java.lang.reflect.Method; 31 import java.util.List; 32 33 /** 34 * Contains helper fields and methods related to extracting colors from the wallpaper. 35 */ 36 public class ExtractionUtils { 37 public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors"; 38 public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId"; 39 40 private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM 41 private static final float MIN_CONTRAST_RATIO = 2f; 42 43 /** 44 * Extract colors in the :wallpaper-chooser process, if the wallpaper id has changed. 45 * When the new colors are saved in the LauncherProvider, 46 * Launcher will be notified in Launcher#onSettingsChanged(String, String). 47 */ 48 public static void startColorExtractionServiceIfNecessary(final Context context) { 49 // Run on a background thread, since the service is asynchronous anyway. 50 Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() { 51 @Override 52 public void run() { 53 if (hasWallpaperIdChanged(context)) { 54 startColorExtractionService(context); 55 } 56 } 57 }); 58 } 59 60 /** Starts the {@link ColorExtractionService} without checking the wallpaper id */ 61 public static void startColorExtractionService(Context context) { 62 context.startService(new Intent(context, ColorExtractionService.class)); 63 } 64 65 private static boolean hasWallpaperIdChanged(Context context) { 66 if (!Utilities.isNycOrAbove()) { 67 // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here. 68 return false; 69 } 70 final SharedPreferences sharedPrefs = Utilities.getPrefs(context); 71 int wallpaperId = getWallpaperId(WallpaperManager.getInstance(context)); 72 int savedWallpaperId = sharedPrefs.getInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, -1); 73 return wallpaperId != savedWallpaperId; 74 } 75 76 public static int getWallpaperId(WallpaperManager wallpaperManager) { 77 // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly. 78 try { 79 Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class); 80 return (int) getWallpaperId.invoke(wallpaperManager, FLAG_SET_SYSTEM); 81 } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { 82 return -1; 83 } 84 } 85 86 public static boolean isSuperLight(Palette p) { 87 return !isLegibleOnWallpaper(Color.WHITE, p.getSwatches()); 88 } 89 90 public static boolean isSuperDark(Palette p) { 91 return !isLegibleOnWallpaper(Color.BLACK, p.getSwatches()); 92 } 93 94 /** 95 * Given a color, returns true if that color is legible on 96 * the given wallpaper color swatches, else returns false. 97 */ 98 private static boolean isLegibleOnWallpaper(int color, List<Palette.Swatch> wallpaperSwatches) { 99 int legiblePopulation = 0; 100 int illegiblePopulation = 0; 101 for (Palette.Swatch swatch : wallpaperSwatches) { 102 if (isLegible(color, swatch.getRgb())) { 103 legiblePopulation += swatch.getPopulation(); 104 } else { 105 illegiblePopulation += swatch.getPopulation(); 106 } 107 } 108 return legiblePopulation > illegiblePopulation; 109 } 110 111 /** @return Whether the foreground color is legible on the background color. */ 112 private static boolean isLegible(int foreground, int background) { 113 background = ColorUtils.setAlphaComponent(background, 255); 114 return ColorUtils.calculateContrast(foreground, background) >= MIN_CONTRAST_RATIO; 115 } 116 117 } 118