Merge "Fullscreen image preview (Part 3)" into ub-launcher3-master
diff --git a/res/layout/grid_item_rotation.xml b/res/layout/grid_item_rotation.xml
deleted file mode 100755
index a255fc8..0000000
--- a/res/layout/grid_item_rotation.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-     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.
--->
-<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/daily_refresh"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_marginTop="8dp"
-    android:background="@color/secondary_color"
-    android:clickable="true"
-    android:focusable="true"
-    android:foreground="?attr/selectableItemBackground"
-    android:paddingEnd="@dimen/rotation_tile_padding_right"
-    android:paddingLeft="@dimen/rotation_tile_padding_left"
-    android:paddingRight="@dimen/rotation_tile_padding_right"
-    android:paddingStart="@dimen/rotation_tile_padding_left"
-    app:cardCornerRadius="?android:dialogCornerRadius"
-    app:cardElevation="0dp">
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|center_horizontal"
-        android:layout_marginBottom="@dimen/rotation_tile_textarea_margin_bottom"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@+id/rotation_tile_title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="@dimen/rotation_tile_textarea_title_margin_bottom"
-            android:text="@string/daily_refresh_tile_title"
-            android:textColor="@color/rotation_tile_enabled_title_text_color"
-            android:textSize="@dimen/rotation_tile_title_size" />
-
-        <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <ImageView
-                android:id="@+id/rotation_tile_refresh_icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="@dimen/rotation_tile_refresh_icon_offset"
-                android:layout_marginStart="@dimen/rotation_tile_refresh_icon_offset"
-                android:src="@drawable/ic_refresh_18px" />
-
-            <TextView
-                android:id="@+id/rotation_tile_message"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="@dimen/rotation_tile_textarea_subtitle_margin_left"
-                android:layout_marginStart="@dimen/rotation_tile_textarea_subtitle_margin_left"
-                android:text="@string/daily_refresh_tile_subtitle"
-                android:textColor="@color/rotation_tile_enabled_subtitle_text_color"
-                android:textSize="@dimen/abc_text_size_caption_material" />
-
-        </LinearLayout>
-
-    </LinearLayout>
-
-</androidx.cardview.widget.CardView>
diff --git a/res/values-notnight-v26/picker_colors.xml b/res/values-notnight-v26/picker_colors.xml
index 0cadb67..9b8150d 100755
--- a/res/values-notnight-v26/picker_colors.xml
+++ b/res/values-notnight-v26/picker_colors.xml
@@ -37,15 +37,6 @@
 
     <color name="select_wallpaper_header_text_color">@color/textColorSecondary</color>
 
-    <color name="rotation_tile_enabled_background_color">@color/accent_color</color>
-    <color name="rotation_tile_enabled_title_text_color">@color/material_white_text</color>
-    <color name="rotation_tile_enabled_subtitle_text_color">@color/white_70_alpha</color>
-    <color name="rotation_tile_enabled_refresh_icon_color">@color/white_70_alpha</color>
-    <color name="rotation_tile_not_enabled_background_color">@color/secondary_color</color>
-    <color name="rotation_tile_not_enabled_title_text_color">@color/accent_color</color>
-    <color name="rotation_tile_not_enabled_subtitle_text_color">@color/black_54_alpha</color>
-    <color name="rotation_tile_not_enabled_refresh_icon_color">@color/black_54_alpha</color>
-
     <color name="individual_tile_title_scrim_color">@color/white_80_alpha</color>
     <color name="individual_tile_title_text_color">@color/textColorPrimary</color>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 45d73ce..56bc73f 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -86,15 +86,6 @@
     <dimen name="select_wallpaper_header_height">48dp</dimen>
     <dimen name="select_wallpaper_header_margin_left">12dp</dimen>
 
-    <!-- Dimensions for "daily refresh" rotation tile in individual picker. -->
-    <dimen name="rotation_tile_padding_left">16dp</dimen>
-    <dimen name="rotation_tile_padding_right">16dp</dimen>
-    <dimen name="rotation_tile_title_size">14sp</dimen>
-    <dimen name="rotation_tile_textarea_margin_bottom">16dp</dimen>
-    <dimen name="rotation_tile_textarea_title_margin_bottom">2dp</dimen>
-    <dimen name="rotation_tile_textarea_subtitle_margin_left">4dp</dimen>
-    <dimen name="rotation_tile_refresh_icon_offset">-3dp</dimen>
-
     <!-- Dimensions for wallpaper tiles in individual picker when in desktop mode. -->
     <dimen name="tile_desktop_progress_bar_size">40dp</dimen>
 
diff --git a/res/values/picker_colors.xml b/res/values/picker_colors.xml
index 3780a30..ee7d998 100755
--- a/res/values/picker_colors.xml
+++ b/res/values/picker_colors.xml
@@ -56,15 +56,6 @@
 
     <color name="select_wallpaper_header_text_color">@color/white_60_alpha</color>
 
-    <color name="rotation_tile_enabled_background_color">@color/accent_color</color>
-    <color name="rotation_tile_enabled_title_text_color">@color/material_white_100</color>
-    <color name="rotation_tile_enabled_subtitle_text_color">@color/white_88_alpha</color>
-    <color name="rotation_tile_enabled_refresh_icon_color">@color/white_88_alpha</color>
-    <color name="rotation_tile_not_enabled_background_color">@color/secondary_color</color>
-    <color name="rotation_tile_not_enabled_title_text_color">@color/material_white_100</color>
-    <color name="rotation_tile_not_enabled_subtitle_text_color">@color/white_88_alpha</color>
-    <color name="rotation_tile_not_enabled_refresh_icon_color">@color/white_88_alpha</color>
-
     <color name="individual_tile_title_scrim_color">@color/translucent_black</color>
     <color name="individual_tile_title_text_color">@color/material_white_100</color>
 
diff --git a/src/com/android/wallpaper/picker/CategoryFragment.java b/src/com/android/wallpaper/picker/CategoryFragment.java
index 007df5e..27b57b2 100755
--- a/src/com/android/wallpaper/picker/CategoryFragment.java
+++ b/src/com/android/wallpaper/picker/CategoryFragment.java
@@ -22,8 +22,10 @@
 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
 
 import android.app.Activity;
+import android.app.WallpaperColors;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.net.Uri;
@@ -74,6 +76,7 @@
 import com.android.wallpaper.util.WallpaperConnection.WallpaperConnectionListener;
 import com.android.wallpaper.widget.LiveTileOverlay;
 import com.android.wallpaper.widget.PreviewPager;
+import com.android.wallpaper.widget.WallpaperColorsLoader;
 
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.MemoryCategory;
@@ -149,6 +152,7 @@
     private ImageView mHomeImageWallpaper;
     private boolean mIsCollapsingByUserSelecting;
     private TimeTicker mTicker;
+    private ImageView mLockIcon;
     private TextView mLockTime;
     private TextView mLockDate;
 
@@ -178,6 +182,7 @@
         lockscreenPreviewCard.findViewById(R.id.workspace_surface).setVisibility(View.GONE);
         lockscreenPreviewCard.findViewById(R.id.wallpaper_surface).setVisibility(View.GONE);
         lockscreenPreviewCard.findViewById(R.id.lock_overlay).setVisibility(View.VISIBLE);
+        mLockIcon = lockscreenPreviewCard.findViewById(R.id.lock_icon);
         mLockTime = lockscreenPreviewCard.findViewById(R.id.lock_time);
         mLockDate = lockscreenPreviewCard.findViewById(R.id.lock_date);
         mWallPaperPreviews.add(lockscreenPreviewCard);
@@ -676,6 +681,12 @@
             } else {
                 LiveTileOverlay.INSTANCE.detach(thumbnailView.getOverlay());
             }
+
+            WallpaperColorsLoader.getWallpaperColors(
+                    wallpaperInfo.getThumbAsset(getContext()),
+                    thumbnailView.getMeasuredWidth(),
+                    thumbnailView.getMeasuredHeight(),
+                    this::updateLockScreenPreviewColor);
         }
 
         thumbnailView.setOnClickListener(view -> {
@@ -684,6 +695,16 @@
         });
     }
 
+    private void updateLockScreenPreviewColor(WallpaperColors colors) {
+        int color = getContext().getColor(
+                (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
+                        ? R.color.text_color_light
+                        : R.color.text_color_dark);
+        mLockIcon.setImageTintList(ColorStateList.valueOf(color));
+        mLockDate.setTextColor(color);
+        mLockTime.setTextColor(color);
+    }
+
     private void updateWallpaperSurface() {
         mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback);
     }
diff --git a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
index 2e32ba1..6da3372 100755
--- a/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
+++ b/src/com/android/wallpaper/picker/individual/IndividualPickerFragment.java
@@ -27,7 +27,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Point;
-import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
@@ -38,13 +37,10 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.cardview.widget.CardView;
 import androidx.fragment.app.DialogFragment;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -54,7 +50,6 @@
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.asset.Asset.DrawableLoadedListener;
-import com.android.wallpaper.config.Flags;
 import com.android.wallpaper.model.Category;
 import com.android.wallpaper.model.CategoryProvider;
 import com.android.wallpaper.model.CategoryReceiver;
@@ -65,7 +60,6 @@
 import com.android.wallpaper.model.WallpaperRotationInitializer;
 import com.android.wallpaper.model.WallpaperRotationInitializer.Listener;
 import com.android.wallpaper.model.WallpaperRotationInitializer.NetworkPreference;
-import com.android.wallpaper.model.WallpaperRotationInitializer.RotationInitializationState;
 import com.android.wallpaper.module.FormFactorChecker;
 import com.android.wallpaper.module.FormFactorChecker.FormFactor;
 import com.android.wallpaper.module.Injector;
@@ -288,20 +282,6 @@
         }
     }
 
-    private static int getResIdForRotationState(@RotationInitializationState int rotationState) {
-        switch (rotationState) {
-            case WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED:
-                return R.string.daily_refresh_tile_subtitle;
-            case WallpaperRotationInitializer.ROTATION_HOME_ONLY:
-                return R.string.home_screen_message;
-            case WallpaperRotationInitializer.ROTATION_HOME_AND_LOCK:
-                return R.string.home_and_lock_short_label;
-            default:
-                Log.e(TAG, "Unknown rotation intialization state: " + rotationState);
-                return R.string.home_screen_message;
-        }
-    }
-
     private void updateDesktopDailyRotationThumbnail(DesktopRotationHolder holder) {
         int wallpapersIndex = mRandom.nextInt(mWallpapers.size());
         Asset newThumbnailAsset = mWallpapers.get(wallpapersIndex).getThumbAsset(
@@ -600,25 +580,10 @@
             mStagedSetWallpaperErrorDialogFragment = null;
         }
 
-        if (isRotationEnabled()) {
-            if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
-                // Refresh the state of the "start rotation" in case something changed the current daily
-                // rotation while this fragment was paused.
-                RotationHolder rotationHolder = (RotationHolder) mImageGrid
-                        .findViewHolderForAdapterPosition(
-                                SPECIAL_FIXED_TILE_ADAPTER_POSITION);
-                // The RotationHolder may be null if the RecyclerView has not created the view
-                // holder yet.
-                if (rotationHolder != null && Flags.dynamicStartRotationTileEnabled) {
-                    refreshRotationHolder(rotationHolder);
-                }
-            } else if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
-                if (mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
-                    // Must be resuming from a previously stopped state, so re-schedule the update of the
-                    // daily wallpapers tile thumbnail.
-                    mUpdateDailyWallpaperThumbRunnable.run();
-                }
-            }
+        if (shouldShowRotationTile() && mWasUpdateRunnableRun && !mWallpapers.isEmpty()) {
+            // Must be resuming from a previously stopped state, so re-schedule the update of the
+            // daily wallpapers tile thumbnail.
+            mUpdateDailyWallpaperThumbRunnable.run();
         }
     }
 
@@ -698,31 +663,6 @@
         mTestingMode = testingMode;
     }
 
-    /**
-     * Asynchronously fetches the refreshed rotation initialization state that is up to date with the
-     * state of the user's device and binds the state of the current category's rotation to the "start
-     * rotation" tile.
-     */
-    private void refreshRotationHolder(RotationHolder rotationHolder) {
-        mWallpaperRotationInitializer.fetchRotationInitializationState(getContext(),
-                rotationState -> {
-                    // Update the UI state of the "start rotation" tile displayed on screen.
-                    // Do this in a Handler so it is scheduled at the end of the message queue.
-                    // This is necessary to ensure we do not remove or add data from the adapter
-                    // while the layout is still being computed. RecyclerView documentation
-                    // therefore recommends performing such changes in a Handler.
-                    new Handler().post(() -> {
-                        // A config change may have destroyed the activity since the refresh
-                        // started, so check for that to avoid an NPE.
-                        if (getActivity() == null) {
-                            return;
-                        }
-
-                        rotationHolder.bindRotationInitializationState(rotationState);
-                    });
-                });
-    }
-
     @Override
     public void startRotation(@NetworkPreference final int networkPreference) {
         if (!isRotationEnabled()) {
@@ -851,7 +791,6 @@
         mCurrentWallpaperBottomSheetPresenter.setCurrentWallpapersExpanded(true);
     }
 
-
     @Override
     public void onSet(int destination) {
         if (mSelectedWallpaperInfo == null) {
@@ -989,7 +928,7 @@
             return;
         }
         int index = mWallpapers.indexOf(wallpaperInfo);
-        index = (isRotationEnabled() || mCategory.supportsCustomPhotos())
+        index = (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
                 ? index + 1 : index;
         ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
         if (holder != null) {
@@ -1005,7 +944,7 @@
             return;
         }
         int index = mWallpapers.indexOf(wallpaperInfo);
-        index = (isRotationEnabled() || mCategory.supportsCustomPhotos())
+        index = (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
                 ? index + 1 : index;
         ViewHolder holder = mImageGrid.findViewHolderForAdapterPosition(index);
         if (holder != null) {
@@ -1056,90 +995,8 @@
         }
     }
 
-    /**
-     * ViewHolder subclass for "daily refresh" tile in the RecyclerView, only shown if rotation is
-     * enabled for this category.
-     */
-    private class RotationHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
-
-        private CardView mTileLayout;
-        private TextView mRotationMessage;
-        private TextView mRotationTitle;
-        private ImageView mRefreshIcon;
-
-        RotationHolder(View itemView) {
-            super(itemView);
-            itemView.setOnClickListener(this);
-
-            mTileLayout = itemView.findViewById(R.id.daily_refresh);
-            mRotationMessage = itemView.findViewById(R.id.rotation_tile_message);
-            mRotationTitle = itemView.findViewById(R.id.rotation_tile_title);
-            mRefreshIcon = itemView.findViewById(R.id.rotation_tile_refresh_icon);
-            mTileLayout.getLayoutParams().height = mTileSizePx.y;
-
-            // If the feature flag for "dynamic start rotation tile" is not enabled, fall back to the
-            // static UI with a blue accent color background and "Tap to turn on" text.
-            if (!Flags.dynamicStartRotationTileEnabled) {
-                mTileLayout.setBackgroundColor(
-                        getResources().getColor(R.color.rotation_tile_enabled_background_color));
-                mRotationMessage.setText(R.string.daily_refresh_tile_subtitle);
-                mRotationTitle.setTextColor(
-                        getResources().getColor(R.color.rotation_tile_enabled_title_text_color));
-                mRotationMessage.setTextColor(
-                        getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color));
-                mRefreshIcon.setColorFilter(
-                        getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color),
-                        Mode.SRC_IN);
-                return;
-            }
-
-            // Initialize the state of the "start rotation" tile (i.e., whether it is gray or blue to
-            // indicate if rotation is turned on for the current category) with last-known rotation state
-            // that could be stale. The last-known rotation state is correct in most cases and is a good
-            // starting point but may not be accurate if the user set a wallpaper through a 3rd party app
-            // while this app was paused.
-            int rotationState = mWallpaperRotationInitializer.getRotationInitializationStateDirty(
-                    getContext());
-            bindRotationInitializationState(rotationState);
-        }
-
-        @Override
-        public void onClick(View v) {
-            DialogFragment startRotationDialogFragment = new StartRotationDialogFragment();
-            startRotationDialogFragment.setTargetFragment(
-                    IndividualPickerFragment.this, UNUSED_REQUEST_CODE);
-            startRotationDialogFragment.show(getFragmentManager(), TAG_START_ROTATION_DIALOG);
-        }
-
-        /**
-         * Binds the provided rotation initialization state to the RotationHolder and updates the tile's
-         * UI to be in sync with the state (i.e., message and color appropriately reflect the state to
-         * the user).
-         */
-        void bindRotationInitializationState(@RotationInitializationState int rotationState) {
-            int newBackgroundColor =
-                    (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
-                            ? getResources().getColor(R.color.rotation_tile_not_enabled_background_color)
-                            : getResources().getColor(R.color.rotation_tile_enabled_background_color);
-            int newTitleTextColor =
-                    (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
-                            ? getResources().getColor(R.color.rotation_tile_not_enabled_title_text_color)
-                            : getResources().getColor(R.color.rotation_tile_enabled_title_text_color);
-            int newSubtitleTextColor =
-                    (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
-                            ? getResources().getColor(R.color.rotation_tile_not_enabled_subtitle_text_color)
-                            : getResources().getColor(R.color.rotation_tile_enabled_subtitle_text_color);
-            int newRefreshIconColor =
-                    (rotationState == WallpaperRotationInitializer.ROTATION_NOT_INITIALIZED)
-                            ? getResources().getColor(R.color.rotation_tile_not_enabled_refresh_icon_color)
-                            : getResources().getColor(R.color.rotation_tile_enabled_refresh_icon_color);
-
-            mTileLayout.setCardBackgroundColor(newBackgroundColor);
-            mRotationTitle.setTextColor(newTitleTextColor);
-            mRotationMessage.setText(getResIdForRotationState(rotationState));
-            mRotationMessage.setTextColor(newSubtitleTextColor);
-            mRefreshIcon.setColorFilter(newRefreshIconColor, Mode.SRC_IN);
-        }
+    private boolean shouldShowRotationTile() {
+        return mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP && isRotationEnabled();
     }
 
     /**
@@ -1178,7 +1035,7 @@
 
         @Override
         public int getItemViewType(int position) {
-            if (isRotationEnabled() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
+            if (shouldShowRotationTile() && position == SPECIAL_FIXED_TILE_ADAPTER_POSITION) {
                 return ITEM_VIEW_TYPE_ROTATION;
             }
 
@@ -1213,26 +1070,18 @@
 
         @Override
         public int getItemCount() {
-            return (isRotationEnabled() || mCategory.supportsCustomPhotos())
+            return (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
                     ? mWallpapers.size() + 1
                     : mWallpapers.size();
         }
 
         private ViewHolder createRotationHolder(ViewGroup parent) {
             LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
-            View view;
-
-            if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
-                view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
-                SelectionAnimator selectionAnimator =
-                        new CheckmarkSelectionAnimator(getActivity(), view);
-                return new DesktopRotationHolder(
-                        getActivity(), mTileSizePx.y, view, selectionAnimator,
-                        IndividualPickerFragment.this);
-            } else { // MOBILE
-                view = layoutInflater.inflate(R.layout.grid_item_rotation, parent, false);
-                return new RotationHolder(view);
-            }
+            View view = layoutInflater.inflate(R.layout.grid_item_rotation_desktop, parent, false);
+            SelectionAnimator selectionAnimator =
+                    new CheckmarkSelectionAnimator(getActivity(), view);
+            return new DesktopRotationHolder(getActivity(), mTileSizePx.y, view, selectionAnimator,
+                    IndividualPickerFragment.this);
         }
 
         private ViewHolder createIndividualHolder(ViewGroup parent) {
@@ -1383,7 +1232,7 @@
         }
 
         void onBindIndividualHolder(ViewHolder holder, int position) {
-            int wallpaperIndex = (isRotationEnabled() || mCategory.supportsCustomPhotos())
+            int wallpaperIndex = (shouldShowRotationTile() || mCategory.supportsCustomPhotos())
                     ? position - 1 : position;
             WallpaperInfo wallpaper = mWallpapers.get(wallpaperIndex);
             ((IndividualHolder) holder).bindWallpaper(wallpaper);
diff --git a/src/com/android/wallpaper/widget/WallpaperColorsLoader.java b/src/com/android/wallpaper/widget/WallpaperColorsLoader.java
new file mode 100644
index 0000000..5ea5c15
--- /dev/null
+++ b/src/com/android/wallpaper/widget/WallpaperColorsLoader.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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.wallpaper.widget;
+
+import android.app.WallpaperColors;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.wallpaper.asset.Asset;
+
+/** A class to load the {@link WallpaperColors} from wallpaper {@link Asset}. */
+public class WallpaperColorsLoader {
+    private static final String TAG = "WallpaperColorsLoader";
+
+    /** Callback of loading {@link WallpaperColors}. */
+    public interface Callback {
+        /** Gets called when {@link WallpaperColors} parsing is succeed. */
+        void onSuccess(WallpaperColors colors);
+
+        /** Gets called when {@link WallpaperColors} parsing is failed. */
+        default void onFailure() {
+            Log.i(TAG, "Can't get wallpaper colors from a null bitmap.");
+        }
+    }
+
+    /** Gets the {@link WallpaperColors} from the wallpaper {@link Asset}. */
+    public static void getWallpaperColors(@NonNull Asset asset, int targetWidth, int targetHeight,
+                                          @NonNull Callback callback) {
+        asset.decodeBitmap(targetWidth, targetHeight, bitmap -> {
+            if (bitmap != null) {
+                boolean shouldRecycle = false;
+                if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
+                    bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+                    shouldRecycle = true;
+                }
+                callback.onSuccess(WallpaperColors.fromBitmap(bitmap));
+                if (shouldRecycle) {
+                    bitmap.recycle();
+                }
+            } else {
+                callback.onFailure();
+            }
+        });
+    }
+}