OSDN Git Service

Added ColorExtractionService and ExtractedColors.
authorTony Wickham <twickham@google.com>
Thu, 17 Mar 2016 22:39:39 +0000 (15:39 -0700)
committerTony Wickham <twickham@google.com>
Fri, 1 Apr 2016 22:40:57 +0000 (15:40 -0700)
- Launcher has an instance of ExtractedColors, which is loaded from
  LauncherProvider in onCreate() and whenever the wallpaper changes.
  - When the wallpaper changes, the ColorExtractionService is started
    in the :wallpaper-chooser process.
  - ColorExtractionService builds an ExtractedColors instance and saves
    it as a String in LauncherProvider.
  - When the results are saved, Launcher gets a callback through
    LauncherProviderChangeListener and reloads the ExtractedColors.
- Whenever Launcher loads Extractecolors, it also re-colors items
  (currently a no-op).

Change-Id: I319e2cfe0a86abcbc6bb39ef6b9fbbcad54ad743

12 files changed:
AndroidManifest.xml
build.gradle
src/com/android/launcher3/Launcher.java
src/com/android/launcher3/LauncherAppState.java
src/com/android/launcher3/LauncherModel.java
src/com/android/launcher3/LauncherProvider.java
src/com/android/launcher3/LauncherProviderChangeListener.java
src/com/android/launcher3/LauncherSettings.java
src/com/android/launcher3/Utilities.java
src/com/android/launcher3/dynamicui/ColorExtractionService.java [new file with mode: 0644]
src/com/android/launcher3/dynamicui/ExtractedColors.java [new file with mode: 0644]
src/com/android/launcher3/dynamicui/ExtractionUtils.java [new file with mode: 0644]

index 418b1d2..80ffa43 100644 (file)
             </intent-filter>
         </receiver>
 
+        <service android:name=".dynamicui.ColorExtractionService"
+            android:exported="false"
+            android:process=":wallpaper_chooser">
+        </service>
+
         <!-- The settings provider contains Home's data, like the workspace favorites -->
         <provider
             android:name="com.android.launcher3.LauncherProvider"
index 6620c10..2306744 100644 (file)
@@ -52,12 +52,14 @@ repositories {
 dependencies {
     compile 'com.android.support:support-v4:23.1.1'
     compile 'com.android.support:recyclerview-v7:23.1.1'
+    compile 'com.android.support:palette-v7:23.2.0'
     compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
     compile project(":WallpaperPicker-Lib")
 
     testCompile 'junit:junit:4.12'
     androidTestCompile 'com.android.support.test:runner:0.5'
     androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
+    androidTestCompile 'com.android.support:support-annotations:23.2.0'
 }
 
 protobuf {
index b16a650..54f0262 100644 (file)
@@ -105,6 +105,7 @@ import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logging.LoggerUtils;
@@ -279,6 +280,7 @@ public class Launcher extends Activity
 
     private LauncherModel mModel;
     private IconCache mIconCache;
+    private ExtractedColors mExtractedColors;
     @Thunk boolean mUserPresent = true;
     private boolean mVisible = false;
     private boolean mHasFocus = false;
@@ -447,6 +449,8 @@ public class Launcher extends Activity
         app.getInvariantDeviceProfile().portraitProfile.setSearchBarHeight(getSearchBarHeight());
         setupViews();
         mDeviceProfile.layout(this);
+        mExtractedColors = new ExtractedColors();
+        loadExtractedColorsAndColorItems();
 
         lockAllApps();
 
@@ -509,6 +513,19 @@ public class Launcher extends Activity
         }
     }
 
+    @Override
+    public void onExtractedColorsChanged() {
+        loadExtractedColorsAndColorItems();
+    }
+
+    private void loadExtractedColorsAndColorItems() {
+        if (mExtractedColors != null) {
+            mExtractedColors.load(this);
+            // TODO: pass mExtractedColors to interested items such as hotseat.
+            mHotseat.setBackgroundColor(mExtractedColors.getColor(ExtractedColors.VIBRANT_INDEX));
+        }
+    }
+
     private LauncherCallbacks mLauncherCallbacks;
 
     public void onPostCreate(Bundle savedInstanceState) {
index e58652b..302a0c3 100644 (file)
@@ -28,6 +28,7 @@ import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.TestingUtils;
 import com.android.launcher3.util.Thunk;
@@ -108,6 +109,11 @@ public class LauncherAppState {
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED);
+        // For extracting colors from the wallpaper
+        if (Utilities.isNycOrAbove()) {
+            // TODO: add a broadcast entry to the manifest for pre-N.
+            filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
+        }
 
         sContext.registerReceiver(mModel, filter);
         UserManagerCompat.getInstance(sContext).enableAndResetCache();
@@ -121,6 +127,8 @@ public class LauncherAppState {
             }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
         }
         new ConfigMonitor(sContext).register();
+
+        ExtractionUtils.startColorExtractionServiceIfNecessary(sContext);
     }
 
     /**
index 3c7366c..f6a067c 100644 (file)
@@ -54,6 +54,7 @@ import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.GridSizeMigrationTask;
@@ -1237,6 +1238,8 @@ public class LauncherModel extends BroadcastReceiver
                         PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE,
                         new String[0], user));
             }
+        } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) {
+            ExtractionUtils.startColorExtractionServiceIfNecessary(context);
         }
     }
 
index 207121b..f076094 100644 (file)
@@ -53,6 +53,7 @@ import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.NoLocaleSqliteContext;
 import com.android.launcher3.util.Thunk;
@@ -257,7 +258,7 @@ public class LauncherProvider extends ContentProvider {
     }
 
     @Override
-    public Bundle call(String method, String arg, Bundle extras) {
+    public Bundle call(String method, final String arg, final Bundle extras) {
         if (Binder.getCallingUid() != Process.myUid()) {
             return null;
         }
@@ -277,13 +278,19 @@ public class LauncherProvider extends ContentProvider {
                 return result;
             }
             case LauncherSettings.Settings.METHOD_SET_BOOLEAN: {
-                boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
+                final boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE);
                 Utilities.getPrefs(getContext()).edit().putBoolean(arg, value).apply();
-                synchronized (LISTENER_LOCK) {
-                    if (mListener != null) {
-                        mListener.onSettingsChanged(arg, value);
+                new MainThreadExecutor().execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (LISTENER_LOCK) {
+                            if (mListener != null) {
+                                mListener.onSettingsChanged(arg, value);
+                            }
+                        }
+
                     }
-                }
+                });
                 if (extras.getBoolean(LauncherSettings.Settings.NOTIFY_BACKUP)) {
                     LauncherBackupAgentHelper.dataChanged(getContext());
                 }
@@ -291,6 +298,29 @@ public class LauncherProvider extends ContentProvider {
                 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, value);
                 return result;
             }
+            case LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID: {
+                String extractedColors = extras.getString(
+                        LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS);
+                int wallpaperId = extras.getInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID);
+                Utilities.getPrefs(getContext()).edit()
+                        .putString(ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, extractedColors)
+                        .putInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, wallpaperId)
+                        .apply();
+                new MainThreadExecutor().execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (LISTENER_LOCK) {
+                            if (mListener != null) {
+                                mListener.onExtractedColorsChanged();
+                            }
+                        }
+
+                    }
+                });
+                Bundle result = new Bundle();
+                result.putString(LauncherSettings.Settings.EXTRA_VALUE, extractedColors);
+                return result;
+            }
             case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
                 clearFlagEmptyDbCreated();
                 return null;
index 1b78e9c..2d2da6e 100644 (file)
@@ -11,5 +11,7 @@ public interface LauncherProviderChangeListener {
 
     public void onSettingsChanged(String settings, boolean value);
 
+    public void onExtractedColorsChanged();
+
     public void onAppWidgetHostReset();
 }
index fb53068..0c16287 100644 (file)
@@ -339,6 +339,11 @@ public class LauncherSettings {
         public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
         public static final String METHOD_MIGRATE_LAUNCHER2_SHORTCUTS = "migrate_l2_shortcuts";
 
+        public static final String METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID =
+                "set_extracted_colors_and_wallpaper_id_setting";
+        public static final String EXTRA_EXTRACTED_COLORS = "extra_extractedColors";
+        public static final String EXTRA_WALLPAPER_ID = "extra_wallpaperId";
+
         public static final String EXTRA_VALUE = "value";
         public static final String EXTRA_DEFAULT_VALUE = "default_value";
         // Extra for set_boolean method to also notify the backup manager of the change.
index 3969d30..ede4279 100644 (file)
@@ -19,6 +19,7 @@ package com.android.launcher3;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.SearchManager;
+import android.app.WallpaperManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
@@ -63,10 +64,13 @@ import android.widget.Toast;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.dynamicui.ColorExtractionService;
 import com.android.launcher3.util.IconNormalizer;
+import com.android.wallpaperpicker.common.WallpaperManagerCompat;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Locale;
diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java
new file mode 100644 (file)
index 0000000..95a62b9
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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 com.android.launcher3.dynamicui;
+
+import android.app.IntentService;
+import android.app.WallpaperManager;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.support.v7.graphics.Palette;
+
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+
+/**
+ * Extracts colors from the wallpaper, and saves results to {@link LauncherProvider}.
+ */
+public class ColorExtractionService extends IntentService {
+
+    public ColorExtractionService() {
+        super("ColorExtractionService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
+        int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager);
+        ExtractedColors extractedColors = new ExtractedColors();
+        if (wallpaperManager.getWallpaperInfo() != null) {
+            // We can't extract colors from live wallpapers, so just use the default color always.
+            extractedColors.updatePalette(null);
+        } else {
+            Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap();
+            Palette palette = Palette.from(wallpaper).generate();
+            extractedColors.updatePalette(palette);
+        }
+
+        // Save the extracted colors and wallpaper id to LauncherProvider.
+        String colorsString = extractedColors.encodeAsString();
+        Bundle extras = new Bundle();
+        extras.putInt(LauncherSettings.Settings.EXTRA_WALLPAPER_ID, wallpaperId);
+        extras.putString(LauncherSettings.Settings.EXTRA_EXTRACTED_COLORS, colorsString);
+        getContentResolver().call(
+                LauncherSettings.Settings.CONTENT_URI,
+                LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID,
+                null, extras);
+    }
+}
diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java
new file mode 100644 (file)
index 0000000..4d17ff7
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2016 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 com.android.launcher3.dynamicui;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v7.graphics.Palette;
+import android.util.Log;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Saves and loads colors extracted from the wallpaper, as well as the associated wallpaper id.
+ */
+public class ExtractedColors {
+    private static final String TAG = "ExtractedColors";
+
+    public static final int DEFAULT_LIGHT = Color.WHITE;
+    public static final int DEFAULT_DARK = Color.BLACK;
+    public static final int DEFAULT_COLOR = DEFAULT_LIGHT;
+
+    // These color profile indices should NOT be changed, since they are used when saving and
+    // loading extracted colors. New colors should always be added at the end.
+    public static final int DEFAULT_INDEX = 0;
+    public static final int VIBRANT_INDEX = 1;
+    public static final int VIBRANT_DARK_INDEX = 2;
+    public static final int VIBRANT_LIGHT_INDEX = 3;
+    public static final int MUTED_INDEX = 4;
+    public static final int MUTED_DARK_INDEX = 5;
+    public static final int MUTED_LIGHT_INDEX = 6;
+
+    public static final int NUM_COLOR_PROFILES = 7;
+
+    private static final String COLOR_SEPARATOR = ",";
+
+    private int[] mColors;
+
+    public ExtractedColors() {
+        mColors = new int[NUM_COLOR_PROFILES];
+    }
+
+    public void setColorAtIndex(int index, int color) {
+        if (index >= 0 && index < mColors.length) {
+            mColors[index] = color;
+        } else {
+            Log.e(TAG, "Attempted to set a color at an invalid index " + index);
+        }
+    }
+
+    /**
+     * Encodes {@link #mColors} as a comma-separated String.
+     */
+    String encodeAsString() {
+        StringBuilder colorsStringBuilder = new StringBuilder();
+        for (int color : mColors) {
+            colorsStringBuilder.append(color).append(COLOR_SEPARATOR);
+        }
+        return colorsStringBuilder.toString();
+    }
+
+    /**
+     * Decodes a comma-separated String into {@link #mColors}.
+     */
+    void decodeFromString(String colorsString) {
+        String[] splitColorsString = colorsString.split(COLOR_SEPARATOR);
+        mColors = new int[splitColorsString.length];
+        for (int i = 0; i < mColors.length; i++) {
+            mColors[i] = Integer.parseInt(splitColorsString[i]);
+        }
+    }
+
+    /**
+     * Loads colors and wallpaper id from {@link Utilities#getPrefs(Context)}.
+     * These were saved there in {@link ColorExtractionService}.
+     */
+    public void load(Context context) {
+        String encodedString = Utilities.getPrefs(context).getString(
+                ExtractionUtils.EXTRACTED_COLORS_PREFERENCE_KEY, DEFAULT_COLOR + "");
+
+        decodeFromString(encodedString);
+    }
+
+    /** @param index must be one of the index values defined at the top of this class. */
+    public int getColor(int index) {
+        if (index >= 0 && index < mColors.length) {
+            return mColors[index];
+        }
+        return DEFAULT_COLOR;
+    }
+
+    /**
+     * Updates colors based on the palette.
+     * If the palette is null, the default color is used in all cases.
+     */
+    public void updatePalette(Palette palette) {
+        if (palette == null) {
+            for (int i = 0; i < NUM_COLOR_PROFILES; i++) {
+                setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR);
+            }
+        } else {
+            setColorAtIndex(ExtractedColors.DEFAULT_INDEX,
+                    ExtractedColors.DEFAULT_COLOR);
+            setColorAtIndex(ExtractedColors.VIBRANT_INDEX,
+                    palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR));
+            setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX,
+                    palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK));
+            setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX,
+                    palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
+            setColorAtIndex(ExtractedColors.MUTED_INDEX,
+                    palette.getMutedColor(ExtractedColors.DEFAULT_COLOR));
+            setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX,
+                    palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK));
+            setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX,
+                    palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT));
+        }
+    }
+}
diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
new file mode 100644 (file)
index 0000000..a2ff607
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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 com.android.launcher3.dynamicui;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+
+import com.android.launcher3.Utilities;
+import com.android.wallpaperpicker.common.WallpaperManagerCompat;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Contains helper fields and methods related to extracting colors from the wallpaper.
+ */
+public class ExtractionUtils {
+    public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors";
+    public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId";
+
+    /**
+     * Extract colors in the :wallpaper-chooser process, if the wallpaper id has changed.
+     * When the new colors are saved in the LauncherProvider,
+     * Launcher will be notified in Launcher#onSettingsChanged(String, String).
+     */
+    public static void startColorExtractionServiceIfNecessary(final Context context) {
+        // Run on a background thread, since the service is asynchronous anyway.
+        Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+            @Override
+            public void run() {
+                if (hasWallpaperIdChanged(context)) {
+                    context.startService(new Intent(context, ColorExtractionService.class));
+                }
+            }
+        });
+    }
+
+    private static boolean hasWallpaperIdChanged(Context context) {
+        if (!Utilities.isNycOrAbove()) {
+            // TODO: update an id in sharedprefs in onWallpaperChanged broadcast, and read it here.
+            return false;
+        }
+        final SharedPreferences sharedPrefs = Utilities.getPrefs(context);
+        int wallpaperId = getWallpaperId(WallpaperManager.getInstance(context));
+        int savedWallpaperId = sharedPrefs.getInt(ExtractionUtils.WALLPAPER_ID_PREFERENCE_KEY, -1);
+        return wallpaperId != savedWallpaperId;
+    }
+
+    public static int getWallpaperId(WallpaperManager wallpaperManager) {
+        // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly.
+        try {
+            Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class);
+            return (int) getWallpaperId.invoke(wallpaperManager,
+                    WallpaperManagerCompat.FLAG_SET_SYSTEM);
+        } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
+            return -1;
+        }
+    }
+}