From 4d8ec15fb51258ffc4d8fc9808756e2e6ea67de0 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 12 Sep 2018 15:47:06 -0700 Subject: [PATCH 01/20] Using velocity tracker for computing the velocity of motion events Change-Id: I14f2f970825a2936f4bb285834405d67daf8667c --- .../uioverrides/TaskViewTouchController.java | 2 +- .../notification/NotificationMainView.java | 2 +- .../AbstractStateChangeTouchController.java | 2 +- .../launcher3/touch/SwipeDetector.java | 98 ++++++++----------- .../launcher3/views/AbstractSlideInView.java | 2 +- .../launcher3/touch/SwipeDetectorTest.java | 37 +++---- 6 files changed, 64 insertions(+), 79 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java index 88f2315757..54269f09fa 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java @@ -220,7 +220,7 @@ public abstract class TaskViewTouchController } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float totalDisplacement = displacement + mDisplacementShift; boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0; diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 5c0e259414..78627ecc10 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -179,7 +179,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { setContentTranslation(canChildBeDismissed() ? displacement : OverScroll.dampedScroll(displacement, getWidth())); mContentTranslateAnimator.cancel(); diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index fd9157e218..85be51312c 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -256,7 +256,7 @@ public abstract class AbstractStateChangeTouchController } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift); float progress = deltaProgress + mStartProgress; updateProgress(progress); diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java index 6ffc0efae7..a0a410e0dc 100644 --- a/src/com/android/launcher3/touch/SwipeDetector.java +++ b/src/com/android/launcher3/touch/SwipeDetector.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.PointF; import android.util.Log; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import androidx.annotation.NonNull; @@ -52,12 +53,6 @@ public class SwipeDetector { */ public static final float RELEASE_VELOCITY_PX_MS = 1.0f; - /** - * The time constant used to calculate dampening in the low-pass filter of scroll velocity. - * Cutoff frequency is set at 10 Hz. - */ - public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10); - /* Scroll state, this is set to true during dragging and animation. */ private ScrollState mState = ScrollState.IDLE; @@ -75,6 +70,8 @@ public class SwipeDetector { * Distance in pixels a touch can wander before we think the user is scrolling. */ abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos); + + abstract float getVelocity(VelocityTracker tracker); } public static final Direction VERTICAL = new Direction() { @@ -88,6 +85,11 @@ public class SwipeDetector { float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { return Math.abs(ev.getX(pointerIndex) - downPos.x); } + + @Override + float getVelocity(VelocityTracker tracker) { + return tracker.getYVelocity(); + } }; public static final Direction HORIZONTAL = new Direction() { @@ -101,6 +103,11 @@ public class SwipeDetector { float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { return Math.abs(ev.getY(pointerIndex) - downPos.y); } + + @Override + float getVelocity(VelocityTracker tracker) { + return tracker.getXVelocity(); + } }; //------------------- ScrollState transition diagram ----------------------------------- @@ -151,16 +158,16 @@ public class SwipeDetector { private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); - private Direction mDir; + private final Direction mDir; private final float mTouchSlop; + private final float mMaxVelocity; /* Client of this gesture detector can register a callback. */ private final Listener mListener; - private long mCurrentMillis; + private VelocityTracker mVelocityTracker; - private float mVelocity; private float mLastDisplacement; private float mDisplacement; @@ -170,24 +177,22 @@ public class SwipeDetector { public interface Listener { void onDragStart(boolean start); - boolean onDrag(float displacement, float velocity); + boolean onDrag(float displacement); void onDragEnd(float velocity, boolean fling); } public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) { - this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir); + this(ViewConfiguration.get(context), l, dir); } @VisibleForTesting - protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) { - mTouchSlop = touchSlope; + protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, + @NonNull Direction dir) { mListener = l; mDir = dir; - } - - public void updateDirection(Direction dir) { - mDir = dir; + mTouchSlop = config.getScaledTouchSlop(); + mMaxVelocity = config.getScaledMaximumFlingVelocity(); } public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { @@ -215,14 +220,22 @@ public class SwipeDetector { } public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getActionMasked()) { + int actionMasked = ev.getActionMasked(); + if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) { + mVelocityTracker.clear(); + } + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (actionMasked) { case MotionEvent.ACTION_DOWN: mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); mLastDisplacement = 0; mDisplacement = 0; - mVelocity = 0; if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { setState(ScrollState.DRAGGING); @@ -247,8 +260,6 @@ public class SwipeDetector { break; } mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos); - computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos), - ev.getEventTime()); // handle state and listener calls. if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) { @@ -265,6 +276,8 @@ public class SwipeDetector { if (mState == ScrollState.DRAGGING) { setState(ScrollState.SETTLING); } + mVelocityTracker.recycle(); + mVelocityTracker = null; break; default: break; @@ -308,55 +321,24 @@ public class SwipeDetector { private boolean reportDragging() { if (mDisplacement != mLastDisplacement) { if (DBG) { - Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f", - mDisplacement, mVelocity)); + Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement)); } mLastDisplacement = mDisplacement; - return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity); + return mListener.onDrag(mDisplacement - mSubtractDisplacement); } return true; } private void reportDragEnd() { + mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); + float velocity = mDir.getVelocity(mVelocityTracker) / 1000; if (DBG) { Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f", - mDisplacement, mVelocity)); + mDisplacement, velocity)); } - mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS); - } - - /** - * Computes the damped velocity. - */ - public float computeVelocity(float delta, long currentMillis) { - long previousMillis = mCurrentMillis; - mCurrentMillis = currentMillis; - - float deltaTimeMillis = mCurrentMillis - previousMillis; - float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0; - if (Math.abs(mVelocity) < 0.001f) { - mVelocity = velocity; - } else { - float alpha = computeDampeningFactor(deltaTimeMillis); - mVelocity = interpolate(mVelocity, velocity, alpha); - } - return mVelocity; - } - - /** - * Returns a time-dependent dampening factor using delta time. - */ - private static float computeDampeningFactor(float deltaTime) { - return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime); - } - - /** - * Returns the linear interpolation between two values - */ - public static float interpolate(float from, float to, float alpha) { - return (1.0f - alpha) * from + alpha * to; + mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS); } public static long calculateDuration(float velocity, float progressNeeded) { diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index 26c8f243b5..f948beb8d2 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -128,7 +128,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView public void onDragStart(boolean start) { } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float range = mContent.getHeight(); displacement = Utilities.boundToRange(displacement, 0, range); setTranslationShift(displacement / range); diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java index 73b6dafd1e..b600473d4b 100644 --- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java +++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java @@ -19,7 +19,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import android.util.Log; -import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.launcher3.testcomponent.TouchEventGenerator; @@ -32,6 +31,7 @@ import org.mockito.MockitoAnnotations; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyFloat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -51,25 +51,28 @@ public class SwipeDetectorTest { @Mock private SwipeDetector.Listener mMockListener; + @Mock + private ViewConfiguration mMockConfig; + @Before public void setup() { MockitoAnnotations.initMocks(this); - mGenerator = new TouchEventGenerator(new TouchEventGenerator.Listener() { - @Override - public void onTouchEvent(MotionEvent event) { - mDetector.onTouchEvent(event); - } - }); + mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev)); + ViewConfiguration orgConfig = ViewConfiguration + .get(InstrumentationRegistry.getTargetContext()); + doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig) + .getScaledMaximumFlingVelocity(); - mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.VERTICAL); + mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL); mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); - mTouchSlop = ViewConfiguration.get(InstrumentationRegistry.getTargetContext()) - .getScaledTouchSlop(); + mTouchSlop = orgConfig.getScaledTouchSlop(); + doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop(); + L("mTouchSlop=", mTouchSlop); } @Test - public void testDragStart_vertical() throws Exception { + public void testDragStart_vertical() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. @@ -77,7 +80,7 @@ public class SwipeDetectorTest { } @Test - public void testDragStart_failed() throws Exception { + public void testDragStart_failed() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 + mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. @@ -85,8 +88,8 @@ public class SwipeDetectorTest { } @Test - public void testDragStart_horizontal() throws Exception { - mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.HORIZONTAL); + public void testDragStart_horizontal() { + mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL); mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); mGenerator.put(0, 100, 100); @@ -96,15 +99,15 @@ public class SwipeDetectorTest { } @Test - public void testDrag() throws Exception { + public void testDrag() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDrag(anyFloat(), anyFloat()); + verify(mMockListener).onDrag(anyFloat()); } @Test - public void testDragEnd() throws Exception { + public void testDragEnd() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); mGenerator.move(0, 100, 100 + mTouchSlop * 2); From 2ca999cf1bd3cbd4647f57b8345c02999c6fa771 Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 24 Sep 2018 17:24:51 -0400 Subject: [PATCH 02/20] Change long press timeout to use a factor of ViewConfiguration.getLongPressTimeout() This way, if the default ViewConfiguration timeout changes, we will adjust accordingly. Bug: 113639506 Change-Id: Ic3b93311c8e8d8196db2850fa641ffc675a16fb2 --- src/com/android/launcher3/BubbleTextView.java | 4 ++-- src/com/android/launcher3/CheckLongPressHelper.java | 12 +++++++----- .../launcher3/allapps/AllAppsGridAdapter.java | 3 +-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 5d401433bf..08bcc1dd25 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -272,8 +272,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** * Overrides the default long press timeout. */ - public void setLongPressTimeout(int longPressTimeout) { - mLongPressHelper.setLongPressTimeout(longPressTimeout); + public void setLongPressTimeoutFactor(float longPressTimeoutFactor) { + mLongPressHelper.setLongPressTimeoutFactor(longPressTimeoutFactor); } @Override diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java index dde733cd16..639c173dcc 100644 --- a/src/com/android/launcher3/CheckLongPressHelper.java +++ b/src/com/android/launcher3/CheckLongPressHelper.java @@ -17,17 +17,18 @@ package com.android.launcher3; import android.view.View; +import android.view.ViewConfiguration; import com.android.launcher3.util.Thunk; public class CheckLongPressHelper { - public static final int DEFAULT_LONG_PRESS_TIMEOUT = 300; + public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f; @Thunk View mView; @Thunk View.OnLongClickListener mListener; @Thunk boolean mHasPerformedLongPress; - private int mLongPressTimeout = DEFAULT_LONG_PRESS_TIMEOUT; + private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR; private CheckForLongPress mPendingCheckForLongPress; class CheckForLongPress implements Runnable { @@ -60,8 +61,8 @@ public class CheckLongPressHelper { /** * Overrides the default long press timeout. */ - public void setLongPressTimeout(int longPressTimeout) { - mLongPressTimeout = longPressTimeout; + public void setLongPressTimeoutFactor(float longPressTimeoutFactor) { + mLongPressTimeoutFactor = longPressTimeoutFactor; } public void postCheckForLongPress() { @@ -70,7 +71,8 @@ public class CheckLongPressHelper { if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } - mView.postDelayed(mPendingCheckForLongPress, mLongPressTimeout); + mView.postDelayed(mPendingCheckForLongPress, + (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor)); } public void cancelLongPress() { diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 0f13550199..69b4bdb1df 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -22,7 +22,6 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnFocusChangeListener; -import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; @@ -252,7 +251,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter Date: Thu, 20 Sep 2018 17:30:03 -0700 Subject: [PATCH 03/20] Support for running tests out of Launcher process This CL doesn't turn it on yet. Bug: 113056917 Test: TaplTests Change-Id: I01007cb9ab330166cbb8a4c1fcd0cee0c60aeba5 --- .../android/launcher3/ui/AbstractLauncherUiTest.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index ba7d9c5237..75efea4ebf 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -82,12 +82,17 @@ public abstract class AbstractLauncherUiTest { protected final LauncherInstrumentation mLauncher; protected Context mTargetContext; protected String mTargetPackage; + protected final boolean mIsInLauncherProcess; private static final String TAG = "AbstractLauncherUiTest"; protected AbstractLauncherUiTest() { - mDevice = UiDevice.getInstance(getInstrumentation()); - mLauncher = new LauncherInstrumentation(getInstrumentation()); + final Instrumentation instrumentation = getInstrumentation(); + mDevice = UiDevice.getInstance(instrumentation); + mLauncher = new LauncherInstrumentation(instrumentation); + + mIsInLauncherProcess = instrumentation.getTargetContext().getPackageName().equals( + mDevice.getLauncherPackageName()); } @Rule @@ -261,6 +266,7 @@ public abstract class AbstractLauncherUiTest { } protected T getFromLauncher(Function f) { + if (!mIsInLauncherProcess) return null; return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity())); } @@ -287,6 +293,7 @@ public abstract class AbstractLauncherUiTest { // flakiness. protected boolean waitForLauncherCondition( Function condition, long timeout) { + if (!mIsInLauncherProcess) return true; return Wait.atMost(() -> getFromLauncher(condition), timeout); } From 29e1b51a04fb28032f60f4d752acfd9dcd229807 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Tue, 25 Sep 2018 11:46:39 -0700 Subject: [PATCH 04/20] Fixing AllAppsIconToHomeTest It's flaky now since apparently a previous test leaves open context menu. This should help. Test: AllAppsIconToHomeTest Change-Id: Iaa723f43963f2bfb2be8d590225cf18a2eec3d13 --- tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java index 23560e881a..a50a8b1a1e 100644 --- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java +++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java @@ -42,7 +42,8 @@ public class AllAppsIconToHomeTest extends AbstractLauncherUiTest { LauncherActivityInfo settingsApp = getSettingsApp(); clearHomescreen(); - mActivityMonitor.startLauncher(); + mDevice.pressHome(); + mDevice.waitForIdle(); // Open all apps and wait for load complete. final UiObject2 appsContainer = openAllApps(); From 1786df36c2574be55a05efb21554e0a27db6a9ca Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Tue, 25 Sep 2018 13:05:23 -0700 Subject: [PATCH 05/20] Fix bug where folder items get clipped. We need to account for both sides in the folder margin calculation. eg. in MW mode with display set to Largest, the folder can reach both edges Bug: 115955939 Change-Id: I145c8c1c2a75891eb3386284f06fc98800fe5ce9 --- src/com/android/launcher3/DeviceProfile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 9839c12dc1..b429018d74 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -373,7 +373,7 @@ public class DeviceProfile { updateFolderCellSize(1f, dm, res); // Don't let the folder get too close to the edges of the screen. - int folderMargin = edgeMarginPx; + int folderMargin = edgeMarginPx * 2; Point totalWorkspacePadding = getTotalWorkspacePadding(); // Check if the icons fit within the available height. From 11137385a3a7d0afd58b69f0a0b037e255dde12f Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 25 Sep 2018 14:24:30 -0700 Subject: [PATCH 06/20] Scaling down the icon in same path as scaling up If for some reason, the animation is not created (eg in case of forced rotation), we should also skip scaling down the icon so that the final UI is always in a consistent state. Bug: 78793089 Change-Id: Ie3e8b6d14b05ee983bc5e12401c1fa078034e392 --- .../src/com/android/quickstep/OverviewCommandHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 1a9915c414..d9626c4bdf 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -280,7 +280,6 @@ public class OverviewCommandHelper { factory.createActivityController(RECENTS_LAUNCH_DURATION, INTERACTION_NORMAL); mActivity = activity; mRecentsView = mActivity.getOverviewPanel(); - mRecentsView.setRunningTaskIconScaledDown(true); if (!mUserEventLogged) { activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON, mHelper.getContainerType(), ContainerType.TASKSWITCHER); @@ -298,6 +297,9 @@ public class OverviewCommandHelper { if (mListener != null) { mListener.unregister(); } + if (mRecentsView != null) { + mRecentsView.setRunningTaskIconScaledDown(true); + } AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimationSuccessListener() { @Override From 0b3053c9fcd93680efb05b51726b145d2609c0b3 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 24 Sep 2018 10:51:52 -0700 Subject: [PATCH 07/20] Extracting icon caching logic into a base class. This will allow using the cache cache for other type of objects, like shortcuts and widgets. Change-Id: I38616d031cb051f93e724d9cc0e8fe9a822b9e3a --- .../launcher3/graphics/BitmapInfo.java | 4 + .../launcher3/graphics/DrawableFactory.java | 4 +- .../launcher3/icons/BaseIconCache.java | 531 ++++++++++++++++++ .../android/launcher3/icons/CachingLogic.java | 63 +++ .../android/launcher3/icons/IconCache.java | 524 +---------------- .../icons/IconCacheUpdateHandler.java | 50 +- .../android/launcher3/model/LoaderTask.java | 3 +- .../model/BaseModelUpdateTaskTestCase.java | 7 +- 8 files changed, 639 insertions(+), 547 deletions(-) create mode 100644 src/com/android/launcher3/icons/BaseIconCache.java create mode 100644 src/com/android/launcher3/icons/CachingLogic.java diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/graphics/BitmapInfo.java index 69c068412a..74b783dfd7 100644 --- a/src/com/android/launcher3/graphics/BitmapInfo.java +++ b/src/com/android/launcher3/graphics/BitmapInfo.java @@ -37,6 +37,10 @@ public class BitmapInfo { info.color = color; } + public final boolean isLowRes() { + return LOW_RES_ICON == icon; + } + public static BitmapInfo fromBitmap(Bitmap bitmap) { return fromBitmap(bitmap, null); } diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index 8377adff89..8cabd2fe60 100644 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ b/src/com/android/launcher3/graphics/DrawableFactory.java @@ -66,7 +66,7 @@ public class DrawableFactory implements ResourceBasedOverride { * Returns a FastBitmapDrawable with the icon. */ public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) { - FastBitmapDrawable drawable = info.iconBitmap == LOW_RES_ICON + FastBitmapDrawable drawable = info.usingLowResIcon() ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context) : new FastBitmapDrawable(info); drawable.setIsDisabled(info.isDisabled()); @@ -74,7 +74,7 @@ public class DrawableFactory implements ResourceBasedOverride { } public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) { - return info.icon == LOW_RES_ICON + return info.isLowRes() ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context) : new FastBitmapDrawable(info); } diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java new file mode 100644 index 0000000000..3c742df1b2 --- /dev/null +++ b/src/com/android/launcher3/icons/BaseIconCache.java @@ -0,0 +1,531 @@ +/* + * 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.icons; + +import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; + +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.os.Build.VERSION; +import android.os.Handler; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.IconProvider; +import com.android.launcher3.ItemInfoWithIcon; +import com.android.launcher3.LauncherFiles; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.graphics.BitmapRenderer; +import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.InstantAppResolver; +import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.Provider; +import com.android.launcher3.util.SQLiteCacheHelper; + +import java.util.HashMap; +import java.util.HashSet; + +import androidx.annotation.NonNull; +import androidx.core.graphics.ColorUtils; + +public class BaseIconCache { + + private static final String TAG = "BaseIconCache"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_IGNORE_CACHE = false; + + private static final int INITIAL_ICON_CACHE_CAPACITY = 50; + + // Empty class name is used for storing package default entry. + public static final String EMPTY_CLASS_NAME = "."; + + public static class CacheEntry extends BitmapInfo { + public CharSequence title = ""; + public CharSequence contentDescription = ""; + } + + private final HashMap mDefaultIcons = new HashMap<>(); + + final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); + final Context mContext; + final PackageManager mPackageManager; + final IconProvider mIconProvider; + final UserManagerCompat mUserManager; + final LauncherAppsCompat mLauncherApps; + + private final HashMap mCache = + new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); + private final InstantAppResolver mInstantAppResolver; + final int mIconDpi; + + final IconDB mIconDb; + final Handler mWorkerHandler; + + private final BitmapFactory.Options mDecodeOptions; + + public BaseIconCache(Context context, int iconDpi, int iconPixelSize) { + mContext = context; + mPackageManager = context.getPackageManager(); + mUserManager = UserManagerCompat.getInstance(mContext); + mLauncherApps = LauncherAppsCompat.getInstance(mContext); + mInstantAppResolver = InstantAppResolver.newInstance(mContext); + mIconDpi = iconDpi; + mIconDb = new IconDB(context, iconPixelSize); + + mIconProvider = IconProvider.newInstance(context); + mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); + + if (BitmapRenderer.USE_HARDWARE_BITMAP) { + mDecodeOptions = new BitmapFactory.Options(); + mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE; + } else { + mDecodeOptions = null; + } + } + + private Drawable getFullResDefaultActivityIcon() { + return Resources.getSystem().getDrawableForDensity(Utilities.ATLEAST_OREO + ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon, + mIconDpi); + } + + private Drawable getFullResIcon(Resources resources, int iconId) { + if (resources != null && iconId != 0) { + try { + return resources.getDrawableForDensity(iconId, mIconDpi); + } catch (Resources.NotFoundException e) { } + } + return getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(String packageName, int iconId) { + try { + return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId); + } catch (PackageManager.NameNotFoundException e) { } + return getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(ActivityInfo info) { + try { + return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo), + info.getIconResource()); + } catch (PackageManager.NameNotFoundException e) { } + return getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(LauncherActivityInfo info) { + return getFullResIcon(info, true); + } + + public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) { + return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); + } + + protected BitmapInfo makeDefaultIcon(UserHandle user) { + try (LauncherIcons li = LauncherIcons.obtain(mContext)) { + return li.createBadgedIconBitmap( + getFullResDefaultActivityIcon(), user, VERSION.SDK_INT); + } + } + + /** + * Remove any records for the supplied ComponentName. + */ + public synchronized void remove(ComponentName componentName, UserHandle user) { + mCache.remove(new ComponentKey(componentName, user)); + } + + /** + * Remove any records for the supplied package name from memory. + */ + private void removeFromMemCacheLocked(String packageName, UserHandle user) { + HashSet forDeletion = new HashSet<>(); + for (ComponentKey key: mCache.keySet()) { + if (key.componentName.getPackageName().equals(packageName) + && key.user.equals(user)) { + forDeletion.add(key); + } + } + for (ComponentKey condemned: forDeletion) { + mCache.remove(condemned); + } + } + + /** + * Removes the entries related to the given package in memory and persistent DB. + */ + public synchronized void removeIconsForPkg(String packageName, UserHandle user) { + removeFromMemCacheLocked(packageName, user); + long userSerial = mUserManager.getSerialNumberForUser(user); + mIconDb.delete( + IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", + new String[]{packageName + "/%", Long.toString(userSerial)}); + } + + public IconCacheUpdateHandler getUpdateHandler() { + mIconProvider.updateSystemStateString(mContext); + return new IconCacheUpdateHandler(this); + } + + /** + * Adds an entry into the DB and the in-memory cache. + * @param replaceExisting if true, it will recreate the bitmap even if it already exists in + * the memory. This is useful then the previous bitmap was created using + * old data. + * package private + */ + synchronized void addIconToDBAndMemCache(T object, CachingLogic cachingLogic, + PackageInfo info, long userSerial, boolean replaceExisting) { + UserHandle user = cachingLogic.getUser(object); + ComponentName componentName = cachingLogic.getComponent(object); + + final ComponentKey key = new ComponentKey(componentName, user); + CacheEntry entry = null; + if (!replaceExisting) { + entry = mCache.get(key); + // We can't reuse the entry if the high-res icon is not present. + if (entry == null || entry.icon == null || entry.isLowRes()) { + entry = null; + } + } + if (entry == null) { + entry = new CacheEntry(); + cachingLogic.loadIcon(mContext, this, object, entry); + } + entry.title = cachingLogic.getLabel(object); + entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); + mCache.put(key, entry); + + ContentValues values = newContentValues(entry.icon, entry.color, + entry.title.toString(), componentName.getPackageName()); + addIconToDB(values, componentName, info, userSerial); + } + + /** + * Updates {@param values} to contain versioning information and adds it to the DB. + * @param values {@link ContentValues} containing icon & title + */ + private void addIconToDB(ContentValues values, ComponentName key, + PackageInfo info, long userSerial) { + values.put(IconDB.COLUMN_COMPONENT, key.flattenToString()); + values.put(IconDB.COLUMN_USER, userSerial); + values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime); + values.put(IconDB.COLUMN_VERSION, info.versionCode); + mIconDb.insertOrReplace(values); + } + + /** + * Fill in {@param infoInOut} with the corresponding icon and label. + */ + public synchronized void getTitleAndIconForApp( + PackageItemInfo infoInOut, boolean useLowResIcon) { + CacheEntry entry = getEntryForPackageLocked( + infoInOut.packageName, infoInOut.user, useLowResIcon); + applyCacheEntry(entry, infoInOut); + } + + 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); + } + + public synchronized BitmapInfo getDefaultIcon(UserHandle user) { + if (!mDefaultIcons.containsKey(user)) { + mDefaultIcons.put(user, makeDefaultIcon(user)); + } + return mDefaultIcons.get(user); + } + + public boolean isDefaultIcon(Bitmap icon, UserHandle user) { + return getDefaultIcon(user).icon == icon; + } + + /** + * Retrieves the entry from the cache. If the entry is not present, it creates a new entry. + * This method is not thread safe, it must be called from a synchronized method. + */ + protected CacheEntry cacheLocked( + @NonNull ComponentName componentName, + @NonNull Provider infoProvider, + @NonNull CachingLogic cachingLogic, + UserHandle user, boolean usePackageIcon, boolean useLowResIcon) { + Preconditions.assertWorkerThread(); + ComponentKey cacheKey = new ComponentKey(componentName, user); + CacheEntry entry = mCache.get(cacheKey); + if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + entry = new CacheEntry(); + mCache.put(cacheKey, entry); + + // Check the DB first. + T object = null; + boolean providerFetchedOnce = false; + + if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) { + object = infoProvider.get(); + providerFetchedOnce = true; + + if (object != null) { + cachingLogic.loadIcon(mContext, this, object, entry); + } else { + if (usePackageIcon) { + CacheEntry packageEntry = getEntryForPackageLocked( + componentName.getPackageName(), user, false); + if (packageEntry != null) { + if (DEBUG) Log.d(TAG, "using package default icon for " + + componentName.toShortString()); + packageEntry.applyTo(entry); + entry.title = packageEntry.title; + entry.contentDescription = packageEntry.contentDescription; + } + } + if (entry.icon == null) { + if (DEBUG) Log.d(TAG, "using default icon for " + + componentName.toShortString()); + getDefaultIcon(user).applyTo(entry); + } + } + } + + if (TextUtils.isEmpty(entry.title)) { + if (object == null && !providerFetchedOnce) { + object = infoProvider.get(); + providerFetchedOnce = true; + } + if (object != null) { + entry.title = cachingLogic.getLabel(object); + entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); + } + } + } + return entry; + } + + public synchronized void clear() { + Preconditions.assertWorkerThread(); + mIconDb.clear(); + } + + /** + * Adds a default package entry in the cache. This entry is not persisted and will be removed + * when the cache is flushed. + */ + public synchronized void cachePackageInstallInfo(String packageName, UserHandle user, + Bitmap icon, CharSequence title) { + removeFromMemCacheLocked(packageName, user); + + ComponentKey cacheKey = getPackageKey(packageName, user); + CacheEntry entry = mCache.get(cacheKey); + + // For icon caching, do not go through DB. Just update the in-memory entry. + if (entry == null) { + entry = new CacheEntry(); + } + if (!TextUtils.isEmpty(title)) { + entry.title = title; + } + if (icon != null) { + LauncherIcons li = LauncherIcons.obtain(mContext); + li.createIconBitmap(icon).applyTo(entry); + li.recycle(); + } + if (!TextUtils.isEmpty(title) && entry.icon != null) { + mCache.put(cacheKey, entry); + } + } + + private static ComponentKey getPackageKey(String packageName, UserHandle user) { + ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME); + return new ComponentKey(cn, user); + } + + /** + * Gets an entry for the package, which can be used as a fallback entry for various components. + * This method is not thread safe, it must be called from a synchronized method. + */ + private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user, + boolean useLowResIcon) { + Preconditions.assertWorkerThread(); + ComponentKey cacheKey = getPackageKey(packageName, user); + CacheEntry entry = mCache.get(cacheKey); + + if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + entry = new CacheEntry(); + boolean entryUpdated = true; + + // Check the DB first. + if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { + try { + int flags = Process.myUserHandle().equals(user) ? 0 : + PackageManager.GET_UNINSTALLED_PACKAGES; + PackageInfo info = mPackageManager.getPackageInfo(packageName, flags); + ApplicationInfo appInfo = info.applicationInfo; + if (appInfo == null) { + throw new NameNotFoundException("ApplicationInfo is null"); + } + + LauncherIcons li = LauncherIcons.obtain(mContext); + // Load the full res icon for the application, but if useLowResIcon is set, then + // only keep the low resolution icon instead of the larger full-sized icon + BitmapInfo iconInfo = li.createBadgedIconBitmap( + appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion, + mInstantAppResolver.isInstantApp(appInfo)); + li.recycle(); + + entry.title = appInfo.loadLabel(mPackageManager); + entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); + entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon; + entry.color = iconInfo.color; + + // Add the icon in the DB here, since these do not get written during + // package updates. + ContentValues values = newContentValues(iconInfo.icon, entry.color, + entry.title.toString(), packageName); + addIconToDB(values, cacheKey.componentName, info, + mUserManager.getSerialNumberForUser(user)); + + } catch (NameNotFoundException e) { + if (DEBUG) Log.d(TAG, "Application not installed " + packageName); + entryUpdated = false; + } + } + + // Only add a filled-out entry to the cache + if (entryUpdated) { + mCache.put(cacheKey, entry); + } + } + return entry; + } + + private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { + Cursor c = null; + try { + c = mIconDb.query( + lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, + IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", + new String[]{ + cacheKey.componentName.flattenToString(), + Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))}); + if (c.moveToNext()) { + // Set the alpha to be 255, so that we never have a wrong color + entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255); + entry.title = c.getString(1); + if (entry.title == null) { + entry.title = ""; + entry.contentDescription = ""; + } else { + entry.contentDescription = mUserManager.getBadgedLabelForUser( + entry.title, cacheKey.user); + } + + if (lowRes) { + entry.icon = LOW_RES_ICON; + } else { + byte[] data = c.getBlob(2); + try { + entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length, + mDecodeOptions); + } catch (Exception e) { } + } + return true; + } + } catch (SQLiteException e) { + Log.d(TAG, "Error reading icon cache", e); + } finally { + if (c != null) { + c.close(); + } + } + return false; + } + + static final class IconDB extends SQLiteCacheHelper { + private final static int RELEASE_VERSION = 25; + + public final static String TABLE_NAME = "icons"; + public final static String COLUMN_ROWID = "rowid"; + public final static String COLUMN_COMPONENT = "componentName"; + public final static String COLUMN_USER = "profileId"; + public final static String COLUMN_LAST_UPDATED = "lastUpdated"; + public final static String COLUMN_VERSION = "version"; + public final static String COLUMN_ICON = "icon"; + public final static String COLUMN_ICON_COLOR = "icon_color"; + public final static String COLUMN_LABEL = "label"; + public final static String COLUMN_SYSTEM_STATE = "system_state"; + + public final static String[] COLUMNS_HIGH_RES = new String[] { + IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON }; + public final static String[] COLUMNS_LOW_RES = new String[] { + IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL }; + + public IconDB(Context context, int iconPixelSize) { + super(context, LauncherFiles.APP_ICONS_DB, + (RELEASE_VERSION << 16) + iconPixelSize, + TABLE_NAME); + } + + @Override + protected void onCreateTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + + COLUMN_COMPONENT + " TEXT NOT NULL, " + + COLUMN_USER + " INTEGER NOT NULL, " + + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_ICON + " BLOB, " + + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_LABEL + " TEXT, " + + COLUMN_SYSTEM_STATE + " TEXT, " + + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " + + ");"); + } + } + + private ContentValues newContentValues(Bitmap icon, int iconColor, String label, + String packageName) { + ContentValues values = new ContentValues(); + values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); + values.put(IconDB.COLUMN_ICON_COLOR, iconColor); + + values.put(IconDB.COLUMN_LABEL, label); + values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName)); + + return values; + } +} diff --git a/src/com/android/launcher3/icons/CachingLogic.java b/src/com/android/launcher3/icons/CachingLogic.java new file mode 100644 index 0000000000..194fedb1fe --- /dev/null +++ b/src/com/android/launcher3/icons/CachingLogic.java @@ -0,0 +1,63 @@ +/* + * 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.icons; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.LauncherActivityInfo; +import android.os.UserHandle; + +import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.graphics.LauncherIcons; + +public interface CachingLogic { + + ComponentName getComponent(T object); + + UserHandle getUser(T object); + + CharSequence getLabel(T object); + + void loadIcon(Context context, BaseIconCache cache, T object, BitmapInfo target); + + CachingLogic LAUNCHER_ACTIVITY_INFO = + new CachingLogic() { + + @Override + public ComponentName getComponent(LauncherActivityInfo object) { + return object.getComponentName(); + } + + @Override + public UserHandle getUser(LauncherActivityInfo object) { + return object.getUser(); + } + + @Override + public CharSequence getLabel(LauncherActivityInfo object) { + return object.getLabel(); + } + + @Override + public void loadIcon(Context context, BaseIconCache cache, LauncherActivityInfo object, + BitmapInfo target) { + LauncherIcons li = LauncherIcons.obtain(context); + li.createBadgedIconBitmap(cache.getFullResIcon(object), object.getUser(), + object.getApplicationInfo().targetSdkVersion).applyTo(target); + li.recycle(); + } + }; +} diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 2035d0e57d..eae6f0175a 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -16,209 +16,42 @@ package com.android.launcher3.icons; -import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; +import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO; -import android.content.ComponentName; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Build.VERSION; import android.os.Handler; import android.os.Process; import android.os.UserHandle; -import android.text.TextUtils; import android.util.Log; import com.android.launcher3.AppInfo; -import com.android.launcher3.IconProvider; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; -import com.android.launcher3.LauncherFiles; import com.android.launcher3.LauncherModel; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; -import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.BitmapRenderer; -import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Provider; -import com.android.launcher3.util.SQLiteCacheHelper; - -import java.util.HashMap; -import java.util.HashSet; import androidx.annotation.NonNull; -import androidx.core.graphics.ColorUtils; /** * Cache of application icons. Icons can be made from any thread. */ -public class IconCache { +public class IconCache extends BaseIconCache { private static final String TAG = "Launcher.IconCache"; - private static final int INITIAL_ICON_CACHE_CAPACITY = 50; - - // Empty class name is used for storing package default entry. - public static final String EMPTY_CLASS_NAME = "."; - - private static final boolean DEBUG = false; - private static final boolean DEBUG_IGNORE_CACHE = false; - - public static class CacheEntry extends BitmapInfo { - public CharSequence title = ""; - public CharSequence contentDescription = ""; - - public boolean isLowRes() { - return LOW_RES_ICON == icon; - } - } - - private final HashMap mDefaultIcons = new HashMap<>(); - - final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - final Context mContext; - final PackageManager mPackageManager; - final IconProvider mIconProvider; - final UserManagerCompat mUserManager; - final LauncherAppsCompat mLauncherApps; - - private final HashMap mCache = - new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); - private final InstantAppResolver mInstantAppResolver; - private final int mIconDpi; - - final IconDB mIconDb; - final Handler mWorkerHandler; - - private final BitmapFactory.Options mDecodeOptions; - private int mPendingIconRequestCount = 0; public IconCache(Context context, InvariantDeviceProfile inv) { - mContext = context; - mPackageManager = context.getPackageManager(); - mUserManager = UserManagerCompat.getInstance(mContext); - mLauncherApps = LauncherAppsCompat.getInstance(mContext); - mInstantAppResolver = InstantAppResolver.newInstance(mContext); - mIconDpi = inv.fillResIconDpi; - mIconDb = new IconDB(context, inv.iconBitmapSize); - - mIconProvider = IconProvider.newInstance(context); - mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); - - if (BitmapRenderer.USE_HARDWARE_BITMAP) { - mDecodeOptions = new BitmapFactory.Options(); - mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE; - } else { - mDecodeOptions = null; - } - } - - private Drawable getFullResDefaultActivityIcon() { - return getFullResIcon(Resources.getSystem(), Utilities.ATLEAST_OREO ? - android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon); - } - - private Drawable getFullResIcon(Resources resources, int iconId) { - Drawable d; - try { - d = resources.getDrawableForDensity(iconId, mIconDpi); - } catch (Resources.NotFoundException e) { - d = null; - } - - return (d != null) ? d : getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(String packageName, int iconId) { - Resources resources; - try { - resources = mPackageManager.getResourcesForApplication(packageName); - } catch (PackageManager.NameNotFoundException e) { - resources = null; - } - if (resources != null) { - if (iconId != 0) { - return getFullResIcon(resources, iconId); - } - } - return getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(ActivityInfo info) { - Resources resources; - try { - resources = mPackageManager.getResourcesForApplication( - info.applicationInfo); - } catch (PackageManager.NameNotFoundException e) { - resources = null; - } - if (resources != null) { - int iconId = info.getIconResource(); - if (iconId != 0) { - return getFullResIcon(resources, iconId); - } - } - - return getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(LauncherActivityInfo info) { - return getFullResIcon(info, true); - } - - public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) { - return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); - } - - protected BitmapInfo makeDefaultIcon(UserHandle user) { - try (LauncherIcons li = LauncherIcons.obtain(mContext)) { - return li.createBadgedIconBitmap( - getFullResDefaultActivityIcon(), user, VERSION.SDK_INT); - } - } - - /** - * Remove any records for the supplied ComponentName. - */ - public synchronized void remove(ComponentName componentName, UserHandle user) { - mCache.remove(new ComponentKey(componentName, user)); - } - - /** - * Remove any records for the supplied package name from memory. - */ - private void removeFromMemCacheLocked(String packageName, UserHandle user) { - HashSet forDeletion = new HashSet<>(); - for (ComponentKey key: mCache.keySet()) { - if (key.componentName.getPackageName().equals(packageName) - && key.user.equals(user)) { - forDeletion.add(key); - } - } - for (ComponentKey condemned: forDeletion) { - mCache.remove(condemned); - } + super(context, inv.fillResIconDpi, inv.iconBitmapSize); } /** @@ -231,76 +64,14 @@ public class IconCache { PackageManager.GET_UNINSTALLED_PACKAGES); long userSerial = mUserManager.getSerialNumberForUser(user); for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) { - addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/); + addIconToDBAndMemCache(app, LAUNCHER_ACTIVITY_INFO, info, userSerial, + false /*replace existing*/); } } catch (NameNotFoundException e) { Log.d(TAG, "Package not found", e); } } - /** - * Removes the entries related to the given package in memory and persistent DB. - */ - public synchronized void removeIconsForPkg(String packageName, UserHandle user) { - removeFromMemCacheLocked(packageName, user); - long userSerial = mUserManager.getSerialNumberForUser(user); - mIconDb.delete( - IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{packageName + "/%", Long.toString(userSerial)}); - } - - public IconCacheUpdateHandler getUpdateHandler() { - mIconProvider.updateSystemStateString(mContext); - return new IconCacheUpdateHandler(this); - } - - /** - * Adds an entry into the DB and the in-memory cache. - * @param replaceExisting if true, it will recreate the bitmap even if it already exists in - * the memory. This is useful then the previous bitmap was created using - * old data. - * package private - */ - synchronized void addIconToDBAndMemCache(LauncherActivityInfo app, - PackageInfo info, long userSerial, boolean replaceExisting) { - final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser()); - CacheEntry entry = null; - if (!replaceExisting) { - entry = mCache.get(key); - // We can't reuse the entry if the high-res icon is not present. - if (entry == null || entry.icon == null || entry.isLowRes()) { - entry = null; - } - } - if (entry == null) { - entry = new CacheEntry(); - LauncherIcons li = LauncherIcons.obtain(mContext); - li.createBadgedIconBitmap(getFullResIcon(app), app.getUser(), - app.getApplicationInfo().targetSdkVersion).applyTo(entry); - li.recycle(); - } - entry.title = app.getLabel(); - entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser()); - mCache.put(key, entry); - - ContentValues values = newContentValues(entry.icon, entry.color, - entry.title.toString(), app.getApplicationInfo().packageName); - addIconToDB(values, app.getComponentName(), info, userSerial); - } - - /** - * Updates {@param values} to contain versioning information and adds it to the DB. - * @param values {@link ContentValues} containing icon & title - */ - private void addIconToDB(ContentValues values, ComponentName key, - PackageInfo info, long userSerial) { - values.put(IconDB.COLUMN_COMPONENT, key.flattenToString()); - values.put(IconDB.COLUMN_USER, userSerial); - values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime); - values.put(IconDB.COLUMN_VERSION, info.versionCode); - mIconDb.insertOrReplace(values); - } - /** * Fetches high-res icon for the provided ItemInfo and updates the caller when done. * @return a request ID that can be used to cancel the request. @@ -343,7 +114,7 @@ public class IconCache { */ public synchronized void updateTitleAndIcon(AppInfo application) { CacheEntry entry = cacheLocked(application.componentName, - Provider.of(null), + Provider.of(null), LAUNCHER_ACTIVITY_INFO, application.user, false, application.usingLowResIcon()); if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) { applyCacheEntry(entry, application); @@ -385,238 +156,10 @@ public class IconCache { @NonNull Provider activityInfoProvider, boolean usePkgIcon, boolean useLowResIcon) { CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider, - infoInOut.user, usePkgIcon, useLowResIcon); + LAUNCHER_ACTIVITY_INFO, infoInOut.user, usePkgIcon, useLowResIcon); applyCacheEntry(entry, infoInOut); } - /** - * Fill in {@param infoInOut} with the corresponding icon and label. - */ - public synchronized void getTitleAndIconForApp( - PackageItemInfo infoInOut, boolean useLowResIcon) { - CacheEntry entry = getEntryForPackageLocked( - infoInOut.packageName, infoInOut.user, useLowResIcon); - applyCacheEntry(entry, infoInOut); - } - - private 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); - } - - public synchronized BitmapInfo getDefaultIcon(UserHandle user) { - if (!mDefaultIcons.containsKey(user)) { - mDefaultIcons.put(user, makeDefaultIcon(user)); - } - return mDefaultIcons.get(user); - } - - public boolean isDefaultIcon(Bitmap icon, UserHandle user) { - return getDefaultIcon(user).icon == icon; - } - - /** - * Retrieves the entry from the cache. If the entry is not present, it creates a new entry. - * This method is not thread safe, it must be called from a synchronized method. - */ - protected CacheEntry cacheLocked( - @NonNull ComponentName componentName, - @NonNull Provider infoProvider, - UserHandle user, boolean usePackageIcon, boolean useLowResIcon) { - Preconditions.assertWorkerThread(); - ComponentKey cacheKey = new ComponentKey(componentName, user); - CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - mCache.put(cacheKey, entry); - - // Check the DB first. - LauncherActivityInfo info = null; - boolean providerFetchedOnce = false; - - if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) { - info = infoProvider.get(); - providerFetchedOnce = true; - - if (info != null) { - LauncherIcons li = LauncherIcons.obtain(mContext); - li.createBadgedIconBitmap(getFullResIcon(info), info.getUser(), - info.getApplicationInfo().targetSdkVersion).applyTo(entry); - li.recycle(); - } else { - if (usePackageIcon) { - CacheEntry packageEntry = getEntryForPackageLocked( - componentName.getPackageName(), user, false); - if (packageEntry != null) { - if (DEBUG) Log.d(TAG, "using package default icon for " + - componentName.toShortString()); - packageEntry.applyTo(entry); - entry.title = packageEntry.title; - entry.contentDescription = packageEntry.contentDescription; - } - } - if (entry.icon == null) { - if (DEBUG) Log.d(TAG, "using default icon for " + - componentName.toShortString()); - getDefaultIcon(user).applyTo(entry); - } - } - } - - if (TextUtils.isEmpty(entry.title)) { - if (info == null && !providerFetchedOnce) { - info = infoProvider.get(); - providerFetchedOnce = true; - } - if (info != null) { - entry.title = info.getLabel(); - entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); - } - } - } - return entry; - } - - public synchronized void clear() { - Preconditions.assertWorkerThread(); - mIconDb.clear(); - } - - /** - * Adds a default package entry in the cache. This entry is not persisted and will be removed - * when the cache is flushed. - */ - public synchronized void cachePackageInstallInfo(String packageName, UserHandle user, - Bitmap icon, CharSequence title) { - removeFromMemCacheLocked(packageName, user); - - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - // For icon caching, do not go through DB. Just update the in-memory entry. - if (entry == null) { - entry = new CacheEntry(); - } - if (!TextUtils.isEmpty(title)) { - entry.title = title; - } - if (icon != null) { - LauncherIcons li = LauncherIcons.obtain(mContext); - li.createIconBitmap(icon).applyTo(entry); - li.recycle(); - } - if (!TextUtils.isEmpty(title) && entry.icon != null) { - mCache.put(cacheKey, entry); - } - } - - private static ComponentKey getPackageKey(String packageName, UserHandle user) { - ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME); - return new ComponentKey(cn, user); - } - - /** - * Gets an entry for the package, which can be used as a fallback entry for various components. - * This method is not thread safe, it must be called from a synchronized method. - */ - private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user, - boolean useLowResIcon) { - Preconditions.assertWorkerThread(); - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - boolean entryUpdated = true; - - // Check the DB first. - if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { - try { - int flags = Process.myUserHandle().equals(user) ? 0 : - PackageManager.GET_UNINSTALLED_PACKAGES; - PackageInfo info = mPackageManager.getPackageInfo(packageName, flags); - ApplicationInfo appInfo = info.applicationInfo; - if (appInfo == null) { - throw new NameNotFoundException("ApplicationInfo is null"); - } - - LauncherIcons li = LauncherIcons.obtain(mContext); - // Load the full res icon for the application, but if useLowResIcon is set, then - // only keep the low resolution icon instead of the larger full-sized icon - BitmapInfo iconInfo = li.createBadgedIconBitmap( - appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion, - mInstantAppResolver.isInstantApp(appInfo)); - li.recycle(); - - entry.title = appInfo.loadLabel(mPackageManager); - entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); - entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon; - entry.color = iconInfo.color; - - // Add the icon in the DB here, since these do not get written during - // package updates. - ContentValues values = newContentValues(iconInfo.icon, entry.color, - entry.title.toString(), packageName); - addIconToDB(values, cacheKey.componentName, info, - mUserManager.getSerialNumberForUser(user)); - - } catch (NameNotFoundException e) { - if (DEBUG) Log.d(TAG, "Application not installed " + packageName); - entryUpdated = false; - } - } - - // Only add a filled-out entry to the cache - if (entryUpdated) { - mCache.put(cacheKey, entry); - } - } - return entry; - } - - private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { - Cursor c = null; - try { - c = mIconDb.query( - lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, - IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{ - cacheKey.componentName.flattenToString(), - Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))}); - if (c.moveToNext()) { - // Set the alpha to be 255, so that we never have a wrong color - entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255); - entry.title = c.getString(1); - if (entry.title == null) { - entry.title = ""; - entry.contentDescription = ""; - } else { - entry.contentDescription = mUserManager.getBadgedLabelForUser( - entry.title, cacheKey.user); - } - - if (lowRes) { - entry.icon = LOW_RES_ICON; - } else { - byte[] data = c.getBlob(2); - try { - entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length, - mDecodeOptions); - } catch (Exception e) { } - } - return true; - } - } catch (SQLiteException e) { - Log.d(TAG, "Error reading icon cache", e); - } finally { - if (c != null) { - c.close(); - } - } - return false; - } - public static abstract class IconLoadRequest implements Runnable { private final Handler mHandler; private final Runnable mEndRunnable; @@ -641,59 +184,6 @@ public class IconCache { } } - static final class IconDB extends SQLiteCacheHelper { - private final static int RELEASE_VERSION = 25; - - public final static String TABLE_NAME = "icons"; - public final static String COLUMN_ROWID = "rowid"; - public final static String COLUMN_COMPONENT = "componentName"; - public final static String COLUMN_USER = "profileId"; - public final static String COLUMN_LAST_UPDATED = "lastUpdated"; - public final static String COLUMN_VERSION = "version"; - public final static String COLUMN_ICON = "icon"; - public final static String COLUMN_ICON_COLOR = "icon_color"; - public final static String COLUMN_LABEL = "label"; - public final static String COLUMN_SYSTEM_STATE = "system_state"; - - public final static String[] COLUMNS_HIGH_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON }; - public final static String[] COLUMNS_LOW_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL }; - - public IconDB(Context context, int iconPixelSize) { - super(context, LauncherFiles.APP_ICONS_DB, - (RELEASE_VERSION << 16) + iconPixelSize, - TABLE_NAME); - } - - @Override - protected void onCreateTable(SQLiteDatabase db) { - db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + - COLUMN_COMPONENT + " TEXT NOT NULL, " + - COLUMN_USER + " INTEGER NOT NULL, " + - COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_ICON + " BLOB, " + - COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_LABEL + " TEXT, " + - COLUMN_SYSTEM_STATE + " TEXT, " + - "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " + - ");"); - } - } - - private ContentValues newContentValues(Bitmap icon, int iconColor, String label, - String packageName) { - ContentValues values = new ContentValues(); - values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); - values.put(IconDB.COLUMN_ICON_COLOR, iconColor); - - values.put(IconDB.COLUMN_LABEL, label); - values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName)); - - return values; - } - /** * Interface for receiving itemInfo with high-res icon. */ diff --git a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java index 04e29013ab..e316c53569 100644 --- a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java +++ b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java @@ -17,7 +17,6 @@ package com.android.launcher3.icons; import android.content.ComponentName; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.Cursor; @@ -29,7 +28,7 @@ import android.util.Log; import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; -import com.android.launcher3.icons.IconCache.IconDB; +import com.android.launcher3.icons.BaseIconCache.IconDB; import java.util.Collections; import java.util.HashMap; @@ -48,10 +47,10 @@ public class IconCacheUpdateHandler { private static final Object ICON_UPDATE_TOKEN = new Object(); private final HashMap mPkgInfoMap; - private final IconCache mIconCache; + private final BaseIconCache mIconCache; private final HashMap> mPackagesToIgnore = new HashMap<>(); - IconCacheUpdateHandler(IconCache cache) { + IconCacheUpdateHandler(BaseIconCache cache) { mIconCache = cache; mPkgInfoMap = new HashMap<>(); @@ -79,11 +78,11 @@ public class IconCacheUpdateHandler { * the DB and are updated. * @return The set of packages for which icons have updated. */ - public void updateIcons(List apps) { + public void updateIcons(List apps, CachingLogic cachingLogic) { if (apps.isEmpty()) { return; } - UserHandle user = apps.get(0).getUser(); + UserHandle user = cachingLogic.getUser(apps.get(0)); Set ignorePackages = mPackagesToIgnore.get(user); if (ignorePackages == null) { @@ -91,13 +90,13 @@ public class IconCacheUpdateHandler { } long userSerial = mIconCache.mUserManager.getSerialNumberForUser(user); - HashMap componentMap = new HashMap<>(); - for (LauncherActivityInfo app : apps) { - componentMap.put(app.getComponentName(), app); + HashMap componentMap = new HashMap<>(); + for (T app : apps) { + componentMap.put(cachingLogic.getComponent(app), app); } HashSet itemsToRemove = new HashSet<>(); - Stack appsToUpdate = new Stack<>(); + Stack appsToUpdate = new Stack<>(); try (Cursor c = mIconCache.mIconDb.query( new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, @@ -130,7 +129,7 @@ public class IconCacheUpdateHandler { long updateTime = c.getLong(indexLastUpdate); int version = c.getInt(indexVersion); - LauncherActivityInfo app = componentMap.remove(component); + T app = componentMap.remove(component); if (version == info.versionCode && updateTime == info.lastUpdateTime && TextUtils.equals(c.getString(systemStateIndex), mIconCache.mIconProvider.getIconSystemState(info.packageName))) { @@ -154,9 +153,10 @@ public class IconCacheUpdateHandler { // Insert remaining apps. if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) { - Stack appsToAdd = new Stack<>(); + Stack appsToAdd = new Stack<>(); appsToAdd.addAll(componentMap.values()); - new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate).scheduleNext(); + new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic) + .scheduleNext(); } } @@ -166,29 +166,31 @@ public class IconCacheUpdateHandler { * LauncherActivityInfo list. Items are updated/added one at a time, so that the * worker thread doesn't get blocked. */ - private class SerializedIconUpdateTask implements Runnable { + private class SerializedIconUpdateTask implements Runnable { private final long mUserSerial; private final UserHandle mUserHandle; - private final Stack mAppsToAdd; - private final Stack mAppsToUpdate; + private final Stack mAppsToAdd; + private final Stack mAppsToUpdate; + private final CachingLogic mCachingLogic; private final HashSet mUpdatedPackages = new HashSet<>(); SerializedIconUpdateTask(long userSerial, UserHandle userHandle, - Stack appsToAdd, Stack appsToUpdate) { + Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic) { mUserHandle = userHandle; mUserSerial = userSerial; mAppsToAdd = appsToAdd; mAppsToUpdate = appsToUpdate; + mCachingLogic = cachingLogic; } @Override public void run() { if (!mAppsToUpdate.isEmpty()) { - LauncherActivityInfo app = mAppsToUpdate.pop(); - String pkg = app.getComponentName().getPackageName(); + T app = mAppsToUpdate.pop(); + String pkg = mCachingLogic.getComponent(app).getPackageName(); PackageInfo info = mPkgInfoMap.get(pkg); mIconCache.addIconToDBAndMemCache( - app, info, mUserSerial, true /*replace existing*/); + app, mCachingLogic, info, mUserSerial, true /*replace existing*/); mUpdatedPackages.add(pkg); if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) { @@ -200,13 +202,13 @@ public class IconCacheUpdateHandler { // Let it run one more time. scheduleNext(); } else if (!mAppsToAdd.isEmpty()) { - LauncherActivityInfo app = mAppsToAdd.pop(); - PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName()); + T app = mAppsToAdd.pop(); + PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName()); // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every // app should have package info, this is not guaranteed by the api if (info != null) { - mIconCache.addIconToDBAndMemCache( - app, info, mUserSerial, false /*replace existing*/); + mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info, + mUserSerial, false /*replace existing*/); } if (!mAppsToAdd.isEmpty()) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 4ccb8d8d6d..0105fbbafd 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -20,6 +20,7 @@ import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; +import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO; import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; import android.appwidget.AppWidgetProviderInfo; @@ -802,7 +803,7 @@ public class LoaderTask implements Runnable { List> activityListPerUser) { int userCount = activityListPerUser.size(); for (int i = 0; i < userCount; i++) { - updateHandler.updateIcons(activityListPerUser.get(i)); + updateHandler.updateIcons(activityListPerUser.get(i), LAUNCHER_ACTIVITY_INFO); } } diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index 59b2da036f..f4aa15a72c 100644 --- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -11,7 +11,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; -import android.content.pm.LauncherActivityInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; @@ -24,6 +23,7 @@ import androidx.test.rule.provider.ProviderTestRule; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppFilter; import com.android.launcher3.AppInfo; +import com.android.launcher3.icons.CachingLogic; import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; @@ -203,9 +203,10 @@ public class BaseModelUpdateTaskTestCase { } @Override - protected CacheEntry cacheLocked( + protected CacheEntry cacheLocked( @NonNull ComponentName componentName, - @NonNull Provider infoProvider, + @NonNull Provider infoProvider, + @NonNull CachingLogic cachingLogic, UserHandle user, boolean usePackageIcon, boolean useLowResIcon) { CacheEntry entry = mCache.get(new ComponentKey(componentName, user)); if (entry == null) { From c533f315cad1232c1141b0a2678bd8736eef9fba Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 25 Sep 2018 18:18:18 -0700 Subject: [PATCH 08/20] Defer removing the task view until the app has drawn Bug: 111896388 Change-Id: I8c900e56fcbbdc400dce646c50f8f14b1da4e17f --- quickstep/libs/sysui_shared.jar | Bin 141469 -> 142780 bytes .../com/android/quickstep/views/TaskView.java | 22 +++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar index 17ff858c475f92db106e3e29897f515255b35fa0..7ff664d41c682a5df3704ca3adafdd5fc0823ffa 100644 GIT binary patch delta 13016 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 ezhp2!`)lh!528f#hVh26=6cDO`gW> delta 11797 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 G=KlesM=M { + if (resultCallback != null) { + // Only post the animation start after the system has indicated that the + // transition has started + resultCallbackHandler.post(() -> resultCallback.accept(true)); + } + }, resultCallbackHandler); + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key, + opts, (success) -> { + if (resultCallback != null && !success) { + // If the call to start activity failed, then post the result + // immediately, otherwise, wait for the animation start callback + // from the activity options above + resultCallbackHandler.post(() -> resultCallback.accept(false)); + } + }, resultCallbackHandler); } - ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key, - opts, resultCallback, resultCallbackHandler); } } From 48cb7bc7a449c0b4e7b7156c77e01f4060e798b8 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Tue, 25 Sep 2018 17:03:34 -0700 Subject: [PATCH 09/20] Move IconNormalizer/ShadowGenerator/LauncherIcons to icons package Bug: 115891474 Sending out the package name changing CL first before I make LauncherIconsHandler and tests around it. Change-Id: Ic10479a06333e1435b392a7072cd08782e710cbd --- .../src/com/android/quickstep/NormalizedIconLoader.java | 4 ++-- src/com/android/launcher3/AutoInstallsLayout.java | 2 +- src/com/android/launcher3/DeviceProfile.java | 2 +- src/com/android/launcher3/FastBitmapDrawable.java | 2 +- src/com/android/launcher3/InstallShortcutReceiver.java | 4 ++-- src/com/android/launcher3/ItemInfoWithIcon.java | 2 +- src/com/android/launcher3/LauncherModel.java | 2 +- src/com/android/launcher3/WidgetPreviewLoader.java | 4 ++-- .../launcher3/allapps/search/AppsSearchContainerLayout.java | 2 +- src/com/android/launcher3/badge/BadgeRenderer.java | 2 +- src/com/android/launcher3/compat/LauncherAppsCompatVO.java | 2 +- src/com/android/launcher3/dragndrop/DragView.java | 2 +- src/com/android/launcher3/graphics/DrawableFactory.java | 3 +-- .../android/launcher3/graphics/PlaceHolderIconDrawable.java | 1 + src/com/android/launcher3/icons/BaseIconCache.java | 4 +--- .../android/launcher3/{graphics => icons}/BitmapInfo.java | 2 +- src/com/android/launcher3/icons/CachingLogic.java | 3 --- .../launcher3/{graphics => icons}/ColorExtractor.java | 2 +- .../launcher3/{graphics => icons}/FixedScaleDrawable.java | 2 +- .../launcher3/{graphics => icons}/IconNormalizer.java | 2 +- .../launcher3/{graphics => icons}/LauncherIcons.java | 6 +++--- .../launcher3/{graphics => icons}/ShadowGenerator.java | 2 +- src/com/android/launcher3/model/LoaderCursor.java | 4 ++-- src/com/android/launcher3/model/LoaderTask.java | 2 +- src/com/android/launcher3/model/PackageUpdatedTask.java | 4 ++-- src/com/android/launcher3/model/ShortcutsChangedTask.java | 2 +- .../android/launcher3/model/UserLockStateChangedTask.java | 2 +- src/com/android/launcher3/popup/PopupPopulator.java | 2 +- src/com/android/launcher3/widget/PendingItemDragHelper.java | 2 +- .../uioverrides/dynamicui/WallpaperManagerCompatVL.java | 2 +- .../launcher3/model/BaseModelUpdateTaskTestCase.java | 2 +- tests/src/com/android/launcher3/model/LoaderCursorTest.java | 2 +- 32 files changed, 38 insertions(+), 43 deletions(-) rename src/com/android/launcher3/{graphics => icons}/BitmapInfo.java (97%) rename src/com/android/launcher3/{graphics => icons}/ColorExtractor.java (99%) rename src/com/android/launcher3/{graphics => icons}/FixedScaleDrawable.java (97%) rename src/com/android/launcher3/{graphics => icons}/IconNormalizer.java (99%) rename src/com/android/launcher3/{graphics => icons}/LauncherIcons.java (99%) rename src/com/android/launcher3/{graphics => icons}/ShadowGenerator.java (99%) diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java index 8f7dbdd366..655776194f 100644 --- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java +++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java @@ -28,9 +28,9 @@ import android.util.LruCache; import android.util.SparseArray; import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.graphics.DrawableFactory; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.systemui.shared.recents.model.IconLoader; import com.android.systemui.shared.recents.model.TaskKeyLruCache; diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 6b0a90a78a..4c34071a92 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -39,7 +39,7 @@ import android.util.Patterns; import com.android.launcher3.LauncherProvider.SqlArguments; import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.util.Thunk; import org.xmlpull.v1.XmlPullParser; diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index b429018d74..256fd6c80c 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -30,7 +30,7 @@ import android.view.WindowManager; import com.android.launcher3.CellLayout.ContainerType; import com.android.launcher3.badge.BadgeRenderer; -import com.android.launcher3.graphics.IconNormalizer; +import com.android.launcher3.icons.IconNormalizer; public class DeviceProfile { diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 7efb6ec945..daf587a736 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -34,7 +34,7 @@ import android.graphics.drawable.Drawable; import android.util.Property; import android.util.SparseArray; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; public class FastBitmapDrawable extends Drawable { diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index b9d45fb02e..7190f12779 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -40,8 +40,8 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index 2ceb0dd71a..6d453c9b38 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -16,7 +16,7 @@ package com.android.launcher3; -import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; +import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; import android.graphics.Bitmap; diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index df1c693382..68abd682dd 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -38,7 +38,7 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.AddWorkspaceItemsTask; import com.android.launcher3.model.BaseModelUpdateTask; diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index cf497fd876..d47dcee92c 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -31,8 +31,8 @@ import android.util.LongSparseArray; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.LauncherIcons; -import com.android.launcher3.graphics.ShadowGenerator; +import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.icons.ShadowGenerator; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.ComponentKey; diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index ab6635e45e..15cc2caae6 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -19,7 +19,7 @@ import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getSize; import static android.view.View.MeasureSpec.makeMeasureSpec; -import static com.android.launcher3.graphics.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; +import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; import android.content.Context; import android.graphics.Rect; diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java index 8998b24ede..5d38aadede 100644 --- a/src/com/android/launcher3/badge/BadgeRenderer.java +++ b/src/com/android/launcher3/badge/BadgeRenderer.java @@ -27,7 +27,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.util.Log; -import com.android.launcher3.graphics.ShadowGenerator; +import com.android.launcher3.icons.ShadowGenerator; /** * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge). diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java index 386b3b3fe3..fb446600c3 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java @@ -33,7 +33,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageUserKey; diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 6ac56ebed3..6d1efd5879 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -56,7 +56,7 @@ import com.android.launcher3.FirstFrameAnimatorHelper; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index 8cabd2fe60..ce83a177a8 100644 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ b/src/com/android/launcher3/graphics/DrawableFactory.java @@ -16,8 +16,6 @@ package com.android.launcher3.graphics; -import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; - import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; @@ -37,6 +35,7 @@ import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java index 18efd47b6e..5f2fb596af 100644 --- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java +++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.Themes; /** diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java index 3c742df1b2..f7e2be11b3 100644 --- a/src/com/android/launcher3/icons/BaseIconCache.java +++ b/src/com/android/launcher3/icons/BaseIconCache.java @@ -15,7 +15,7 @@ */ package com.android.launcher3.icons; -import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; +import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; import android.content.ComponentName; import android.content.ContentValues; @@ -48,9 +48,7 @@ import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.BitmapInfo; import com.android.launcher3.graphics.BitmapRenderer; -import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.InstantAppResolver; diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/icons/BitmapInfo.java similarity index 97% rename from src/com/android/launcher3/graphics/BitmapInfo.java rename to src/com/android/launcher3/icons/BitmapInfo.java index 74b783dfd7..ebe05113e8 100644 --- a/src/com/android/launcher3/graphics/BitmapInfo.java +++ b/src/com/android/launcher3/icons/BitmapInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; diff --git a/src/com/android/launcher3/icons/CachingLogic.java b/src/com/android/launcher3/icons/CachingLogic.java index 194fedb1fe..13fdc6a0b8 100644 --- a/src/com/android/launcher3/icons/CachingLogic.java +++ b/src/com/android/launcher3/icons/CachingLogic.java @@ -20,9 +20,6 @@ import android.content.Context; import android.content.pm.LauncherActivityInfo; import android.os.UserHandle; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.LauncherIcons; - public interface CachingLogic { ComponentName getComponent(T object); diff --git a/src/com/android/launcher3/graphics/ColorExtractor.java b/src/com/android/launcher3/icons/ColorExtractor.java similarity index 99% rename from src/com/android/launcher3/graphics/ColorExtractor.java rename to src/com/android/launcher3/icons/ColorExtractor.java index da5da9cea6..87bda825cc 100644 --- a/src/com/android/launcher3/graphics/ColorExtractor.java +++ b/src/com/android/launcher3/icons/ColorExtractor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Color; diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/icons/FixedScaleDrawable.java similarity index 97% rename from src/com/android/launcher3/graphics/FixedScaleDrawable.java rename to src/com/android/launcher3/icons/FixedScaleDrawable.java index 0f0e42428d..e594f477eb 100644 --- a/src/com/android/launcher3/graphics/FixedScaleDrawable.java +++ b/src/com/android/launcher3/icons/FixedScaleDrawable.java @@ -1,4 +1,4 @@ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.annotation.TargetApi; import android.content.res.Resources; diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/icons/IconNormalizer.java similarity index 99% rename from src/com/android/launcher3/graphics/IconNormalizer.java rename to src/com/android/launcher3/icons/IconNormalizer.java index df00815c07..73177825bd 100644 --- a/src/com/android/launcher3/graphics/IconNormalizer.java +++ b/src/com/android/launcher3/icons/IconNormalizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.content.Context; import android.graphics.Bitmap; diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java similarity index 99% rename from src/com/android/launcher3/graphics/LauncherIcons.java rename to src/com/android/launcher3/icons/LauncherIcons.java index 7db67c9828..b4cbf65304 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import static android.graphics.Paint.DITHER_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; -import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR; +import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR; import android.content.ComponentName; import android.content.Context; @@ -44,7 +44,7 @@ import android.os.UserHandle; import com.android.launcher3.AppInfo; import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.icons.IconCache; +import com.android.launcher3.graphics.BitmapRenderer; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppState; diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/icons/ShadowGenerator.java similarity index 99% rename from src/com/android/launcher3/graphics/ShadowGenerator.java rename to src/com/android/launcher3/icons/ShadowGenerator.java index d2d1699bf7..57d463a806 100644 --- a/src/com/android/launcher3/graphics/ShadowGenerator.java +++ b/src/com/android/launcher3/icons/ShadowGenerator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.content.Context; import android.graphics.Bitmap; diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 77e9721bce..87aef02f76 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -43,8 +43,8 @@ import com.android.launcher3.Workspace; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.GridOccupancy; diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 0105fbbafd..d6b7b0f53a 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -61,7 +61,7 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIconPreviewVerifier; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.shortcuts.DeepShortcutManager; diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 0af53118d3..c004e217b1 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -38,8 +38,8 @@ import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 59f3d1c60a..020bc41822 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -23,7 +23,7 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 9521a9e5b5..9f02d4fdad 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -26,7 +26,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java index 450062961a..c14c00eb87 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -24,7 +24,7 @@ import android.service.notification.StatusBarNotification; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.shortcuts.DeepShortcutManager; diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java index 74ab14f55d..8ea9bd479c 100644 --- a/src/com/android/launcher3/widget/PendingItemDragHelper.java +++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java @@ -34,7 +34,7 @@ import com.android.launcher3.R; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.LivePreviewWidgetCell; import com.android.launcher3.graphics.DragPreviewProvider; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; /** * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java index 7883442546..6808859438 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java @@ -45,7 +45,7 @@ import android.util.Log; import android.util.Pair; import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.ColorExtractor; +import com.android.launcher3.icons.ColorExtractor; import java.io.IOException; import java.util.ArrayList; diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index f4aa15a72c..1e5643982f 100644 --- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -32,7 +32,7 @@ import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherProvider; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.Provider; import com.android.launcher3.util.TestLauncherProvider; diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index 42a2764010..a21f861771 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -17,7 +17,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; import org.junit.Before; import org.junit.Test; From a4483361ff800ac3bc01e0d5242086ce801234a8 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Wed, 26 Sep 2018 12:26:53 -0700 Subject: [PATCH 10/20] Fix ClassNotFoundException for FixedScaleDrawable Change-Id: I6198a77ddb49505ead86de91dd7096a681873a1d --- res/drawable-v26/adaptive_icon_drawable_wrapper.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml index 2d78b699f4..9f13cf5719 100644 --- a/res/drawable-v26/adaptive_icon_drawable_wrapper.xml +++ b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml @@ -17,6 +17,6 @@ - + From b0edaeb268db46c00729ae29d0abf36b6be614e5 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 26 Sep 2018 13:16:24 -0700 Subject: [PATCH 11/20] Align rounded corner of fastscroller popup with the top of the thumb. Bug: 63852509 Change-Id: I90b6d3e87206a53cfb4c8025a8e5f9597cf73898 --- .../views/RecyclerViewFastScroller.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index f0d6de20f6..883cbeed9c 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -175,6 +175,7 @@ public class RecyclerViewFastScroller extends View { if (mThumbOffsetY == y) { return; } + updatePopupY((int) y); mThumbOffsetY = y; invalidate(); } @@ -281,7 +282,6 @@ public class RecyclerViewFastScroller extends View { mPopupView.setText(sectionName); } animatePopupVisibility(!sectionName.isEmpty()); - updatePopupY(lastY); mLastTouchY = boundedY; setThumbOffsetY((int) mLastTouchY); } @@ -299,11 +299,14 @@ public class RecyclerViewFastScroller extends View { canvas.translate(0, mThumbOffsetY); halfW += mThumbPadding; - float r = mWidth + mThumbPadding + mThumbPadding; + float r = getScrollThumbRadius(); canvas.drawRoundRect(-halfW, 0, halfW, mThumbHeight, r, r, mThumbPaint); canvas.restoreToCount(saveCount); } + private float getScrollThumbRadius() { + return mWidth + mThumbPadding + mThumbPadding; + } /** * Animates the width of the scrollbar. @@ -352,11 +355,15 @@ public class RecyclerViewFastScroller extends View { } private void updatePopupY(int lastTouchY) { + if (!mPopupVisible) { + return; + } int height = mPopupView.getHeight(); - float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height) - + mRv.getScrollBarTop(); - top = Utilities.boundToRange(top, - mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height); + // Aligns the rounded corner of the pop up with the top of the thumb. + float top = mRv.getScrollBarTop() + lastTouchY + (getScrollThumbRadius() / 2f) + - (height / 2f); + top = Utilities.boundToRange(top, 0, + getTop() + mRv.getScrollBarTop() + mRv.getScrollbarTrackHeight() - height); mPopupView.setTranslationY(top); } From 9f2e997b309c0f14c21401d17686ecf9c2aa6d92 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 26 Sep 2018 15:30:34 -0700 Subject: [PATCH 12/20] Fixing packageInfo map not getting initialized properly Change-Id: I395a2a3175675815d6d12a04898132094d3a889c --- src/com/android/launcher3/icons/IconCacheUpdateHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java index e316c53569..64e3fbf19b 100644 --- a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java +++ b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java @@ -67,9 +67,8 @@ public class IconCacheUpdateHandler { private void createPackageInfoMap() { PackageManager pm = mIconCache.mPackageManager; - HashMap pkgInfoMap = new HashMap<>(); for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) { - pkgInfoMap.put(info.packageName, info); + mPkgInfoMap.put(info.packageName, info); } } From 665bc46d54c8d5325a2ed6ef579af1dd59ccea04 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 26 Sep 2018 15:44:30 -0700 Subject: [PATCH 13/20] Add some gesture logging to track down quickscrub launch issue - Keep rudimentary log of the last few gestures for dumping with the BR - Also renaming updateInteractionType since we only use it to change to the quickscrub starting interaction type now, which is less confusing Bug: 112783625 Change-Id: Ic024684caf2841cd7c09df9481163ea0c0ae03bd --- .../quickstep/ActivityControlHelper.java | 11 +- .../quickstep/DeferredTouchConsumer.java | 4 +- .../android/quickstep/MotionEventQueue.java | 4 +- .../quickstep/OtherActivityTouchConsumer.java | 32 ++++-- .../quickstep/QuickScrubController.java | 7 +- .../com/android/quickstep/TouchConsumer.java | 2 +- .../quickstep/TouchInteractionLog.java | 108 ++++++++++++++++++ .../quickstep/TouchInteractionService.java | 77 ++++++++----- .../WindowTransformSwipeHandler.java | 53 +++++---- 9 files changed, 225 insertions(+), 73 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/TouchInteractionLog.java diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index f39a0072c7..206c8be4b4 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -91,7 +91,7 @@ public interface ActivityControlHelper { * Updates the UI to indicate quick interaction. */ void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo, - boolean activityVisible); + boolean activityVisible, TouchInteractionLog touchInteractionLog); float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, Context context); @@ -153,13 +153,14 @@ public interface ActivityControlHelper { @Override public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo, - boolean activityVisible) { + boolean activityVisible, TouchInteractionLog touchInteractionLog) { LauncherState fromState = activity.getStateManager().getState(); activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible); QuickScrubController controller = activity.getOverviewPanel() .getQuickScrubController(); - controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this); + controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this, + touchInteractionLog); if (!activityVisible) { // For the duration of the gesture, lock the screen orientation to ensure that we @@ -425,14 +426,14 @@ public interface ActivityControlHelper { @Override public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo, - boolean activityVisible) { + boolean activityVisible, TouchInteractionLog touchInteractionLog) { QuickScrubController controller = activity.getOverviewPanel() .getQuickScrubController(); // TODO: match user is as well boolean startingFromHome = !activityVisible && (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent)); - controller.onQuickScrubStart(startingFromHome, this); + controller.onQuickScrubStart(startingFromHome, this, touchInteractionLog); if (activityVisible) { mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub, OVERVIEW_TRANSITION_MS); diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java index 8e83bd0792..5996df78cf 100644 --- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java @@ -49,8 +49,8 @@ public class DeferredTouchConsumer implements TouchConsumer { } @Override - public void updateTouchTracking(int interactionType) { - mTarget.updateTouchTracking(interactionType); + public void onQuickScrubStart() { + mTarget.onQuickScrubStart(); } @Override diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java index f73be6cba9..8a598a3a84 100644 --- a/quickstep/src/com/android/quickstep/MotionEventQueue.java +++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java @@ -146,7 +146,7 @@ public class MotionEventQueue { if (event.getActionMasked() == ACTION_VIRTUAL) { switch (event.getAction()) { case ACTION_QUICK_SCRUB_START: - mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB); + mConsumer.onQuickScrubStart(); break; case ACTION_QUICK_SCRUB_PROGRESS: mConsumer.onQuickScrubProgress(event.getX()); @@ -162,7 +162,7 @@ public class MotionEventQueue { break; case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB: mConsumer.onShowOverviewFromAltTab(); - mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB); + mConsumer.onQuickScrubStart(); break; case ACTION_QUICK_STEP: mConsumer.onQuickStep(event); diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index 0e811f771f..4417a3da77 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -79,6 +79,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC private final Choreographer mBackgroundThreadChoreographer; private final OverviewCallbacks mOverviewCallbacks; private final TaskOverlayFactory mTaskOverlayFactory; + private final TouchInteractionLog mTouchInteractionLog; private final boolean mIsDeferredDownTarget; private final PointF mDownPos = new PointF(); @@ -100,7 +101,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) { + TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker, + TouchInteractionLog touchInteractionLog) { super(base); mRunningTask = runningTaskInfo; @@ -113,6 +115,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget); mOverviewCallbacks = overviewCallbacks; mTaskOverlayFactory = taskOverlayFactory; + mTouchInteractionLog = touchInteractionLog; + mTouchInteractionLog.setTouchConsumer(this); } @Override @@ -125,6 +129,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC if (mVelocityTracker == null) { return; } + mTouchInteractionLog.addMotionEvent(ev); switch (ev.getActionMasked()) { case ACTION_DOWN: { TraceHelper.beginSection("TouchInt"); @@ -215,10 +220,13 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC } private void startTouchTrackingForWindowAnimation(long touchTimeMs) { + mTouchInteractionLog.startRecentsAnimation(); + // Create the shared handler RecentsAnimationState animationState = new RecentsAnimationState(); final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler( - animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper); + animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper, + mTouchInteractionLog); // Preload the plan mRecentsModel.loadTasks(mRunningTask.id, null); @@ -315,7 +323,13 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC } @Override - public void updateTouchTracking(int interactionType) { + public Choreographer getIntrimChoreographer(MotionEventQueue queue) { + mEventQueue = queue; + return mBackgroundThreadChoreographer; + } + + @Override + public void onQuickScrubStart() { if (!mPassedInitialSlop && mIsDeferredDownTarget && mInteractionHandler == null) { // If we deferred starting the window animation on touch down, then // start tracking now @@ -323,20 +337,16 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mPassedInitialSlop = true; } + mTouchInteractionLog.startQuickScrub(); if (mInteractionHandler != null) { - mInteractionHandler.updateInteractionType(interactionType); + mInteractionHandler.onQuickScrubStart(); } notifyGestureStarted(); } - @Override - public Choreographer getIntrimChoreographer(MotionEventQueue queue) { - mEventQueue = queue; - return mBackgroundThreadChoreographer; - } - @Override public void onQuickScrubEnd() { + mTouchInteractionLog.endQuickScrub("onQuickScrubEnd"); if (mInteractionHandler != null) { mInteractionHandler.onQuickScrubEnd(); } @@ -344,6 +354,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC @Override public void onQuickScrubProgress(float progress) { + mTouchInteractionLog.setQuickScrubProgress(progress); if (mInteractionHandler != null) { mInteractionHandler.onQuickScrubProgress(progress); } @@ -351,6 +362,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC @Override public void onQuickStep(MotionEvent ev) { + mTouchInteractionLog.startQuickStep(); if (mIsDeferredDownTarget) { // Deferred gesture, start the animation and gesture tracking once we pass the actual // touch slop diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java index cbc7a6793d..943225647c 100644 --- a/quickstep/src/com/android/quickstep/QuickScrubController.java +++ b/quickstep/src/com/android/quickstep/QuickScrubController.java @@ -69,6 +69,7 @@ public class QuickScrubController implements OnAlarmListener { private boolean mFinishedTransitionToQuickScrub; private Runnable mOnFinishedTransitionToQuickScrubRunnable; private ActivityControlHelper mActivityControlHelper; + private TouchInteractionLog mTouchInteractionLog; public QuickScrubController(BaseActivity activity, RecentsView recentsView) { mActivity = activity; @@ -79,13 +80,15 @@ public class QuickScrubController implements OnAlarmListener { } } - public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper) { + public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper, + TouchInteractionLog touchInteractionLog) { prepareQuickScrub(TAG); mInQuickScrub = true; mStartedFromHome = startingFromHome; mQuickScrubSection = 0; mFinishedTransitionToQuickScrub = false; mActivityControlHelper = controlHelper; + mTouchInteractionLog = touchInteractionLog; snapToNextTaskIfAvailable(); mActivity.getUserEventDispatcher().resetActionDurationMillis(); @@ -101,7 +104,9 @@ public class QuickScrubController implements OnAlarmListener { TaskView taskView = mRecentsView.getTaskViewAt(page); if (taskView != null) { mWaitingForTaskLaunch = true; + mTouchInteractionLog.launchTaskStart(); taskView.launchTask(true, (result) -> { + mTouchInteractionLog.launchTaskEnd(result); if (!result) { taskView.notifyTaskLaunchFailed(TAG); breakOutOfQuickScrub(); diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java index 646fc57f25..225d29bd37 100644 --- a/quickstep/src/com/android/quickstep/TouchConsumer.java +++ b/quickstep/src/com/android/quickstep/TouchConsumer.java @@ -43,7 +43,7 @@ public interface TouchConsumer extends Consumer { default void reset() { } - default void updateTouchTracking(@InteractionType int interactionType) { } + default void onQuickScrubStart() { } default void onQuickScrubEnd() { } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionLog.java b/quickstep/src/com/android/quickstep/TouchInteractionLog.java new file mode 100644 index 0000000000..053efbb65b --- /dev/null +++ b/quickstep/src/com/android/quickstep/TouchInteractionLog.java @@ -0,0 +1,108 @@ +/* + * 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.quickstep; + +import android.view.MotionEvent; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.LinkedList; + +/** + * Keeps track of debugging logs for a particular quickstep/scrub gesture. + */ +public class TouchInteractionLog { + + // The number of gestures to log + private static final int MAX_NUM_LOG_GESTURES = 5; + + private final Calendar mCalendar = Calendar.getInstance(); + private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MMM dd - kk:mm:ss:SSS"); + private final LinkedList> mGestureLogs = new LinkedList<>(); + + public void prepareForNewGesture() { + mGestureLogs.add(new ArrayList<>()); + while (mGestureLogs.size() > MAX_NUM_LOG_GESTURES) { + mGestureLogs.pop(); + } + getCurrentLog().add("[" + mDateFormat.format(mCalendar.getTime()) + "]"); + } + + public void setTouchConsumer(TouchConsumer consumer) { + getCurrentLog().add("tc=" + consumer.getClass().getSimpleName()); + } + + public void addMotionEvent(MotionEvent event) { + getCurrentLog().add("ev=" + event.getActionMasked()); + } + + public void startQuickStep() { + getCurrentLog().add("qstStart"); + } + + public void startQuickScrub() { + getCurrentLog().add("qsStart"); + } + + public void setQuickScrubProgress(float progress) { + getCurrentLog().add("qsP=" + progress); + } + + public void endQuickScrub(String reason) { + getCurrentLog().add("qsEnd=" + reason); + } + + public void startRecentsAnimation() { + getCurrentLog().add("raStart"); + } + + public void startRecentsAnimationCallback(int numTargets) { + getCurrentLog().add("raStartCb=" + numTargets); + } + + public void cancelRecentsAnimation() { + getCurrentLog().add("raCancel"); + } + + public void finishRecentsAnimation(boolean toHome) { + getCurrentLog().add("raFinish=" + toHome); + } + + public void launchTaskStart() { + getCurrentLog().add("launchStart"); + } + + public void launchTaskEnd(boolean result) { + getCurrentLog().add("launchEnd=" + result); + } + + public void dump(PrintWriter pw) { + pw.println("TouchInteractionLog {"); + for (ArrayList gesture : mGestureLogs) { + pw.print(" "); + for (String log : gesture) { + pw.print(log + " "); + } + pw.println(); + } + pw.println("}"); + } + + private ArrayList getCurrentLog() { + return mGestureLogs.getLast(); + } +} diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index bd79301113..b9f95ccfa4 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -52,6 +52,8 @@ 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.NavigationBarCompat.HitTarget; +import java.io.FileDescriptor; +import java.io.PrintWriter; /** * Service connected by system-UI for handling touch interaction. @@ -81,6 +83,8 @@ public class TouchInteractionService extends Service { @Override public void onPreMotionEvent(@HitTarget int downHitTarget) { + mTouchInteractionLog.prepareForNewGesture(); + TraceHelper.beginSection("SysUiBinder"); setupTouchConsumer(downHitTarget); TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget); @@ -179,6 +183,7 @@ public class TouchInteractionService extends Service { private OverviewInteractionState mOverviewInteractionState; private OverviewCallbacks mOverviewCallbacks; private TaskOverlayFactory mTaskOverlayFactory; + private TouchInteractionLog mTouchInteractionLog; private Choreographer mMainThreadChoreographer; private Choreographer mBackgroundThreadChoreographer; @@ -196,6 +201,7 @@ public class TouchInteractionService extends Service { mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this); mOverviewCallbacks = OverviewCallbacks.get(this); mTaskOverlayFactory = TaskOverlayFactory.get(this); + mTouchInteractionLog = new TouchInteractionLog(); sConnected = true; @@ -240,7 +246,7 @@ public class TouchInteractionService extends Service { } else if (forceToLauncher || runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) { return OverviewTouchConsumer.newInstance( - mOverviewCommandHelper.getActivityControlHelper(), false); + mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog); } else { if (tracker == null) { tracker = VelocityTracker.obtain(); @@ -249,7 +255,7 @@ public class TouchInteractionService extends Service { mOverviewCommandHelper.overviewIntent, mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor, mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks, - mTaskOverlayFactory, tracker); + mTaskOverlayFactory, tracker, mTouchInteractionLog); } } @@ -262,6 +268,11 @@ public class TouchInteractionService extends Service { mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance()); } + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mTouchInteractionLog.dump(pw); + } + public static class OverviewTouchConsumer implements TouchConsumer { @@ -272,6 +283,7 @@ public class TouchInteractionService extends Service { private final PointF mDownPos = new PointF(); private final int mTouchSlop; private final QuickScrubController mQuickScrubController; + private final TouchInteractionLog mTouchInteractionLog; private final boolean mStartingInActivityBounds; @@ -283,7 +295,7 @@ public class TouchInteractionService extends Service { private boolean mEndPending = false; OverviewTouchConsumer(ActivityControlHelper activityHelper, T activity, - boolean startingInActivityBounds) { + boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) { mActivityHelper = activityHelper; mActivity = activity; mTarget = activity.getDragLayer(); @@ -292,6 +304,8 @@ public class TouchInteractionService extends Service { mQuickScrubController = mActivity.getOverviewPanel() .getQuickScrubController(); + mTouchInteractionLog = touchInteractionLog; + mTouchInteractionLog.setTouchConsumer(this); } @Override @@ -299,6 +313,7 @@ public class TouchInteractionService extends Service { if (mInvalidated) { return; } + mTouchInteractionLog.addMotionEvent(ev); int action = ev.getActionMasked(); if (action == ACTION_DOWN) { if (mStartingInActivityBounds) { @@ -372,44 +387,48 @@ public class TouchInteractionService extends Service { OverviewCallbacks.get(mActivity).closeAllWindows(); ActivityManagerWrapper.getInstance() .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + mTouchInteractionLog.startQuickStep(); } @Override - public void updateTouchTracking(int interactionType) { + public void onQuickScrubStart() { if (mInvalidated) { return; } - if (interactionType == INTERACTION_QUICK_SCRUB) { + mTouchInteractionLog.startQuickScrub(); + if (!mQuickScrubController.prepareQuickScrub(TAG)) { + mInvalidated = true; + mTouchInteractionLog.endQuickScrub("onQuickScrubStart"); + return; + } + OverviewCallbacks.get(mActivity).closeAllWindows(); + ActivityManagerWrapper.getInstance() + .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + + mStartPending = true; + Runnable action = () -> { if (!mQuickScrubController.prepareQuickScrub(TAG)) { mInvalidated = true; + mTouchInteractionLog.endQuickScrub("onQuickScrubStart"); return; } - OverviewCallbacks.get(mActivity).closeAllWindows(); - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + mActivityHelper.onQuickInteractionStart(mActivity, null, true, + mTouchInteractionLog); + mQuickScrubController.onQuickScrubProgress(mLastProgress); + mStartPending = false; - mStartPending = true; - Runnable action = () -> { - if (!mQuickScrubController.prepareQuickScrub(TAG)) { - mInvalidated = true; - return; - } - mActivityHelper.onQuickInteractionStart(mActivity, null, true); - mQuickScrubController.onQuickScrubProgress(mLastProgress); - mStartPending = false; + if (mEndPending) { + mQuickScrubController.onQuickScrubEnd(); + mEndPending = false; + } + }; - if (mEndPending) { - mQuickScrubController.onQuickScrubEnd(); - mEndPending = false; - } - }; - - mActivityHelper.executeOnWindowAvailable(mActivity, action); - } + mActivityHelper.executeOnWindowAvailable(mActivity, action); } @Override public void onQuickScrubEnd() { + mTouchInteractionLog.endQuickScrub("onQuickScrubEnd"); if (mInvalidated) { return; } @@ -422,6 +441,7 @@ public class TouchInteractionService extends Service { @Override public void onQuickScrubProgress(float progress) { + mTouchInteractionLog.setQuickScrubProgress(progress); mLastProgress = progress; if (mInvalidated || mStartPending) { return; @@ -429,13 +449,14 @@ public class TouchInteractionService extends Service { mQuickScrubController.onQuickScrubProgress(progress); } - public static TouchConsumer newInstance( - ActivityControlHelper activityHelper, boolean startingInActivityBounds) { + public static TouchConsumer newInstance(ActivityControlHelper activityHelper, + boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) { BaseDraggingActivity activity = activityHelper.getCreatedActivity(); if (activity == null) { return TouchConsumer.NO_OP; } - return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds); + return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds, + touchInteractionLog); } } } diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index a2e9af841b..9991552987 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -198,6 +198,7 @@ public class WindowTransformSwipeHandler { private final Context mContext; private final ActivityControlHelper mActivityControlHelper; private final ActivityInitListener mActivityInitListener; + private final TouchInteractionLog mTouchInteractionLog; private final int mRunningTaskId; private final RunningTaskInfo mRunningTaskInfo; @@ -239,7 +240,8 @@ public class WindowTransformSwipeHandler { private Bundle mAssistData; WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context, - long touchTimeMs, ActivityControlHelper controller) { + long touchTimeMs, ActivityControlHelper controller, + TouchInteractionLog touchInteractionLog) { this.id = id; mContext = context; mRunningTaskInfo = runningTaskInfo; @@ -248,6 +250,7 @@ public class WindowTransformSwipeHandler { mActivityControlHelper = controller; mActivityInitListener = mActivityControlHelper .createActivityInitListener(this::onActivityInit); + mTouchInteractionLog = touchInteractionLog; initStateCallbacks(); } @@ -319,7 +322,7 @@ public class WindowTransformSwipeHandler { this::notifyTransitionCancelled); mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START - | STATE_APP_CONTROLLER_RECEIVED, this::onQuickScrubStart); + | STATE_APP_CONTROLLER_RECEIVED, this::onQuickScrubStartUi); mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START | STATE_SCALED_CONTROLLER_RECENTS, this::onFinishedTransitionToQuickScrub); mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_CURRENT_TASK_FINISHED @@ -503,25 +506,6 @@ public class WindowTransformSwipeHandler { .getHighResThumbnailLoader().setVisible(true); } - public void updateInteractionType(@InteractionType int interactionType) { - if (mInteractionType != INTERACTION_NORMAL) { - throw new IllegalArgumentException( - "Can't change interaction type from " + mInteractionType); - } - if (interactionType != INTERACTION_QUICK_SCRUB) { - throw new IllegalArgumentException( - "Can't change interaction type to " + interactionType); - } - mInteractionType = interactionType; - mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub); - - setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED); - - // Start the window animation without waiting for launcher. - animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR, - true /* goingToHome */); - } - private void shiftAnimationDestinationForQuickscrub() { TransformedRect tempRect = new TransformedRect(); mActivityControlHelper @@ -669,6 +653,7 @@ public class WindowTransformSwipeHandler { initTransitionEndpoints(dp); mRecentsAnimationWrapper.setController(controller, targets); + mTouchInteractionLog.startRecentsAnimationCallback(targets.apps.length); setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); mPassedOverviewThreshold = false; @@ -678,6 +663,7 @@ public class WindowTransformSwipeHandler { mRecentsAnimationWrapper.setController(null, null); mActivityInitListener.unregister(); setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); + mTouchInteractionLog.cancelRecentsAnimation(); } public void onGestureStarted() { @@ -729,7 +715,8 @@ public class WindowTransformSwipeHandler { // Hide the task view, if not already hidden setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); - return OverviewTouchConsumer.newInstance(mActivityControlHelper, true); + return OverviewTouchConsumer.newInstance(mActivityControlHelper, true, + mTouchInteractionLog); } private void handleNormalGestureEnd(float endVelocity, boolean isFling) { @@ -854,6 +841,7 @@ public class WindowTransformSwipeHandler { @UiThread private void resumeLastTask() { mRecentsAnimationWrapper.finish(false /* toHome */, null); + mTouchInteractionLog.finishRecentsAnimation(false); } public void reset() { @@ -950,6 +938,7 @@ public class WindowTransformSwipeHandler { mRecentsAnimationWrapper.finish(true /* toHome */, () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } + mTouchInteractionLog.finishRecentsAnimation(true); } private void setupLauncherUiAfterSwipeUpAnimation() { @@ -969,7 +958,22 @@ public class WindowTransformSwipeHandler { reset(); } - private void onQuickScrubStart() { + public void onQuickScrubStart() { + if (mInteractionType != INTERACTION_NORMAL) { + throw new IllegalArgumentException( + "Can't change interaction type from " + mInteractionType); + } + mInteractionType = INTERACTION_QUICK_SCRUB; + mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub); + + setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED); + + // Start the window animation without waiting for launcher. + animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR, + true /* goingToHome */); + } + + private void onQuickScrubStartUi() { if (!mQuickScrubController.prepareQuickScrub(TAG)) { mQuickScrubBlocked = true; setStateOnUiThread(STATE_RESUME_LAST_TASK | STATE_HANDLER_INVALIDATED); @@ -980,7 +984,8 @@ public class WindowTransformSwipeHandler { mLauncherTransitionController = null; } - mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false); + mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false, + mTouchInteractionLog); // Inform the last progress in case we skipped before. mQuickScrubController.onQuickScrubProgress(mCurrentQuickScrubProgress); From 43524d0daa5c17ec1b9bfc52d4272ee347a7c529 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Wed, 26 Sep 2018 18:15:42 -0700 Subject: [PATCH 14/20] Attempting to fix flakes in AllAppsIconToHomeTest Pressing an icon in AllApps doesn't show a context menu. The flake doesn't repro locally, the suspects are: 1. Too short wait time 2. App being partially covered by navbar. Hence the fixes. This patch is temporary, and will be replaced with a permanent one when this will be converted to TAPL. Test: AllAppsIconToHomeTest Change-Id: I0a03ff8827a5bc7940af3ec956d4b62330a16c66 --- .../launcher3/ui/AbstractLauncherUiTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 75efea4ebf..9cbab5e183 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -17,6 +17,7 @@ package com.android.launcher3.ui; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import android.app.Instrumentation; import android.content.BroadcastReceiver; @@ -74,7 +75,7 @@ public abstract class AbstractLauncherUiTest { public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; public static final long SHORT_UI_TIMEOUT= 300; - public static final long DEFAULT_UI_TIMEOUT = 3000; + public static final long DEFAULT_UI_TIMEOUT = 10000; public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5; protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); @@ -151,16 +152,20 @@ public abstract class AbstractLauncherUiTest { * @return the matching object. */ protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { - do { + container.setGestureMargins(0, 0, 0, 200); + + int i = 0; + for (; ; ) { // findObject can only execute after spring settles. mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT); UiObject2 widget = container.findObject(condition); if (widget != null && widget.getVisibleBounds().intersects( - 0, 0, mDevice.getDisplayWidth(), mDevice.getDisplayHeight())) { + 0, 0, mDevice.getDisplayWidth(), mDevice.getDisplayHeight() - 200)) { return widget; } - } while (container.scroll(Direction.DOWN, 1f)); - return container.findObject(condition); + if (++i > 40) fail("Too many attempts"); + container.scroll(Direction.DOWN, 1f); + } } /** From 2660ca5d0e3a19840a0c8cad09a96c88a7a864ad Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 19 Sep 2018 14:31:24 -0700 Subject: [PATCH 15/20] Ensure that we don't additionally crop the launcher surface - Now that we are controlling the launcher app surface as well while swiping up into overview, skip applying the crop to any opening remote animation targets. Bug: 70341013 Test: Swipe up, and ensure everything still works Change-Id: I87b4021c0fc0e2997185d4d12f26b2e06999ff57 --- .../src/com/android/quickstep/util/ClipAnimationHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java index 8aaa40c6bb..711ef586ed 100644 --- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java +++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java @@ -181,6 +181,8 @@ public class ClipAnimationHelper { } alpha = mTaskAlphaCallback.apply(app, alpha); + } else { + crop = null; } params[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, From ac8154a23d3ad5f8604f8b9d4f24dc317816fe43 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 26 Sep 2018 12:00:30 -0700 Subject: [PATCH 16/20] Caching widget labels in icon cache to avoid lookup at startup Change-Id: Ie026ee47905454bd70e774d422cd7fe142aec7e2 --- .../LauncherAppWidgetProviderInfo.java | 19 ++- src/com/android/launcher3/LauncherModel.java | 13 ++ .../compat/LauncherAppsCompatVL.java | 2 +- .../compat/ShortcutConfigActivityInfo.java | 22 ++-- .../launcher3/dragndrop/AddItemActivity.java | 40 ++++-- .../PinShortcutRequestActivityInfo.java | 3 +- .../launcher3/icons/BaseIconCache.java | 39 +++--- .../android/launcher3/icons/CachingLogic.java | 31 ++++- .../launcher3/icons/ComponentWithLabel.java | 29 +++++ .../android/launcher3/icons/IconCache.java | 17 ++- .../icons/IconCacheUpdateHandler.java | 116 ++++++++++++++---- .../android/launcher3/model/LoaderTask.java | 36 +++--- .../android/launcher3/model/WidgetItem.java | 12 +- .../android/launcher3/model/WidgetsModel.java | 43 ++++++- .../model/BaseModelUpdateTaskTestCase.java | 4 +- .../widget/WidgetsListAdapterTest.java | 4 +- 16 files changed, 325 insertions(+), 105 deletions(-) create mode 100644 src/com/android/launcher3/icons/ComponentWithLabel.java diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java index 80758c99b9..b2b05b17bb 100644 --- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java @@ -2,11 +2,15 @@ package com.android.launcher3; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; +import android.os.UserHandle; + +import com.android.launcher3.icons.ComponentWithLabel; /** * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords @@ -14,7 +18,8 @@ import android.os.Parcel; * (who's implementation is owned by the launcher). This object represents a widget type / class, * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo} */ -public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo { +public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo + implements ComponentWithLabel { public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-"; @@ -102,4 +107,14 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo { return 0; } } - } + + @Override + public final ComponentName getComponent() { + return provider; + } + + @Override + public final UserHandle getUser() { + return getProfile(); + } +} diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 68abd682dd..5e09f8adc5 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -599,6 +599,19 @@ public class LauncherModel extends BroadcastReceiver CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); } + /** + * Called when the labels for the widgets has updated in the icon cache. + */ + public void onWidgetLabelsUpdated(HashSet updatedPackages, UserHandle user) { + enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app); + bindUpdatedWidgets(dataModel); + } + }); + } + public void enqueueModelUpdateTask(ModelUpdateTask task) { task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor); runOnWorkerThread(task); diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java index ba0ac6e4a6..b6413918f9 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java @@ -200,7 +200,7 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) { if (packageUser == null || packageUser.mPackageName .equals(info.activityInfo.packageName)) { - result.add(new ShortcutConfigActivityInfoVL(info.activityInfo, pm)); + result.add(new ShortcutConfigActivityInfoVL(info.activityInfo)); } } return result; diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java index d260e24e7e..76eec6d40f 100644 --- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java +++ b/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java @@ -32,6 +32,7 @@ import android.os.UserHandle; import android.util.Log; import android.widget.Toast; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; @@ -40,7 +41,7 @@ import com.android.launcher3.ShortcutInfo; /** * Wrapper class for representing a shortcut configure activity. */ -public abstract class ShortcutConfigActivityInfo { +public abstract class ShortcutConfigActivityInfo implements ComponentWithLabel { private static final String TAG = "SCActivityInfo"; @@ -52,10 +53,12 @@ public abstract class ShortcutConfigActivityInfo { mUser = user; } + @Override public ComponentName getComponent() { return mCn; } + @Override public UserHandle getUser() { return mUser; } @@ -64,8 +67,6 @@ public abstract class ShortcutConfigActivityInfo { return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; } - public abstract CharSequence getLabel(); - public abstract Drawable getFullResIcon(IconCache cache); /** @@ -94,8 +95,8 @@ public abstract class ShortcutConfigActivityInfo { } /** - * Returns true if various properties ({@link #getLabel()}, {@link #getFullResIcon}) can - * be safely persisted. + * Returns true if various properties ({@link #getLabel(PackageManager)}, + * {@link #getFullResIcon}) can be safely persisted. */ public boolean isPersistable() { return true; @@ -104,18 +105,15 @@ public abstract class ShortcutConfigActivityInfo { static class ShortcutConfigActivityInfoVL extends ShortcutConfigActivityInfo { private final ActivityInfo mInfo; - private final PackageManager mPm; - - public ShortcutConfigActivityInfoVL(ActivityInfo info, PackageManager pm) { + public ShortcutConfigActivityInfoVL(ActivityInfo info) { super(new ComponentName(info.packageName, info.name), Process.myUserHandle()); mInfo = info; - mPm = pm; } @Override - public CharSequence getLabel() { - return mInfo.loadLabel(mPm); + public CharSequence getLabel(PackageManager pm) { + return mInfo.loadLabel(pm); } @Override @@ -135,7 +133,7 @@ public abstract class ShortcutConfigActivityInfo { } @Override - public CharSequence getLabel() { + public CharSequence getLabel(PackageManager pm) { return mInfo.getLabel(); } diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index 278eefdd92..daf7dc6d99 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -32,6 +32,7 @@ import android.graphics.Canvas; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.view.MotionEvent; @@ -46,6 +47,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompatVO; @@ -54,6 +56,8 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.InstantAppResolver; +import com.android.launcher3.util.LooperExecutor; +import com.android.launcher3.util.Provider; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; @@ -78,7 +82,6 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener // Widget request specific options. private LauncherAppWidgetHost mAppWidgetHost; private AppWidgetManagerCompat mAppWidgetManager; - private PendingAddWidgetInfo mPendingWidgetInfo; private int mPendingBindWidgetId; private Bundle mWidgetOptions; @@ -189,10 +192,9 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener private void setupShortcut() { PinShortcutRequestActivityInfo shortcutInfo = new PinShortcutRequestActivityInfo(mRequest, this); - WidgetItem item = new WidgetItem(shortcutInfo); mWidgetCell.getWidgetView().setTag(new PendingAddShortcutInfo(shortcutInfo)); - mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); - mWidgetCell.ensurePreview(); + applyWidgetItemAsync( + () -> new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager())); } private boolean setupWidget() { @@ -207,18 +209,32 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this); - mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo); - mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX); - mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY); - mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo); + PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(widgetInfo); + pendingInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX); + pendingInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY); + mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); + mWidgetCell.getWidgetView().setTag(pendingInfo); - WidgetItem item = new WidgetItem(widgetInfo, getPackageManager(), mIdp); - mWidgetCell.getWidgetView().setTag(mPendingWidgetInfo); - mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); - mWidgetCell.ensurePreview(); + applyWidgetItemAsync(() -> new WidgetItem(widgetInfo, mIdp, mApp.getIconCache())); return true; } + private void applyWidgetItemAsync(final Provider itemProvider) { + new AsyncTask() { + @Override + protected WidgetItem doInBackground(Void... voids) { + return itemProvider.get(); + } + + @Override + protected void onPostExecute(WidgetItem item) { + mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); + mWidgetCell.ensurePreview(); + } + }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper())); + // TODO: Create a worker looper executor and reuse that everywhere. + } + /** * Called when the cancel button is clicked. */ diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java index bd919bcbcb..64655cc056 100644 --- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java +++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.PinItemRequest; +import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Drawable; import android.os.Build; @@ -65,7 +66,7 @@ class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo { } @Override - public CharSequence getLabel() { + public CharSequence getLabel(PackageManager pm) { return mInfo.getShortLabel(); } diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java index f7e2be11b3..6433103f94 100644 --- a/src/com/android/launcher3/icons/BaseIconCache.java +++ b/src/com/android/launcher3/icons/BaseIconCache.java @@ -226,12 +226,12 @@ public class BaseIconCache { entry = new CacheEntry(); cachingLogic.loadIcon(mContext, this, object, entry); } - entry.title = cachingLogic.getLabel(object); + entry.title = cachingLogic.getLabel(object, mPackageManager); entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); mCache.put(key, entry); - ContentValues values = newContentValues(entry.icon, entry.color, - entry.title.toString(), componentName.getPackageName()); + ContentValues values = newContentValues(entry, entry.title.toString(), + componentName.getPackageName()); addIconToDB(values, componentName, info, userSerial); } @@ -280,16 +280,25 @@ public class BaseIconCache { * This method is not thread safe, it must be called from a synchronized method. */ protected CacheEntry cacheLocked( - @NonNull ComponentName componentName, - @NonNull Provider infoProvider, - @NonNull CachingLogic cachingLogic, - UserHandle user, boolean usePackageIcon, boolean useLowResIcon) { + @NonNull ComponentName componentName, @NonNull UserHandle user, + @NonNull Provider infoProvider, @NonNull CachingLogic cachingLogic, + boolean usePackageIcon, boolean useLowResIcon) { + return cacheLocked(componentName, user, infoProvider, cachingLogic, usePackageIcon, + useLowResIcon, true); + } + + protected CacheEntry cacheLocked( + @NonNull ComponentName componentName, @NonNull UserHandle user, + @NonNull Provider infoProvider, @NonNull CachingLogic cachingLogic, + boolean usePackageIcon, boolean useLowResIcon, boolean addToMemCache) { Preconditions.assertWorkerThread(); ComponentKey cacheKey = new ComponentKey(componentName, user); CacheEntry entry = mCache.get(cacheKey); if (entry == null || (entry.isLowRes() && !useLowResIcon)) { entry = new CacheEntry(); - mCache.put(cacheKey, entry); + if (addToMemCache) { + mCache.put(cacheKey, entry); + } // Check the DB first. T object = null; @@ -327,7 +336,7 @@ public class BaseIconCache { providerFetchedOnce = true; } if (object != null) { - entry.title = cachingLogic.getLabel(object); + entry.title = cachingLogic.getLabel(object, mPackageManager); entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); } } @@ -413,8 +422,8 @@ public class BaseIconCache { // Add the icon in the DB here, since these do not get written during // package updates. - ContentValues values = newContentValues(iconInfo.icon, entry.color, - entry.title.toString(), packageName); + ContentValues values = newContentValues( + iconInfo, entry.title.toString(), packageName); addIconToDB(values, cacheKey.componentName, info, mUserManager.getSerialNumberForUser(user)); @@ -515,11 +524,11 @@ public class BaseIconCache { } } - private ContentValues newContentValues(Bitmap icon, int iconColor, String label, - String packageName) { + private ContentValues newContentValues(BitmapInfo bitmapInfo, String label, String packageName) { ContentValues values = new ContentValues(); - values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); - values.put(IconDB.COLUMN_ICON_COLOR, iconColor); + values.put(IconDB.COLUMN_ICON, + bitmapInfo.isLowRes() ? null : Utilities.flattenBitmap(bitmapInfo.icon)); + values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color); values.put(IconDB.COLUMN_LABEL, label); values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName)); diff --git a/src/com/android/launcher3/icons/CachingLogic.java b/src/com/android/launcher3/icons/CachingLogic.java index 13fdc6a0b8..24186ef990 100644 --- a/src/com/android/launcher3/icons/CachingLogic.java +++ b/src/com/android/launcher3/icons/CachingLogic.java @@ -18,6 +18,7 @@ package com.android.launcher3.icons; import android.content.ComponentName; import android.content.Context; import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageManager; import android.os.UserHandle; public interface CachingLogic { @@ -26,7 +27,7 @@ public interface CachingLogic { UserHandle getUser(T object); - CharSequence getLabel(T object); + CharSequence getLabel(T object, PackageManager pm); void loadIcon(Context context, BaseIconCache cache, T object, BitmapInfo target); @@ -44,7 +45,7 @@ public interface CachingLogic { } @Override - public CharSequence getLabel(LauncherActivityInfo object) { + public CharSequence getLabel(LauncherActivityInfo object, PackageManager pm) { return object.getLabel(); } @@ -57,4 +58,30 @@ public interface CachingLogic { li.recycle(); } }; + + CachingLogic COMPONENT_WITH_LABEL = + new CachingLogic() { + + @Override + public ComponentName getComponent(ComponentWithLabel object) { + return object.getComponent(); + } + + @Override + public UserHandle getUser(ComponentWithLabel object) { + return object.getUser(); + } + + @Override + public CharSequence getLabel(ComponentWithLabel object, PackageManager pm) { + return object.getLabel(pm); + } + + @Override + public void loadIcon(Context context, BaseIconCache cache, + ComponentWithLabel object, BitmapInfo target) { + // Do not load icon. + target.icon = BitmapInfo.LOW_RES_ICON; + } + }; } diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java new file mode 100644 index 0000000000..2badb4c373 --- /dev/null +++ b/src/com/android/launcher3/icons/ComponentWithLabel.java @@ -0,0 +1,29 @@ +/* + * 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.icons; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.os.UserHandle; + +public interface ComponentWithLabel { + + ComponentName getComponent(); + + UserHandle getUser(); + + CharSequence getLabel(PackageManager pm); +} diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index eae6f0175a..434945550c 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -16,6 +16,7 @@ package com.android.launcher3.icons; +import static com.android.launcher3.icons.CachingLogic.COMPONENT_WITH_LABEL; import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO; import android.content.Context; @@ -114,8 +115,8 @@ public class IconCache extends BaseIconCache { */ public synchronized void updateTitleAndIcon(AppInfo application) { CacheEntry entry = cacheLocked(application.componentName, - Provider.of(null), LAUNCHER_ACTIVITY_INFO, - application.user, false, application.usingLowResIcon()); + application.user, Provider.of(null), LAUNCHER_ACTIVITY_INFO, + false, application.usingLowResIcon()); if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) { applyCacheEntry(entry, application); } @@ -148,6 +149,13 @@ public class IconCache extends BaseIconCache { } } + public synchronized String getTitleNoCache(ComponentWithLabel info) { + CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), Provider.of(info), + COMPONENT_WITH_LABEL, false /* usePackageIcon */, true /* useLowResIcon */, + false /* addToMemCache */); + return Utilities.trim(entry.title); + } + /** * Fill in {@param shortcutInfo} with the icon and label for {@param info} */ @@ -155,8 +163,9 @@ public class IconCache extends BaseIconCache { @NonNull ItemInfoWithIcon infoInOut, @NonNull Provider activityInfoProvider, boolean usePkgIcon, boolean useLowResIcon) { - CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider, - LAUNCHER_ACTIVITY_INFO, infoInOut.user, usePkgIcon, useLowResIcon); + CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user, + activityInfoProvider, + LAUNCHER_ACTIVITY_INFO, usePkgIcon, useLowResIcon); applyCacheEntry(entry, infoInOut); } diff --git a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java index 64e3fbf19b..8b3af01042 100644 --- a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java +++ b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java @@ -25,15 +25,17 @@ import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.SparseBooleanArray; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; import com.android.launcher3.icons.BaseIconCache.IconDB; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map.Entry; import java.util.Set; import java.util.Stack; @@ -44,12 +46,28 @@ public class IconCacheUpdateHandler { private static final String TAG = "IconCacheUpdateHandler"; + /** + * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}. + * This mode is used for the first run. + */ + private static final boolean MODE_SET_INVALID_ITEMS = true; + + /** + * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all + * subsequent runs, which essentially acts as set-union of all valid items. + */ + private static final boolean MODE_CLEAR_VALID_ITEMS = false; + private static final Object ICON_UPDATE_TOKEN = new Object(); private final HashMap mPkgInfoMap; private final BaseIconCache mIconCache; + private final HashMap> mPackagesToIgnore = new HashMap<>(); + private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray(); + private boolean mFilterMode = MODE_SET_INVALID_ITEMS; + IconCacheUpdateHandler(BaseIconCache cache) { mIconCache = cache; @@ -77,24 +95,43 @@ public class IconCacheUpdateHandler { * the DB and are updated. * @return The set of packages for which icons have updated. */ - public void updateIcons(List apps, CachingLogic cachingLogic) { - if (apps.isEmpty()) { - return; + public void updateIcons(List apps, CachingLogic cachingLogic, + OnUpdateCallback onUpdateCallback) { + // Filter the list per user + HashMap> userComponentMap = new HashMap<>(); + int count = apps.size(); + for (int i = 0; i < count; i++) { + T app = apps.get(i); + UserHandle userHandle = cachingLogic.getUser(app); + HashMap componentMap = userComponentMap.get(userHandle); + if (componentMap == null) { + componentMap = new HashMap<>(); + userComponentMap.put(userHandle, componentMap); + } + componentMap.put(cachingLogic.getComponent(app), app); } - UserHandle user = cachingLogic.getUser(apps.get(0)); + for (Entry> entry : userComponentMap.entrySet()) { + updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback); + } + + // From now on, clear every valid item from the global valid map. + mFilterMode = MODE_CLEAR_VALID_ITEMS; + } + + /** + * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in + * the DB and are updated. + * @return The set of packages for which icons have updated. + */ + private void updateIconsPerUser(UserHandle user, HashMap componentMap, + CachingLogic cachingLogic, OnUpdateCallback onUpdateCallback) { Set ignorePackages = mPackagesToIgnore.get(user); if (ignorePackages == null) { ignorePackages = Collections.emptySet(); } - long userSerial = mIconCache.mUserManager.getSerialNumberForUser(user); - HashMap componentMap = new HashMap<>(); - for (T app : apps) { - componentMap.put(cachingLogic.getComponent(app), app); - } - HashSet itemsToRemove = new HashSet<>(); Stack appsToUpdate = new Stack<>(); try (Cursor c = mIconCache.mIconDb.query( @@ -114,10 +151,15 @@ public class IconCacheUpdateHandler { String cn = c.getString(indexComponent); ComponentName component = ComponentName.unflattenFromString(cn); PackageInfo info = mPkgInfoMap.get(component.getPackageName()); + + int rowId = c.getInt(rowIndex); if (info == null) { if (!ignorePackages.contains(component.getPackageName())) { - mIconCache.remove(component, user); - itemsToRemove.add(c.getInt(rowIndex)); + + if (mFilterMode == MODE_SET_INVALID_ITEMS) { + mIconCache.remove(component, user); + mItemsToDelete.put(rowId, true); + } } continue; } @@ -132,11 +174,17 @@ public class IconCacheUpdateHandler { if (version == info.versionCode && updateTime == info.lastUpdateTime && TextUtils.equals(c.getString(systemStateIndex), mIconCache.mIconProvider.getIconSystemState(info.packageName))) { + + if (mFilterMode == MODE_CLEAR_VALID_ITEMS) { + mItemsToDelete.put(rowId, false); + } continue; } if (app == null) { - mIconCache.remove(component, user); - itemsToRemove.add(c.getInt(rowIndex)); + if (mFilterMode == MODE_SET_INVALID_ITEMS) { + mIconCache.remove(component, user); + mItemsToDelete.put(rowId, true); + } } else { appsToUpdate.add(app); } @@ -145,17 +193,28 @@ public class IconCacheUpdateHandler { Log.d(TAG, "Error reading icon cache", e); // Continue updating whatever we have read so far } - if (!itemsToRemove.isEmpty()) { - mIconCache.mIconDb.delete( - Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null); - } // Insert remaining apps. if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) { Stack appsToAdd = new Stack<>(); appsToAdd.addAll(componentMap.values()); - new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic) - .scheduleNext(); + new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic, + onUpdateCallback).scheduleNext(); + } + } + + public void finish() { + // Commit all deletes + ArrayList deleteIds = new ArrayList<>(); + int count = mItemsToDelete.size(); + for (int i = 0; i < count; i++) { + if (mItemsToDelete.valueAt(i)) { + deleteIds.add(mItemsToDelete.keyAt(i)); + } + } + if (!deleteIds.isEmpty()) { + mIconCache.mIconDb.delete( + Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, deleteIds), null); } } @@ -172,14 +231,17 @@ public class IconCacheUpdateHandler { private final Stack mAppsToUpdate; private final CachingLogic mCachingLogic; private final HashSet mUpdatedPackages = new HashSet<>(); + private final OnUpdateCallback mOnUpdateCallback; SerializedIconUpdateTask(long userSerial, UserHandle userHandle, - Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic) { + Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic, + OnUpdateCallback onUpdateCallback) { mUserHandle = userHandle; mUserSerial = userSerial; mAppsToAdd = appsToAdd; mAppsToUpdate = appsToUpdate; mCachingLogic = cachingLogic; + mOnUpdateCallback = onUpdateCallback; } @Override @@ -193,9 +255,8 @@ public class IconCacheUpdateHandler { mUpdatedPackages.add(pkg); if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) { - // No more app to update. Notify model. - LauncherAppState.getInstance(mIconCache.mContext).getModel() - .onPackageIconsUpdated(mUpdatedPackages, mUserHandle); + // No more app to update. Notify callback. + mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle); } // Let it run one more time. @@ -221,4 +282,9 @@ public class IconCacheUpdateHandler { SystemClock.uptimeMillis() + 1); } } + + public interface OnUpdateCallback { + + void onPackageIconsUpdated(HashSet updatedPackages, UserHandle user); + } } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index d6b7b0f53a..e0da6b172b 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -20,6 +20,7 @@ import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; +import static com.android.launcher3.icons.CachingLogic.COMPONENT_WITH_LABEL; import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO; import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; @@ -44,6 +45,7 @@ import android.util.MutableInt; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCacheUpdateHandler; import com.android.launcher3.icons.IconCache; import com.android.launcher3.InstallShortcutReceiver; @@ -184,7 +186,7 @@ public class LoaderTask implements Runnable { // second step TraceHelper.partitionSection(TAG, "step 2.1: loading all apps"); - List> activityListPerUser = loadAllApps(); + List allActivityList = loadAllApps(); TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps"); verifyNotStopped(); @@ -194,7 +196,8 @@ public class LoaderTask implements Runnable { TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache"); IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); setIgnorePackages(updateHandler); - updateIconCacheForApps(updateHandler, activityListPerUser); + updateHandler.updateIcons(allActivityList, LAUNCHER_ACTIVITY_INFO, + mApp.getModel()::onPackageIconsUpdated); // Take a break TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle"); @@ -216,12 +219,21 @@ public class LoaderTask implements Runnable { // fourth step TraceHelper.partitionSection(TAG, "step 4.1: loading widgets"); - mBgDataModel.widgetsModel.update(mApp, null); + List allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets"); mResults.bindWidgets(); + verifyNotStopped(); + TraceHelper.partitionSection(TAG, "step 4.3: Update icon cache"); + updateHandler.updateIcons(allWidgetsList, COMPONENT_WITH_LABEL, + mApp.getModel()::onWidgetLabelsUpdated); + + verifyNotStopped(); + TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update"); + updateHandler.finish(); + transaction.commit(); } catch (CancellationException e) { // Loader stopped, ignore @@ -799,17 +811,9 @@ public class LoaderTask implements Runnable { updateHandler.setPackagesToIgnore(Process.myUserHandle(), packagesToIgnore); } - private void updateIconCacheForApps(IconCacheUpdateHandler updateHandler, - List> activityListPerUser) { - int userCount = activityListPerUser.size(); - for (int i = 0; i < userCount; i++) { - updateHandler.updateIcons(activityListPerUser.get(i), LAUNCHER_ACTIVITY_INFO); - } - } - - private List> loadAllApps() { + private List loadAllApps() { final List profiles = mUserManager.getUserProfiles(); - List> activityListPerUser = new ArrayList<>(); + List allActivityList = new ArrayList<>(); // Clear the list of apps mBgAllAppsList.clear(); for (UserHandle user : profiles) { @@ -818,7 +822,7 @@ public class LoaderTask implements Runnable { // Fail if we don't have any apps // TODO: Fix this. Only fail for the current user. if (apps == null || apps.isEmpty()) { - return activityListPerUser; + return allActivityList; } boolean quietMode = mUserManager.isQuietModeEnabled(user); // Create the ApplicationInfos @@ -827,7 +831,7 @@ public class LoaderTask implements Runnable { // This builds the icon bitmaps. mBgAllAppsList.add(new AppInfo(app, user, quietMode), app); } - activityListPerUser.add(apps); + allActivityList.addAll(apps); } if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { @@ -840,7 +844,7 @@ public class LoaderTask implements Runnable { } mBgAllAppsList.added = new ArrayList<>(); - return activityListPerUser; + return allActivityList; } private void loadDeepShortcuts() { diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java index 1e96dec20c..e38529b3df 100644 --- a/src/com/android/launcher3/model/WidgetItem.java +++ b/src/com/android/launcher3/model/WidgetItem.java @@ -9,6 +9,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.ShortcutConfigActivityInfo; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.util.ComponentKey; import java.text.Collator; @@ -29,11 +30,11 @@ public class WidgetItem extends ComponentKey implements Comparable { public final String label; public final int spanX, spanY; - public WidgetItem(LauncherAppWidgetProviderInfo info, PackageManager pm, - InvariantDeviceProfile idp) { + public WidgetItem(LauncherAppWidgetProviderInfo info, + InvariantDeviceProfile idp, IconCache iconCache) { super(info.provider, info.getProfile()); - label = Utilities.trim(info.getLabel(pm)); + label = iconCache.getTitleNoCache(info); widgetInfo = info; activityInfo = null; @@ -41,9 +42,10 @@ public class WidgetItem extends ComponentKey implements Comparable { spanY = Math.min(info.spanY, idp.numRows); } - public WidgetItem(ShortcutConfigActivityInfo info) { + public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) { super(info.getComponent(), info.getUser()); - label = Utilities.trim(info.getLabel()); + label = info.isPersistable() ? iconCache.getTitleNoCache(info) : + Utilities.trim(info.getLabel(pm)); widgetInfo = null; activityInfo = info; spanX = spanY = 1; diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index 82f4fe1894..9a17ec65cb 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -11,6 +11,7 @@ import android.os.UserHandle; import android.util.Log; import com.android.launcher3.AppFilter; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; @@ -31,7 +32,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import androidx.annotation.Nullable; @@ -76,26 +80,32 @@ public class WidgetsModel { * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise * only widgets and shortcuts associated with the package/user are. */ - public void update(LauncherAppState app, @Nullable PackageUserKey packageUser) { + public List update(LauncherAppState app, @Nullable PackageUserKey packageUser) { Preconditions.assertWorkerThread(); Context context = app.getContext(); final ArrayList widgetsAndShortcuts = new ArrayList<>(); + List updatedItems = new ArrayList<>(); try { - PackageManager pm = context.getPackageManager(); InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); + PackageManager pm = app.getContext().getPackageManager(); // Widgets AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) { - widgetsAndShortcuts.add(new WidgetItem(LauncherAppWidgetProviderInfo - .fromProviderInfo(context, widgetInfo), pm, idp)); + LauncherAppWidgetProviderInfo launcherWidgetInfo = + LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo); + + widgetsAndShortcuts.add(new WidgetItem( + launcherWidgetInfo, idp, app.getIconCache())); + updatedItems.add(launcherWidgetInfo); } // Shortcuts for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context) .getCustomShortcutActivityList(packageUser)) { - widgetsAndShortcuts.add(new WidgetItem(info)); + widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm)); + updatedItems.add(info); } setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser); } catch (Exception e) { @@ -110,6 +120,7 @@ public class WidgetsModel { } app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser); + return updatedItems; } private synchronized void setWidgetsAndShortcuts(ArrayList rawWidgetsShortcuts, @@ -204,4 +215,26 @@ public class WidgetsModel { iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); } } + + public void onPackageIconsUpdated(Set packageNames, UserHandle user, + LauncherAppState app) { + for (Entry> entry : mWidgetsList.entrySet()) { + if (packageNames.contains(entry.getKey().packageName)) { + ArrayList items = entry.getValue(); + int count = items.size(); + for (int i = 0; i < count; i++) { + WidgetItem item = items.get(i); + if (item.user.equals(user)) { + if (item.activityInfo != null) { + items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(), + app.getContext().getPackageManager())); + } else { + items.set(i, new WidgetItem(item.widgetInfo, + app.getInvariantDeviceProfile(), app.getIconCache())); + } + } + } + } + } + } } \ No newline at end of file diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index 1e5643982f..8503547771 100644 --- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -205,9 +205,9 @@ public class BaseModelUpdateTaskTestCase { @Override protected CacheEntry cacheLocked( @NonNull ComponentName componentName, - @NonNull Provider infoProvider, + UserHandle user, @NonNull Provider infoProvider, @NonNull CachingLogic cachingLogic, - UserHandle user, boolean usePackageIcon, boolean useLowResIcon) { + boolean usePackageIcon, boolean useLowResIcon) { CacheEntry entry = mCache.get(new ComponentKey(componentName, user)); if (entry == null) { entry = new CacheEntry(); diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java index 307a53eefd..a31d8a6561 100644 --- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java +++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.Bitmap; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -129,11 +128,10 @@ public class WidgetsListAdapterTest { if (num <= 0) return result; MultiHashMap newMap = new MultiHashMap(); - PackageManager pm = mContext.getPackageManager(); AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) { WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo - .fromProviderInfo(mContext, widgetInfo), pm, mTestProfile); + .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache); PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName()); pInfo.title = pInfo.packageName; From be2307bbeab4b4c43a797ef5f26fc4ab21a20ed4 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Mon, 1 Oct 2018 10:02:45 -0700 Subject: [PATCH 17/20] Reduce falsing on swipe down for notification shade. b/116879058 Change-Id: If3dde635cdff09faf27dbab2cd022b9d246c7c2b --- .../uioverrides/StatusBarTouchController.java | 3 ++- .../launcher3/touch/TouchEventTranslator.java | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java index 4d567865e6..35f46cfc7f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java @@ -84,7 +84,8 @@ public class StatusBarTouchController implements TouchController { } if (action == ACTION_MOVE) { float dy = ev.getY() - mTranslator.getDownY(); - if (dy > mTouchSlop) { + float dx = ev.getX() - mTranslator.getDownX(); + if (dy > mTouchSlop && dy > Math.abs(dx)) { mTranslator.dispatchDownEvents(ev); mTranslator.processMotionEvent(ev); return true; diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java index 8a5c93264c..8333d32468 100644 --- a/src/com/android/launcher3/touch/TouchEventTranslator.java +++ b/src/com/android/launcher3/touch/TouchEventTranslator.java @@ -19,7 +19,6 @@ import android.graphics.PointF; import android.util.Log; import android.util.Pair; import android.util.SparseArray; -import android.util.SparseLongArray; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; @@ -37,13 +36,15 @@ public class TouchEventTranslator { private class DownState { long timeStamp; + float downX; float downY; - public DownState(long timeStamp, float downY) { + public DownState(long timeStamp, float downX, float downY) { this.timeStamp = timeStamp; + this.downX = downX; this.downY = downY; } }; - private final DownState ZERO = new DownState(0, 0f); + private final DownState ZERO = new DownState(0, 0f, 0f); private final Consumer mListener; @@ -65,12 +66,16 @@ public class TouchEventTranslator { mFingers.clear(); } + public float getDownX() { + return mDownEvents.get(0).downX; + } + public float getDownY() { return mDownEvents.get(0).downY; } public void setDownParameters(int idx, MotionEvent e) { - DownState ev = new DownState(e.getEventTime(), e.getY(idx)); + DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx)); mDownEvents.append(idx, ev); } From c78b6fa46506826cb13cee5c18c2dce7e8b689ee Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Mon, 1 Oct 2018 12:27:35 -0700 Subject: [PATCH 18/20] Disabling TestDragIcon tests The bug below is for re-enabling disabled tests. The problem is not reproable locally, at least after several hours of attempts. For now, we'd benefit more for enabling presubmits. Bug: 117106893 Test: will see on TAP Change-Id: Idd3f80accaf444eef29c151efea542924fcc713b --- tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index 050f046d00..d9fef8171f 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -33,6 +33,7 @@ import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ShellCommandRule; import com.android.launcher3.widget.WidgetCell; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,12 +48,14 @@ public class AddWidgetTest extends AbstractLauncherUiTest { @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind(); @Test + @Ignore public void testDragIcon_portrait() throws Throwable { lockRotation(true); performTest(); } @Test + @Ignore public void testDragIcon_landscape() throws Throwable { lockRotation(false); performTest(); From a2dc3c72119c734550caf053fc559ac84987df46 Mon Sep 17 00:00:00 2001 From: Vadim Tryshev Date: Mon, 1 Oct 2018 17:28:53 -0700 Subject: [PATCH 19/20] Disabling ShortcutsToHomeTest tests The bug below is for re-enabling disabled tests. For now, we'd benefit more from enabling presubmits. Bug: 117106893 Test: will see on TAP Change-Id: I322deb9a64423587b329004f6cab4da1ff4f197d --- tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java index ff1dcf3d6c..793bd8f46b 100644 --- a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java +++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java @@ -17,6 +17,7 @@ import com.android.launcher3.util.Condition; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ShellCommandRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,12 +32,14 @@ public class ShortcutsToHomeTest extends AbstractLauncherUiTest { @Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher(); @Test + @Ignore public void testDragIcon_portrait() throws Throwable { lockRotation(true); performTest(); } @Test + @Ignore public void testDragIcon_landscape() throws Throwable { lockRotation(false); performTest(); From f8e04b64645034b2abc748f96508e2680a2c566b Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 2 Oct 2018 16:19:29 -0700 Subject: [PATCH 20/20] Break out of quickscrub if task fails to launch - Return the user to their previous state if quickscrub fails to launch the new task (ie. if it finishes itself mid-launch). Bug: 117163033 Change-Id: If03cf0431be40d9b81dfcc5dffcb2bf4844bbbd2 --- .../android/quickstep/QuickScrubController.java | 15 +++++++++++++++ .../com/android/quickstep/views/RecentsView.java | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java index 943225647c..34207670e8 100644 --- a/quickstep/src/com/android/quickstep/QuickScrubController.java +++ b/quickstep/src/com/android/quickstep/QuickScrubController.java @@ -67,6 +67,7 @@ public class QuickScrubController implements OnAlarmListener { private int mQuickScrubSection; private boolean mStartedFromHome; private boolean mFinishedTransitionToQuickScrub; + private int mLaunchingTaskId; private Runnable mOnFinishedTransitionToQuickScrubRunnable; private ActivityControlHelper mActivityControlHelper; private TouchInteractionLog mTouchInteractionLog; @@ -105,6 +106,7 @@ public class QuickScrubController implements OnAlarmListener { if (taskView != null) { mWaitingForTaskLaunch = true; mTouchInteractionLog.launchTaskStart(); + mLaunchingTaskId = taskView.getTask().key.id; taskView.launchTask(true, (result) -> { mTouchInteractionLog.launchTaskEnd(result); if (!result) { @@ -146,6 +148,7 @@ public class QuickScrubController implements OnAlarmListener { mActivityControlHelper = null; mOnFinishedTransitionToQuickScrubRunnable = null; mRecentsView.setNextPageSwitchRunnable(null); + mLaunchingTaskId = 0; } /** @@ -211,6 +214,18 @@ public class QuickScrubController implements OnAlarmListener { } } + public void onTaskRemoved(int taskId) { + if (mLaunchingTaskId == taskId) { + // The task has been removed mid-launch, break out of quickscrub and return the user + // to where they were before (and notify the launch failed) + TaskView taskView = mRecentsView.getTaskView(taskId); + if (taskView != null) { + taskView.notifyTaskLaunchFailed(TAG); + } + breakOutOfQuickScrub(); + } + } + public void snapToNextTaskIfAvailable() { if (mInQuickScrub && mRecentsView.getChildCount() > 0) { int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index f3dec9a814..8ad3cee6f2 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -183,6 +183,10 @@ public abstract class RecentsView extends PagedView impl if (!mHandleTaskStackChanges) { return; } + + // Notify the quick scrub controller that a particular task has been removed + mQuickScrubController.onTaskRemoved(taskId); + BackgroundExecutor.get().submit(() -> { TaskView taskView = getTaskView(taskId); if (taskView == null) {