From ba7dba3983b95bc4dfe33fd4a6362cd11a31abd4 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 15 Oct 2018 15:28:03 -0700 Subject: [PATCH 1/8] Fixing state not set properly when configuration changes and remote animation callback comes after threshold is crossed The transition length is updated when removeAnimations is received. If the gesture delta is crossed before that, we were ending up in long-swipe mode as due to an incomplete check. Bug: 117549010 Change-Id: If5471c538ac51b84f176a1fad42112e84046da63 --- .../android/quickstep/WindowTransformSwipeHandler.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 9991552987..dab5ddcf25 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -538,7 +538,7 @@ public class WindowTransformSwipeHandler { public void updateDisplacement(float displacement) { // We are moving in the negative x/y direction displacement = -displacement; - if (displacement > mTransitionDragLength) { + if (displacement > mTransitionDragLength && mTransitionDragLength > 0) { mCurrentShift.updateValue(1); if (!mBgLongSwipeMode) { @@ -813,8 +813,12 @@ public class WindowTransformSwipeHandler { long startMillis = SystemClock.uptimeMillis(); executeOnUiThread(() -> { // Animate the launcher components at the same time as the window, always on UI thread. - if (mLauncherTransitionController != null && !mWasLauncherAlreadyVisible - && start != end && duration > 0) { + if (mLauncherTransitionController == null) { + return; + } + if (start == end || duration <= 0) { + mLauncherTransitionController.getAnimationPlayer().end(); + } else { // Adjust start progress and duration in case we are on a different thread. long elapsedMillis = SystemClock.uptimeMillis() - startMillis; elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration); From d0ae492e9faf2f5c6a686479698585d615423a34 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 11 Oct 2018 10:37:53 -0700 Subject: [PATCH 2/8] Changing the FolderIcon shape based on AdpativeIcon > Creating an abstract interface to represent a folderIcon shape > Defined few common folder shapes > Picking the folder shape closest to the AdaptiveIcon shape Bug: 111433118 Change-Id: Ib35eddbdd6692767aa9dbe6aae1a379a68cc456a --- .../launcher3/MainProcessInitializer.java | 2 + src/com/android/launcher3/Workspace.java | 6 +- src/com/android/launcher3/folder/Folder.java | 25 ++ .../folder/FolderAnimationManager.java | 13 +- .../android/launcher3/folder/FolderIcon.java | 16 +- .../android/launcher3/folder/FolderShape.java | 422 ++++++++++++++++++ .../launcher3/folder/PreviewBackground.java | 49 +- 7 files changed, 468 insertions(+), 65 deletions(-) create mode 100644 src/com/android/launcher3/folder/FolderShape.java diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java index a18dfde310..a2538930e1 100644 --- a/src/com/android/launcher3/MainProcessInitializer.java +++ b/src/com/android/launcher3/MainProcessInitializer.java @@ -19,6 +19,7 @@ package com.android.launcher3; import android.content.Context; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.folder.FolderShape; import com.android.launcher3.graphics.IconShapeOverride; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.ResourceBasedOverride; @@ -39,5 +40,6 @@ public class MainProcessInitializer implements ResourceBasedOverride { FeatureFlags.initialize(context); IconShapeOverride.apply(context); SessionCommitReceiver.applyDefaultUserPrefs(context); + FolderShape.init(); } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 11e601c7f3..3f7d68d12f 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -127,8 +127,8 @@ public class Workspace extends PagedView private static final int DEFAULT_PAGE = 0; - private static final boolean MAP_NO_RECURSE = false; - private static final boolean MAP_RECURSE = true; + public static final boolean MAP_NO_RECURSE = false; + public static final boolean MAP_RECURSE = true; // The screen id used for the empty screen always present to the right. public static final int EXTRA_EMPTY_SCREEN_ID = -201; @@ -3121,7 +3121,7 @@ public class Workspace extends PagedView * @param recurse true: iterate over folder children. false: op get the folders themselves. * @param op the operator to map over the shortcuts */ - void mapOverItems(boolean recurse, ItemOperator op) { + public void mapOverItems(boolean recurse, ItemOperator op) { ArrayList containers = getAllShortcutAndWidgetContainers(); final int containerCount = containers.size(); for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index c4d1058005..94c8d4549d 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -26,6 +26,8 @@ import android.animation.AnimatorSet; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.Rect; import android.text.InputType; import android.text.Selection; @@ -151,6 +153,8 @@ public class Folder extends AbstractFloatingView implements DragSource, // Cell ranks used for drag and drop @Thunk int mTargetRank, mPrevTargetRank, mEmptyCellRank; + private Path mClipPath; + @ViewDebug.ExportedProperty(category = "launcher", mapping = { @ViewDebug.IntToString(from = STATE_NONE, to = "STATE_NONE"), @@ -1476,4 +1480,25 @@ public class Folder extends AbstractFloatingView implements DragSource, sHintText = res.getString(R.string.folder_hint_text); } } + + /** + * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of + * rounded rect. + */ + public void setClipPath(Path clipPath) { + mClipPath = clipPath; + invalidate(); + } + + @Override + public void draw(Canvas canvas) { + if (mClipPath != null) { + int count = canvas.save(); + canvas.clipPath(mClipPath); + super.draw(canvas); + canvas.restoreToCount(count); + } else { + super.draw(canvas); + } + } } diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 1277a20909..fa890b99df 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -19,6 +19,7 @@ package com.android.launcher3.folder; import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; +import static com.android.launcher3.folder.FolderShape.getShape; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -40,7 +41,6 @@ import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PropertyResetListener; -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Themes; @@ -166,7 +166,6 @@ public class FolderAnimationManager { Math.round((totalOffsetX + initialSize) / initialScale), Math.round((paddingOffsetY + initialSize) / initialScale)); Rect endRect = new Rect(0, 0, lp.width, lp.height); - float initialRadius = initialSize / initialScale / 2f; float finalRadius = Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics()); // Create the animators. @@ -189,14 +188,8 @@ public class FolderAnimationManager { play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale)); play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor)); play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening)); - RoundedRectRevealOutlineProvider outlineProvider = new RoundedRectRevealOutlineProvider( - initialRadius, finalRadius, startRect, endRect) { - @Override - public boolean shouldRemoveElevationDuringAnimation() { - return true; - } - }; - play(a, outlineProvider.createRevealAnimator(mFolder, !mIsOpening)); + play(a, getShape().createRevealAnimator( + mFolder, startRect, endRect, finalRadius, !mIsOpening)); // Animate the elevation midway so that the shadow is not noticeable in the background. int midDuration = mDuration / 2; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index d09f03612f..429d44fcb6 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -75,6 +75,7 @@ import androidx.annotation.NonNull; * An icon that can appear on in the workspace representing an {@link Folder}. */ public class FolderIcon extends FrameLayout implements FolderListener { + @Thunk Launcher mLauncher; @Thunk Folder mFolder; private FolderInfo mInfo; @@ -477,20 +478,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { if (mFolder == null) return; if (mFolder.getItemCount() == 0 && !mAnimating) return; - final int saveCount; - - if (canvas.isHardwareAccelerated()) { - saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null); - } else { - saveCount = canvas.save(); - canvas.clipPath(mBackground.getClipPath()); - } - + final int saveCount = canvas.save(); + canvas.clipPath(mBackground.getClipPath()); mPreviewItemManager.draw(canvas); - - if (canvas.isHardwareAccelerated()) { - mBackground.clipCanvasHardware(canvas); - } canvas.restoreToCount(saveCount); if (!mBackground.drawingDelegated()) { diff --git a/src/com/android/launcher3/folder/FolderShape.java b/src/com/android/launcher3/folder/FolderShape.java new file mode 100644 index 0000000000..ae279cb3dc --- /dev/null +++ b/src/com/android/launcher3/folder/FolderShape.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2018 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.folder; + +import static com.android.launcher3.Workspace.MAP_NO_RECURSE; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.FloatArrayEvaluator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Region.Op; +import android.graphics.RegionIterator; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +import android.view.ViewOutlineProvider; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; + +/** + * Abstract representation of the shape of a folder icon + */ +public abstract class FolderShape { + + private static FolderShape sInstance = new Circle(); + + public static FolderShape getShape() { + return sInstance; + } + + private static FolderShape[] getAllShapes() { + return new FolderShape[] { + new Circle(), + new RoundedSquare(8f / 50), // Ratios based on path defined in config_icon_mask + new RoundedSquare(30f / 50), + new Square(), + new TearDrop(), + new Squircle()}; + } + + public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, + Paint paint); + + public abstract void addShape(Path path, float offsetX, float offsetY, float radius); + + public abstract Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect, + float endRadius, boolean isReversed); + + /** + * Abstract shape where the reveal animation is a derivative of a round rect animation + */ + private static abstract class SimpleRectShape extends FolderShape { + + @Override + public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect, + float endRadius, boolean isReversed) { + return new RoundedRectRevealOutlineProvider( + getStartRadius(startRect), endRadius, startRect, endRect) { + @Override + public boolean shouldRemoveElevationDuringAnimation() { + return true; + } + }.createRevealAnimator(target, isReversed); + } + + protected abstract float getStartRadius(Rect startRect); + } + + /** + * Abstract shape which draws using {@link Path} + */ + private static abstract class PathShape extends FolderShape { + + private final Path mTmpPath = new Path(); + + @Override + public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, + Paint paint) { + mTmpPath.reset(); + addShape(mTmpPath, offsetX, offsetY, radius); + canvas.drawPath(mTmpPath, paint); + } + + protected abstract AnimatorUpdateListener newUpdateListener( + Rect startRect, Rect endRect, float endRadius, Path outPath); + + @Override + public final Animator createRevealAnimator(Folder target, Rect startRect, Rect endRect, + float endRadius, boolean isReversed) { + Path path = new Path(); + AnimatorUpdateListener listener = + newUpdateListener(startRect, endRect, endRadius, path); + + ValueAnimator va = + isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f); + va.addListener(new AnimatorListenerAdapter() { + private ViewOutlineProvider mOldOutlineProvider; + + public void onAnimationStart(Animator animation) { + mOldOutlineProvider = target.getOutlineProvider(); + target.setOutlineProvider(null); + + target.setTranslationZ(-target.getElevation()); + } + + public void onAnimationEnd(Animator animation) { + target.setTranslationZ(0); + target.setClipPath(null); + target.setOutlineProvider(mOldOutlineProvider); + } + }); + + va.addUpdateListener((anim) -> { + path.reset(); + listener.onAnimationUpdate(anim); + target.setClipPath(path); + }); + + return va; + } + } + + public static final class Circle extends SimpleRectShape { + + @Override + public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { + canvas.drawCircle(radius + offsetX, radius + offsetY, radius, p); + } + + @Override + public void addShape(Path path, float offsetX, float offsetY, float radius) { + path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW); + } + + @Override + protected float getStartRadius(Rect startRect) { + return startRect.width() / 2f; + } + } + + public static class Square extends SimpleRectShape { + + @Override + public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { + float cx = radius + offsetX; + float cy = radius + offsetY; + canvas.drawRect(cx - radius, cy - radius, cx + radius, cy + radius, p); + } + + @Override + public void addShape(Path path, float offsetX, float offsetY, float radius) { + float cx = radius + offsetX; + float cy = radius + offsetY; + path.addRect(cx - radius, cy - radius, cx + radius, cy + radius, Path.Direction.CW); + } + + @Override + protected float getStartRadius(Rect startRect) { + return 0; + } + } + + public static class RoundedSquare extends SimpleRectShape { + + /** + * Ratio of corner radius to half size. Based on the + */ + private final float mRadiusFactor; + + public RoundedSquare(float radiusFactor) { + mRadiusFactor = radiusFactor; + } + + @Override + public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { + float cx = radius + offsetX; + float cy = radius + offsetY; + float cr = radius * mRadiusFactor; + canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p); + } + + @Override + public void addShape(Path path, float offsetX, float offsetY, float radius) { + float cx = radius + offsetX; + float cy = radius + offsetY; + float cr = radius * mRadiusFactor; + path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, + Path.Direction.CW); + } + + @Override + protected float getStartRadius(Rect startRect) { + return (startRect.width() / 2f) * mRadiusFactor; + } + } + + public static class TearDrop extends PathShape { + + /** + * Radio of short radius to large radius, based on the shape options defined in the config. + */ + private static final float RADIUS_RATIO = 15f / 50; + + private final float[] mTempRadii = new float[8]; + + @Override + public void addShape(Path p, float offsetX, float offsetY, float r1) { + float r2 = r1 * RADIUS_RATIO; + float cx = r1 + offsetX; + float cy = r1 + offsetY; + + p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2), + Path.Direction.CW); + } + + private float[] getRadiiArray(float r1, float r2) { + mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] = + mTempRadii[6] = mTempRadii[7] = r1; + mTempRadii[4] = mTempRadii[5] = r2; + return mTempRadii; + } + + @Override + protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, + float endRadius, Path outPath) { + float r1 = startRect.width() / 2f; + float r2 = r1 * RADIUS_RATIO; + + float[] startValues = new float[] { + startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2}; + float[] endValues = new float[] { + endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius}; + + FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]); + + return (anim) -> { + float progress = (Float) anim.getAnimatedValue(); + float[] values = evaluator.evaluate(progress, startValues, endValues); + outPath.addRoundRect( + values[0], values[1], values[2], values[3], + getRadiiArray(values[4], values[5]), Path.Direction.CW); + }; + } + } + + public static class Squircle extends PathShape { + + /** + * Radio of radius to circle radius, based on the shape options defined in the config. + */ + private static final float RADIUS_RATIO = 10f / 50; + + @Override + public void addShape(Path p, float offsetX, float offsetY, float r) { + float cx = r + offsetX; + float cy = r + offsetY; + float control = r - r * RADIUS_RATIO; + + p.moveTo(cx, cy - r); + addLeftCurve(cx, cy, r, control, p); + addRightCurve(cx, cy, r, control, p); + addLeftCurve(cx, cy, -r, -control, p); + addRightCurve(cx, cy, -r, -control, p); + p.close(); + } + + private void addLeftCurve(float cx, float cy, float r, float control, Path path) { + path.cubicTo( + cx - control, cy - r, + cx - r, cy - control, + cx - r, cy); + } + + private void addRightCurve(float cx, float cy, float r, float control, Path path) { + path.cubicTo( + cx - r, cy + control, + cx - control, cy + r, + cx, cy + r); + } + + @Override + protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, + float endR, Path outPath) { + + float startCX = startRect.exactCenterX(); + float startCY = startRect.exactCenterY(); + float startR = startRect.width() / 2f; + float startControl = startR - startR * RADIUS_RATIO; + float startHShift = 0; + float startVShift = 0; + + float endCX = endRect.exactCenterX(); + float endCY = endRect.exactCenterY(); + // Approximate corner circle using bezier curves + // http://spencermortensen.com/articles/bezier-circle/ + float endControl = endR * 0.551915024494f; + float endHShift = endRect.width() / 2f - endR; + float endVShift = endRect.height() / 2f - endR; + + return (anim) -> { + float progress = (Float) anim.getAnimatedValue(); + + float cx = (1 - progress) * startCX + progress * endCX; + float cy = (1 - progress) * startCY + progress * endCY; + float r = (1 - progress) * startR + progress * endR; + float control = (1 - progress) * startControl + progress * endControl; + float hShift = (1 - progress) * startHShift + progress * endHShift; + float vShift = (1 - progress) * startVShift + progress * endVShift; + + outPath.moveTo(cx, cy - vShift - r); + outPath.rLineTo(-hShift, 0); + + addLeftCurve(cx - hShift, cy - vShift, r, control, outPath); + outPath.rLineTo(0, vShift + vShift); + + addRightCurve(cx - hShift, cy + vShift, r, control, outPath); + outPath.rLineTo(hShift + hShift, 0); + + addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath); + outPath.rLineTo(0, -vShift - vShift); + + addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath); + outPath.close(); + }; + } + } + + /** + * Initializes the shape which is closest to closest to the {@link AdaptiveIconDrawable} + */ + public static void init() { + if (!Utilities.ATLEAST_OREO) { + return; + } + new MainThreadExecutor().execute(FolderShape::pickShapeInBackground); + } + + @TargetApi(Build.VERSION_CODES.O) + protected static void pickShapeInBackground() { + // Pick any large size + int size = 200; + + Region full = new Region(0, 0, size, size); + Region iconR = new Region(); + AdaptiveIconDrawable drawable = new AdaptiveIconDrawable( + new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK)); + drawable.setBounds(0, 0, size, size); + iconR.setPath(drawable.getIconMask(), full); + + Path shapePath = new Path(); + Region shapeR = new Region(); + Rect tempRect = new Rect(); + + // Find the shape with minimum area of divergent region. + int minArea = Integer.MAX_VALUE; + FolderShape closestShape = null; + for (FolderShape shape : getAllShapes()) { + shapePath.reset(); + shape.addShape(shapePath, 0, 0, size / 2f); + shapeR.setPath(shapePath, full); + shapeR.op(iconR, Op.XOR); + + RegionIterator itr = new RegionIterator(shapeR); + int area = 0; + + while (itr.next(tempRect)) { + area += tempRect.width() * tempRect.height(); + } + if (area < minArea) { + minArea = area; + closestShape = shape; + } + } + + if (closestShape != null) { + FolderShape shape = closestShape; + new MainThreadExecutor().execute(() -> updateFolderShape(shape)); + } + } + + private static void updateFolderShape(FolderShape shape) { + sInstance = shape; + LauncherAppState app = LauncherAppState.getInstanceNoCreate(); + if (app == null) { + return; + } + Launcher launcher = (Launcher) app.getModel().getCallback(); + if (launcher != null) { + launcher.getWorkspace().mapOverItems(MAP_NO_RECURSE, (i, v) -> { + if (v instanceof FolderIcon) { + v.invalidate(); + } + return false; + }); + } + } +} diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index ceb1a8c372..8443953bd9 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -16,6 +16,8 @@ package com.android.launcher3.folder; +import static com.android.launcher3.folder.FolderShape.getShape; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -49,16 +51,6 @@ public class PreviewBackground { private static final int CONSUMPTION_ANIMATION_DURATION = 100; - private final PorterDuffXfermode mClipPorterDuffXfermode - = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); - // Create a RadialGradient such that it draws a black circle and then extends with - // transparent. To achieve this, we keep the gradient to black for the range [0, 1) and - // just at the edge quickly change it to transparent. - private final RadialGradient mClipShader = new RadialGradient(0, 0, 1, - new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT }, - new float[] {0, 0.999f, 1}, - Shader.TileMode.CLAMP); - private final PorterDuffXfermode mShadowPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT); private RadialGradient mShadowShader = null; @@ -208,8 +200,7 @@ public class PreviewBackground { mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(getBgColor()); - drawCircle(canvas, 0 /* deltaRadius */); - + getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint); drawShadow(canvas); } @@ -244,7 +235,7 @@ public class PreviewBackground { mPaint.setShader(null); if (canvas.isHardwareAccelerated()) { mPaint.setXfermode(mShadowPorterDuffXfermode); - canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint); + getShape().drawShape(canvas, offsetX, offsetY, radius, mPaint); mPaint.setXfermode(null); } @@ -287,7 +278,10 @@ public class PreviewBackground { mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); - drawCircle(canvas, 1 /* deltaRadius */); + + float inset = 1f; + getShape().drawShape(canvas, + getOffsetX() + inset, getOffsetY() + inset, getScaledRadius() - inset, mPaint); } public void drawLeaveBehind(Canvas canvas) { @@ -296,40 +290,17 @@ public class PreviewBackground { mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(Color.argb(160, 245, 245, 245)); - drawCircle(canvas, 0 /* deltaRadius */); + getShape().drawShape(canvas, getOffsetX(), getOffsetY(), getScaledRadius(), mPaint); mScale = originalScale; } - private void drawCircle(Canvas canvas,float deltaRadius) { - float radius = getScaledRadius(); - canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(), - radius - deltaRadius, mPaint); - } - public Path getClipPath() { mPath.reset(); - float r = getScaledRadius(); - mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW); + getShape().addShape(mPath, getOffsetX(), getOffsetY(), getScaledRadius()); return mPath; } - // It is the callers responsibility to save and restore the canvas layers. - void clipCanvasHardware(Canvas canvas) { - mPaint.setColor(Color.BLACK); - mPaint.setStyle(Paint.Style.FILL); - mPaint.setXfermode(mClipPorterDuffXfermode); - - float radius = getScaledRadius(); - mShaderMatrix.setScale(radius, radius); - mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY()); - mClipShader.setLocalMatrix(mShaderMatrix); - mPaint.setShader(mClipShader); - canvas.drawPaint(mPaint); - mPaint.setXfermode(null); - mPaint.setShader(null); - } - private void delegateDrawing(CellLayout delegate, int cellX, int cellY) { if (mDrawingDelegate != delegate) { delegate.addFolderBackground(this); From 6e11a2d9f275c74e91a8b5af049f6126ed34d176 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 4 Oct 2018 10:39:37 -0700 Subject: [PATCH 3/8] Keep input consumer registered and rely on enabled state instead Bug: 117224991 Test: Ensure swipe up still works Change-Id: I719ce41959fca2157a3db614e90d404551299007 --- .../quickstep/OtherActivityTouchConsumer.java | 9 +++-- .../quickstep/RecentsAnimationWrapper.java | 37 +++---------------- .../quickstep/TouchInteractionService.java | 7 +++- .../WindowTransformSwipeHandler.java | 9 +++-- 4 files changed, 23 insertions(+), 39 deletions(-) diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index 4417a3da77..94ec69a0c2 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -52,6 +52,7 @@ import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.AssistDataReceiver; import com.android.systemui.shared.system.BackgroundExecutor; +import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.NavigationBarCompat; import com.android.systemui.shared.system.NavigationBarCompat.HitTarget; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; @@ -80,6 +81,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC private final OverviewCallbacks mOverviewCallbacks; private final TaskOverlayFactory mTaskOverlayFactory; private final TouchInteractionLog mTouchInteractionLog; + private final InputConsumerController mInputConsumer; private final boolean mIsDeferredDownTarget; private final PointF mDownPos = new PointF(); @@ -101,8 +103,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl, MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer, @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks, - TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker, - TouchInteractionLog touchInteractionLog) { + TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer, + VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog) { super(base); mRunningTask = runningTaskInfo; @@ -117,6 +119,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mTaskOverlayFactory = taskOverlayFactory; mTouchInteractionLog = touchInteractionLog; mTouchInteractionLog.setTouchConsumer(this); + mInputConsumer = inputConsumer; } @Override @@ -226,7 +229,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC RecentsAnimationState animationState = new RecentsAnimationState(); final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler( animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper, - mTouchInteractionLog); + mInputConsumer, mTouchInteractionLog); // Preload the plan mRecentsModel.loadTasks(mRunningTask.id, null); diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java index eea3971f40..2f3cb5f373 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java @@ -53,24 +53,18 @@ public class RecentsAnimationWrapper { new LooperExecutor(UiThreadHelper.getBackgroundLooper()); private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - private InputConsumerController mInputConsumer = - InputConsumerController.getRecentsAnimationInputConsumer(); + private final InputConsumerController mInputConsumer; private final Supplier mTouchProxySupplier; - private boolean mInputConsumerUnregistered; - private boolean mTouchProxyEnabled; - private TouchConsumer mTouchConsumer; private boolean mTouchInProgress; - private boolean mInputConsumerUnregisterPending; private boolean mFinishPending; - public RecentsAnimationWrapper(Supplier touchProxySupplier) { - // Register the input consumer on the UI thread, to ensure that it runs after any pending - // unregister calls + public RecentsAnimationWrapper(InputConsumerController inputConsumer, + Supplier touchProxySupplier) { + mInputConsumer = inputConsumer; mTouchProxySupplier = touchProxySupplier; - mMainThreadExecutor.execute(mInputConsumer::registerInputConsumer); } public synchronized void setController( @@ -109,6 +103,7 @@ public class RecentsAnimationWrapper { public void finish(boolean toHome, Runnable onFinishComplete) { if (!toHome) { mExecutorService.submit(() -> finishBg(false, onFinishComplete)); + return; } mMainThreadExecutor.execute(() -> { @@ -152,28 +147,12 @@ public class RecentsAnimationWrapper { } } - public void unregisterInputConsumer() { - mMainThreadExecutor.execute(this::unregisterInputConsumerUi); - } - - private void unregisterInputConsumerUi() { - if (mTouchProxyEnabled && mTouchInProgress) { - mInputConsumerUnregisterPending = true; - } else { - mInputConsumerUnregistered = true; - mInputConsumer.unregisterInputConsumer(); - } - } - public void enableTouchProxy() { mMainThreadExecutor.execute(this::enableTouchProxyUi); } private void enableTouchProxyUi() { - if (!mInputConsumerUnregistered) { - mTouchProxyEnabled = true; - mInputConsumer.setTouchListener(this::onInputConsumerTouch); - } + mInputConsumer.setTouchListener(this::onInputConsumerTouch); } private boolean onInputConsumerTouch(MotionEvent ev) { @@ -184,10 +163,6 @@ public class RecentsAnimationWrapper { } else if (action == ACTION_CANCEL || action == ACTION_UP) { // Finish any pending actions mTouchInProgress = false; - if (mInputConsumerUnregisterPending) { - mInputConsumerUnregisterPending = false; - mInputConsumer.unregisterInputConsumer(); - } if (mFinishPending) { mFinishPending = false; mExecutorService.submit(() -> finishBg(true, null)); diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index cded799831..9371a4c1cf 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -51,6 +51,7 @@ import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ChoreographerCompat; +import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.NavigationBarCompat.HitTarget; import java.io.FileDescriptor; @@ -185,6 +186,7 @@ public class TouchInteractionService extends Service { private OverviewCallbacks mOverviewCallbacks; private TaskOverlayFactory mTaskOverlayFactory; private TouchInteractionLog mTouchInteractionLog; + private InputConsumerController mInputConsumer; private Choreographer mMainThreadChoreographer; private Choreographer mBackgroundThreadChoreographer; @@ -203,6 +205,8 @@ public class TouchInteractionService extends Service { mOverviewCallbacks = OverviewCallbacks.get(this); mTaskOverlayFactory = TaskOverlayFactory.get(this); mTouchInteractionLog = new TouchInteractionLog(); + mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); + mInputConsumer.registerInputConsumer(); sConnected = true; @@ -213,6 +217,7 @@ public class TouchInteractionService extends Service { @Override public void onDestroy() { + mInputConsumer.unregisterInputConsumer(); mOverviewCommandHelper.onDestroy(); sConnected = false; super.onDestroy(); @@ -256,7 +261,7 @@ public class TouchInteractionService extends Service { mOverviewCommandHelper.overviewIntent, mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor, mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks, - mTaskOverlayFactory, tracker, mTouchInteractionLog); + mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog); } } diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index 9991552987..c4c660c647 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -78,6 +78,7 @@ import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.LatencyTrackerCompat; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -226,8 +227,7 @@ public class WindowTransformSwipeHandler { private @InteractionType int mInteractionType = INTERACTION_NORMAL; - private final RecentsAnimationWrapper mRecentsAnimationWrapper = - new RecentsAnimationWrapper(this::createNewTouchProxyHandler); + private final RecentsAnimationWrapper mRecentsAnimationWrapper; private final long mTouchTimeMs; private long mLauncherFrameDrawnTime; @@ -241,7 +241,7 @@ public class WindowTransformSwipeHandler { WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs, ActivityControlHelper controller, - TouchInteractionLog touchInteractionLog) { + InputConsumerController inputConsumer, TouchInteractionLog touchInteractionLog) { this.id = id; mContext = context; mRunningTaskInfo = runningTaskInfo; @@ -251,6 +251,8 @@ public class WindowTransformSwipeHandler { mActivityInitListener = mActivityControlHelper .createActivityInitListener(this::onActivityInit); mTouchInteractionLog = touchInteractionLog; + mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer, + this::createNewTouchProxyHandler); initStateCallbacks(); } @@ -860,7 +862,6 @@ public class WindowTransformSwipeHandler { } mActivityInitListener.unregister(); - mRecentsAnimationWrapper.unregisterInputConsumer(); mTaskSnapshot = null; } From b2498b279095fbddb8d05031158638d79491bb0d Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 16 Oct 2018 16:36:02 -0700 Subject: [PATCH 4/8] Using support lib implementation for launcher preference Bug: 117519297 Change-Id: Icea5e022a337436e48db9376fd441f805dc34e54 --- Android.mk | 3 +- AndroidManifest-common.xml | 2 +- res/xml/launcher_preferences.xml | 11 +- .../android/launcher3/SettingsActivity.java | 326 ------------------ .../launcher3/graphics/IconShapeOverride.java | 6 +- .../settings/IconBadgingPreference.java | 138 ++++++++ .../settings/PreferenceHighlighter.java | 127 +++++++ .../launcher3/settings/SettingsActivity.java | 250 ++++++++++++++ .../launcher3/util/ListViewHighlighter.java | 142 -------- .../launcher3/views/ButtonPreference.java | 65 ---- 10 files changed, 527 insertions(+), 543 deletions(-) delete mode 100644 src/com/android/launcher3/SettingsActivity.java create mode 100644 src/com/android/launcher3/settings/IconBadgingPreference.java create mode 100644 src/com/android/launcher3/settings/PreferenceHighlighter.java create mode 100644 src/com/android/launcher3/settings/SettingsActivity.java delete mode 100644 src/com/android/launcher3/util/ListViewHighlighter.java delete mode 100644 src/com/android/launcher3/views/ButtonPreference.java diff --git a/Android.mk b/Android.mk index b8e6c858f9..bf9b8a0d7d 100644 --- a/Android.mk +++ b/Android.mk @@ -67,7 +67,8 @@ LOCAL_MODULE_TAGS := optional LOCAL_STATIC_ANDROID_LIBRARIES := \ androidx.recyclerview_recyclerview \ - androidx.dynamicanimation_dynamicanimation + androidx.dynamicanimation_dynamicanimation \ + androidx.preference_preference LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 8f4d5bece5..5b00b7d027 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -156,7 +156,7 @@ The settings activity. To extend point settings_fragment_name to appropriate fragment class --> diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml index 1df7c2fbaf..c55cc49855 100644 --- a/res/xml/launcher_preferences.xml +++ b/res/xml/launcher_preferences.xml @@ -14,9 +14,10 @@ limitations under the License. --> - + - - + - - + diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java deleted file mode 100644 index a17f614192..0000000000 --- a/src/com/android/launcher3/SettingsActivity.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2015 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; - -import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; -import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue; -import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.Fragment; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; -import android.provider.Settings; -import android.text.TextUtils; -import android.view.View; -import android.widget.Adapter; -import android.widget.ListView; - -import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.graphics.IconShapeOverride; -import com.android.launcher3.notification.NotificationListener; -import com.android.launcher3.util.ListViewHighlighter; -import com.android.launcher3.util.SecureSettingsObserver; -import com.android.launcher3.views.ButtonPreference; - -import java.util.Objects; - -/** - * Settings activity for Launcher. Currently implements the following setting: Allow rotation - */ -public class SettingsActivity extends Activity - implements PreferenceFragment.OnPreferenceStartFragmentCallback { - - private static final String FLAGS_PREFERENCE_KEY = "flag_toggler"; - - private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging"; - /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ - private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners"; - - private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; - private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; - private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; - private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (savedInstanceState == null) { - Fragment f = Fragment.instantiate(this, getString(R.string.settings_fragment_name)); - // Display the fragment as the main content. - getFragmentManager().beginTransaction() - .replace(android.R.id.content, f) - .commit(); - } - } - - protected PreferenceFragment getNewFragment() { - return new LauncherSettingsFragment(); - } - - @Override - public boolean onPreferenceStartFragment( - PreferenceFragment preferenceFragment, Preference pref) { - if (getFragmentManager().isStateSaved()) { - // Sometimes onClick can come after onPause because of being posted on the handler. - // Skip starting new fragments in that case. - return false; - } - Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras()); - if (f instanceof DialogFragment) { - ((DialogFragment) f).show(getFragmentManager(), pref.getKey()); - } else { - getFragmentManager() - .beginTransaction() - .replace(android.R.id.content, f) - .addToBackStack(pref.getKey()) - .commit(); - } - return true; - } - - /** - * This fragment shows the launcher preferences. - */ - public static class LauncherSettingsFragment extends PreferenceFragment { - - private SecureSettingsObserver mIconBadgingObserver; - - private String mPreferenceKey; - private boolean mPreferenceHighlighted = false; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY); - } - - getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY); - addPreferencesFromResource(R.xml.launcher_preferences); - - // Only show flag toggler UI if this build variant implements that. - Preference flagToggler = findPreference(FLAGS_PREFERENCE_KEY); - if (flagToggler != null && !FeatureFlags.showFlagTogglerUi()) { - getPreferenceScreen().removePreference(flagToggler); - } - - ContentResolver resolver = getActivity().getContentResolver(); - - ButtonPreference iconBadgingPref = - (ButtonPreference) findPreference(ICON_BADGING_PREFERENCE_KEY); - if (!Utilities.ATLEAST_OREO) { - getPreferenceScreen().removePreference( - findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY)); - getPreferenceScreen().removePreference(iconBadgingPref); - } else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) { - getPreferenceScreen().removePreference(iconBadgingPref); - } else { - // Listen to system notification badge settings while this UI is active. - mIconBadgingObserver = newNotificationSettingsObserver( - getActivity(), new IconBadgingObserver(iconBadgingPref, resolver)); - mIconBadgingObserver.register(); - // Also listen if notification permission changes - mIconBadgingObserver.getResolver().registerContentObserver( - Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false, - mIconBadgingObserver); - mIconBadgingObserver.dispatchOnChange(); - } - - Preference iconShapeOverride = findPreference(IconShapeOverride.KEY_PREFERENCE); - if (iconShapeOverride != null) { - if (IconShapeOverride.isSupported(getActivity())) { - IconShapeOverride.handlePreferenceUi((ListPreference) iconShapeOverride); - } else { - getPreferenceScreen().removePreference(iconShapeOverride); - } - } - - // Setup allow rotation preference - Preference rotationPref = findPreference(ALLOW_ROTATION_PREFERENCE_KEY); - if (getResources().getBoolean(R.bool.allow_rotation)) { - // Launcher supports rotation by default. No need to show this setting. - getPreferenceScreen().removePreference(rotationPref); - } else { - // Initialize the UI once - rotationPref.setDefaultValue(getAllowRotationDefaultValue()); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted); - } - - @Override - public void onResume() { - super.onResume(); - - Intent intent = getActivity().getIntent(); - mPreferenceKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY); - if (isAdded() && !mPreferenceHighlighted && !TextUtils.isEmpty(mPreferenceKey)) { - getView().postDelayed(this::highlightPreference, DELAY_HIGHLIGHT_DURATION_MILLIS); - } - } - - private void highlightPreference() { - Preference pref = findPreference(mPreferenceKey); - if (pref == null || getPreferenceScreen() == null) { - return; - } - PreferenceScreen screen = getPreferenceScreen(); - if (Utilities.ATLEAST_OREO) { - screen = selectPreferenceRecursive(pref, screen); - } - if (screen == null) { - return; - } - - View root = screen.getDialog() != null - ? screen.getDialog().getWindow().getDecorView() : getView(); - ListView list = root.findViewById(android.R.id.list); - if (list == null || list.getAdapter() == null) { - return; - } - Adapter adapter = list.getAdapter(); - - // Find the position - int position = -1; - for (int i = adapter.getCount() - 1; i >= 0; i--) { - if (pref == adapter.getItem(i)) { - position = i; - break; - } - } - new ListViewHighlighter(list, position); - mPreferenceHighlighted = true; - } - - @Override - public void onDestroy() { - if (mIconBadgingObserver != null) { - mIconBadgingObserver.unregister(); - mIconBadgingObserver = null; - } - super.onDestroy(); - } - - @TargetApi(Build.VERSION_CODES.O) - private PreferenceScreen selectPreferenceRecursive( - Preference pref, PreferenceScreen topParent) { - if (!(pref.getParent() instanceof PreferenceScreen)) { - return null; - } - - PreferenceScreen parent = (PreferenceScreen) pref.getParent(); - if (Objects.equals(parent.getKey(), topParent.getKey())) { - return parent; - } else if (selectPreferenceRecursive(parent, topParent) != null) { - ((PreferenceScreen) parent.getParent()) - .onItemClick(null, null, parent.getOrder(), 0); - return parent; - } else { - return null; - } - } - } - - /** - * Content observer which listens for system badging setting changes, - * and updates the launcher badging setting subtext accordingly. - */ - private static class IconBadgingObserver implements SecureSettingsObserver.OnChangeListener { - - private final ButtonPreference mBadgingPref; - private final ContentResolver mResolver; - - public IconBadgingObserver(ButtonPreference badgingPref, ContentResolver resolver) { - mBadgingPref = badgingPref; - mResolver = resolver; - } - - @Override - public void onSettingsChanged(boolean enabled) { - int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off; - - boolean serviceEnabled = true; - if (enabled) { - // Check if the listener is enabled or not. - String enabledListeners = - Settings.Secure.getString(mResolver, NOTIFICATION_ENABLED_LISTENERS); - ComponentName myListener = - new ComponentName(mBadgingPref.getContext(), NotificationListener.class); - serviceEnabled = enabledListeners != null && - (enabledListeners.contains(myListener.flattenToString()) || - enabledListeners.contains(myListener.flattenToShortString())); - if (!serviceEnabled) { - summary = R.string.title_missing_notification_access; - } - } - mBadgingPref.setWidgetFrameVisible(!serviceEnabled); - mBadgingPref.setFragment( - serviceEnabled ? null : NotificationAccessConfirmation.class.getName()); - mBadgingPref.setSummary(summary); - - } - } - - public static class NotificationAccessConfirmation - extends DialogFragment implements DialogInterface.OnClickListener { - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - String msg = context.getString(R.string.msg_missing_notification_access, - context.getString(R.string.derived_app_name)); - return new AlertDialog.Builder(context) - .setTitle(R.string.title_missing_notification_access) - .setMessage(msg) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.title_change_settings, this) - .create(); - } - - @Override - public void onClick(DialogInterface dialogInterface, int i) { - ComponentName cn = new ComponentName(getActivity(), NotificationListener.class); - Bundle showFragmentArgs = new Bundle(); - showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString()); - - Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString()) - .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs); - getActivity().startActivity(intent); - } - } -} diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java index cadc6e35eb..b636c6d478 100644 --- a/src/com/android/launcher3/graphics/IconShapeOverride.java +++ b/src/com/android/launcher3/graphics/IconShapeOverride.java @@ -26,9 +26,6 @@ import android.content.Intent; import android.content.res.Resources; import android.os.Build; import android.os.SystemClock; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -42,6 +39,9 @@ import com.android.launcher3.util.LooperExecutor; import java.lang.reflect.Field; import androidx.annotation.NonNull; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.Preference.OnPreferenceChangeListener; /** * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}. diff --git a/src/com/android/launcher3/settings/IconBadgingPreference.java b/src/com/android/launcher3/settings/IconBadgingPreference.java new file mode 100644 index 0000000000..7c97b38d28 --- /dev/null +++ b/src/com/android/launcher3/settings/IconBadgingPreference.java @@ -0,0 +1,138 @@ +/* + * 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.settings; + +import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY; +import static com.android.launcher3.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGS; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.util.AttributeSet; +import android.view.View; + +import com.android.launcher3.R; +import com.android.launcher3.notification.NotificationListener; +import com.android.launcher3.util.SecureSettingsObserver; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +/** + * A {@link Preference} for indicating icon badging status. + * Also has utility methods for updating UI based on badging status changes. + */ +public class IconBadgingPreference extends Preference + implements SecureSettingsObserver.OnChangeListener { + + private boolean mWidgetFrameVisible = false; + + /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ + private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners"; + + public IconBadgingPreference( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public IconBadgingPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public IconBadgingPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public IconBadgingPreference(Context context) { + super(context); + } + + private void setWidgetFrameVisible(boolean isVisible) { + if (mWidgetFrameVisible != isVisible) { + mWidgetFrameVisible = isVisible; + notifyChanged(); + } + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + View widgetFrame = holder.findViewById(android.R.id.widget_frame); + if (widgetFrame != null) { + widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onSettingsChanged(boolean enabled) { + int summary = enabled ? R.string.icon_badging_desc_on : R.string.icon_badging_desc_off; + + boolean serviceEnabled = true; + if (enabled) { + // Check if the listener is enabled or not. + String enabledListeners = Settings.Secure.getString( + getContext().getContentResolver(), NOTIFICATION_ENABLED_LISTENERS); + ComponentName myListener = + new ComponentName(getContext(), NotificationListener.class); + serviceEnabled = enabledListeners != null && + (enabledListeners.contains(myListener.flattenToString()) || + enabledListeners.contains(myListener.flattenToShortString())); + if (!serviceEnabled) { + summary = R.string.title_missing_notification_access; + } + } + setWidgetFrameVisible(!serviceEnabled); + setFragment(serviceEnabled ? null : NotificationAccessConfirmation.class.getName()); + setSummary(summary); + } + + public static class NotificationAccessConfirmation + extends DialogFragment implements DialogInterface.OnClickListener { + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Context context = getActivity(); + String msg = context.getString(R.string.msg_missing_notification_access, + context.getString(R.string.derived_app_name)); + return new AlertDialog.Builder(context) + .setTitle(R.string.title_missing_notification_access) + .setMessage(msg) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.title_change_settings, this) + .create(); + } + + @Override + public void onClick(DialogInterface dialogInterface, int i) { + ComponentName cn = new ComponentName(getActivity(), NotificationListener.class); + Bundle showFragmentArgs = new Bundle(); + showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString()); + + Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(EXTRA_FRAGMENT_ARG_KEY, cn.flattenToString()) + .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs); + getActivity().startActivity(intent); + } + } +} diff --git a/src/com/android/launcher3/settings/PreferenceHighlighter.java b/src/com/android/launcher3/settings/PreferenceHighlighter.java new file mode 100644 index 0000000000..4ed4cf1133 --- /dev/null +++ b/src/com/android/launcher3/settings/PreferenceHighlighter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 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.settings; + +import static androidx.core.graphics.ColorUtils.setAlphaComponent; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.Property; +import android.view.View; + +import com.android.launcher3.util.Themes; + +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.ItemDecoration; +import androidx.recyclerview.widget.RecyclerView.State; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; + +/** + * Utility class for highlighting a preference + */ +public class PreferenceHighlighter extends ItemDecoration implements Runnable { + + private static final Property HIGHLIGHT_COLOR = + new Property(Integer.TYPE, "highlightColor") { + + @Override + public Integer get(PreferenceHighlighter highlighter) { + return highlighter.mHighlightColor; + } + + @Override + public void set(PreferenceHighlighter highlighter, Integer value) { + highlighter.mHighlightColor = value; + highlighter.mRv.invalidateItemDecorations(); + } + }; + + private static final long HIGHLIGHT_DURATION = 15000L; + private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L; + private static final long HIGHLIGHT_FADE_IN_DURATION = 200L; + private static final int END_COLOR = setAlphaComponent(Color.WHITE, 0); + + private final Paint mPaint = new Paint(); + private final RecyclerView mRv; + private final int mIndex; + + private boolean mHighLightStarted = false; + private int mHighlightColor = END_COLOR; + + + public PreferenceHighlighter(RecyclerView rv, int index) { + mRv = rv; + mIndex = index; + } + + @Override + public void run() { + mRv.addItemDecoration(this); + mRv.smoothScrollToPosition(mIndex); + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, State state) { + ViewHolder holder = parent.findViewHolderForAdapterPosition(mIndex); + if (holder == null) { + return; + } + if (!mHighLightStarted && state.getRemainingScrollVertical() != 0) { + // Wait until scrolling stopped + return; + } + + if (!mHighLightStarted) { + // Start highlight + int colorTo = setAlphaComponent(Themes.getColorAccent(mRv.getContext()), 66); + ObjectAnimator anim = ObjectAnimator.ofArgb(this, HIGHLIGHT_COLOR, END_COLOR, colorTo); + anim.setDuration(HIGHLIGHT_FADE_IN_DURATION); + anim.setRepeatMode(ValueAnimator.REVERSE); + anim.setRepeatCount(4); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + removeHighlight(); + } + }); + anim.start(); + mHighLightStarted = true; + } + + View view = holder.itemView; + mPaint.setColor(mHighlightColor); + c.drawRect(0, view.getY(), parent.getWidth(), view.getY() + view.getHeight(), mPaint); + } + + private void removeHighlight() { + ObjectAnimator anim = ObjectAnimator.ofArgb( + this, HIGHLIGHT_COLOR, mHighlightColor, END_COLOR); + anim.setDuration(HIGHLIGHT_FADE_OUT_DURATION); + anim.setStartDelay(HIGHLIGHT_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRv.removeItemDecoration(PreferenceHighlighter.this); + } + }); + anim.start(); + } +} diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java new file mode 100644 index 0000000000..66420d079e --- /dev/null +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2015 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.settings; + +import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY; +import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; +import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue; +import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.LauncherFiles; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.IconShapeOverride; +import com.android.launcher3.util.SecureSettingsObserver; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragment; +import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback; +import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback; +import androidx.preference.PreferenceGroup.PreferencePositionCallback; +import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; + +/** + * Settings activity for Launcher. Currently implements the following setting: Allow rotation + */ +public class SettingsActivity extends Activity + implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback { + + private static final String FLAGS_PREFERENCE_KEY = "flag_toggler"; + + private static final String ICON_BADGING_PREFERENCE_KEY = "pref_icon_badging"; + /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */ + private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners"; + + public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; + public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; + private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; + public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + Bundle args = new Bundle(); + String prefKey = getIntent().getStringExtra(EXTRA_FRAGMENT_ARG_KEY); + if (!TextUtils.isEmpty(prefKey)) { + args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey); + } + + Fragment f = Fragment.instantiate( + this, getString(R.string.settings_fragment_name), args); + // Display the fragment as the main content. + getFragmentManager().beginTransaction() + .replace(android.R.id.content, f) + .commit(); + } + } + + private boolean startFragment(String fragment, Bundle args, String key) { + if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) { + // Sometimes onClick can come after onPause because of being posted on the handler. + // Skip starting new fragments in that case. + return false; + } + Fragment f = Fragment.instantiate(this, fragment, args); + if (f instanceof DialogFragment) { + ((DialogFragment) f).show(getFragmentManager(), key); + } else { + getFragmentManager() + .beginTransaction() + .replace(android.R.id.content, f) + .addToBackStack(key) + .commit(); + } + return true; + } + + @Override + public boolean onPreferenceStartFragment( + PreferenceFragment preferenceFragment, Preference pref) { + return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey()); + } + + @Override + public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) { + Bundle args = new Bundle(); + args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey()); + return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey()); + } + + /** + * This fragment shows the launcher preferences. + */ + public static class LauncherSettingsFragment extends PreferenceFragment { + + private SecureSettingsObserver mIconBadgingObserver; + + private String mHighLightKey; + private boolean mPreferenceHighlighted = false; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + final Bundle args = getArguments(); + mHighLightKey = args == null ? null : args.getString(EXTRA_FRAGMENT_ARG_KEY); + if (rootKey == null && !TextUtils.isEmpty(mHighLightKey)) { + rootKey = getParentKeyForPref(mHighLightKey); + } + + if (savedInstanceState != null) { + mPreferenceHighlighted = savedInstanceState.getBoolean(SAVE_HIGHLIGHTED_KEY); + } + + getPreferenceManager().setSharedPreferencesName(LauncherFiles.SHARED_PREFERENCES_KEY); + setPreferencesFromResource(R.xml.launcher_preferences, rootKey); + + PreferenceScreen screen = getPreferenceScreen(); + for (int i = screen.getPreferenceCount() - 1; i >= 0; i--) { + Preference preference = screen.getPreference(i); + if (!initPreference(preference)) { + screen.removePreference(preference); + } + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted); + } + + protected String getParentKeyForPref(String key) { + return null; + } + + /** + * Initializes a preference. This is called for every preference. Returning false here + * will remove that preference from the list. + */ + protected boolean initPreference(Preference preference) { + switch (preference.getKey()) { + case ICON_BADGING_PREFERENCE_KEY: + if (!Utilities.ATLEAST_OREO || + !getResources().getBoolean(R.bool.notification_badging_enabled)) { + return false; + } + + // Listen to system notification badge settings while this UI is active. + mIconBadgingObserver = newNotificationSettingsObserver( + getActivity(), (IconBadgingPreference) preference); + mIconBadgingObserver.register(); + // Also listen if notification permission changes + mIconBadgingObserver.getResolver().registerContentObserver( + Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false, + mIconBadgingObserver); + mIconBadgingObserver.dispatchOnChange(); + return true; + + case ADD_ICON_PREFERENCE_KEY: + return Utilities.ATLEAST_OREO; + + case IconShapeOverride.KEY_PREFERENCE: + if (!IconShapeOverride.isSupported(getActivity())) { + return false; + } + IconShapeOverride.handlePreferenceUi((ListPreference) preference); + return true; + + case ALLOW_ROTATION_PREFERENCE_KEY: + if (getResources().getBoolean(R.bool.allow_rotation)) { + // Launcher supports rotation by default. No need to show this setting. + return false; + } + // Initialize the UI once + preference.setDefaultValue(getAllowRotationDefaultValue()); + return true; + + case FLAGS_PREFERENCE_KEY: + // Only show flag toggler UI if this build variant implements that. + return FeatureFlags.showFlagTogglerUi(); + } + + return true; + } + + @Override + public void onResume() { + super.onResume(); + + if (isAdded() && !mPreferenceHighlighted) { + PreferenceHighlighter highlighter = createHighlighter(); + if (highlighter != null) { + getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS); + mPreferenceHighlighted = true; + } + } + } + + private PreferenceHighlighter createHighlighter() { + if (TextUtils.isEmpty(mHighLightKey)) { + return null; + } + + PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + return null; + } + + RecyclerView list = getListView(); + PreferencePositionCallback callback = (PreferencePositionCallback) list.getAdapter(); + int position = callback.getPreferenceAdapterPosition(mHighLightKey); + return position >= 0 ? new PreferenceHighlighter(list, position) : null; + } + + @Override + public void onDestroy() { + if (mIconBadgingObserver != null) { + mIconBadgingObserver.unregister(); + mIconBadgingObserver = null; + } + super.onDestroy(); + } + } +} diff --git a/src/com/android/launcher3/util/ListViewHighlighter.java b/src/com/android/launcher3/util/ListViewHighlighter.java deleted file mode 100644 index c9fe228d58..0000000000 --- a/src/com/android/launcher3/util/ListViewHighlighter.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2018 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.animation.ArgbEvaluator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.AbsListView.RecyclerListener; -import android.widget.ListView; - -import com.android.launcher3.R; - -import androidx.core.graphics.ColorUtils; - -/** - * Utility class to scroll and highlight a list view item - */ -public class ListViewHighlighter implements OnScrollListener, RecyclerListener, - OnLayoutChangeListener { - - private final ListView mListView; - private int mPosHighlight; - - private boolean mColorAnimated = false; - - public ListViewHighlighter(ListView listView, int posHighlight) { - mListView = listView; - mPosHighlight = posHighlight; - mListView.setOnScrollListener(this); - mListView.setRecyclerListener(this); - mListView.addOnLayoutChangeListener(this); - - mListView.post(this::tryHighlight); - } - - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - mListView.post(this::tryHighlight); - } - - private void tryHighlight() { - if (mPosHighlight < 0 || mListView.getChildCount() == 0) { - return; - } - if (!highlightIfVisible(mListView.getFirstVisiblePosition(), - mListView.getLastVisiblePosition())) { - mListView.smoothScrollToPosition(mPosHighlight); - } - } - - @Override - public void onScrollStateChanged(AbsListView absListView, int i) { } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - highlightIfVisible(firstVisibleItem, firstVisibleItem + visibleItemCount - 1); - } - - private boolean highlightIfVisible(int start, int end) { - if (mPosHighlight < 0 || mListView.getChildCount() == 0) { - return false; - } - if (start > mPosHighlight || mPosHighlight > end) { - return false; - } - highlightView(mListView.getChildAt(mPosHighlight - start)); - - // finish highlight - mListView.setOnScrollListener(null); - mListView.removeOnLayoutChangeListener(this); - - mPosHighlight = -1; - return true; - } - - @Override - public void onMovedToScrapHeap(View view) { - unhighlightView(view); - } - - private void highlightView(View view) { - if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) { - // already highlighted - } else { - view.setTag(R.id.view_highlighted, true); - view.setTag(R.id.view_unhighlight_background, view.getBackground()); - view.setBackground(getHighlightBackground()); - view.postDelayed(() -> { - unhighlightView(view); - }, 15000L); - } - } - - private void unhighlightView(View view) { - if (Boolean.TRUE.equals(view.getTag(R.id.view_highlighted))) { - Object background = view.getTag(R.id.view_unhighlight_background); - if (background instanceof Drawable) { - view.setBackground((Drawable) background); - } - view.setTag(R.id.view_unhighlight_background, null); - view.setTag(R.id.view_highlighted, false); - } - } - - private ColorDrawable getHighlightBackground() { - int color = ColorUtils.setAlphaComponent(Themes.getColorAccent(mListView.getContext()), 26); - if (mColorAnimated) { - return new ColorDrawable(color); - } - mColorAnimated = true; - ColorDrawable bg = new ColorDrawable(Color.WHITE); - ObjectAnimator anim = ObjectAnimator.ofInt(bg, "color", Color.WHITE, color); - anim.setEvaluator(new ArgbEvaluator()); - anim.setDuration(200L); - anim.setRepeatMode(ValueAnimator.REVERSE); - anim.setRepeatCount(4); - anim.start(); - return bg; - } -} diff --git a/src/com/android/launcher3/views/ButtonPreference.java b/src/com/android/launcher3/views/ButtonPreference.java deleted file mode 100644 index fdcf2ca5ba..0000000000 --- a/src/com/android/launcher3/views/ButtonPreference.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.views; - -import android.content.Context; -import android.preference.Preference; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; - -/** - * Extension of {@link Preference} which makes the widget layout clickable. - * - * @see #setWidgetLayoutResource(int) - */ -public class ButtonPreference extends Preference { - - private boolean mWidgetFrameVisible = false; - - public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public ButtonPreference(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ButtonPreference(Context context) { - super(context); - } - - public void setWidgetFrameVisible(boolean isVisible) { - if (mWidgetFrameVisible != isVisible) { - mWidgetFrameVisible = isVisible; - notifyChanged(); - } - } - - @Override - protected void onBindView(View view) { - super.onBindView(view); - - ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame); - if (widgetFrame != null) { - widgetFrame.setVisibility(mWidgetFrameVisible ? View.VISIBLE : View.GONE); - } - } -} From 0ca6c30bf022fda776abf913e0cf61c464c44601 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Fri, 19 Oct 2018 11:39:02 -0700 Subject: [PATCH 5/8] Enabling creating additional shortcuts by any launcher Bug: 117888000 Test: manual test with a prototype Change-Id: I4d678a6c35341e61c74ed295d2b2202c5eebe034 --- .../android/quickstep/TaskOverlayFactory.java | 35 ++++++++++++++++--- .../android/quickstep/views/TaskMenuView.java | 22 +++++------- .../quickstep/views/TaskThumbnailView.java | 4 +++ .../com/android/quickstep/views/TaskView.java | 21 ++++++++--- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java index 72895167f8..59a937f4cc 100644 --- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java @@ -20,21 +20,34 @@ import android.content.Context; import android.graphics.Matrix; import android.view.View; +import androidx.annotation.AnyThread; + +import com.android.launcher3.BaseActivity; +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.ResourceBasedOverride; +import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; -import androidx.annotation.AnyThread; +import java.util.ArrayList; +import java.util.List; /** * Factory class to create and add an overlays on the TaskView */ public class TaskOverlayFactory implements ResourceBasedOverride { - private static TaskOverlayFactory sInstance; + /** Note that these will be shown in order from top to bottom, if available for the task. */ + private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{ + new TaskSystemShortcut.AppInfo(), + new TaskSystemShortcut.SplitScreen(), + new TaskSystemShortcut.Pin(), + new TaskSystemShortcut.Install(), + }; + public static TaskOverlayFactory get(Context context) { Preconditions.assertUIThread(); if (sInstance == null) { @@ -55,9 +68,23 @@ public class TaskOverlayFactory implements ResourceBasedOverride { public static class TaskOverlay { - public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) { } + public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) { + } - public void reset() { } + public void reset() { + } + public List getEnabledShortcuts(TaskView taskView) { + final ArrayList shortcuts = new ArrayList<>(); + final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext()); + for (TaskSystemShortcut menuOption : MENU_OPTIONS) { + View.OnClickListener onClickListener = + menuOption.getOnClickListener(activity, taskView); + if (onClickListener != null) { + shortcuts.add(menuOption); + } + } + return shortcuts; + } } } diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java index c4afad7c99..667165bf2e 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java @@ -43,6 +43,8 @@ import com.android.quickstep.TaskSystemShortcut; import com.android.quickstep.TaskUtils; import com.android.quickstep.views.IconView.OnScaleUpdateListener; +import java.util.List; + /** * Contains options for a recent task when long-pressing its icon. */ @@ -50,14 +52,6 @@ public class TaskMenuView extends AbstractFloatingView { private static final Rect sTempRect = new Rect(); - /** Note that these will be shown in order from top to bottom, if available for the task. */ - public static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[] { - new TaskSystemShortcut.AppInfo(), - new TaskSystemShortcut.SplitScreen(), - new TaskSystemShortcut.Pin(), - new TaskSystemShortcut.Install(), - }; - private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() { @Override public void onScaleUpdate(float scale) { @@ -197,11 +191,13 @@ public class TaskMenuView extends AbstractFloatingView { params.topMargin = (int) -mThumbnailTopMargin; mTaskIcon.setLayoutParams(params); - for (TaskSystemShortcut menuOption : MENU_OPTIONS) { - OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView); - if (onClickListener != null) { - addMenuOption(menuOption, onClickListener); - } + final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext()); + final List shortcuts = + taskView.getTaskOverlay().getEnabledShortcuts(taskView); + final int count = shortcuts.size(); + for (int i = 0; i < count; ++i) { + final TaskSystemShortcut menuOption = shortcuts.get(i); + addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView)); } } diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index 7223f97f44..ce65de1e09 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -173,6 +173,10 @@ public class TaskThumbnailView extends View { return 0; } + public TaskOverlay getTaskOverlay() { + return mOverlay; + } + @Override protected void onDraw(Canvas canvas) { drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius); diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index c1424c4751..56074f0f55 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -42,12 +42,12 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.Toast; -import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; +import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskSystemShortcut; import com.android.quickstep.TaskUtils; import com.android.quickstep.views.RecentsView.PageCallbacks; @@ -56,8 +56,9 @@ import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskCallbacks; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; - import com.android.systemui.shared.system.ActivityOptionsCompat; + +import java.util.List; import java.util.function.Consumer; /** @@ -190,6 +191,10 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback return mIconView; } + public TaskOverlayFactory.TaskOverlay getTaskOverlay() { + return mSnapshotView.getTaskOverlay(); + } + public void launchTask(boolean animate) { launchTask(animate, (result) -> { if (!result) { @@ -384,7 +389,11 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback final Context context = getContext(); final BaseDraggingActivity activity = fromContext(context); - for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) { + final List shortcuts = + mSnapshotView.getTaskOverlay().getEnabledShortcuts(this); + final int count = shortcuts.size(); + for (int i = 0; i < count; ++i) { + final TaskSystemShortcut menuOption = shortcuts.get(i); OnClickListener onClickListener = menuOption.getOnClickListener(activity, this); if (onClickListener != null) { info.addAction(menuOption.createAccessibilityAction(context)); @@ -407,7 +416,11 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback return true; } - for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) { + final List shortcuts = + mSnapshotView.getTaskOverlay().getEnabledShortcuts(this); + final int count = shortcuts.size(); + for (int i = 0; i < count; ++i) { + final TaskSystemShortcut menuOption = shortcuts.get(i); if (menuOption.hasHandlerForAction(action)) { OnClickListener onClickListener = menuOption.getOnClickListener( fromContext(getContext()), this); From cda96a5b43b7facfd64ef58f54763862731c9bc4 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Thu, 18 Oct 2018 15:05:45 -0700 Subject: [PATCH 6/8] Setup make icon-loader library Bug: 115891474 Test: make -j10 icon-loader Next step: Launcher will depend on icon-loader in next CL Change-Id: I797ddb857cf8be79f3be6ca2f174c593ca3713a5 --- Android.bp | 20 ++++++++++++++++ .../launcher3/InstallShortcutReceiver.java | 6 ++--- .../android/launcher3/ItemInfoWithIcon.java | 8 +++++++ src/com/android/launcher3/LauncherModel.java | 2 +- .../compat/LauncherAppsCompatVO.java | 2 +- .../android/launcher3/dragndrop/DragView.java | 23 ++++++++++++------- .../launcher3/icons/BaseIconCache.java | 2 +- .../launcher3/icons/BaseIconFactory.java | 4 ++-- .../android/launcher3/icons/BitmapInfo.java | 7 ------ .../android/launcher3/icons/IconCache.java | 2 +- .../launcher3/icons/IconNormalizer.java | 17 ++++---------- .../launcher3/icons/ShadowGenerator.java | 13 ++++------- .../android/launcher3/model/LoaderCursor.java | 6 ++--- .../android/launcher3/model/LoaderTask.java | 4 ++-- .../launcher3/model/PackageUpdatedTask.java | 2 +- .../launcher3/model/ShortcutsChangedTask.java | 4 ++-- .../model/UserLockStateChangedTask.java | 2 +- .../launcher3/popup/PopupPopulator.java | 2 +- 18 files changed, 70 insertions(+), 56 deletions(-) diff --git a/Android.bp b/Android.bp index 2608280c92..4b327020c3 100644 --- a/Android.bp +++ b/Android.bp @@ -28,3 +28,23 @@ java_library_static { ], platform_apis: true, } + + +android_library { + name: "icon-loader", + sdk_version: "28", + static_libs: [ + "androidx.core_core", + ], + resource_dirs: [ + "res", + ], + srcs: [ + "src/com/android/launcher3/icons/BaseIconFactory.java", + "src/com/android/launcher3/icons/BitmapInfo.java", + "src/com/android/launcher3/icons/IconNormalizer.java", + "src/com/android/launcher3/icons/FixedScaleDrawable.java", + "src/com/android/launcher3/icons/ShadowGenerator.java", + "src/com/android/launcher3/icons/ColorExtractor.java", + ], +} diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index 7190f12779..851454b77b 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -480,7 +480,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { final LauncherAppState app = LauncherAppState.getInstance(mContext); // Set default values until proper values is loaded. appInfo.title = ""; - app.getIconCache().getDefaultIcon(user).applyTo(appInfo); + appInfo.applyFrom(app.getIconCache().getDefaultIcon(user)); final ShortcutInfo si = appInfo.makeShortcut(); if (Looper.myLooper() == LauncherModel.getWorkerLooper()) { app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */); @@ -495,7 +495,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } else if (shortcutInfo != null) { ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext); LauncherIcons li = LauncherIcons.obtain(mContext); - li.createShortcutIcon(shortcutInfo).applyTo(si); + si.applyFrom(li.createShortcutIcon(shortcutInfo)); li.recycle(); return Pair.create((ItemInfo) si, (Object) shortcutInfo); } else if (providerInfo != null) { @@ -656,7 +656,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { if (iconInfo == null) { iconInfo = app.getIconCache().getDefaultIcon(info.user); } - iconInfo.applyTo(info); + info.applyFrom(iconInfo); info.title = Utilities.trim(name); info.contentDescription = UserManagerCompat.getInstance(app.getContext()) diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index 6d453c9b38..e29f92713d 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -20,6 +20,8 @@ import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; import android.graphics.Bitmap; +import com.android.launcher3.icons.BitmapInfo; + /** * Represents an ItemInfo which also holds an icon. */ @@ -118,4 +120,10 @@ public abstract class ItemInfoWithIcon extends ItemInfo { public boolean usingLowResIcon() { return iconBitmap == LOW_RES_ICON; } + + public void applyFrom(BitmapInfo info) { + iconBitmap = info.icon; + iconColor = info.color; + } + } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index cbfde25596..8e9021f58b 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -657,7 +657,7 @@ public class LauncherModel extends BroadcastReceiver updateAndBindShortcutInfo(() -> { si.updateFromDeepShortcutInfo(info, mApp.getContext()); LauncherIcons li = LauncherIcons.obtain(mApp.getContext()); - li.createShortcutIcon(info).applyTo(si); + si.applyFrom(li.createShortcutIcon(info)); li.recycle(); return si; }); diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java index fb446600c3..82617fe7ee 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java @@ -139,7 +139,7 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL { ShortcutInfo info = new ShortcutInfo(compat, context); // Apply the unbadged icon and fetch the actual icon asynchronously. LauncherIcons li = LauncherIcons.obtain(context); - li.createShortcutIcon(compat, false /* badged */).applyTo(info); + info.applyFrom(li.createShortcutIcon(compat, false /* badged */)); li.recycle(); LauncherAppState.getInstance(context).getModel() .updateAndBindShortcutInfo(info, compat); diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 6d1efd5879..b6a8b50a95 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -204,7 +204,7 @@ public class DragView extends View { public void run() { LauncherAppState appState = LauncherAppState.getInstance(mLauncher); Object[] outObj = new Object[1]; - final Drawable dr = getFullDrawable(info, appState, outObj); + Drawable dr = getFullDrawable(info, appState, outObj); if (dr instanceof AdaptiveIconDrawable) { int w = mBitmap.getWidth(); @@ -220,10 +220,20 @@ public class DragView extends View { mBadge = getBadge(info, appState, outObj[0]); mBadge.setBounds(badgeBounds); - LauncherIcons li = LauncherIcons.obtain(mLauncher); - Utilities.scaleRectAboutCenter(bounds, - li.getNormalizer().getScale(dr, null, null, null)); - li.recycle(); + // Do not draw the background in case of folder as its translucent + mDrawBitmap = !(dr instanceof FolderAdaptiveIcon); + + try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) { + Drawable nDr; // drawable to be normalized + if (mDrawBitmap) { + nDr = dr; + } else { + // Since we just want the scale, avoid heavy drawing operations + nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null); + } + Utilities.scaleRectAboutCenter(bounds, + li.getNormalizer().getScale(nDr, null, null, null)); + } AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr; // Shrink very tiny bit so that the clip path is smaller than the original bitmap @@ -259,9 +269,6 @@ public class DragView extends View { // Assign the variable on the UI thread to avoid race conditions. mScaledMaskPath = mask; - // Do not draw the background in case of folder as its translucent - mDrawBitmap = !(dr instanceof FolderAdaptiveIcon); - if (info.isDisabled()) { FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null); d.setIsDisabled(true); diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java index 6433103f94..9198c24825 100644 --- a/src/com/android/launcher3/icons/BaseIconCache.java +++ b/src/com/android/launcher3/icons/BaseIconCache.java @@ -261,7 +261,7 @@ public class BaseIconCache { protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) { info.title = Utilities.trim(entry.title); info.contentDescription = entry.contentDescription; - ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info); + info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry); } public synchronized BitmapInfo getDefaultIcon(UserHandle user) { diff --git a/src/com/android/launcher3/icons/BaseIconFactory.java b/src/com/android/launcher3/icons/BaseIconFactory.java index db723b7208..c8c9618279 100644 --- a/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/src/com/android/launcher3/icons/BaseIconFactory.java @@ -69,14 +69,14 @@ public class BaseIconFactory { public ShadowGenerator getShadowGenerator() { if (mShadowGenerator == null) { - mShadowGenerator = new ShadowGenerator(mContext); + mShadowGenerator = new ShadowGenerator(mIconBitmapSize); } return mShadowGenerator; } public IconNormalizer getNormalizer() { if (mNormalizer == null) { - mNormalizer = new IconNormalizer(mContext); + mNormalizer = new IconNormalizer(mContext, mIconBitmapSize); } return mNormalizer; } diff --git a/src/com/android/launcher3/icons/BitmapInfo.java b/src/com/android/launcher3/icons/BitmapInfo.java index ebe05113e8..245561ea53 100644 --- a/src/com/android/launcher3/icons/BitmapInfo.java +++ b/src/com/android/launcher3/icons/BitmapInfo.java @@ -18,8 +18,6 @@ package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; -import com.android.launcher3.ItemInfoWithIcon; - public class BitmapInfo { public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8); @@ -27,11 +25,6 @@ public class BitmapInfo { public Bitmap icon; public int color; - public void applyTo(ItemInfoWithIcon info) { - info.iconBitmap = icon; - info.iconColor = color; - } - public void applyTo(BitmapInfo info) { info.icon = icon; info.color = color; diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 434945550c..41a53e572f 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -139,7 +139,7 @@ public class IconCache extends BaseIconCache { // null info means not installed, but if we have a component from the intent then // we should still look in the cache for restored app icons. if (info.getTargetComponent() == null) { - getDefaultIcon(info.user).applyTo(info); + info.applyFrom(getDefaultIcon(info.user)); info.title = ""; info.contentDescription = ""; } else { diff --git a/src/com/android/launcher3/icons/IconNormalizer.java b/src/com/android/launcher3/icons/IconNormalizer.java index 73177825bd..4052a55147 100644 --- a/src/com/android/launcher3/icons/IconNormalizer.java +++ b/src/com/android/launcher3/icons/IconNormalizer.java @@ -28,14 +28,9 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.Log; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.Utilities; -import com.android.launcher3.dragndrop.FolderAdaptiveIcon; - import java.nio.ByteBuffer; import androidx.annotation.NonNull; @@ -84,9 +79,9 @@ public class IconNormalizer { private final Matrix mMatrix; /** package private **/ - IconNormalizer(Context context) { + IconNormalizer(Context context, int iconBitmapSize) { // Use twice the icon size as maximum size to avoid scaling down twice. - mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2; + mMaxSize = iconBitmapSize * 2; mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8); mCanvas = new Canvas(mBitmap); mPixels = new byte[mMaxSize * mMaxSize]; @@ -193,17 +188,13 @@ public class IconNormalizer { */ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds, @Nullable Path path, @Nullable boolean[] outMaskShape) { - if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) { + if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) { if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) { if (outBounds != null) { outBounds.set(mAdaptiveIconBounds); } return mAdaptiveIconScale; } - if (d instanceof FolderAdaptiveIcon) { - // Since we just want the scale, avoid heavy drawing operations - d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null); - } } int width = d.getIntrinsicWidth(); int height = d.getIntrinsicHeight(); @@ -314,7 +305,7 @@ public class IconNormalizer { float areaScale = area / (width * height); // Use sqrt of the final ratio as the images is scaled across both width and height. float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1; - if (Utilities.ATLEAST_OREO && d instanceof AdaptiveIconDrawable && + if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable && mAdaptiveIconScale == SCALE_NOT_INITIALIZED) { mAdaptiveIconScale = scale; mAdaptiveIconBounds.set(mBounds); diff --git a/src/com/android/launcher3/icons/ShadowGenerator.java b/src/com/android/launcher3/icons/ShadowGenerator.java index 57d463a806..6491b7ec15 100644 --- a/src/com/android/launcher3/icons/ShadowGenerator.java +++ b/src/com/android/launcher3/icons/ShadowGenerator.java @@ -16,7 +16,6 @@ package com.android.launcher3.icons; -import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BlurMaskFilter; @@ -28,23 +27,19 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; -import com.android.launcher3.LauncherAppState; - import androidx.core.graphics.ColorUtils; /** * Utility class to add shadows to bitmaps. */ public class ShadowGenerator { - - // Percent of actual icon size - private static final float HALF_DISTANCE = 0.5f; public static final float BLUR_FACTOR = 0.5f/48; // Percent of actual icon size public static final float KEY_SHADOW_DISTANCE = 1f/48; private static final int KEY_SHADOW_ALPHA = 61; - + // Percent of actual icon size + private static final float HALF_DISTANCE = 0.5f; private static final int AMBIENT_SHADOW_ALPHA = 30; private final int mIconSize; @@ -53,8 +48,8 @@ public class ShadowGenerator { private final Paint mDrawPaint; private final BlurMaskFilter mDefaultBlurMaskFilter; - public ShadowGenerator(Context context) { - mIconSize = LauncherAppState.getIDP(context).iconBitmapSize; + public ShadowGenerator(int iconSize) { + mIconSize = iconSize; mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 94cf5c286a..ea4d32b282 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -153,7 +153,7 @@ public class LoaderCursor extends CursorWrapper { info.title = getTitle(); // the fallback icon if (!loadIcon(info)) { - mIconCache.getDefaultIcon(info.user).applyTo(info); + info.applyFrom(mIconCache.getDefaultIcon(info.user)); } // TODO: If there's an explicit component and we can't install that, delete it. @@ -176,7 +176,7 @@ public class LoaderCursor extends CursorWrapper { BitmapInfo iconInfo = li.createIconBitmap(info.iconResource); li.recycle(); if (iconInfo != null) { - iconInfo.applyTo(info); + info.applyFrom(iconInfo); return true; } } @@ -185,7 +185,7 @@ public class LoaderCursor extends CursorWrapper { // Failed to load from resource, try loading from DB. byte[] data = getBlob(iconIndex); try (LauncherIcons li = LauncherIcons.obtain(mContext)) { - li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)).applyTo(info); + info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length))); return true; } catch (Exception e) { Log.e(TAG, "Failed to load icon for info " + info, e); diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 1ec7af2bb8..405125e12d 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -498,8 +498,8 @@ public class LoaderTask implements Runnable { c.loadIcon(finalInfo) ? finalInfo.iconBitmap : null; LauncherIcons li = LauncherIcons.obtain(context); - li.createShortcutIcon(pinnedShortcut, - true /* badged */, fallbackIconProvider).applyTo(info); + info.applyFrom(li.createShortcutIcon(pinnedShortcut, + true /* badged */, fallbackIconProvider)); li.recycle(); if (pmHelper.isAppSuspended( pinnedShortcut.getPackage(), info.user)) { diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 201a63e116..0f67f0cd48 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -194,7 +194,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { BitmapInfo iconInfo = li.createIconBitmap(si.iconResource); li.recycle(); if (iconInfo != null) { - iconInfo.applyTo(si); + si.applyFrom(iconInfo); infoUpdated = true; } } diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 020bc41822..47fcd9e13b 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -96,8 +96,8 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { // If the shortcut is pinned but no longer has an icon in the system, // keep the current icon instead of reverting to the default icon. LauncherIcons li = LauncherIcons.obtain(context); - li.createShortcutIcon(fullDetails, true, Provider.of(shortcutInfo.iconBitmap)) - .applyTo(shortcutInfo); + shortcutInfo.applyFrom(li.createShortcutIcon(fullDetails, true, + Provider.of(shortcutInfo.iconBitmap))); li.recycle(); updatedShortcutInfos.add(shortcutInfo); } diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 9f02d4fdad..40c1912252 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -95,7 +95,7 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { // If the shortcut is pinned but no longer has an icon in the system, // keep the current icon instead of reverting to the default icon. LauncherIcons li = LauncherIcons.obtain(context); - li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)).applyTo(si); + si.applyFrom(li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap))); li.recycle(); } else { si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER; diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java index c14c00eb87..61113b80fd 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -150,7 +150,7 @@ public class PopupPopulator { final ShortcutInfo si = new ShortcutInfo(shortcut, launcher); // Use unbadged icon for the menu. LauncherIcons li = LauncherIcons.obtain(launcher); - li.createShortcutIcon(shortcut, false /* badged */).applyTo(si); + si.applyFrom(li.createShortcutIcon(shortcut, false /* badged */)); li.recycle(); si.rank = i; From dc7d25d3e18971e1199075a4b7e58a5ddfdf4d59 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Mon, 15 Oct 2018 14:01:04 -0700 Subject: [PATCH 7/8] Adding SystemShortcut for RemoteAction Bug: 117888000 Test: manual test with a prototype Change-Id: I10ed0a8c0237a85af5ac32a7fc68e1f9d2ffc5ba --- protos/launcher_log.proto | 1 + res/values/config.xml | 1 + res/values/strings.xml | 3 + .../launcher3/popup/RemoteActionShortcut.java | 72 +++++++++++++++++++ .../launcher3/popup/SystemShortcut.java | 16 +++-- 5 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 src/com/android/launcher3/popup/RemoteActionShortcut.java diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto index 41dd0bda4f..b3ed365b88 100644 --- a/protos/launcher_log.proto +++ b/protos/launcher_log.proto @@ -112,6 +112,7 @@ enum ControlType { CANCEL_TARGET = 14; TASK_PREVIEW = 15; SPLIT_SCREEN_TARGET = 16; + REMOTE_ACTION_SHORTCUT = 17; } enum TipType { diff --git a/res/values/config.xml b/res/values/config.xml index 0efaccf194..85c2e65c51 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -123,6 +123,7 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index eb6b28413d..7e5784d0bf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -347,4 +347,7 @@ Notifications and apps are off Close Closed + + + Failed: %1$s diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java new file mode 100644 index 0000000000..af0d3daa58 --- /dev/null +++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 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.popup; + +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.userevent.nano.LauncherLogProto; + +public class RemoteActionShortcut extends SystemShortcut { + private static final String TAG = "RemoteActionShortcut"; + + private final RemoteAction mAction; + + public RemoteActionShortcut(RemoteAction action) { + super(action.getIcon(), action.getTitle(), action.getContentDescription(), + R.id.action_remote_action_shortcut); + mAction = action; + } + + @Override + public View.OnClickListener getOnClickListener( + final Launcher launcher, final ItemInfo itemInfo) { + return view -> { + AbstractFloatingView.closeAllOpenViews(launcher); + + try { + mAction.getActionIntent().send(0, + (pendingIntent, intent, resultCode, resultData, resultExtras) -> { + if (resultData != null && !resultData.isEmpty()) { + Log.e(TAG, "Remote action returned result: " + mAction.getTitle() + + " : " + resultData); + Toast.makeText(launcher, resultData, Toast.LENGTH_SHORT).show(); + } + }, new Handler(Looper.getMainLooper())); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Remote action canceled: " + mAction.getTitle(), e); + Toast.makeText(launcher, launcher.getString( + R.string.remote_action_failed, + mAction.getTitle()), + Toast.LENGTH_SHORT) + .show(); + } + + launcher.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP, + LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view); + }; + } +} diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index b80ba8abe1..f9a2007930 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -6,8 +6,10 @@ import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import android.content.Context; import android.content.Intent; import android.graphics.Rect; -import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; @@ -36,7 +38,7 @@ import java.util.List; public abstract class SystemShortcut extends ItemInfo { private final int mIconResId; private final int mLabelResId; - private final Drawable mIcon; + private final Icon mIcon; private final CharSequence mLabel; private final CharSequence mContentDescription; private final int mAccessibilityActionId; @@ -50,7 +52,7 @@ public abstract class SystemShortcut extends Ite mContentDescription = null; } - public SystemShortcut(Drawable icon, CharSequence label, CharSequence contentDescription, + public SystemShortcut(Icon icon, CharSequence label, CharSequence contentDescription, int accessibilityActionId) { mIcon = icon; mLabel = label; @@ -71,7 +73,9 @@ public abstract class SystemShortcut extends Ite public void setIconAndLabelFor(View iconView, TextView labelView) { if (mIcon != null) { - iconView.setBackground(mIcon); + mIcon.loadDrawableAsync(iconView.getContext(), + iconView::setBackground, + new Handler(Looper.getMainLooper())); } else { iconView.setBackgroundResource(mIconResId); } @@ -85,7 +89,9 @@ public abstract class SystemShortcut extends Ite public void setIconAndContentDescriptionFor(ImageView view) { if (mIcon != null) { - view.setImageDrawable(mIcon); + mIcon.loadDrawableAsync(view.getContext(), + view::setImageDrawable, + new Handler(Looper.getMainLooper())); } else { view.setImageResource(mIconResId); } From 72c8c311ba78d8a3d2dab2a2bc0e8f1d8091da0b Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Fri, 19 Oct 2018 15:57:40 -0700 Subject: [PATCH 8/8] Create OWNERS file in master branch Test: adding a meta file and doesn't affect build Bug: 117838787 Change-Id: I9a0f4ae526179f4e5bd7420b30d10c53c1b427bf --- OWNERS | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 OWNERS diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..a8c84eb2d7 --- /dev/null +++ b/OWNERS @@ -0,0 +1,4 @@ +adamcohen@google.com +hyunyoungs@google.com +sunnygoyal@google.com +winsonc@google.com