Adding compat implementation for the new WallpaperManager APIs for color extraction

Change-Id: Ie06c9ac3a77cd33d22ce298a55e234078895c3a0
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 3a60a98..dd14466 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -85,6 +85,11 @@
             android:process=":wallpaper_chooser">
         </service>
 
+        <service
+            android:name="com.android.launcher3.compat.WallpaperManagerCompatVL$ColorExtractionService"
+            android:exported="false"
+            android:process=":wallpaper_chooser" />
+
         <service android:name="com.android.launcher3.notification.NotificationListener"
                  android:enabled="@bool/notification_badging_enabled"
                  android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
diff --git a/src/com/android/launcher3/compat/WallpaperColorsCompat.java b/src/com/android/launcher3/compat/WallpaperColorsCompat.java
new file mode 100644
index 0000000..fd08f94
--- /dev/null
+++ b/src/com/android/launcher3/compat/WallpaperColorsCompat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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.compat;
+
+import android.util.SparseIntArray;
+
+/**
+ * A compatibility layer around platform implementation of WallpaperColors
+ */
+public class WallpaperColorsCompat {
+
+    private final SparseIntArray mColors;
+    private final boolean mSupportsDarkText;
+
+    public WallpaperColorsCompat(SparseIntArray colors, boolean supportsDarkText) {
+        mColors = colors;
+        mSupportsDarkText = supportsDarkText;
+    }
+
+    /**
+     * A map of color code to their occurrences. The bigger the int, the more relevant the color.
+     */
+    public SparseIntArray getColors() {
+        return mColors;
+    }
+
+    public boolean supportsDarkText() {
+        return mSupportsDarkText;
+    }
+}
diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompat.java b/src/com/android/launcher3/compat/WallpaperManagerCompat.java
new file mode 100644
index 0000000..cbcabdf
--- /dev/null
+++ b/src/com/android/launcher3/compat/WallpaperManagerCompat.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.compat;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+
+import com.android.launcher3.Utilities;
+
+public abstract class WallpaperManagerCompat {
+
+    private static final Object sInstanceLock = new Object();
+    private static WallpaperManagerCompat sInstance;
+
+    public static WallpaperManagerCompat getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                context = context.getApplicationContext();
+
+                if (Utilities.isAtLeastO()) {
+                    try {
+                        sInstance = new WallpaperManagerCompatVOMR1(context);
+                    } catch (Exception e) {
+                        // The wallpaper APIs do not yet exist
+                    }
+                }
+                if (sInstance == null) {
+                    sInstance = new WallpaperManagerCompatVL(context);
+                }
+            }
+            return sInstance;
+        }
+    }
+
+
+    public abstract @Nullable WallpaperColorsCompat getWallpaperColors(int which);
+
+    public abstract void addOnColorsChangedListener(OnColorsChangedListenerCompat listener);
+
+    /**
+     * Interface definition for a callback to be invoked when colors change on a wallpaper.
+     */
+    public interface OnColorsChangedListenerCompat {
+
+        void onColorsChanged(WallpaperColorsCompat colors, int which);
+    }
+}
diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
new file mode 100644
index 0000000..b175f21
--- /dev/null
+++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017 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.compat;
+
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
+import android.app.IntentService;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.support.annotation.Nullable;
+import android.support.v7.graphics.Palette;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseIntArray;
+
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.Utilities;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class WallpaperManagerCompatVL extends WallpaperManagerCompat {
+
+    private static final String TAG = "WMCompatVL";
+
+    private static final String VERSION_PREFIX = "1,";
+    private static final String KEY_COLORS = "wallpaper_parsed_colors";
+    private static final String EXTRA_RECEIVER = "receiver";
+
+    private final ArrayList<OnColorsChangedListenerCompat> mListeners = new ArrayList<>();
+
+    private final Context mContext;
+    private WallpaperColorsCompat mColorsCompat;
+
+    WallpaperManagerCompatVL(Context context) {
+        mContext = context;
+
+        String colors = prefs(mContext).getString(KEY_COLORS, "");
+        int wallpaperId = -1;
+        if (colors.startsWith(VERSION_PREFIX)) {
+            Pair<Integer, WallpaperColorsCompat> storedValue = parseValue(colors);
+            wallpaperId = storedValue.first;
+            mColorsCompat = storedValue.second;
+        }
+
+        if (wallpaperId == -1 || wallpaperId != getWallpaperId(context)) {
+            reloadColors();
+        }
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                reloadColors();
+            }
+        }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+    }
+
+    @Nullable
+    @Override
+    public WallpaperColorsCompat getWallpaperColors(int which) {
+        return which == FLAG_SYSTEM ? mColorsCompat : null;
+    }
+
+    @Override
+    public void addOnColorsChangedListener(OnColorsChangedListenerCompat listener) {
+        mListeners.add(listener);
+    }
+
+    private void reloadColors() {
+        ResultReceiver receiver = new ResultReceiver(new Handler()) {
+
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                handleResult(resultData.getString(KEY_COLORS));
+            }
+        };
+        mContext.startService(new Intent(mContext, ColorExtractionService.class)
+                .putExtra(EXTRA_RECEIVER, receiver));
+    }
+
+    private void handleResult(String result) {
+        prefs(mContext).edit().putString(KEY_COLORS, result).apply();
+        mColorsCompat = parseValue(result).second;
+        for (OnColorsChangedListenerCompat listener : mListeners) {
+            listener.onColorsChanged(mColorsCompat, FLAG_SYSTEM);
+        }
+    }
+
+    private static SharedPreferences prefs(Context context) {
+        return context.getSharedPreferences(
+                LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
+    }
+
+    private static final int getWallpaperId(Context context) {
+        if (!Utilities.ATLEAST_NOUGAT) {
+            return -1;
+        }
+        return context.getSystemService(WallpaperManager.class).getWallpaperId(FLAG_SYSTEM);
+    }
+
+    /**
+     * Parses the stored value and returns the wallpaper id and wallpaper colors.
+     */
+    private static Pair<Integer, WallpaperColorsCompat> parseValue(String value) {
+        String[] parts = value.split(",");
+        Integer wallpaperId = Integer.parseInt(parts[1]);
+        if (parts.length == 2) {
+            return Pair.create(wallpaperId, null);
+        }
+
+        SparseIntArray colors = new SparseIntArray((parts.length - 2) / 2);
+        for (int i = 2; i < parts.length; i += 2) {
+            colors.put(Integer.parseInt(parts[i]), Integer.parseInt(parts[i + 1]));
+        }
+        return Pair.create(wallpaperId, new WallpaperColorsCompat(colors, false));
+    }
+
+    /**
+     * Intent service to handle color extraction
+     */
+    public static class ColorExtractionService extends IntentService {
+        private static final int MAX_WALLPAPER_EXTRACTION_AREA = 112 * 112;
+
+        public ColorExtractionService() {
+            super("ColorExtractionService");
+        }
+
+        /**
+         * Extracts the wallpaper colors and sends the result back through the receiver.
+         */
+        @Override
+        protected void onHandleIntent(@Nullable Intent intent) {
+            int wallpaperId = getWallpaperId(this);
+
+            Bitmap bitmap = null;
+            Drawable drawable = null;
+
+            WallpaperManager wm = WallpaperManager.getInstance(this);
+            WallpaperInfo info = wm.getWallpaperInfo();
+            if (info != null) {
+                // For live wallpaper, extract colors from thumbnail
+                drawable = info.loadThumbnail(getPackageManager());
+            } else {
+                if (Utilities.ATLEAST_NOUGAT) {
+                    try (ParcelFileDescriptor fd = wm.getWallpaperFile(FLAG_SYSTEM)) {
+                        BitmapRegionDecoder decoder = BitmapRegionDecoder
+                                .newInstance(fd.getFileDescriptor(), false);
+
+                        int requestedArea = decoder.getWidth() * decoder.getHeight();
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+
+                        if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) {
+                            double areaRatio =
+                                    MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea;
+                            double nearestPowOf2 =
+                                    Math.floor(Math.log(areaRatio) / (2 * Math.log(2)));
+                            options.inSampleSize = (int) Math.pow(2, nearestPowOf2);
+                        }
+                        Rect region = new Rect(0, 0, decoder.getWidth(), decoder.getHeight());
+                        bitmap = decoder.decodeRegion(region, options);
+                        decoder.recycle();
+                    } catch (IOException | NullPointerException e) {
+                        Log.e(TAG, "Fetching partial bitmap failed, trying old method", e);
+                    }
+                }
+                if (bitmap == null) {
+                    drawable = wm.getDrawable();
+                }
+            }
+
+            if (drawable != null) {
+                // Calculate how big the bitmap needs to be.
+                // This avoids unnecessary processing and allocation inside Palette.
+                final int requestedArea = drawable.getIntrinsicWidth() *
+                        drawable.getIntrinsicHeight();
+                double scale = 1;
+                if (requestedArea > MAX_WALLPAPER_EXTRACTION_AREA) {
+                    scale = Math.sqrt(MAX_WALLPAPER_EXTRACTION_AREA / (double) requestedArea);
+                }
+                bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * scale),
+                        (int) (drawable.getIntrinsicHeight() * scale), Bitmap.Config.ARGB_8888);
+                final Canvas bmpCanvas = new Canvas(bitmap);
+                drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
+                drawable.draw(bmpCanvas);
+            }
+
+            String value = VERSION_PREFIX + wallpaperId;
+
+            if (bitmap != null) {
+                Palette palette = Palette.from(bitmap).generate();
+                bitmap.recycle();
+
+                StringBuilder builder = new StringBuilder(value);
+                for (Palette.Swatch swatch : palette.getSwatches()) {
+                    builder.append(',')
+                            .append(swatch.getRgb())
+                            .append(',')
+                            .append(swatch.getPopulation());
+                }
+                value = builder.toString();
+            }
+
+            ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RECEIVER);
+            Bundle result = new Bundle();
+            result.putString(KEY_COLORS, value);
+            receiver.send(0, result);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java
new file mode 100644
index 0000000..c74ccc0
--- /dev/null
+++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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.compat;
+
+import android.annotation.TargetApi;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseIntArray;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
+
+@TargetApi(Build.VERSION_CODES.O)
+public class WallpaperManagerCompatVOMR1 extends WallpaperManagerCompat {
+
+    private static final String TAG = "WMCompatVOMR1";
+
+    private final WallpaperManager mWm;
+
+    private final Class mOCLClass;
+    private final Method mAddOCLMethod;
+
+    private final Method mWCGetMethod;
+    private final Method mWCGetColorsMethod;
+    private final Method mWCSupportsDarkTextMethod;
+
+    WallpaperManagerCompatVOMR1(Context context) throws Exception {
+        mWm = context.getSystemService(WallpaperManager.class);
+
+        mOCLClass = Class.forName("android.app.WallpaperManager$OnColorsChangedListener");
+        mAddOCLMethod = WallpaperManager.class.getDeclaredMethod(
+                "addOnColorsChangedListener", mOCLClass);
+
+        mWCGetMethod = WallpaperManager.class.getDeclaredMethod("getWallpaperColors", int.class);
+        Class wallpaperColorsClass = mWCGetMethod.getReturnType();
+        mWCGetColorsMethod = wallpaperColorsClass.getDeclaredMethod("getColors");
+        mWCSupportsDarkTextMethod = wallpaperColorsClass.getDeclaredMethod("supportsDarkText");
+    }
+
+    @Nullable
+    @Override
+    public WallpaperColorsCompat getWallpaperColors(int which) {
+        try {
+            return convertColorsObject(mWCGetMethod.invoke(mWm, which));
+        } catch (Exception e) {
+            Log.e(TAG, "Error calling wallpaper API", e);
+            return null;
+        }
+    }
+
+    @Override
+    public void addOnColorsChangedListener(final OnColorsChangedListenerCompat listener) {
+        Object onChangeListener = Proxy.newProxyInstance(
+                WallpaperManager.class.getClassLoader(),
+                new Class[]{mOCLClass},
+                new InvocationHandler() {
+                    @Override
+                    public Object invoke(Object o, Method method, Object[] objects)
+                            throws Throwable {
+                        String methodName = method.getName();
+                        if ("onColorsChanged".equals(methodName)) {
+                            listener.onColorsChanged(
+                                    convertColorsObject(objects[0]), (Integer) objects[1]);
+                        } else if ("toString".equals(methodName)) {
+                            return listener.toString();
+                        }
+                        return null;
+                    }
+                });
+        try {
+            mAddOCLMethod.invoke(mWm, onChangeListener);
+        } catch (Exception e) {
+            Log.e(TAG, "Error calling wallpaper API", e);
+        }
+    }
+
+    private WallpaperColorsCompat convertColorsObject(Object colors) throws Exception {
+        if (colors == null) {
+            return null;
+        }
+        List<Pair<Color, Integer>> list = (List) mWCGetColorsMethod.invoke(colors);
+        boolean supportsDarkText = (Boolean) mWCSupportsDarkTextMethod.invoke(colors);
+        SparseIntArray colorMap = new SparseIntArray(list.size());
+        for (Pair<Color, Integer> color : list) {
+            colorMap.put(color.first.toArgb(), color.second);
+        }
+        return new WallpaperColorsCompat(colorMap, supportsDarkText);
+    }
+}
diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java
index e60a1bd..96fe31a 100644
--- a/src/com/android/launcher3/dynamicui/ExtractedColors.java
+++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java
@@ -170,7 +170,7 @@
         try {
             WallpaperManager.class.getDeclaredMethod("getWallpaperColors", int.class);
             ColorExtractor extractor = new ColorExtractor(context);
-            ColorExtractor.GradientColors colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM);
+            ColorExtractor.GradientColors colors = extractor.getColors();
             setColorAtIndex(ALLAPPS_GRADIENT_MAIN_INDEX, colors.getMainColor());
             setColorAtIndex(ALLAPPS_GRADIENT_SECONDARY_INDEX, colors.getSecondaryColor());
         } catch (NoSuchMethodException e) {
diff --git a/src/com/android/launcher3/dynamicui/colorextraction/ColorExtractor.java b/src/com/android/launcher3/dynamicui/colorextraction/ColorExtractor.java
index 153b529..9855867 100644
--- a/src/com/android/launcher3/dynamicui/colorextraction/ColorExtractor.java
+++ b/src/com/android/launcher3/dynamicui/colorextraction/ColorExtractor.java
@@ -1,16 +1,15 @@
 package com.android.launcher3.dynamicui.colorextraction;
 
-import android.app.WallpaperManager;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
 import android.content.Context;
 import android.graphics.Color;
-import android.os.Parcelable;
-import android.util.Log;
 
+import com.android.launcher3.compat.WallpaperColorsCompat;
+import com.android.launcher3.compat.WallpaperManagerCompat;
 import com.android.launcher3.dynamicui.colorextraction.types.ExtractionType;
 import com.android.launcher3.dynamicui.colorextraction.types.Tonal;
 
-import java.lang.reflect.Method;
-
 
 /**
  * Class to process wallpaper colors and generate a tonal palette based on them.
@@ -18,59 +17,32 @@
  * TODO remove this class if available by platform
  */
 public class ColorExtractor {
-    private static final String TAG = "ColorExtractor";
     private static final int FALLBACK_COLOR = Color.WHITE;
 
+    private final Context mContext;
     private int mMainFallbackColor = FALLBACK_COLOR;
     private int mSecondaryFallbackColor = FALLBACK_COLOR;
     private final GradientColors mSystemColors;
-    private final GradientColors mLockColors;
-    private final Context mContext;
     private final ExtractionType mExtractionType;
 
     public ColorExtractor(Context context) {
         mContext = context;
         mSystemColors = new GradientColors();
-        mLockColors = new GradientColors();
         mExtractionType = new Tonal();
-        WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class);
 
-        if (wallpaperManager == null) {
-            Log.w(TAG, "Can't listen to color changes!");
-        } else {
-            Parcelable wallpaperColorsObj;
-            try {
-                Method method = WallpaperManager.class
-                        .getDeclaredMethod("getWallpaperColors", int.class);
-
-                wallpaperColorsObj = (Parcelable) method.invoke(wallpaperManager,
-                        WallpaperManager.FLAG_SYSTEM);
-                extractInto(new WallpaperColorsCompat(wallpaperColorsObj), mSystemColors);
-                wallpaperColorsObj = (Parcelable) method.invoke(wallpaperManager,
-                        WallpaperManager.FLAG_LOCK);
-                extractInto(new WallpaperColorsCompat(wallpaperColorsObj), mLockColors);
-            } catch (Exception e) {
-                Log.e(TAG, "reflection failed", e);
-            }
-        }
+        extractFrom(WallpaperManagerCompat.getInstance(context).getWallpaperColors(FLAG_SYSTEM));
     }
 
-    public GradientColors getColors(int which) {
-        if (which == WallpaperManager.FLAG_LOCK) {
-            return mLockColors;
-        } else if (which == WallpaperManager.FLAG_SYSTEM) {
-            return mSystemColors;
-        } else {
-            throw new IllegalArgumentException("which should be either FLAG_SYSTEM or FLAG_LOCK");
-        }
+    public GradientColors getColors() {
+        return mSystemColors;
     }
 
-    private void extractInto(WallpaperColorsCompat inWallpaperColors, GradientColors outGradientColors) {
-        applyFallback(outGradientColors);
+    private void extractFrom(WallpaperColorsCompat inWallpaperColors) {
+        applyFallback(mSystemColors);
         if (inWallpaperColors == null) {
             return;
         }
-        mExtractionType.extractInto(inWallpaperColors, outGradientColors);
+        mExtractionType.extractInto(inWallpaperColors, mSystemColors);
     }
 
     private void applyFallback(GradientColors outGradientColors) {
diff --git a/src/com/android/launcher3/dynamicui/colorextraction/WallpaperColorsCompat.java b/src/com/android/launcher3/dynamicui/colorextraction/WallpaperColorsCompat.java
deleted file mode 100644
index f80a675..0000000
--- a/src/com/android/launcher3/dynamicui/colorextraction/WallpaperColorsCompat.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.android.launcher3.dynamicui.colorextraction;
-
-import android.graphics.Color;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Pair;
-
-import java.util.List;
-
-/**
- * A wrapper around platform implementation of WallpaperColors until the
- * updated SDK is available.
- *
- * TODO remove this class if available by platform
- */
-public class WallpaperColorsCompat implements Parcelable {
-
-    private final Parcelable mObject;
-
-    public WallpaperColorsCompat(Parcelable object) {
-        mObject = object;
-    }
-
-    private Object invokeMethod(String methodName) {
-        try {
-            return mObject.getClass().getDeclaredMethod(methodName).invoke(mObject);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int i) {
-        parcel.writeParcelable(mObject, i);
-    }
-
-    public static final Parcelable.Creator<WallpaperColorsCompat> CREATOR =
-            new Parcelable.Creator<WallpaperColorsCompat>() {
-                public WallpaperColorsCompat createFromParcel(Parcel source) {
-                    Parcelable object = source.readParcelable(null);
-                    return new WallpaperColorsCompat(object);
-                }
-
-                public WallpaperColorsCompat[] newArray(int size) {
-                    return new WallpaperColorsCompat[size];
-                }
-            };
-
-    public List<Pair<Color, Integer>> getColors() {
-        try {
-            return (List<Pair<Color, Integer>>) invokeMethod("getColors");
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    public boolean supportsDarkText() {
-        try {
-            return (Boolean) invokeMethod("supportsDarkText");
-        } catch (Exception e) {
-            return false;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/dynamicui/colorextraction/types/ExtractionType.java b/src/com/android/launcher3/dynamicui/colorextraction/types/ExtractionType.java
index 166c7c6..88958a4 100644
--- a/src/com/android/launcher3/dynamicui/colorextraction/types/ExtractionType.java
+++ b/src/com/android/launcher3/dynamicui/colorextraction/types/ExtractionType.java
@@ -1,7 +1,7 @@
 package com.android.launcher3.dynamicui.colorextraction.types;
 
+import com.android.launcher3.compat.WallpaperColorsCompat;
 import com.android.launcher3.dynamicui.colorextraction.ColorExtractor;
-import com.android.launcher3.dynamicui.colorextraction.WallpaperColorsCompat;
 
 
 /**
diff --git a/src/com/android/launcher3/dynamicui/colorextraction/types/Tonal.java b/src/com/android/launcher3/dynamicui/colorextraction/types/Tonal.java
index 1e165a3..800dcd2 100644
--- a/src/com/android/launcher3/dynamicui/colorextraction/types/Tonal.java
+++ b/src/com/android/launcher3/dynamicui/colorextraction/types/Tonal.java
@@ -6,11 +6,15 @@
 import android.support.v4.graphics.ColorUtils;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseIntArray;
 
+import com.android.launcher3.compat.WallpaperColorsCompat;
 import com.android.launcher3.dynamicui.colorextraction.ColorExtractor;
-import com.android.launcher3.dynamicui.colorextraction.WallpaperColorsCompat;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
 
 
 /**
@@ -29,9 +33,10 @@
     private static final float MIN_COLOR_OCCURRENCE = 0.1f;
     private static final float MIN_LUMINOSITY = 0.5f;
 
-    public void extractInto(WallpaperColorsCompat wallpaperColors,
-                            ColorExtractor.GradientColors gradientColors) {
-        if (wallpaperColors.getColors().size() == 0) {
+    public void extractInto(
+            WallpaperColorsCompat wallpaperColors, ColorExtractor.GradientColors gradientColors) {
+        SparseIntArray colorsArray = wallpaperColors.getColors();
+        if (colorsArray.size() == 0) {
             return;
         }
         // Tonal is not really a sort, it takes a color from the extracted
@@ -39,24 +44,30 @@
         // palettes. The best fit is tweaked to be closer to the source color
         // and replaces the original palette
 
+        List<Pair<Integer, Integer>> colors = new ArrayList<>(colorsArray.size());
+        for (int i = colorsArray.size() - 1; i >= 0; i --) {
+            colors.add(Pair.create(colorsArray.keyAt(i), colorsArray.valueAt(i)));
+        }
+
         // First find the most representative color in the image
-        populationSort(wallpaperColors);
+        populationSort(colors);
+
         // Calculate total
         int total = 0;
-        for (Pair<Color, Integer> weightedColor : wallpaperColors.getColors()) {
+        for (Pair<Integer, Integer> weightedColor : colors) {
             total += weightedColor.second;
         }
 
         // Get bright colors that occur often enough in this image
-        Pair<Color, Integer> bestColor = null;
+        Pair<Integer, Integer> bestColor = null;
         float[] hsl = new float[3];
-        for (Pair<Color, Integer> weightedColor : wallpaperColors.getColors()) {
+        for (Pair<Integer, Integer> weightedColor : colors) {
             float colorOccurrence = weightedColor.second / (float) total;
             if (colorOccurrence < MIN_COLOR_OCCURRENCE) {
                 break;
             }
 
-            int colorValue = weightedColor.first.toArgb();
+            int colorValue = weightedColor.first;
             ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue),
                     Color.blue(colorValue), hsl);
             if (hsl[2] > MIN_LUMINOSITY) {
@@ -66,10 +77,10 @@
 
         // Fallback to first color
         if (bestColor == null) {
-            bestColor = wallpaperColors.getColors().get(0);
+            bestColor = colors.get(0);
         }
 
-        int colorValue = bestColor.first.toArgb();
+        int colorValue = bestColor.first;
         ColorUtils.RGBToHSL(Color.red(colorValue), Color.green(colorValue), Color.blue(colorValue),
                 hsl);
         hsl[0] /= 360.0f; // normalize
@@ -105,10 +116,10 @@
         gradientColors.setSecondaryColor(ColorUtils.HSLToColor(hsl));
     }
 
-    private static void populationSort(@NonNull WallpaperColorsCompat wallpaperColors) {
-        wallpaperColors.getColors().sort(new Comparator<Pair<Color, Integer>>() {
+    private static void populationSort(@NonNull List<Pair<Integer, Integer>> wallpaperColors) {
+        Collections.sort(wallpaperColors, new Comparator<Pair<Integer, Integer>>() {
             @Override
-            public int compare(Pair<Color, Integer> a, Pair<Color, Integer> b) {
+            public int compare(Pair<Integer, Integer> a, Pair<Integer, Integer> b) {
                 return b.second - a.second;
             }
         });