Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 242c805f authored by Jon Miranda's avatar Jon Miranda
Browse files

Use color extraction for arrow popup.

- Updated logic to accept any view that is a a child of draglayer
  (from being workspace dependent)
- Move color extraction logic from widget class to utility class.
- Initial CL, future CL will interpolate between colors
  and expand color extraction to other abstract floating views.

Bug: 175329686
Test: long press on colorful wallpaper and test colors are extracted
      test in multiwindow mode
Change-Id: I18a6bb5013de4eea7e9b45810401f5b9b3cd7302
parent 51991a73
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.launcher3;

import static android.animation.ValueAnimator.areAnimatorsEnabled;

import static com.android.launcher3.Utilities.getBoundsForViewInDragLayer;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;

import android.animation.Animator;
@@ -193,6 +194,7 @@ public class CellLayout extends ViewGroup {
    private static final int INVALID_DIRECTION = -100;

    private final Rect mTempRect = new Rect();
    private final RectF mTempRectF = new RectF();

    private static final Paint sPaint = new Paint();

@@ -1067,11 +1069,16 @@ public class CellLayout extends ViewGroup {
        // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
        View view = dragObject.dragView.getContentView();
        if (view instanceof LauncherAppWidgetHostView) {
            Workspace workspace =
                    Launcher.getLauncher(dragObject.dragView.getContext()).getWorkspace();
            Launcher launcher = Launcher.getLauncher(dragObject.dragView.getContext());
            Workspace workspace = launcher.getWorkspace();
            int screenId = workspace.getIdForScreen(this);
            int pageId = workspace.getPageIndexForScreenId(screenId);
            cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);

            // Now get the rect in drag layer coordinates.
            getBoundsForViewInDragLayer(launcher.getDragLayer(), workspace, mTempRect, false,
                    mTempRectF);
            Utilities.setRect(mTempRectF, mTempRect);
            ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, pageId);
        }
    }
+50 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -84,6 +85,7 @@ import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.PendingAddShortcutInfo;

import java.lang.reflect.Method;
@@ -104,6 +106,8 @@ public final class Utilities {
    private static final Pattern sTrimPattern =
            Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");

    private static final float[] sTmpFloatArray = new float[4];

    private static final int[] sLoc0 = new int[2];
    private static final int[] sLoc1 = new int[2];
    private static final Matrix sMatrix = new Matrix();
@@ -133,6 +137,15 @@ public final class Utilities {
            Build.TYPE.toLowerCase(Locale.ROOT).contains("debug") ||
            Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");

    /**
     * Returns true if theme is dark.
     */
    public static boolean isDarkTheme(Context context) {
        Configuration configuration = context.getResources().getConfiguration();
        int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
        return nightMode == Configuration.UI_MODE_NIGHT_YES;
    }

    public static boolean isDevelopersOptionsEnabled(Context context) {
        return Settings.Global.getInt(context.getApplicationContext().getContentResolver(),
                        Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
@@ -218,6 +231,33 @@ public final class Utilities {
        return scale;
    }

    /**
     * Returns bounds for a child view of DragLayer, in drag layer coordinates.
     *
     * see {@link com.android.launcher3.dragndrop.DragLayer}.
     *
     * @param viewBounds Bounds of the view wanted in drag layer coordinates, relative to the view
     *                   itself. eg. (0, 0, view.getWidth, view.getHeight)
     * @param ignoreTransform If true, view transform is ignored
     * @param outRect The out rect where we return the bounds of {@param view} in drag layer coords.
     */
    public static void getBoundsForViewInDragLayer(BaseDragLayer dragLayer, View view,
            Rect viewBounds, boolean ignoreTransform, RectF outRect) {
        float[] points = sTmpFloatArray;
        points[0] = viewBounds.left;
        points[1] = viewBounds.top;
        points[2] = viewBounds.right;
        points[3] = viewBounds.bottom;

        Utilities.getDescendantCoordRelativeToAncestor(view, dragLayer, points,
                false, ignoreTransform);
        outRect.set(
                Math.min(points[0], points[2]),
                Math.min(points[1], points[3]),
                Math.max(points[0], points[2]),
                Math.max(points[1], points[3]));
    }

    /**
     * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
     */
@@ -273,6 +313,16 @@ public final class Utilities {
        return new int[] {sLoc1[0] - sLoc0[0], sLoc1[1] - sLoc0[1]};
    }

    /**
     * Helper method to set rectOut with rectFSrc.
     */
    public static void setRect(RectF rectFSrc, Rect rectOut) {
        rectOut.left = (int) rectFSrc.left;
        rectOut.top = (int) rectFSrc.top;
        rectOut.right = (int) rectFSrc.right;
        rectOut.bottom = (int) rectFSrc.bottom;
    }

    public static void scaleRectFAboutCenter(RectF r, float scale) {
        if (scale != 1.0f) {
            float cx = r.centerX();
+2 −4
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
@@ -34,6 +33,7 @@ import android.util.Property;
import android.util.SparseArray;
import android.view.ContextThemeWrapper;

import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.GraphicsUtils;
@@ -119,9 +119,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable {
                info,
                IconPalette.getPreloadProgressColor(context, info.bitmap.color),
                getPreloadColors(context),
            (context.getResources().getConfiguration().uiMode
                    & Configuration.UI_MODE_NIGHT_MASK
                    & Configuration.UI_MODE_NIGHT_YES) != 0) /* isDarkMode */;
                Utilities.isDarkTheme(context));
    }

    public PreloadIconDrawable(
+100 −5
Original line number Diff line number Diff line
@@ -16,9 +16,9 @@

package com.android.launcher3.popup;

import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.util.ColorExtractionUtils.getColorExtractionRect;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -31,6 +31,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -41,17 +42,21 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.dragndrop.DragLayer;
@@ -59,9 +64,11 @@ import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.LocalColorExtractor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

/**
 * A container for shortcuts to deep links and notifications associated with an app.
@@ -73,6 +80,9 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>

    // +1 for system shortcut view
    private static final int MAX_NUM_CHILDREN = MAX_SHORTCUTS + 1;
    // Index used to get background color when using local wallpaper color extraction,
    private static final int LIGHT_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_50;
    private static final int DARK_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_800;

    private final Rect mTempRect = new Rect();

@@ -104,8 +114,14 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>

    private Runnable mOnCloseCallback = () -> { };

    // The rect string of the view that the arrow is attached to, in screen reference frame.
    private String mArrowColorRectString;
    private int mArrowColor;
    private final int[] mColors;
    private final HashMap<String, View> mViewForRect = new HashMap<>();

    private final int mColorExtractionIndex;
    @Nullable private LocalColorExtractor mColorExtractor;

    public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
@@ -113,7 +129,9 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>
        mOutlineRadius = Themes.getDialogCornerRadius(context);
        mLauncher = BaseDraggingActivity.fromContext(context);
        mIsRtl = Utilities.isRtl(getResources());

        mColorExtractionIndex = Utilities.isDarkTheme(context)
                ? DARK_COLOR_EXTRACTION_INDEX
                : LIGHT_COLOR_EXTRACTION_INDEX;
        setClipToOutline(true);
        setOutlineProvider(new ViewOutlineProvider() {
            @Override
@@ -158,6 +176,10 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>
                mColors[i] =
                        (int) argb.evaluate((i + 1) * step, primaryColor, secondaryColor);
            }

            if (Utilities.ATLEAST_S) {
                setupColorExtraction();
            }
        }
    }

@@ -342,6 +364,15 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>
            // so we centered it instead. In that case we don't want to showDefaultOptions the arrow.
            mArrow.setVisibility(INVISIBLE);
        } else {
            updateArrowColor();
        }

        mArrow.setPivotX(mArrowWidth / 2.0f);
        mArrow.setPivotY(mIsAboveIcon ? mArrowHeight : 0);
    }

    private void updateArrowColor() {
        if (!Gravity.isVertical(mGravity)) {
            mArrow.setBackground(new RoundedArrowDrawable(
                    mArrowWidth, mArrowHeight, mArrowPointRadius,
                    mOutlineRadius, getMeasuredWidth(), getMeasuredHeight(),
@@ -350,9 +381,6 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>
                    mArrowColor));
            mArrow.setElevation(getElevation());
        }

        mArrow.setPivotX(mArrowWidth / 2.0f);
        mArrow.setPivotY(mIsAboveIcon ? mArrowHeight : 0);
    }

    /**
@@ -671,6 +699,11 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>
        getPopupContainer().removeView(this);
        getPopupContainer().removeView(mArrow);
        mOnCloseCallback.run();
        mArrowColorRectString = null;
        mViewForRect.clear();
        if (mColorExtractor != null) {
            mColorExtractor.removeLocations();
        }
    }

    /**
@@ -680,6 +713,68 @@ public abstract class ArrowPopup<T extends StatefulActivity<LauncherState>>
        mOnCloseCallback = callback;
    }

    private void setupColorExtraction() {
        Workspace workspace = mLauncher.findViewById(R.id.workspace);
        if (workspace == null) {
            return;
        }

        mColorExtractor = LocalColorExtractor.newInstance(mLauncher);
        mColorExtractor.setListener((rect, extractedColors) -> {
            String rectString = rect.toShortString();
            View v = mViewForRect.get(rectString);
            if (v != null) {
                int newColor = extractedColors.get(mColorExtractionIndex);
                setChildColor(v, newColor);
                if (rectString.equals(mArrowColorRectString)) {
                    mArrowColor = newColor;
                    updateArrowColor();
                }
            }
        });

        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                getViewTreeObserver().removeOnPreDrawListener(this);

                ArrayList<RectF> locations = new ArrayList<>();
                Rect r = new Rect();

                int count = getChildCount();
                int numVisibleChild = 0;
                for (int i = 0; i < count; i++) {
                    View view = getChildAt(i);
                    if (view.getVisibility() == VISIBLE) {
                        RectF rf = new RectF();
                        getColorExtractionRect(Launcher.getLauncher(getContext()),
                                workspace.getCurrentPage(), view, rf);
                        if (rf.isEmpty()) {
                            numVisibleChild++;
                            continue;
                        }

                        locations.add(rf);
                        String rectString = rf.toShortString();
                        mViewForRect.put(rectString, view);

                        // Arrow color matches the first child or the last child.
                        if (!mIsAboveIcon && numVisibleChild == 0) {
                            mArrowColorRectString = rectString;
                        } else if (mIsAboveIcon) {
                            mArrowColorRectString = rectString;
                        }

                        numVisibleChild++;
                    }
                }

                mColorExtractor.addLocation(locations);
                return false;
            }
        });
    }

    protected BaseDragLayer getPopupContainer() {
        return mLauncher.getDragLayer();
    }
+110 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.util;

import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;

/**
 * Utility class used to map launcher views to wallpaper rect.
 */
public class ColorExtractionUtils {

    public static final String TAG = "ColorExtractionUtils";

    private static final Rect sTempRect = new Rect();
    private static final RectF sTempRectF = new RectF();

    /**
     * Takes a view and returns its rect that can be used by the wallpaper local color extractor.
     *
     * @param launcher Launcher class class.
     * @param pageId The page the workspace item is on.
     * @param v The view.
     * @param colorExtractionRectOut The location rect, but converted to a format expected by the
     *                               wallpaper local color extractor.
     */
    public static void getColorExtractionRect(Launcher launcher, int pageId, View v,
            RectF colorExtractionRectOut) {
        Rect viewRect = sTempRect;
        viewRect.set(0, 0, v.getWidth(), v.getHeight());
        Utilities.getBoundsForViewInDragLayer(launcher.getDragLayer(), v, viewRect, false,
                sTempRectF);
        Utilities.setRect(sTempRectF, viewRect);
        getColorExtractionRect(launcher, pageId, viewRect, colorExtractionRectOut);
    }

    /**
     * Takes a rect in drag layer coordinates and returns the rect that can be used by the wallpaper
     * local color extractor.
     *
     * @param launcher Launcher class.
     * @param pageId The page the workspace item is on.
     * @param rectInDragLayer The relevant bounds of the view in drag layer coordinates.
     * @param colorExtractionRectOut The location rect, but converted to a format expected by the
     *                               wallpaper local color extractor.
     */
    public static void getColorExtractionRect(Launcher launcher, int pageId, Rect rectInDragLayer,
            RectF colorExtractionRectOut) {
        // If the view hasn't been measured and laid out, we cannot do this.
        if (rectInDragLayer.isEmpty()) {
            colorExtractionRectOut.setEmpty();
            return;
        }

        Resources res = launcher.getResources();
        DeviceProfile dp = launcher.getDeviceProfile().inv.getDeviceProfile(launcher);
        float screenWidth = dp.widthPx;
        float screenHeight = dp.heightPx;
        int numScreens = launcher.getWorkspace().getNumPagesForWallpaperParallax();
        pageId = Utilities.isRtl(res) ? numScreens - pageId - 1 : pageId;
        float relativeScreenWidth = 1f / numScreens;

        int[] dragLayerBounds = new int[2];
        launcher.getDragLayer().getLocationOnScreen(dragLayerBounds);
        // Translate from drag layer coordinates to screen coordinates.
        int screenLeft = rectInDragLayer.left + dragLayerBounds[0];
        int screenTop = rectInDragLayer.top + dragLayerBounds[1];
        int screenRight = rectInDragLayer.right + dragLayerBounds[0];
        int screenBottom = rectInDragLayer.bottom + dragLayerBounds[1];

        // This is the position of the view relative to the wallpaper, as expected by the
        // local color extraction of the WallpaperManager.
        // The coordinate system is such that, on the horizontal axis, each screen has a
        // distinct range on the [0,1] segment. So if there are 3 screens, they will have the
        // ranges [0, 1/3], [1/3, 2/3] and [2/3, 1]. The position on the subrange should be
        // the position of the view relative to the screen. For the vertical axis, this is
        // simply the location of the view relative to the screen.
        // Translate from drag layer coordinates to screen coordinates
        colorExtractionRectOut.left = (screenLeft / screenWidth + pageId) * relativeScreenWidth;
        colorExtractionRectOut.right = (screenRight / screenWidth + pageId) * relativeScreenWidth;
        colorExtractionRectOut.top = screenTop / screenHeight;
        colorExtractionRectOut.bottom = screenBottom / screenHeight;

        if (colorExtractionRectOut.left < 0
                || colorExtractionRectOut.right > 1
                || colorExtractionRectOut.top < 0
                || colorExtractionRectOut.bottom > 1) {
            colorExtractionRectOut.setEmpty();
        }
    }
}
Loading