From fbfccf9e2d0d9e782c6fe8c6ccb1bc34fc4d4e83 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Wed, 28 Oct 2020 22:33:51 -0700 Subject: [PATCH 01/27] Handle the animation when restarting apps Fixes: 170338921 Test: manual Change-Id: I5a5722cb62fb53a68d140621ad79ed97f4d98571 --- .../android/quickstep/AbsSwipeUpHandler.java | 33 ++++++++++++++----- .../quickstep/RecentsAnimationTargets.java | 9 +++++ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 6cf3c7ade9..43c088b585 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -1020,7 +1020,7 @@ public abstract class AbsSwipeUpHandler, Q extends protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration); - private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() { + private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() { @Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { @@ -1459,13 +1459,33 @@ public abstract class AbsSwipeUpHandler, Q extends protected abstract void finishRecentsControllerToHome(Runnable callback); + private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() { + @Override + public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, + boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { + if (mRecentsAnimationTargets.hasTask(task.taskId)) { + launchOtherTaskInLiveTileMode(task.taskId, mRecentsAnimationTargets.apps); + } + ActivityManagerWrapper.getInstance().unregisterTaskStackListener( + mLiveTileRestartListener); + } + }; + private void setupLauncherUiAfterSwipeUpToRecentsAnimation() { endLauncherTransitionController(); mActivityInterface.onSwipeUpToRecentsComplete(); mRecentsView.onSwipeUpAnimationSuccess(); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler( - this::launchOtherTaskInLiveTileMode); + appearedTaskTarget -> { + RemoteAnimationTargetCompat[] apps = Arrays.copyOf( + mRecentsAnimationTargets.apps, + mRecentsAnimationTargets.apps.length + 1); + apps[apps.length - 1] = appearedTaskTarget; + launchOtherTaskInLiveTileMode(appearedTaskTarget.taskId, apps); + }); + ActivityManagerWrapper.getInstance().registerTaskStackListener( + mLiveTileRestartListener); } SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG); @@ -1473,17 +1493,12 @@ public abstract class AbsSwipeUpHandler, Q extends reset(); } - private void launchOtherTaskInLiveTileMode(RemoteAnimationTargetCompat appearedTaskTarget) { - TaskView taskView = mRecentsView.getTaskView(appearedTaskTarget.taskId); + private void launchOtherTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) { + TaskView taskView = mRecentsView.getTaskView(taskId); if (taskView == null) { return; } - RemoteAnimationTargetCompat[] apps = Arrays.copyOf( - mRecentsAnimationTargets.apps, - mRecentsAnimationTargets.apps.length + 1); - apps[apps.length - 1] = appearedTaskTarget; - AnimatorSet anim = new AnimatorSet(); TaskViewUtils.composeRecentsLaunchAnimator( anim, taskView, apps, diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java index 718c5baa2c..da0a66431e 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java @@ -41,4 +41,13 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets { public boolean hasTargets() { return unfilteredApps.length != 0; } + + public boolean hasTask(int taskId) { + for (RemoteAnimationTargetCompat target : unfilteredApps) { + if (target.taskId == taskId) { + return true; + } + } + return false; + } } From 6dcba895816c3fa71cda7ec01b7c3628f6e52c49 Mon Sep 17 00:00:00 2001 From: Sreyas Date: Fri, 16 Oct 2020 23:40:09 -0700 Subject: [PATCH 02/27] Refactoring DigitalWellbeing Banner to not use footers. Removing use of footers as they are no longer necessary. Refactoring DWB code to a separate class to be cleaner. Also fixes bug with DWB animating in twice. Bug: 171317883 Bug: 171317874 Tested:Manual Change-Id: I78c4e541e7afa76301c3afc373ab3fa251a2c3b0 --- .../views/DigitalWellBeingToast.java | 82 ++++++++-- .../com/android/quickstep/views/TaskView.java | 154 +----------------- 2 files changed, 77 insertions(+), 159 deletions(-) diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java index 95bde801bf..6630aede3d 100644 --- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -17,6 +17,8 @@ package com.android.quickstep.views; import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS; +import static android.view.Gravity.BOTTOM; +import static android.view.Gravity.CENTER_HORIZONTAL; import static com.android.launcher3.Utilities.prefixTextWithIcon; import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; @@ -27,6 +29,7 @@ import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.AppUsageLimit; +import android.graphics.Outline; import android.icu.text.MeasureFormat; import android.icu.text.MeasureFormat.FormatWidth; import android.icu.util.Measure; @@ -35,6 +38,9 @@ import android.os.Build; import android.os.UserHandle; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.StringRes; @@ -61,6 +67,10 @@ public final class DigitalWellBeingToast { private Task mTask; private boolean mHasLimit; private long mAppRemainingTimeMs; + private View mBanner; + private ViewOutlineProvider mOldBannerOutlineProvider; + private float mBannerOffsetPercentage; + private float mBannerAlpha = 1f; public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) { mActivity = activity; @@ -68,18 +78,10 @@ public final class DigitalWellBeingToast { mLauncherApps = activity.getSystemService(LauncherApps.class); } - private void setTaskFooter(View view) { - View oldFooter = mTaskView.setFooter(TaskView.INDEX_DIGITAL_WELLBEING_TOAST, view); - if (oldFooter != null) { - oldFooter.setOnClickListener(null); - mActivity.getViewCache().recycleView(R.layout.digital_wellbeing_toast, oldFooter); - } - } - private void setNoLimit() { mHasLimit = false; mTaskView.setContentDescription(mTask.titleDescription); - setTaskFooter(null); + replaceBanner(null); mAppRemainingTimeMs = 0; } @@ -90,7 +92,7 @@ public final class DigitalWellBeingToast { mActivity, mTaskView); toast.setText(prefixTextWithIcon(mActivity, R.drawable.ic_hourglass_top, getText())); toast.setOnClickListener(this::openAppUsageSettings); - setTaskFooter(toast); + replaceBanner(toast); mTaskView.setContentDescription( getContentDescriptionForTask(mTask, appUsageLimitTimeMs, appRemainingTimeMs)); @@ -233,4 +235,64 @@ public final class DigitalWellBeingToast { getText(appRemainingTimeMs)) : task.titleDescription; } + + private void replaceBanner(View view) { + resetOldBanner(); + setBanner(view); + } + + private void resetOldBanner() { + if (mBanner != null) { + mBanner.setOutlineProvider(mOldBannerOutlineProvider); + mTaskView.removeView(mBanner); + mBanner.setOnClickListener(null); + mActivity.getViewCache().recycleView(R.layout.digital_wellbeing_toast, mBanner); + } + } + + private void setBanner(View view) { + mBanner = view; + if (view != null) { + setupAndAddBanner(); + setBannerOutline(); + } + } + + private void setupAndAddBanner() { + FrameLayout.LayoutParams layoutParams = + (FrameLayout.LayoutParams) mBanner.getLayoutParams(); + layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL; + layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams) + mTaskView.getThumbnail().getLayoutParams()).bottomMargin; + mBanner.setTranslationY(mBannerOffsetPercentage * mBanner.getHeight()); + mBanner.setAlpha(mBannerAlpha); + mTaskView.addView(mBanner); + } + + private void setBannerOutline() { + mOldBannerOutlineProvider = mBanner.getOutlineProvider(); + mBanner.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + mOldBannerOutlineProvider.getOutline(view, outline); + outline.offset(0, -Math.round(view.getTranslationY())); + } + }); + mBanner.setClipToOutline(true); + } + + void updateBannerOffset(float offsetPercentage) { + if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) { + mBannerOffsetPercentage = offsetPercentage; + mBanner.setTranslationY(offsetPercentage * mBanner.getHeight()); + mBanner.invalidateOutline(); + } + } + + void updateBannerAlpha(float alpha) { + if (mBanner != null && mBannerAlpha != alpha) { + mBannerAlpha = alpha; + mBanner.setAlpha(alpha); + } + } } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 5154018f6c..23da7835c8 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -42,7 +42,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.animation.ValueAnimator; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; @@ -194,12 +193,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private boolean mEndQuickswitchCuj; - // Order in which the footers appear. Lower order appear below higher order. - public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0; - private final FooterWrapper[] mFooters = new FooterWrapper[2]; - private float mFooterVerticalOffset = 0; - private float mFooterAlpha = 1; - private int mStackHeight; private View mContextualChipWrapper; private View mContextualChip; private final float[] mIconCenterCoords = new float[2]; @@ -343,7 +336,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { if (mContextualChipWrapper != null) { mContextualChipWrapper.setAlpha(comp(modalness)); } - updateFooterVerticalOffset(mFooterVerticalOffset); + mDigitalWellBeingToast.updateBannerOffset(modalness); } public TaskMenuView getMenuView() { @@ -567,7 +560,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mContextualChip.setScaleX(scale); mContextualChip.setScaleY(scale); } - updateFooterVerticalOffset(1.0f - scale); + mDigitalWellBeingToast.updateBannerOffset(1f - scale); } public void setIconScaleAnimStartProgress(float startProgress) { @@ -639,12 +632,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA); setCurveScale(curveScaleForCurveInterpolation); - mFooterAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f); - for (FooterWrapper footer : mFooters) { - if (footer != null) { - footer.mView.setAlpha(mFooterAlpha); - } - } + float dwbBannerAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, + 0f, 1f); + mDigitalWellBeingToast.updateBannerAlpha(dwbBannerAlpha); if (mMenuView != null) { PagedOrientationHandler pagedOrientationHandler = getPagedOrientationHandler(); @@ -656,57 +646,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } } - /** - * Sets the footer at the specific index and returns the previously set footer. - */ - public View setFooter(int index, View view) { - View oldFooter = null; - - // If the footer are is already collapsed, do not animate entry - boolean shouldAnimateEntry = mFooterVerticalOffset <= 0; - - if (mFooters[index] != null) { - oldFooter = mFooters[index].mView; - mFooters[index].release(); - removeView(oldFooter); - - // If we are replacing an existing footer, do not animate entry - shouldAnimateEntry = false; - } - if (view != null) { - int indexToAdd = getChildCount(); - for (int i = index - 1; i >= 0; i--) { - if (mFooters[i] != null) { - indexToAdd = indexOfChild(mFooters[i].mView); - break; - } - } - - addView(view, indexToAdd); - LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); - layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL; - layoutParams.bottomMargin = - ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin; - view.setAlpha(mFooterAlpha); - mFooters[index] = new FooterWrapper(view); - if (shouldAnimateEntry) { - mFooters[index].animateEntry(); - } - } else { - mFooters[index] = null; - } - - mStackHeight = 0; - for (FooterWrapper footer : mFooters) { - if (footer != null) { - footer.setVerticalShift(mStackHeight); - mStackHeight += footer.mExpectedHeight; - } - } - - return oldFooter; - } - /** * Sets the contextual chip. * @@ -780,24 +719,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight()); setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT); } - - mStackHeight = 0; - for (FooterWrapper footer : mFooters) { - if (footer != null) { - mStackHeight += footer.mView.getHeight(); - } - } - updateFooterVerticalOffset(0); - } - - private void updateFooterVerticalOffset(float offset) { - mFooterVerticalOffset = offset; - - for (FooterWrapper footer : mFooters) { - if (footer != null) { - footer.updateFooterOffset(); - } - } } public static float getCurveScaleForInterpolation(float linearInterpolation) { @@ -860,71 +781,6 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } } - private class FooterWrapper extends ViewOutlineProvider { - - final View mView; - final ViewOutlineProvider mOldOutlineProvider; - final ViewOutlineProvider mDelegate; - - final int mExpectedHeight; - final int mOldPaddingBottom; - - int mAnimationOffset = 0; - int mEntryAnimationOffset = 0; - - public FooterWrapper(View view) { - mView = view; - mOldOutlineProvider = view.getOutlineProvider(); - mDelegate = mOldOutlineProvider == null - ? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider; - - mExpectedHeight = getExpectedViewHeight(view); - mOldPaddingBottom = view.getPaddingBottom(); - - if (mOldOutlineProvider != null) { - view.setOutlineProvider(this); - view.setClipToOutline(true); - } - } - - public void setVerticalShift(int shift) { - mView.setPadding(mView.getPaddingLeft(), mView.getPaddingTop(), - mView.getPaddingRight(), mOldPaddingBottom + shift); - } - - @Override - public void getOutline(View view, Outline outline) { - mDelegate.getOutline(view, outline); - outline.offset(0, -mAnimationOffset - mEntryAnimationOffset); - } - - void updateFooterOffset() { - float offset = Utilities.or(mFooterVerticalOffset, mModalness); - mAnimationOffset = Math.round(mStackHeight * offset); - mView.setTranslationY(mAnimationOffset + mEntryAnimationOffset - + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom - + mCurrentFullscreenParams.mCurrentDrawnInsets.top); - mView.invalidateOutline(); - } - - void release() { - mView.setOutlineProvider(mOldOutlineProvider); - setVerticalShift(0); - } - - void animateEntry() { - ValueAnimator animator = ValueAnimator.ofFloat(0, 1); - animator.addUpdateListener(anim -> { - float factor = 1 - anim.getAnimatedFraction(); - int totalShift = mExpectedHeight + mView.getPaddingBottom() - mOldPaddingBottom; - mEntryAnimationOffset = Math.round(factor * totalShift); - updateFooterOffset(); - }); - animator.setDuration(100); - animator.start(); - } - } - private int getExpectedViewHeight(View view) { int expectedHeight; int h = view.getLayoutParams().height; From 8cadc1281f4ef6a64d0bca9c7a9a0415e2e024af Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 4 Nov 2020 14:29:18 -0800 Subject: [PATCH 03/27] Using UI_HELPER_THREAD for various activity manager interactions Bug: 170648272 Change-Id: Iec36389be5d747c5dc4d2bb2a8138758d48efa53 --- .../com/android/launcher3/BaseQuickstepLauncher.java | 11 ++++++----- .../launcher3/uioverrides/QuickstepLauncher.java | 5 ++--- .../touchcontrollers/NavBarToHomeTouchController.java | 5 ++--- .../touchcontrollers/QuickSwitchTouchController.java | 5 ++--- .../com/android/quickstep/OverviewCommandHelper.java | 6 ++---- quickstep/src/com/android/quickstep/TaskUtils.java | 11 +++++++++++ .../inputconsumers/OtherActivityInputConsumer.java | 4 ++-- .../inputconsumers/OverviewInputConsumer.java | 5 ++--- .../src/com/android/quickstep/views/RecentsView.java | 9 ++++----- 9 files changed, 33 insertions(+), 28 deletions(-) diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 44d43c6784..456727c069 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -19,11 +19,10 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY; -import com.android.systemui.shared.system.InteractionJankMonitorWrapper; - import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.ActivityOptions; @@ -48,12 +47,14 @@ import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TaskUtils; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ActivityOptionsCompat; +import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.stream.Stream; @@ -123,7 +124,8 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override protected void onUiChangedWhileSleeping() { // Remove the snapshot because the content view may have obvious changes. - ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this); + UI_HELPER_EXECUTOR.execute( + () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this)); } @Override @@ -203,8 +205,7 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override protected void closeOpenViews(boolean animate) { super.closeOpenViews(animate); - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index e808f8cb78..e02c0c35a6 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -74,10 +74,10 @@ import com.android.launcher3.util.UiThreadHelper.AsyncCommand; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TaskUtils; import com.android.quickstep.util.QuickstepOnboardingPrefs; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.system.ActivityManagerWrapper; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -175,8 +175,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override protected void showAllAppsFromIntent(boolean alreadyOnHome) { - ActivityManagerWrapper.getInstance().closeSystemWindows( - CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); super.showAllAppsFromIntent(alreadyOnHome); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index d210bc6851..5181a86960 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -49,11 +49,11 @@ import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.util.TouchController; +import com.android.quickstep.TaskUtils; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.OverviewToHomeAnim; import com.android.quickstep.views.RecentsView; -import com.android.systemui.shared.system.ActivityManagerWrapper; /** * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps. @@ -233,8 +233,7 @@ public class NavBarToHomeTouchController implements TouchController, AbstractFloatingView.closeAllOpenViews(mLauncher); // TODO: add to WW log } - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } else { // Quickly return to the state we came from (we didn't move far). ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index b8c2030852..d39b6f5d97 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -45,9 +45,9 @@ import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SystemUiProxy; +import com.android.quickstep.TaskUtils; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.system.ActivityManagerWrapper; /** * Handles quick switching to a recent task from the home screen. @@ -92,8 +92,7 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll public void onDragStart(boolean start, float startDisplacement) { super.onDragStart(start, startDisplacement); mStartContainerType = LAUNCHER_STATE_BACKGROUND; - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } @Override diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 9f7871a29c..1badfbcb53 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -65,16 +65,14 @@ public class OverviewCommandHelper { return; } - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); MAIN_EXECUTOR.execute(new RecentsActivityCommand<>()); } @BinderThread public void onOverviewShown(boolean triggeredFromAltTab) { if (triggeredFromAltTab) { - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab)); } diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java index 04b488da9a..c9db1533ff 100644 --- a/quickstep/src/com/android/quickstep/TaskUtils.java +++ b/quickstep/src/com/android/quickstep/TaskUtils.java @@ -16,6 +16,8 @@ package com.android.quickstep; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -27,6 +29,7 @@ import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageManagerHelper; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.List; @@ -86,4 +89,12 @@ public final class TaskUtils { } return false; } + + /** + * Requests that the system close any open system windows (including other SystemUI). + */ + public static void closeSystemWindowsAsync(String reason) { + UI_HELPER_EXECUTOR.execute( + () -> ActivityManagerWrapper.getInstance().closeSystemWindows(reason)); + } } diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 82f489f680..f2f9fb7af6 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -63,6 +63,7 @@ import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.RotationTouchHelper; import com.android.quickstep.TaskAnimationManager; +import com.android.quickstep.TaskUtils; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.CachedEventDispatcher; import com.android.quickstep.util.MotionPauseDetector; @@ -362,8 +363,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mInputEventReceiver.setBatchingEnabled(true); mActivityInterface.closeOverlay(); - ActivityManagerWrapper.getInstance().closeSystemWindows( - CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); // Notify the handler that the gesture has actually started mInteractionHandler.onGestureStarted(isLikelyToStartNewTask); diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java index 32b1c580ba..f9c52f9f9b 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java @@ -32,8 +32,8 @@ import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; +import com.android.quickstep.TaskUtils; import com.android.quickstep.util.ActiveGestureLog; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; /** @@ -91,8 +91,7 @@ public class OverviewInputConsumer> mTargetHandledTouch = true; if (!mStartingInActivityBounds) { mActivityInterface.closeOverlay(); - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); ActiveGestureLog.INSTANCE.addLog("startQuickstep"); } if (mInputMonitor != null) { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 264a182dc2..f35eb1fa5d 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -107,7 +107,6 @@ import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties; -import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.OverScroller; @@ -124,7 +123,6 @@ import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskThumbnailCache; -import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; @@ -1468,8 +1466,8 @@ public abstract class RecentsView extends PagedView private void removeTask(TaskView taskView, int index, EndState endState) { if (taskView.getTask() != null) { - ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id); - ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key); + UI_HELPER_EXECUTOR.execute(() -> + ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id)); mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo()) .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); } @@ -1618,7 +1616,8 @@ public abstract class RecentsView extends PagedView mPendingAnimation.addEndListener((endState) -> { if (endState.isSuccess) { // Remove all the task views now - ActivityManagerWrapper.getInstance().removeAllRecentTasks(); + UI_HELPER_EXECUTOR.execute( + ActivityManagerWrapper.getInstance()::removeAllRecentTasks); removeTasksViewsAndClearAllButton(); startHome(); } From d91bad956f10499265b022c407c6b1d28592dac6 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Thu, 5 Nov 2020 11:14:14 -0600 Subject: [PATCH 04/27] Add more logs for WorkTabTest flake investigation Bug: 159671700 Change-Id: I92f64bafa756a65d3e63514474028b82b059e357 --- tests/src/com/android/launcher3/ui/WorkTabTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java index 8d594de926..df0770d931 100644 --- a/tests/src/com/android/launcher3/ui/WorkTabTest.java +++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java @@ -65,12 +65,13 @@ public class WorkTabTest extends AbstractLauncherUiTest { String[] tokens = output.split("\\s+"); mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]); - + Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Created new user uid" + mProfileUserId); mDevice.executeShellCommand("am start-user " + mProfileUserId); } @After public void removeWorkProfile() throws Exception { + Log.d(TestProtocol.WORK_PROFILE_REMOVED, "(teardown) removing uid" + mProfileUserId); mDevice.executeShellCommand("pm remove-user " + mProfileUserId); } From 6e5efb0929fac4f914efa2ceaaa6a7ef2fa4cf71 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 2 Nov 2020 11:31:59 -0600 Subject: [PATCH 05/27] Consolidate Hero search result with SearchResultIconRow With this, we can now show app title and support drag/drop for shortcut results. Bug: 172245107 preview: https://drive.google.com/file/d/1A4eKKTDPht-MDbfA2VFI3OuAO36fc3AS/view?usp=sharing Change-Id: Icf94a2d23b44bfe5527aea71e27178906e5deb3e --- res/layout/search_result_hero_app.xml | 74 ------ res/layout/search_result_icon_row.xml | 67 ++++- res/layout/search_result_suggest.xml | 9 +- res/values/attrs.xml | 14 +- res/values/dimens.xml | 5 + .../launcher3/allapps/AllAppsGridAdapter.java | 7 - .../launcher3/touch/ItemClickHandler.java | 2 + .../launcher3/views/HeroSearchResultView.java | 209 --------------- .../launcher3/views/SearchResultIcon.java | 134 +++++++++- .../launcher3/views/SearchResultIconRow.java | 249 ++++++++---------- .../views/SearchResultSuggestion.java | 62 +++++ .../views/ThumbnailSearchResultView.java | 5 +- 12 files changed, 378 insertions(+), 459 deletions(-) delete mode 100644 res/layout/search_result_hero_app.xml delete mode 100644 src/com/android/launcher3/views/HeroSearchResultView.java create mode 100644 src/com/android/launcher3/views/SearchResultSuggestion.java diff --git a/res/layout/search_result_hero_app.xml b/res/layout/search_result_hero_app.xml deleted file mode 100644 index bd0e42b6a9..0000000000 --- a/res/layout/search_result_hero_app.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/search_result_icon_row.xml b/res/layout/search_result_icon_row.xml index ef3c8b2857..b51896e8a7 100644 --- a/res/layout/search_result_icon_row.xml +++ b/res/layout/search_result_icon_row.xml @@ -1,4 +1,4 @@ - + android:padding="@dimen/dynamic_grid_edge_margin"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/search_result_suggest.xml b/res/layout/search_result_suggest.xml index 1d8c8039e6..01e25d5893 100644 --- a/res/layout/search_result_suggest.xml +++ b/res/layout/search_result_suggest.xml @@ -12,7 +12,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"> - \ No newline at end of file + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index c3ad1eb823..96c30b5f03 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -1,5 +1,4 @@ - - 24dp + + 16sp + 15sp + 12sp + diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index f7731915e3..7018f20a55 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -75,8 +75,6 @@ public class AllAppsGridAdapter extends public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5; - public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6; - public static final int VIEW_TYPE_SEARCH_ROW_WITH_BUTTON = 1 << 7; public static final int VIEW_TYPE_SEARCH_ROW = 1 << 8; @@ -178,7 +176,6 @@ public class AllAppsGridAdapter extends boolean isCountedForAccessibility() { return viewType == VIEW_TYPE_ICON - || viewType == VIEW_TYPE_SEARCH_HERO_APP || viewType == VIEW_TYPE_SEARCH_ROW_WITH_BUTTON || viewType == VIEW_TYPE_SEARCH_SLICE || viewType == VIEW_TYPE_SEARCH_ROW @@ -411,9 +408,6 @@ public class AllAppsGridAdapter extends case VIEW_TYPE_SEARCH_CORPUS_TITLE: return new ViewHolder( mLayoutInflater.inflate(R.layout.search_section_title, parent, false)); - case VIEW_TYPE_SEARCH_HERO_APP: - return new ViewHolder(mLayoutInflater.inflate( - R.layout.search_result_hero_app, parent, false)); case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON: return new ViewHolder(mLayoutInflater.inflate( R.layout.search_result_play_item, parent, false)); @@ -478,7 +472,6 @@ public class AllAppsGridAdapter extends break; case VIEW_TYPE_SEARCH_CORPUS_TITLE: case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON: - case VIEW_TYPE_SEARCH_HERO_APP: case VIEW_TYPE_SEARCH_ROW: case VIEW_TYPE_SEARCH_ICON: case VIEW_TYPE_SEARCH_ICON_ROW: diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index d56391da3d..9b9cb0a61b 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -96,6 +96,8 @@ public class ItemClickHandler { if (v instanceof PendingAppWidgetHostView) { onClickPendingWidget((PendingAppWidgetHostView) v, launcher); } + } else if (tag instanceof RemoteActionItemInfo) { + onClickRemoteAction(launcher, (RemoteActionItemInfo) tag); } } diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java deleted file mode 100644 index a098df9f31..0000000000 --- a/src/com/android/launcher3/views/HeroSearchResultView.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.views; - -import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; - -import android.content.Context; -import android.content.pm.ShortcutInfo; -import android.graphics.Point; -import android.util.AttributeSet; -import android.util.Pair; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; - -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.DragSource; -import com.android.launcher3.DropTarget; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.R; -import com.android.launcher3.allapps.AllAppsStore; -import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler; -import com.android.launcher3.allapps.search.SearchEventTracker; -import com.android.launcher3.dragndrop.DragOptions; -import com.android.launcher3.dragndrop.DraggableView; -import com.android.launcher3.graphics.DragPreviewProvider; -import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; -import com.android.launcher3.touch.ItemLongClickListener; -import com.android.launcher3.util.ComponentKey; -import com.android.systemui.plugins.shared.SearchTarget; -import com.android.systemui.plugins.shared.SearchTargetEvent; - -import java.util.ArrayList; -import java.util.List; - -/** - * A view representing a high confidence app search result that includes shortcuts - * TODO (sfufa@) consolidate this with SearchResultIconRow - */ -public class HeroSearchResultView extends LinearLayout implements DragSource, SearchTargetHandler { - - public static final String TARGET_TYPE_HERO_APP = "hero_app"; - - public static final int MAX_SHORTCUTS_COUNT = 2; - - private SearchTarget mSearchTarget; - private BubbleTextView mBubbleTextView; - private View mIconView; - private BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2]; - - - public HeroSearchResultView(Context context) { - super(context); - } - - public HeroSearchResultView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public HeroSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - Launcher launcher = Launcher.getLauncher(getContext()); - DeviceProfile grid = launcher.getDeviceProfile(); - mIconView = findViewById(R.id.icon); - ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams(); - iconParams.height = grid.allAppsIconSizePx; - iconParams.width = grid.allAppsIconSizePx; - - - mBubbleTextView = findViewById(R.id.bubble_text); - mBubbleTextView.setOnClickListener(view -> { - handleSelection(SearchTargetEvent.SELECT); - launcher.getItemOnClickListener().onClick(view); - }); - mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this)); - - - mDeepShortcutTextViews[0] = findViewById(R.id.shortcut_0); - mDeepShortcutTextViews[1] = findViewById(R.id.shortcut_1); - for (BubbleTextView bubbleTextView : mDeepShortcutTextViews) { - bubbleTextView.setLayoutParams( - new LinearLayout.LayoutParams(grid.allAppsIconSizePx, - grid.allAppsIconSizePx)); - bubbleTextView.setOnClickListener(view -> { - WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag(); - SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget, - SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build(); - SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event); - launcher.getItemOnClickListener().onClick(view); - }); - } - } - - @Override - public void applySearchTarget(SearchTarget searchTarget) { - mSearchTarget = searchTarget; - AllAppsStore apps = Launcher.getLauncher(getContext()).getAppsView().getAppsStore(); - AppInfo appInfo = apps.getApp(new ComponentKey(searchTarget.getComponentName(), - searchTarget.getUserHandle())); - List infos = mSearchTarget.getShortcutInfos(); - - ArrayList> shortcuts = new ArrayList<>(); - for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) { - ShortcutInfo shortcutInfo = infos.get(i); - ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext()); - si.rank = i; - shortcuts.add(new Pair<>(shortcutInfo, si)); - } - - mBubbleTextView.applyFromApplicationInfo(appInfo); - mIconView.setBackground(mBubbleTextView.getIcon()); - mIconView.setTag(appInfo); - LauncherAppState appState = LauncherAppState.getInstance(getContext()); - for (int i = 0; i < mDeepShortcutTextViews.length; i++) { - BubbleTextView shortcutView = mDeepShortcutTextViews[i]; - mDeepShortcutTextViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE); - if (i < shortcuts.size()) { - Pair p = shortcuts.get(i); - //apply ItemInfo and prepare view - shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second); - MODEL_EXECUTOR.execute(() -> { - // load unbadged shortcut in background and update view when icon ready - appState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first); - MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second)); - }); - } - } - SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this); - } - - @Override - public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) { - mBubbleTextView.setVisibility(VISIBLE); - mBubbleTextView.setIconVisible(true); - } - - private void setWillDrawIcon(boolean willDraw) { - mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE); - } - - /** - * Drag and drop handler for popup items in Launcher activity - */ - public static class HeroItemDragHandler implements OnLongClickListener { - private final Launcher mLauncher; - private final HeroSearchResultView mContainer; - - HeroItemDragHandler(Context context, HeroSearchResultView container) { - mLauncher = Launcher.getLauncher(context); - mContainer = container; - } - - @Override - public boolean onLongClick(View v) { - if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; - mContainer.setWillDrawIcon(false); - - DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON); - WorkspaceItemInfo itemInfo = new WorkspaceItemInfo((AppInfo) v.getTag()); - itemInfo.container = CONTAINER_ALL_APPS; - DragPreviewProvider previewProvider = new ShortcutDragPreviewProvider( - mContainer.mIconView, new Point()); - mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView, - draggableView, mContainer, itemInfo, previewProvider, new DragOptions()); - - SearchTargetEvent event = new SearchTargetEvent.Builder(mContainer.mSearchTarget, - SearchTargetEvent.LONG_PRESS).build(); - SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(event); - return false; - } - } - - @Override - public void handleSelection(int eventType) { - ItemInfo itemInfo = (ItemInfo) mBubbleTextView.getTag(); - if (itemInfo == null) return; - Launcher launcher = Launcher.getLauncher(getContext()); - launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo); - - SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); - } -} diff --git a/src/com/android/launcher3/views/SearchResultIcon.java b/src/com/android/launcher3/views/SearchResultIcon.java index ea06d5b20f..0851b5432f 100644 --- a/src/com/android/launcher3/views/SearchResultIcon.java +++ b/src/com/android/launcher3/views/SearchResultIcon.java @@ -15,16 +15,35 @@ */ package com.android.launcher3.views; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.UserHandle; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.UiThread; import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.allapps.search.SearchEventTracker; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.ItemInfoWithIcon; +import com.android.launcher3.model.data.RemoteActionItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.ComponentKey; import com.android.systemui.plugins.shared.SearchTarget; @@ -39,6 +58,17 @@ public class SearchResultIcon extends BubbleTextView implements public static final String TARGET_TYPE_APP = "app"; + public static final String TARGET_TYPE_HERO_APP = "hero_app"; + public static final String TARGET_TYPE_SHORTCUT = "shortcut"; + public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action"; + public static final String TARGET_TYPE_SUGGEST = "suggest"; + + public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result"; + public static final String REMOTE_ACTION_TOKEN = "action_token"; + + + private static final String[] LONG_PRESS_SUPPORTED_TYPES = + new String[]{TARGET_TYPE_APP, TARGET_TYPE_SHORTCUT, TARGET_TYPE_HERO_APP}; private final Launcher mLauncher; @@ -64,26 +94,96 @@ public class SearchResultIcon extends BubbleTextView implements setOnFocusChangeListener(mLauncher.getFocusHandler()); setOnClickListener(this); setOnLongClickListener(this); - getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx; + setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + mLauncher.getDeviceProfile().allAppsCellHeightPx)); } @Override public void applySearchTarget(SearchTarget searchTarget) { mSearchTarget = searchTarget; - AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore(); SearchEventTracker.getInstance(getContext()).registerWeakHandler(mSearchTarget, this); - if (searchTarget.getItemType().equals(TARGET_TYPE_APP)) { - AppInfo appInfo = appsStore.getApp(new ComponentKey(searchTarget.getComponentName(), - searchTarget.getUserHandle())); - applyFromApplicationInfo(appInfo); + switch (searchTarget.getItemType()) { + case TARGET_TYPE_APP: + case TARGET_TYPE_HERO_APP: + prepareUsingApp(searchTarget.getComponentName(), searchTarget.getUserHandle()); + break; + case TARGET_TYPE_SHORTCUT: + prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0)); + break; + case TARGET_TYPE_REMOTE_ACTION: + case TARGET_TYPE_SUGGEST: + prepareUsingRemoteAction(searchTarget.getRemoteAction(), + searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN), + searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START), + searchTarget.getItemType().equals(TARGET_TYPE_REMOTE_ACTION)); + break; } } + private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) { + AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore(); + AppInfo appInfo = appsStore.getApp(new ComponentKey(componentName, userHandle)); + applyFromApplicationInfo(appInfo); + } + + + private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) { + WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()); + applyFromWorkspaceItem(workspaceItemInfo); + LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext()); + MODEL_EXECUTOR.execute(() -> { + launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo); + reapplyItemInfoAsync(workspaceItemInfo); + }); + } + + private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start, + boolean useIconToBadge) { + RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start); + + applyFromRemoteActionInfo(itemInfo); + if (!loadIconFromResource()) { + UI_HELPER_EXECUTOR.post(() -> { + // If the Drawable from the remote action is not AdaptiveBitmap, styling will not + // work. + try (LauncherIcons li = LauncherIcons.obtain(getContext())) { + Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext()); + BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user, + Build.VERSION.SDK_INT); + + if (useIconToBadge) { + BitmapInfo placeholder = li.createIconBitmap( + itemInfo.getRemoteAction().getTitle().toString().substring(0, 1), + bitmap.color); + itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap); + } else { + itemInfo.bitmap = bitmap; + } + reapplyItemInfoAsync(itemInfo); + } + }); + } + } + + @UiThread + void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) { + MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon)); + } + + @Override public void handleSelection(int eventType) { mLauncher.getItemOnClickListener().onClick(this); - SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); + reportEvent(eventType); + } + + private void reportEvent(int eventType) { + SearchTargetEvent.Builder b = new SearchTargetEvent.Builder(mSearchTarget, eventType); + if (mSearchTarget.getItemType().equals(TARGET_TYPE_SHORTCUT)) { + b.setShortcutPosition(0); + } + SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(b.build()); + } @Override @@ -93,8 +193,22 @@ public class SearchResultIcon extends BubbleTextView implements @Override public boolean onLongClick(View view) { - SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, SearchTargetEvent.LONG_PRESS).build()); + if (!supportsLongPress(mSearchTarget.getItemType())) { + return false; + } + reportEvent(SearchTargetEvent.LONG_PRESS); return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view); + + } + + private boolean supportsLongPress(String type) { + for (String t : LONG_PRESS_SUPPORTED_TYPES) { + if (t.equals(type)) return true; + } + return false; + } + + protected boolean loadIconFromResource() { + return false; } } diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java index bdbe890e8d..03332c1501 100644 --- a/src/com/android/launcher3/views/SearchResultIconRow.java +++ b/src/com/android/launcher3/views/SearchResultIconRow.java @@ -17,188 +17,173 @@ package com.android.launcher3.views; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; -import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ShortcutInfo; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Build; +import android.os.UserHandle; import android.util.AttributeSet; -import android.widget.EditText; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.allapps.search.SearchEventTracker; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.model.data.RemoteActionItemInfo; +import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.touch.ItemClickHandler; -import com.android.launcher3.util.Themes; import com.android.systemui.plugins.shared.SearchTarget; import com.android.systemui.plugins.shared.SearchTargetEvent; +import java.util.ArrayList; +import java.util.List; + /** - * A view representing a stand alone shortcut search result + * A full width representation of {@link SearchResultIcon} with a secondary label and inline + * shortcuts */ -public class SearchResultIconRow extends DoubleShadowBubbleTextView implements - AllAppsSearchBarController.SearchTargetHandler { +public class SearchResultIconRow extends LinearLayout implements + AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener, + View.OnLongClickListener { + public static final int MAX_SHORTCUTS_COUNT = 2; - public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action"; - public static final String TARGET_TYPE_SUGGEST = "suggest"; - public static final String TARGET_TYPE_SHORTCUT = "shortcut"; - - - public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result"; - public static final String REMOTE_ACTION_TOKEN = "action_token"; - - private final boolean mMatchesInset; + private final Launcher mLauncher; + private final LauncherAppState mLauncherAppState; + private SearchResultIcon mResultIcon; + private TextView mTitleView; + private TextView mDescriptionView; + private BubbleTextView[] mShortcutViews = new BubbleTextView[2]; private SearchTarget mSearchTarget; + private PackageItemInfo mProviderInfo; - @Nullable private Drawable mCustomIcon; - public SearchResultIconRow(@NonNull Context context) { + public SearchResultIconRow(Context context) { this(context, null, 0); } - public SearchResultIconRow(@NonNull Context context, + public SearchResultIconRow(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public SearchResultIconRow(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr) { + public SearchResultIconRow(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.SearchResultIconRow, defStyleAttr, 0); - mMatchesInset = a.getBoolean(R.styleable.SearchResultIconRow_matchTextInsetWithQuery, - false); - - int customIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0); - - if (customIconResId != 0) { - mCustomIcon = Launcher.getLauncher(context).getDrawable(customIconResId); - } - - a.recycle(); - } - - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Launcher launcher = Launcher.getLauncher(getContext()); - if (mMatchesInset && launcher.getAppsView() != null && getParent() != null) { - EditText editText = launcher.getAppsView().getSearchUiManager().getEditText(); - if (editText != null) { - int counterOffset = getIconSize() + getCompoundDrawablePadding() / 2; - setPadding(editText.getLeft() - counterOffset, getPaddingTop(), - getPaddingRight(), getPaddingBottom()); - } - } + mLauncher = Launcher.getLauncher(getContext()); + mLauncherAppState = LauncherAppState.getInstance(getContext()); } @Override - protected void drawFocusHighlight(Canvas canvas) { - mHighlightPaint.setColor(mHighlightColor); - float r = Themes.getDialogCornerRadius(getContext()); - canvas.drawRoundRect(0, 0, getWidth(), getHeight(), r, r, mHighlightPaint); - } + protected void onFinishInflate() { + super.onFinishInflate(); + int iconSize = mLauncher.getDeviceProfile().allAppsIconSizePx; + mResultIcon = findViewById(R.id.icon); + mTitleView = findViewById(R.id.title); + mDescriptionView = findViewById(R.id.desc); + mShortcutViews[0] = findViewById(R.id.shortcut_0); + mShortcutViews[1] = findViewById(R.id.shortcut_1); + mResultIcon.getLayoutParams().height = iconSize; + mResultIcon.getLayoutParams().width = iconSize; + for (BubbleTextView bubbleTextView : mShortcutViews) { + ViewGroup.LayoutParams lp = bubbleTextView.getLayoutParams(); + lp.width = iconSize; + bubbleTextView.setOnClickListener(view -> { + WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag(); + SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget, + SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build(); + SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event); + mLauncher.getItemOnClickListener().onClick(view); + }); + } + setOnClickListener(this); + setOnLongClickListener(this); + } @Override public void applySearchTarget(SearchTarget searchTarget) { mSearchTarget = searchTarget; - String type = searchTarget.getItemType(); - if (type.equals(TARGET_TYPE_REMOTE_ACTION) || type.equals(TARGET_TYPE_SUGGEST)) { - prepareUsingRemoteAction(searchTarget.getRemoteAction(), - searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN), - searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START), - type.equals(TARGET_TYPE_REMOTE_ACTION)); + mResultIcon.applySearchTarget(searchTarget); + mResultIcon.setTextVisibility(false); + mTitleView.setText(mResultIcon.getText()); + String itemType = searchTarget.getItemType(); + boolean showDesc = itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT); + mDescriptionView.setVisibility(showDesc ? VISIBLE : GONE); - } else if (type.equals(TARGET_TYPE_SHORTCUT)) { - prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0)); + if (itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT)) { + ShortcutInfo shortcutInfo = searchTarget.getShortcutInfos().get(0); + setProviderDetails(new ComponentName(shortcutInfo.getPackage(), ""), + shortcutInfo.getUserHandle()); + } else if (itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) { + showInlineShortcuts(mSearchTarget.getShortcutInfos()); + } + if (!itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) { + showInlineShortcuts(new ArrayList<>()); } - setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT)); - SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this); } - private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) { - WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()); - applyFromWorkspaceItem(workspaceItemInfo); - LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext()); - if (!loadIconFromResource()) { - MODEL_EXECUTOR.execute(() -> { - launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo); - reapplyItemInfoAsync(workspaceItemInfo); + private void showInlineShortcuts(List infos) { + if (infos == null) return; + ArrayList> shortcuts = new ArrayList<>(); + for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) { + ShortcutInfo shortcutInfo = infos.get(i); + ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext()); + si.rank = i; + shortcuts.add(new Pair<>(shortcutInfo, si)); + } + + for (int i = 0; i < mShortcutViews.length; i++) { + BubbleTextView shortcutView = mShortcutViews[i]; + mShortcutViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE); + if (i < shortcuts.size()) { + Pair p = shortcuts.get(i); + //apply ItemInfo and prepare view + shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second); + MODEL_EXECUTOR.execute(() -> { + // load unbadged shortcut in background and update view when icon ready + mLauncherAppState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first); + MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second)); + }); + } + } + } + + + private void setProviderDetails(ComponentName componentName, UserHandle userHandle) { + PackageItemInfo packageItemInfo = new PackageItemInfo(componentName.getPackageName()); + if (mProviderInfo == packageItemInfo) return; + MODEL_EXECUTOR.post(() -> { + packageItemInfo.user = userHandle; + mLauncherAppState.getIconCache().getTitleAndIconForApp(packageItemInfo, true); + MAIN_EXECUTOR.post(() -> { + mDescriptionView.setText(packageItemInfo.title); + mProviderInfo = packageItemInfo; }); - } - } - - private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start, - boolean useIconToBadge) { - RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start); - - applyFromRemoteActionInfo(itemInfo); - if (itemInfo.isEscapeHatch() || !loadIconFromResource()) { - UI_HELPER_EXECUTOR.post(() -> { - // If the Drawable from the remote action is not AdaptiveBitmap, styling will not - // work. - try (LauncherIcons li = LauncherIcons.obtain(getContext())) { - Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext()); - BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user, - Build.VERSION.SDK_INT); - - if (useIconToBadge) { - BitmapInfo placeholder = li.createIconBitmap( - itemInfo.getRemoteAction().getTitle().toString().substring(0, 1), - bitmap.color); - itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap); - } else { - itemInfo.bitmap = bitmap; - } - reapplyItemInfoAsync(itemInfo); - } - }); - } - - } - - private boolean loadIconFromResource() { - if (mCustomIcon == null) return false; - setIcon(mCustomIcon); - return true; - } - - void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) { - MAIN_EXECUTOR.post(() -> { - reapplyItemInfo(itemInfoWithIcon); - mCustomIcon = getIcon(); }); } @Override public void handleSelection(int eventType) { - ItemInfo itemInfo = (ItemInfo) getTag(); - Launcher launcher = Launcher.getLauncher(getContext()); - if (itemInfo instanceof WorkspaceItemInfo) { - ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher); - } else { - ItemClickHandler.onClickRemoteAction(launcher, (RemoteActionItemInfo) itemInfo); - } - SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent( - new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); + mResultIcon.handleSelection(eventType); + } + + @Override + public void onClick(View view) { + mResultIcon.performClick(); + } + + @Override + public boolean onLongClick(View view) { + mResultIcon.performLongClick(); + return false; } } diff --git a/src/com/android/launcher3/views/SearchResultSuggestion.java b/src/com/android/launcher3/views/SearchResultSuggestion.java new file mode 100644 index 0000000000..ab94bf0342 --- /dev/null +++ b/src/com/android/launcher3/views/SearchResultSuggestion.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.ViewGroup; + +import com.android.launcher3.R; + +/** + * {@link SearchResultIconRow} with custom drawable resource + */ +public class SearchResultSuggestion extends SearchResultIcon { + + private final Drawable mCustomIcon; + + public SearchResultSuggestion(Context context) { + this(context, null, 0); + } + + public SearchResultSuggestion(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchResultSuggestion(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SearchResultSuggestion, defStyle, 0); + mCustomIcon = a.getDrawable(R.styleable.SearchResultSuggestion_customIcon); + a.recycle(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + ViewGroup.LayoutParams lp = getLayoutParams(); + lp.height = BaseDragLayer.LayoutParams.WRAP_CONTENT; + } + + @Override + protected boolean loadIconFromResource() { + setIcon(mCustomIcon); + return true; + } +} diff --git a/src/com/android/launcher3/views/ThumbnailSearchResultView.java b/src/com/android/launcher3/views/ThumbnailSearchResultView.java index e929d7fbd8..f213f229a8 100644 --- a/src/com/android/launcher3/views/ThumbnailSearchResultView.java +++ b/src/com/android/launcher3/views/ThumbnailSearchResultView.java @@ -15,8 +15,9 @@ */ package com.android.launcher3.views; -import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_SHOULD_START; -import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_TOKEN; + +import static com.android.launcher3.views.SearchResultIcon.REMOTE_ACTION_SHOULD_START; +import static com.android.launcher3.views.SearchResultIcon.REMOTE_ACTION_TOKEN; import android.content.Context; import android.content.Intent; From ff9e7d6aa96cf5d95ed4c06383c6b4b5beadb31e Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 21 Oct 2020 15:42:05 -0700 Subject: [PATCH 06/27] Removing separate Cancel and End callbacks and using AnimationListener instead This removes the additional step of invoking these callbacks separately Change-Id: I0b60047a44f179ba725f15b1e791e336884869c9 --- .../NavBarToHomeTouchController.java | 7 +-- ...ButtonNavbarToOverviewTouchController.java | 9 ++-- .../NoButtonQuickSwitchTouchController.java | 11 +++-- .../PortraitStatesTouchController.java | 29 +++++------ .../QuickSwitchTouchController.java | 4 +- .../TaskViewTouchController.java | 36 ++++---------- .../android/quickstep/views/RecentsView.java | 48 ++++++++----------- .../com/android/quickstep/views/TaskView.java | 9 ++-- .../android/launcher3/LauncherAnimUtils.java | 26 +++++++++- .../AllAppsInsetTransitionController.java | 18 +++---- .../anim/AnimatorPlaybackController.java | 35 -------------- .../launcher3/anim/PendingAnimation.java | 48 ++++++++++++------- .../AbstractStateChangeTouchController.java | 34 ++++++------- 13 files changed, 141 insertions(+), 173 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index 5181a86960..f73e2f2f0a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -17,6 +17,8 @@ package com.android.launcher3.uioverrides.touchcontrollers; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU; +import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; +import static com.android.launcher3.LauncherAnimUtils.newCancelListener; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; @@ -24,7 +26,6 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; -import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.ValueAnimator; @@ -186,8 +187,8 @@ public class NavBarToHomeTouchController implements TouchController, if (topView != null) { topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder); } - mCurrentAnimation = builder.createPlaybackController() - .setOnCancelRunnable(this::clearState); + mCurrentAnimation = builder.createPlaybackController(); + mCurrentAnimation.getTarget().addListener(newCancelListener(this::clearState)); } private void clearState() { diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 2a3bdbfcb8..702c519ace 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides.touchcontrollers; +import static com.android.launcher3.LauncherAnimUtils.newCancelListener; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.NORMAL; @@ -191,15 +192,17 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch return; } mNormalToHintOverviewScrimAnimator = null; - mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> { + mCurrentAnimation.getTarget().addListener(newCancelListener(() -> mLauncher.getStateManager().goToState(OVERVIEW, true, () -> { mOverviewResistYAnim = AnimatorControllerWithResistance .createRecentsResistanceFromOverviewAnim(mLauncher, null) .createPlaybackController(); mReachedOverview = true; maybeSwipeInteractionToOverviewComplete(); - }); - }); + }))); + + mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener); + mCurrentAnimation.dispatchOnCancel(); mStartedOverview = true; VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index f378848a71..df433f8cc0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.uioverrides.touchcontrollers; +import static com.android.launcher3.LauncherAnimUtils.newCancelListener; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS; @@ -44,6 +45,7 @@ import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.graphics.PointF; @@ -92,6 +94,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, private final MotionPauseDetector mMotionPauseDetector; private final float mMotionPauseMinDisplacement; private final LauncherRecentsView mRecentsView; + protected final AnimatorListener mClearStateOnCancelListener = + newCancelListener(this::clearState); private boolean mNoIntercept; private LauncherState mStartState; @@ -204,8 +208,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, config.duration = (long) (Math.max(mXRange, mYRange) * 2); config.animFlags = config.animFlags | SKIP_OVERVIEW; mNonOverviewAnim = mLauncher.getStateManager() - .createAnimationToNewWorkspace(toState, config) - .setOnCancelRunnable(this::clearState); + .createAnimationToNewWorkspace(toState, config); + mNonOverviewAnim.getTarget().addListener(mClearStateOnCancelListener); } private void setupOverviewAnimators() { @@ -379,7 +383,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, if (canceled) { // Let the state manager know that the animation didn't go to the target state, // but don't clean up yet (we already clean up when the animation completes). - mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable(); + mNonOverviewAnim.getTarget().removeListener(mClearStateOnCancelListener); + mNonOverviewAnim.dispatchOnCancel(); } float startProgress = mNonOverviewAnim.getProgressFraction(); float endProgress = canceled ? 0 : 1; diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index 037d9889fd..3c9b808f2f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -245,29 +245,29 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr config.animFlags = animFlags; config.duration = maxAccuracy; - cancelPendingAnim(); + if (mCurrentAnimation != null) { + mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener); + mCurrentAnimation.dispatchOnCancel(); + } + mGoingBetweenStates = true; if (mFromState == OVERVIEW && mToState == NORMAL && mOverviewPortraitStateTouchHelper.shouldSwipeDownReturnToApp()) { // Reset the state manager, when changing the interaction mode mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */); - mPendingAnimation = mOverviewPortraitStateTouchHelper - .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR); - Runnable onCancelRunnable = () -> { - cancelPendingAnim(); - clearState(); - }; - mCurrentAnimation = mPendingAnimation.createPlaybackController() - .setOnCancelRunnable(onCancelRunnable); + mGoingBetweenStates = false; + mCurrentAnimation = mOverviewPortraitStateTouchHelper + .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR) + .createPlaybackController(); mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation); RecentsView recentsView = mLauncher.getOverviewPanel(); totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler()); } else { mCurrentAnimation = mLauncher.getStateManager() - .createAnimationToNewWorkspace(mToState, config) - .setOnCancelRunnable(this::clearState); + .createAnimationToNewWorkspace(mToState, config); } + mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener); if (totalShift == 0) { totalShift = Math.signum(mFromState.ordinal - mToState.ordinal) @@ -276,13 +276,6 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr return 1 / totalShift; } - private void cancelPendingAnim() { - if (mPendingAnimation != null) { - mPendingAnimation.finish(false); - mPendingAnimation = null; - } - } - @Override protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration, LauncherState targetState, float velocity, boolean isFling) { diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index d39b6f5d97..fc9e1bbfe5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -106,8 +106,8 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll setupInterpolators(config); config.duration = (long) (getShiftRange() * 2); mCurrentAnimation = mLauncher.getStateManager() - .createAnimationToNewWorkspace(mToState, config) - .setOnCancelRunnable(this::clearState); + .createAnimationToNewWorkspace(mToState, config); + mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener); mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> updateFullscreenProgress((Float) valueAnimator.getAnimatedValue())); return 1 / getShiftRange(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 186caf6dc1..97296957fe 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -16,6 +16,7 @@ package com.android.launcher3.uioverrides.touchcontrollers; import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE; +import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE; @@ -50,16 +51,12 @@ public abstract class TaskViewTouchController extends AnimatorListenerAdapter implements TouchController, SingleAxisSwipeDetector.Listener { - // Progress after which the transition is assumed to be a success in case user does not fling - public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; - protected final T mActivity; private final SingleAxisSwipeDetector mDetector; private final RecentsView mRecentsView; private final int[] mTempCords = new int[2]; private final boolean mIsRtl; - private PendingAnimation mPendingAnimation; private AnimatorPlaybackController mCurrentAnimation; private boolean mCurrentAnimationIsGoingUp; @@ -200,10 +197,8 @@ public abstract class TaskViewTouchController } if (mCurrentAnimation != null) { mCurrentAnimation.setPlayFraction(0); - } - if (mPendingAnimation != null) { - mPendingAnimation.finish(false); - mPendingAnimation = null; + mCurrentAnimation.getTarget().removeListener(this); + mCurrentAnimation.dispatchOnCancel(); } PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler(); @@ -216,15 +211,16 @@ public abstract class TaskViewTouchController // The interpolator controlling the most prominent visual movement. We use this to determine // whether we passed SUCCESS_TRANSITION_PROGRESS. final Interpolator currentInterpolator; + PendingAnimation pa; if (goingUp) { currentInterpolator = Interpolators.LINEAR; - mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged, + pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged, true /* animateTaskView */, true /* removeTask */, maxDuration); mEndDisplacement = -secondaryTaskDimension; } else { currentInterpolator = Interpolators.ZOOM_IN; - mPendingAnimation = mRecentsView.createTaskLaunchAnimation( + pa = mRecentsView.createTaskLaunchAnimation( mTaskBeingDragged, maxDuration, currentInterpolator); // Since the thumbnail is what is filling the screen, based the end displacement on it. @@ -234,12 +230,8 @@ public abstract class TaskViewTouchController mEndDisplacement = secondaryLayerDimension - mTempCords[1]; } mEndDisplacement *= verticalFactor; + mCurrentAnimation = pa.createPlaybackController(); - if (mCurrentAnimation != null) { - mCurrentAnimation.setOnCancelRunnable(null); - } - mCurrentAnimation = mPendingAnimation.createPlaybackController() - .setOnCancelRunnable(this::clearState); // Setting this interpolator doesn't affect the visual motion, but is used to determine // whether we successfully reached the target state in onDragEnd(). mCurrentAnimation.getTarget().setInterpolator(currentInterpolator); @@ -303,27 +295,15 @@ public abstract class TaskViewTouchController animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity); } - mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd)); + mCurrentAnimation.setEndAction(this::clearState); mCurrentAnimation.startWithVelocity(mActivity, goingToEnd, velocity, mEndDisplacement, animationDuration); } - private void onCurrentAnimationEnd(boolean wasSuccess) { - if (mPendingAnimation != null) { - mPendingAnimation.finish(wasSuccess); - mPendingAnimation = null; - } - clearState(); - } - private void clearState() { mDetector.finishedScrolling(); mDetector.setDetectableScrollConditions(0, false); mTaskBeingDragged = null; mCurrentAnimation = null; - if (mPendingAnimation != null) { - mPendingAnimation.finish(false); - mPendingAnimation = null; - } } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index f35eb1fa5d..7efa5bd8c4 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -22,6 +22,7 @@ import static android.view.View.MeasureSpec.makeMeasureSpec; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; +import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; @@ -39,7 +40,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; import static com.android.launcher3.statehandlers.DepthController.DEPTH; -import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; @@ -99,7 +99,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.anim.PendingAnimation.EndState; import com.android.launcher3.anim.SpringProperty; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; @@ -772,7 +771,7 @@ public abstract class RecentsView extends PagedView protected void applyLoadPlan(ArrayList tasks) { if (mPendingAnimation != null) { - mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks)); + mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks)); return; } @@ -1464,19 +1463,10 @@ public abstract class RecentsView extends PagedView verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp); } - private void removeTask(TaskView taskView, int index, EndState endState) { - if (taskView.getTask() != null) { - UI_HELPER_EXECUTOR.execute(() -> - ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id)); - mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo()) - .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); - } - } - public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, boolean shouldRemoveTask, long duration) { if (mPendingAnimation != null) { - mPendingAnimation.finish(false); + mPendingAnimation.createPlaybackController().dispatchOnCancel(); } PendingAnimation anim = new PendingAnimation(duration); @@ -1559,22 +1549,27 @@ public abstract class RecentsView extends PagedView } mPendingAnimation = anim; - mPendingAnimation.addEndListener(new Consumer() { + mPendingAnimation.addEndListener(new Consumer() { @Override - public void accept(EndState endState) { - if (ENABLE_QUICKSTEP_LIVE_TILE.get() && - taskView.isRunningTask() && endState.isSuccess) { - finishRecentsAnimation(true /* toHome */, () -> onEnd(endState)); + public void accept(Boolean success) { + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask() && success) { + finishRecentsAnimation(true /* toHome */, () -> onEnd(success)); } else { - onEnd(endState); + onEnd(success); } } @SuppressWarnings("WrongCall") - private void onEnd(EndState endState) { - if (endState.isSuccess) { + private void onEnd(boolean success) { + if (success) { if (shouldRemoveTask) { - removeTask(taskView, draggedIndex, endState); + if (taskView.getTask() != null) { + UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() + .removeTask(taskView.getTask().key.id)); + mActivity.getStatsLogManager().logger() + .withItemInfo(taskView.getItemInfo()) + .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); + } } int pageToSnapTo = mCurrentPage; @@ -1613,8 +1608,8 @@ public abstract class RecentsView extends PagedView } mPendingAnimation = anim; - mPendingAnimation.addEndListener((endState) -> { - if (endState.isSuccess) { + mPendingAnimation.addEndListener(isSuccess -> { + if (isSuccess) { // Remove all the task views now UI_HELPER_EXECUTOR.execute( ActivityManagerWrapper.getInstance()::removeAllRecentTasks); @@ -1642,7 +1637,6 @@ public abstract class RecentsView extends PagedView protected void runDismissAnimation(PendingAnimation pendingAnim) { AnimatorPlaybackController controller = pendingAnim.createPlaybackController(); controller.dispatchOnStart(); - controller.setEndAction(() -> pendingAnim.finish(true)); controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN); controller.start(); } @@ -2186,8 +2180,8 @@ public abstract class RecentsView extends PagedView mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator); mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); } - mPendingAnimation.addEndListener((endState) -> { - if (endState.isSuccess) { + mPendingAnimation.addEndListener(isSuccess -> { + if (isSuccess) { Consumer onLaunchResult = (result) -> { onTaskLaunchAnimationEnd(result); if (!result) { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index 5154018f6c..1b85b2ea8d 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -72,7 +72,6 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.statemanager.StatefulActivity; @@ -380,11 +379,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } public AnimatorPlaybackController createLaunchAnimationForRunningTask() { - final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation( - this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR); - AnimatorPlaybackController currentAnimation = pendingAnimation.createPlaybackController(); - currentAnimation.setEndAction(() -> pendingAnimation.finish(true)); - return currentAnimation; + return getRecentsView().createTaskLaunchAnimation( + this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR) + .createPlaybackController(); } public void launchTask(boolean animate) { diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index d9cf7f19b3..803f8d2c3d 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -16,6 +16,9 @@ package com.android.launcher3; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; import android.graphics.drawable.Drawable; import android.util.FloatProperty; import android.util.IntProperty; @@ -29,8 +32,8 @@ public class LauncherAnimUtils { */ public static final int SPRING_LOADED_EXIT_DELAY = 500; - // The progress of an animation to all apps must be at least this far along to snap to all apps. - public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f; + // Progress after which the transition is assumed to be a success + public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; public static final IntProperty DRAWABLE_ALPHA = new IntProperty("drawableAlpha") { @@ -131,4 +134,23 @@ public class LauncherAnimUtils { return view.getAlpha(); } }; + + /** + * Utility method to create an {@link AnimatorListener} which executes a callback on animation + * cancel. + */ + public static AnimatorListener newCancelListener(Runnable callback) { + return new AnimatorListenerAdapter() { + + boolean mDispatched = false; + + @Override + public void onAnimationCancel(Animator animation) { + if (!mDispatched) { + mDispatched = true; + callback.run(); + } + } + }; + } } diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java index 08d573f08b..01d01ea35e 100644 --- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.allapps; +import android.annotation.TargetApi; import android.graphics.Insets; import android.os.Build; import android.util.Log; @@ -26,9 +27,8 @@ import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.core.os.BuildCompat; +import com.android.launcher3.Utilities; import com.android.launcher3.util.UiThreadHelper; /** @@ -68,7 +68,7 @@ public class AllAppsInsetTransitionController { } public void hide() { - if (!BuildCompat.isAtLeastR()) return; + if (!Utilities.ATLEAST_R) return; WindowInsets insets = mApps.getRootWindowInsets(); if (insets == null) return; @@ -89,9 +89,9 @@ public class AllAppsInsetTransitionController { * * @param progress value between 0..1 */ - @RequiresApi(api = Build.VERSION_CODES.R) + @TargetApi(Build.VERSION_CODES.R) public void onDragStart(float progress) { - if (!BuildCompat.isAtLeastR()) return; + if (!Utilities.ATLEAST_R) return; // Until getRootWindowInsets().isVisible(...) method returns correct value, // only support InsetController based IME transition during swipe up and @@ -164,7 +164,7 @@ public class AllAppsInsetTransitionController { * If IME bounds after touch sequence finishes, call finish. */ private boolean handleFinishOnFling(WindowInsetsAnimationController controller) { - if (!BuildCompat.isAtLeastR()) return false; + if (!Utilities.ATLEAST_R) return false; if (mState == State.FLING_END_TOP) { controller.finish(true); @@ -181,9 +181,9 @@ public class AllAppsInsetTransitionController { * * @param progress value between 0..1 */ - @RequiresApi(api = 30) + @TargetApi(Build.VERSION_CODES.R) public void setProgress(float progress) { - if (!BuildCompat.isAtLeastR()) return; + if (!Utilities.ATLEAST_R) return; // progress that equals to 0 or 1 is error prone. Do not use them. // Instead use onDragStart and onAnimationEnd if (mAnimationController == null || progress <= 0f || progress >= 1f) return; @@ -218,7 +218,7 @@ public class AllAppsInsetTransitionController { * * @param progress value between 0..1 */ - @RequiresApi(api = 30) + @TargetApi(Build.VERSION_CODES.R) public void onAnimationEnd(float progress) { if (DEBUG) { Log.d(TAG, "onAnimationEnd progress=" + progress diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java index dcdfb6e51a..edaf51dbc6 100644 --- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java +++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java @@ -29,8 +29,6 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.content.Context; -import androidx.annotation.Nullable; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -72,7 +70,6 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL private Runnable mEndAction; protected boolean mTargetCancelled = false; - protected Runnable mOnCancelRunnable; /** package private */ AnimatorPlaybackController(AnimatorSet anim, long duration, ArrayList childAnims) { @@ -88,16 +85,11 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL @Override public void onAnimationCancel(Animator animation) { mTargetCancelled = true; - if (mOnCancelRunnable != null) { - mOnCancelRunnable.run(); - mOnCancelRunnable = null; - } } @Override public void onAnimationEnd(Animator animation) { mTargetCancelled = false; - mOnCancelRunnable = null; } @Override @@ -269,33 +261,6 @@ public class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateL } } - /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */ - public void dispatchOnCancelWithoutCancelRunnable() { - dispatchOnCancelWithoutCancelRunnable(null); - } - - /** - * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This - * is intended to be used only if you need to cancel but want to defer cleaning up yourself. - * @param callback An optional callback to run after dispatching the cancel but before resetting - * the onCancelRunnable. - */ - public void dispatchOnCancelWithoutCancelRunnable(@Nullable Runnable callback) { - Runnable onCancel = mOnCancelRunnable; - setOnCancelRunnable(null); - dispatchOnCancel(); - if (callback != null) { - callback.run(); - } - setOnCancelRunnable(onCancel); - } - - - public AnimatorPlaybackController setOnCancelRunnable(Runnable runnable) { - mOnCancelRunnable = runnable; - return this; - } - public void dispatchOnStart() { callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart); } diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 6dd316ed60..4e90c9e67f 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -15,10 +15,12 @@ */ package com.android.launcher3.anim; +import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur; import android.animation.Animator; import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -43,8 +45,6 @@ import java.util.function.Consumer; */ public class PendingAnimation implements PropertySetter { - private final ArrayList> mEndListeners = new ArrayList<>(); - private final ArrayList mAnimHolders = new ArrayList<>(); private final AnimatorSet mAnim; private final long mDuration; @@ -73,13 +73,6 @@ public class PendingAnimation implements PropertySetter { addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders); } - public void finish(boolean isSuccess) { - for (Consumer listeners : mEndListeners) { - listeners.accept(new EndState(isSuccess)); - } - mEndListeners.clear(); - } - @Override public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) { if (view == null || view.getAlpha() == alpha) { @@ -163,19 +156,38 @@ public class PendingAnimation implements PropertySetter { } /** - * Add a listener of receiving the end state. - * Note that the listeners are called as a result of calling {@link #finish(boolean)} - * and not automatically + * Add a listener of receiving the success/failure callback in the end. */ - public void addEndListener(Consumer listener) { - mEndListeners.add(listener); + public void addEndListener(Consumer listener) { + if (mProgressAnimator == null) { + mProgressAnimator = ValueAnimator.ofFloat(0, 1); + } + mProgressAnimator.addListener(new EndStateCallbackWrapper(listener)); } - public static class EndState { - public boolean isSuccess; + private static class EndStateCallbackWrapper extends AnimatorListenerAdapter { - public EndState(boolean isSuccess) { - this.isSuccess = isSuccess; + private final Consumer mListener; + private boolean mCalled = false; + + EndStateCallbackWrapper(Consumer listener) { + mListener = listener; + } + + @Override + public void onAnimationCancel(Animator animation) { + if (!mCalled) { + mCalled = true; + mListener.accept(false); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCalled) { + ValueAnimator anim = (ValueAnimator) animation; + mListener.accept(anim.getAnimatedFraction() > SUCCESS_TRANSITION_PROGRESS); + } } } } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 57c0ad92af..a9d0e614e4 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -15,7 +15,8 @@ */ package com.android.launcher3.touch; -import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS; +import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; +import static com.android.launcher3.LauncherAnimUtils.newCancelListener; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; @@ -32,6 +33,7 @@ import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC; import static com.android.launcher3.util.DisplayController.getSingleFrameMs; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ValueAnimator; @@ -48,7 +50,6 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.StatsLogManager; @@ -64,9 +65,6 @@ import com.android.launcher3.util.TouchController; public abstract class AbstractStateChangeTouchController implements TouchController, SingleAxisSwipeDetector.Listener { - // Progress after which the transition is assumed to be a success in case user does not fling - public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f; - /** * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this. */ @@ -77,6 +75,9 @@ public abstract class AbstractStateChangeTouchController protected final SingleAxisSwipeDetector mDetector; protected final SingleAxisSwipeDetector.Direction mSwipeDirection; + protected final AnimatorListener mClearStateOnCancelListener = + newCancelListener(this::clearState); + private boolean mNoIntercept; private boolean mIsLogContainerSet; protected int mStartContainerType; @@ -85,7 +86,7 @@ public abstract class AbstractStateChangeTouchController protected LauncherState mFromState; protected LauncherState mToState; protected AnimatorPlaybackController mCurrentAnimation; - protected PendingAnimation mPendingAnimation; + protected boolean mGoingBetweenStates = true; private float mStartProgress; // Ratio of transition process [0, 1] to drag displacement (px) @@ -209,7 +210,7 @@ public abstract class AbstractStateChangeTouchController mStartProgress = 0; mPassedOverviewAtomicThreshold = false; if (mCurrentAnimation != null) { - mCurrentAnimation.setOnCancelRunnable(null); + mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener); } int animComponents = goingBetweenNormalAndOverview(mFromState, mToState) ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS; @@ -237,7 +238,7 @@ public abstract class AbstractStateChangeTouchController LauncherState toState) { return (fromState == NORMAL || fromState == OVERVIEW) && (toState == NORMAL || toState == OVERVIEW) - && mPendingAnimation == null; + && mGoingBetweenStates; } @Override @@ -412,9 +413,8 @@ public abstract class AbstractStateChangeTouchController ? mToState : mFromState; // snap to top or bottom using the release velocity } else { - float successProgress = mToState == ALL_APPS - ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS; - targetState = (interpolatedProgress > successProgress) ? mToState : mFromState; + targetState = + (interpolatedProgress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState; } final float endProgress; @@ -438,7 +438,8 @@ public abstract class AbstractStateChangeTouchController } else { // Let the state manager know that the animation didn't go to the target state, // but don't cancel ourselves (we already clean up when the animation completes). - mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(); + mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener); + mCurrentAnimation.dispatchOnCancel(); endProgress = 0; if (progress <= 0) { @@ -522,13 +523,7 @@ public abstract class AbstractStateChangeTouchController mAtomicComponentsController = null; } clearState(); - boolean shouldGoToTargetState = true; - if (mPendingAnimation != null) { - boolean reachedTarget = mToState == targetState; - mPendingAnimation.finish(reachedTarget); - mPendingAnimation = null; - shouldGoToTargetState = !reachedTarget; - } + boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState); if (shouldGoToTargetState) { goToTargetState(targetState); } @@ -568,6 +563,7 @@ public abstract class AbstractStateChangeTouchController mAtomicAnim.cancel(); mAtomicAnim = null; } + mGoingBetweenStates = true; mScheduleResumeAtomicComponent = false; mDetector.finishedScrolling(); mDetector.setDetectableScrollConditions(0, false); From d15915f5fd677ccef710198f6bad8a97e3eb4efd Mon Sep 17 00:00:00 2001 From: thiruram Date: Thu, 5 Nov 2020 18:06:10 -0800 Subject: [PATCH 07/27] Fixes invalid ids for UiEvents: LAUNCHER_FOLDER_CONVERTED_TO_ICON, LAUNCHER_HOTSEAT_PREDICTION_PINNED Bug: 169100235 Change-Id: I1cae4e61b97947c5a7e56daeaccac0ece55f021c --- .../android/launcher3/logging/StatsLogManager.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 7e90769143..0b445bca4e 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -297,12 +297,6 @@ public class StatsLogManager implements ResourceBasedOverride { @UiEvent(doc = "User tapped on image content in Overview Select mode.") LAUNCHER_SELECT_MODE_IMAGE(627), - @UiEvent(doc = "A folder was replaced by a single item") - LAUNCHER_FOLDER_CONVERTED_TO_ICON(628), - - @UiEvent(doc = "A hotseat prediction item was pinned") - LAUNCHER_HOTSEAT_PREDICTION_PINNED(629), - @UiEvent(doc = "Activity to add external item was started") LAUNCHER_ADD_EXTERNAL_ITEM_START(641), @@ -318,6 +312,12 @@ public class StatsLogManager implements ResourceBasedOverride { @UiEvent(doc = "Item was dragged in external item addition flow") LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED(645), + @UiEvent(doc = "A folder was replaced by a single item") + LAUNCHER_FOLDER_CONVERTED_TO_ICON(646), + + @UiEvent(doc = "A hotseat prediction item was pinned") + LAUNCHER_HOTSEAT_PREDICTION_PINNED(647), + @UiEvent(doc = "Undo event was tapped.") LAUNCHER_UNDO(648), From 984e0d0a634a16fbdd77c5bdfc016f24e4fbdf90 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 2 Nov 2020 13:45:08 -0600 Subject: [PATCH 08/27] Show settings icon at start of settings results Bug: 170792963 Test: Manual Screenshot: https://screenshot.googleplex.com/MVBtDZGtQ3aChwU Change-Id: I16a28a7bd9e3129b40102e020cd7c0111e6fe29b --- res/layout/search_result_icon_row.xml | 8 +- res/layout/search_result_settings_row.xml | 64 +++++++------- res/layout/search_result_slice.xml | 31 +++++-- res/values/dimens.xml | 9 +- .../launcher3/allapps/AllAppsGridAdapter.java | 18 +--- ...er.java => SearchResultSettingsSlice.java} | 83 +++++++++++++++---- .../views/SearchSettingsRowView.java | 49 +++++++++-- 7 files changed, 178 insertions(+), 84 deletions(-) rename src/com/android/launcher3/views/{SearchSliceWrapper.java => SearchResultSettingsSlice.java} (50%) diff --git a/res/layout/search_result_icon_row.xml b/res/layout/search_result_icon_row.xml index b51896e8a7..280bbc992c 100644 --- a/res/layout/search_result_icon_row.xml +++ b/res/layout/search_result_icon_row.xml @@ -40,13 +40,13 @@ android:gravity="start|center_vertical" android:textAlignment="viewStart" android:textColor="?android:attr/textColorPrimary" - android:textSize="@dimen/settings_hero_title_size" /> + android:textSize="@dimen/search_hero_title_size" /> @@ -57,7 +57,7 @@ android:layout_height="match_parent" android:gravity="start|center_vertical" launcher:iconDisplay="shortcut_popup" - android:textSize="@dimen/settings_hero_subtitle_size" + android:textSize="@dimen/search_hero_subtitle_size" launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" launcher:layoutHorizontal="false" /> @@ -67,7 +67,7 @@ android:layout_width="@dimen/deep_shortcut_icon_size" android:layout_height="match_parent" launcher:iconDisplay="shortcut_popup" - android:textSize="@dimen/settings_hero_inline_button_size" + android:textSize="@dimen/search_hero_inline_button_size" launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" launcher:layoutHorizontal="false" /> diff --git a/res/layout/search_result_settings_row.xml b/res/layout/search_result_settings_row.xml index 19daf346e2..05f7561628 100644 --- a/res/layout/search_result_settings_row.xml +++ b/res/layout/search_result_settings_row.xml @@ -18,40 +18,42 @@ android:background="?android:attr/selectableItemBackground" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" + android:orientation="horizontal" android:gravity="center_vertical" - android:padding="4dp" - android:minHeight="48dp" - android:textColor="?android:attr/textColorPrimary" - android:textSize="14sp"> + android:padding="@dimen/dynamic_grid_cell_padding_x" + android:textColor="?android:attr/textColorPrimary"> - + + - - - - + android:layout_weight="1"> + + + + \ No newline at end of file diff --git a/res/layout/search_result_slice.xml b/res/layout/search_result_slice.xml index ea1d49a56d..24d75e9eb6 100644 --- a/res/layout/search_result_slice.xml +++ b/res/layout/search_result_slice.xml @@ -1,5 +1,4 @@ - - - \ No newline at end of file + android:layout_height="wrap_content"> + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 5387e1bad6..7df3f774e5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -249,8 +249,11 @@ 24dp - 16sp - 15sp - 12sp + 16sp + 15sp + 12sp + 36dp + 16dp + 4dp diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 7018f20a55..434bc1453d 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -37,7 +37,6 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityRecordCompat; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import androidx.slice.widget.SliceView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BubbleTextView; @@ -47,7 +46,6 @@ import com.android.launcher3.allapps.search.SearchSectionInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.util.PackageManagerHelper; -import com.android.launcher3.views.SearchSliceWrapper; import com.android.systemui.plugins.shared.SearchTarget; import java.util.List; @@ -461,17 +459,10 @@ public class AllAppsGridAdapter extends } else { searchView.setVisibility(View.GONE); } - break; - case VIEW_TYPE_SEARCH_SLICE: - SliceView sliceView = (SliceView) holder.itemView; - SearchAdapterItem slicePayload = (SearchAdapterItem) mApps.getAdapterItems().get( - position); - SearchTarget searchTarget = slicePayload.getSearchTarget(); - sliceView.setTag(new SearchSliceWrapper(mLauncher, sliceView, searchTarget)); - break; case VIEW_TYPE_SEARCH_CORPUS_TITLE: case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON: + case VIEW_TYPE_SEARCH_SLICE: case VIEW_TYPE_SEARCH_ROW: case VIEW_TYPE_SEARCH_ICON: case VIEW_TYPE_SEARCH_ICON_ROW: @@ -496,13 +487,6 @@ public class AllAppsGridAdapter extends if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) { ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration(); } - if (holder.itemView instanceof SliceView) { - SliceView sliceView = (SliceView) holder.itemView; - if (sliceView.getTag() instanceof SearchSliceWrapper) { - ((SearchSliceWrapper) sliceView.getTag()).destroy(); - } - sliceView.setTag(null); - } } @Override diff --git a/src/com/android/launcher3/views/SearchSliceWrapper.java b/src/com/android/launcher3/views/SearchResultSettingsSlice.java similarity index 50% rename from src/com/android/launcher3/views/SearchSliceWrapper.java rename to src/com/android/launcher3/views/SearchResultSettingsSlice.java index f8a7dc08d6..2d726e79d4 100644 --- a/src/com/android/launcher3/views/SearchSliceWrapper.java +++ b/src/com/android/launcher3/views/SearchResultSettingsSlice.java @@ -17,9 +17,13 @@ package com.android.launcher3.views; import android.content.Context; import android.net.Uri; +import android.util.AttributeSet; import android.util.Log; +import android.view.View; +import android.widget.LinearLayout; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.slice.Slice; import androidx.slice.SliceItem; @@ -28,55 +32,98 @@ import androidx.slice.widget.SliceLiveData; import androidx.slice.widget.SliceView; import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.allapps.search.SearchEventTracker; import com.android.systemui.plugins.shared.SearchTarget; import com.android.systemui.plugins.shared.SearchTargetEvent; /** - * A Wrapper class for {@link SliceView} search results + * A slice view wrapper with settings app icon at start */ -public class SearchSliceWrapper implements SliceView.OnSliceActionListener { +public class SearchResultSettingsSlice extends LinearLayout implements + AllAppsSearchBarController.SearchTargetHandler, SliceView.OnSliceActionListener { + public static final String TARGET_TYPE_SLICE = "settings_slice"; private static final String TAG = "SearchSliceController"; private static final String URI_EXTRA_KEY = "slice_uri"; - - private final Launcher mLauncher; - private final SearchTarget mSearchTarget; - private final SliceView mSliceView; + private SliceView mSliceView; + private View mIcon; private LiveData mSliceLiveData; + private SearchTarget mSearchTarget; + private Launcher mLauncher; - public SearchSliceWrapper(Context context, SliceView sliceView, SearchTarget searchTarget) { - mLauncher = Launcher.getLauncher(context); + public SearchResultSettingsSlice(Context context) { + this(context, null, 0); + } + + public SearchResultSettingsSlice(Context context, + @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchResultSettingsSlice(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(getContext()); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSliceView = findViewById(R.id.slice); + mIcon = findViewById(R.id.icon); + SearchSettingsRowView.applySettingsIcon(mLauncher, mIcon); + } + + @Override + public void applySearchTarget(SearchTarget searchTarget) { + reset(); mSearchTarget = searchTarget; - mSliceView = sliceView; - sliceView.setOnSliceActionListener(this); try { mSliceLiveData = SliceLiveData.fromUri(mLauncher, getSliceUri()); - mSliceLiveData.observe((Launcher) mLauncher, sliceView); + mSliceLiveData.observe(mLauncher, mSliceView); } catch (Exception ex) { Log.e(TAG, "unable to bind slice", ex); } } - /** - * Unregisters event handlers and removes lifecycle observer - */ - public void destroy() { - mSliceView.setOnSliceActionListener(null); - mSliceLiveData.removeObservers(mLauncher); + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mSliceView.setOnSliceActionListener(this); } @Override - public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) { + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + reset(); + } + + @Override + public void handleSelection(int eventType) { SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent( new SearchTargetEvent.Builder(mSearchTarget, SearchTargetEvent.CHILD_SELECT).build()); } + private void reset() { + mSliceView.setOnSliceActionListener(null); + if (mSliceLiveData != null) { + mSliceLiveData.removeObservers(mLauncher); + } + } + + @Override + public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) { + handleSelection(SearchTargetEvent.CHILD_SELECT); + } + private Uri getSliceUri() { return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY); } + } diff --git a/src/com/android/launcher3/views/SearchSettingsRowView.java b/src/com/android/launcher3/views/SearchSettingsRowView.java index f0884f88aa..160ee6514b 100644 --- a/src/com/android/launcher3/views/SearchSettingsRowView.java +++ b/src/com/android/launcher3/views/SearchSettingsRowView.java @@ -15,8 +15,14 @@ */ package com.android.launcher3.views; +import static com.android.launcher3.FastBitmapDrawable.newIcon; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.os.Bundle; import android.text.TextUtils; import android.util.AttributeSet; @@ -27,38 +33,41 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.allapps.search.SearchEventTracker; +import com.android.launcher3.model.data.PackageItemInfo; import com.android.systemui.plugins.shared.SearchTarget; import com.android.systemui.plugins.shared.SearchTargetEvent; import java.util.ArrayList; +import java.util.List; /** - * A row of tappable TextViews with a breadcrumb for settings search. + * A row of clickable TextViews with a breadcrumb for settings search. */ public class SearchSettingsRowView extends LinearLayout implements View.OnClickListener, AllAppsSearchBarController.SearchTargetHandler { public static final String TARGET_TYPE_SETTINGS_ROW = "settings_row"; - + private View mIconView; private TextView mTitleView; - private TextView mDescriptionView; private TextView mBreadcrumbsView; private Intent mIntent; private SearchTarget mSearchTarget; public SearchSettingsRowView(@NonNull Context context) { - super(context); + this(context, null, 0); } public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs, @@ -69,10 +78,11 @@ public class SearchSettingsRowView extends LinearLayout implements @Override protected void onFinishInflate() { super.onFinishInflate(); + mIconView = findViewById(R.id.icon); mTitleView = findViewById(R.id.title); - mDescriptionView = findViewById(R.id.description); mBreadcrumbsView = findViewById(R.id.breadcrumbs); setOnClickListener(this); + applySettingsIcon(Launcher.getLauncher(getContext()), mIconView); } @Override @@ -81,6 +91,7 @@ public class SearchSettingsRowView extends LinearLayout implements Bundle bundle = searchTarget.getExtras(); mIntent = bundle.getParcelable("intent"); showIfAvailable(mTitleView, bundle.getString("title")); + mIconView.setContentDescription(bundle.getString("title")); ArrayList breadcrumbs = bundle.getStringArrayList("breadcrumbs"); //TODO: implement RTL friendly breadcrumbs view showIfAvailable(mBreadcrumbsView, breadcrumbs != null @@ -113,4 +124,30 @@ public class SearchSettingsRowView extends LinearLayout implements SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent( new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); } + + /** + * Requests settings app icon from {@link com.android.launcher3.icons.IconCache} and applies + * to to view + */ + public static void applySettingsIcon(Launcher launcher, View view) { + LauncherAppState appState = LauncherAppState.getInstance(launcher); + MODEL_EXECUTOR.post(() -> { + PackageItemInfo packageItemInfo = new PackageItemInfo(getSettingsPackageName(launcher)); + appState.getIconCache().getTitleAndIconForApp(packageItemInfo, false); + MAIN_EXECUTOR.post(() -> { + FastBitmapDrawable iconDrawable = newIcon(appState.getContext(), packageItemInfo); + view.setBackground(iconDrawable); + }); + }); + } + + private static String getSettingsPackageName(Launcher launcher) { + Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS); + List resolveInfos = launcher.getPackageManager().queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + if (resolveInfos.size() == 0) { + return ""; + } + return resolveInfos.get(0).activityInfo.packageName; + } } From 37432444fc61921b5cba9940a23a8d6c6a63d067 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Sun, 1 Nov 2020 13:06:43 -0800 Subject: [PATCH 09/27] Initial setup for widgets in Search LiveSearchManager creates AppWidgetHost when user starts a new search session and destroys it when user returns to home. In addition, it also manages the creation and caching of PlaceholderSearchWidget which can be used to create AppWidgetHostViews. Bug: 168321831 Test: Manual Change-Id: I06a893028e55aa6e0702a4f1cd7a2edbb1f61671 --- res/layout/search_result_widget_live.xml | 5 + .../launcher3/AppWidgetResizeFrame.java | 2 +- src/com/android/launcher3/Launcher.java | 10 ++ .../launcher3/allapps/AllAppsGridAdapter.java | 13 +- .../allapps/AllAppsTransitionController.java | 4 + .../allapps/search/LiveSearchManager.java | 125 ++++++++++++++++ .../search/SearchWidgetInfoContainer.java | 79 ++++++++++ .../launcher3/views/SearchResultWidget.java | 135 ++++++++++++++++++ 8 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 res/layout/search_result_widget_live.xml create mode 100644 src/com/android/launcher3/allapps/search/LiveSearchManager.java create mode 100644 src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java create mode 100644 src/com/android/launcher3/views/SearchResultWidget.java diff --git a/res/layout/search_result_widget_live.xml b/res/layout/search_result_widget_live.xml new file mode 100644 index 0000000000..0dd8a06c9a --- /dev/null +++ b/res/layout/search_result_widget_live.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index 168d9c479c..bc3e341806 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -36,7 +36,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O private static final Rect sTmpRect = new Rect(); // Represents the cell size on the grid in the two orientations. - private static final MainThreadInitializedObject CELL_SIZE = + public static final MainThreadInitializedObject CELL_SIZE = new MainThreadInitializedObject<>(c -> { InvariantDeviceProfile inv = LauncherAppState.getIDP(c); return new Point[] {inv.landscapeProfile.getCellSize(), diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 699734c0c5..38ebe14d01 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -111,6 +111,7 @@ import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DiscoveryBounce; +import com.android.launcher3.allapps.search.LiveSearchManager; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; @@ -276,6 +277,8 @@ public class Launcher extends StatefulActivity implements Launche private LifecycleRegistry mLifecycleRegistry; + private LiveSearchManager mLiveSearchManager; + @Thunk Workspace mWorkspace; @Thunk @@ -389,6 +392,8 @@ public class Launcher extends StatefulActivity implements Launche mIconCache = app.getIconCache(); mAccessibilityDelegate = new LauncherAccessibilityDelegate(this); + mLiveSearchManager = new LiveSearchManager(this); + mDragController = new DragController(this); mAllAppsController = new AllAppsTransitionController(this); mStateManager = new StateManager<>(this, NORMAL); @@ -492,6 +497,10 @@ public class Launcher extends StatefulActivity implements Launche return mLifecycleRegistry; } + public LiveSearchManager getLiveSearchManager() { + return mLiveSearchManager; + } + protected LauncherOverlayManager getDefaultOverlay() { return new LauncherOverlayManager() { }; } @@ -1583,6 +1592,7 @@ public class Launcher extends StatefulActivity implements Launche mAppTransitionManager.unregisterRemoteAnimations(); mUserChangedCallbackCloseable.close(); mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED); + mLiveSearchManager.stop(); } public LauncherAccessibilityDelegate getAccessibilityDelegate() { diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 434bc1453d..5b4268111b 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -46,6 +46,7 @@ import com.android.launcher3.allapps.search.SearchSectionInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.views.SearchResultWidget; import com.android.systemui.plugins.shared.SearchTarget; import java.util.List; @@ -89,6 +90,8 @@ public class AllAppsGridAdapter extends public static final int VIEW_TYPE_SEARCH_ICON = 1 << 14; + public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15; + // Common view type masks public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER; public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_SEARCH_ICON; @@ -181,7 +184,8 @@ public class AllAppsGridAdapter extends || viewType == VIEW_TYPE_SEARCH_THUMBNAIL || viewType == VIEW_TYPE_SEARCH_ICON_ROW || viewType == VIEW_TYPE_SEARCH_ICON - || viewType == VIEW_TYPE_SEARCH_SUGGEST; + || viewType == VIEW_TYPE_SEARCH_SUGGEST + || viewType == VIEW_TYPE_SEARCH_WIDGET_LIVE; } } @@ -427,6 +431,9 @@ public class AllAppsGridAdapter extends case VIEW_TYPE_SEARCH_SUGGEST: return new ViewHolder(mLayoutInflater.inflate( R.layout.search_result_suggest, parent, false)); + case VIEW_TYPE_SEARCH_WIDGET_LIVE: + return new ViewHolder(mLayoutInflater.inflate( + R.layout.search_result_widget_live, parent, false)); default: throw new RuntimeException("Unexpected view type"); } @@ -469,6 +476,7 @@ public class AllAppsGridAdapter extends case VIEW_TYPE_SEARCH_PEOPLE: case VIEW_TYPE_SEARCH_THUMBNAIL: case VIEW_TYPE_SEARCH_SUGGEST: + case VIEW_TYPE_SEARCH_WIDGET_LIVE: SearchAdapterItem item = (SearchAdapterItem) mApps.getAdapterItems().get(position); SearchTargetHandler payloadResultView = (SearchTargetHandler) holder.itemView; @@ -487,6 +495,9 @@ public class AllAppsGridAdapter extends if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) { ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration(); } + if (holder.itemView instanceof SearchResultWidget) { + ((SearchResultWidget) holder.itemView).removeListener(); + } } @Override diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 0c488a609b..f9ab196792 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -264,11 +264,15 @@ public class AllAppsTransitionController implements StateHandler, if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) { mInsetController.onAnimationEnd(mProgress); if (Float.compare(mProgress, 0f) == 0) { + mLauncher.getLiveSearchManager().start(); EditText editText = mAppsView.getSearchUiManager().getEditText(); if (editText != null) { editText.requestFocus(); } } + else { + mLauncher.getLiveSearchManager().stop(); + } // TODO: should make the controller hide synchronously } } diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java new file mode 100644 index 0000000000..e8a00d958a --- /dev/null +++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps.search; + +import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget; + +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.widget.PendingAddWidgetInfo; + +import java.util.HashMap; + +/** + * Manages Lifecycle for Live search results + */ +public class LiveSearchManager { + + public static final int SEARCH_APPWIDGET_HOST_ID = 2048; + + private final Launcher mLauncher; + private final AppWidgetManager mAppWidgetManger; + private HashMap mWidgetPlaceholders = new HashMap<>(); + private SearchWidgetHost mSearchWidgetHost; + + public LiveSearchManager(Launcher launcher) { + mLauncher = launcher; + mAppWidgetManger = AppWidgetManager.getInstance(launcher); + } + + /** + * Creates new {@link AppWidgetHostView} from {@link AppWidgetProviderInfo}. Caches views for + * quicker result within the same search session + */ + public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) { + if (mSearchWidgetHost == null) { + throw new RuntimeException("AppWidgetHost has not been created yet"); + } + + ComponentName provider = providerInfo.provider; + UserHandle userHandle = providerInfo.getProfile(); + + ComponentKey key = new ComponentKey(provider, userHandle); + SearchWidgetInfoContainer view = mWidgetPlaceholders.getOrDefault(key, null); + if (mWidgetPlaceholders.containsKey(key)) { + return mWidgetPlaceholders.get(key); + } + LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo( + mLauncher, providerInfo); + PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo); + + Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo); + int appWidgetId = mSearchWidgetHost.allocateAppWidgetId(); + boolean success = mAppWidgetManger.bindAppWidgetIdIfAllowed(appWidgetId, userHandle, + provider, options); + if (!success) { + mWidgetPlaceholders.put(key, null); + return null; + } + + view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(mLauncher, appWidgetId, + providerInfo); + view.setTag(pendingAddWidgetInfo); + mWidgetPlaceholders.put(key, view); + return view; + } + + /** + * Start search session + */ + public void start() { + stop(); + mSearchWidgetHost = new SearchWidgetHost(mLauncher); + mSearchWidgetHost.startListening(); + } + + /** + * Stop search session + */ + public void stop() { + if (mSearchWidgetHost != null) { + mSearchWidgetHost.stopListening(); + mSearchWidgetHost.deleteHost(); + for (SearchWidgetInfoContainer placeholder : mWidgetPlaceholders.values()) { + placeholder.clearListeners(); + } + mWidgetPlaceholders.clear(); + mSearchWidgetHost = null; + } + } + + static class SearchWidgetHost extends AppWidgetHost { + SearchWidgetHost(Context context) { + super(context, SEARCH_APPWIDGET_HOST_ID); + } + + @Override + protected AppWidgetHostView onCreateView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + return new SearchWidgetInfoContainer(context); + } + } +} diff --git a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java new file mode 100644 index 0000000000..b5c22688e5 --- /dev/null +++ b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps.search; + +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.widget.RemoteViews; + +import java.util.ArrayList; +import java.util.List; + +/** + * A placeholder {@link AppWidgetHostView} used for managing widget search results + */ +public class SearchWidgetInfoContainer extends AppWidgetHostView { + private int mAppWidgetId; + private AppWidgetProviderInfo mProviderInfo; + private RemoteViews mViews; + private List mListeners = new ArrayList<>(); + + public SearchWidgetInfoContainer(Context context) { + super(context); + } + + @Override + public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { + mAppWidgetId = appWidgetId; + mProviderInfo = info; + for (AppWidgetHostView listener : mListeners) { + listener.setAppWidget(mAppWidgetId, mProviderInfo); + } + } + + @Override + public void updateAppWidget(RemoteViews remoteViews) { + mViews = remoteViews; + for (AppWidgetHostView listener : mListeners) { + listener.updateAppWidget(remoteViews); + } + } + + /** + * Create a live {@link AppWidgetHostView} from placeholder + */ + public void attachWidget(AppWidgetHostView hv) { + hv.setTag(getTag()); + hv.setAppWidget(mAppWidgetId, mProviderInfo); + hv.updateAppWidget(mViews); + mListeners.add(hv); + } + + /** + * stops AppWidgetHostView from getting updates + */ + public void detachWidget(AppWidgetHostView hostView) { + mListeners.remove(hostView); + } + + /** + * Removes all AppWidgetHost update listeners + */ + public void clearListeners() { + mListeners.clear(); + } +} diff --git a/src/com/android/launcher3/views/SearchResultWidget.java b/src/com/android/launcher3/views/SearchResultWidget.java new file mode 100644 index 0000000000..65131be1a7 --- /dev/null +++ b/src/com/android/launcher3/views/SearchResultWidget.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.views; + +import android.appwidget.AppWidgetHostView; +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.AppWidgetResizeFrame; +import com.android.launcher3.Launcher; +import com.android.launcher3.allapps.search.AllAppsSearchBarController; +import com.android.launcher3.allapps.search.SearchEventTracker; +import com.android.launcher3.allapps.search.SearchWidgetInfoContainer; +import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.systemui.plugins.shared.SearchTarget; +import com.android.systemui.plugins.shared.SearchTargetEvent; + +/** + * displays live version of a widget upon receiving {@link AppWidgetProviderInfo} from Search + * provider + */ +public class SearchResultWidget extends RelativeLayout implements + AllAppsSearchBarController.SearchTargetHandler { + + private static final String TAG = "SearchResultWidget"; + + public static final String TARGET_TYPE_WIDGET_LIVE = "widget"; + + private final Launcher mLauncher; + private final AppWidgetHostView mHostView; + + private SearchTarget mSearchTarget; + private AppWidgetProviderInfo mProviderInfo; + + private SearchWidgetInfoContainer mInfoContainer; + + public SearchResultWidget(@NonNull Context context) { + this(context, null, 0); + } + + public SearchResultWidget(@NonNull Context context, + @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchResultWidget(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + mHostView = new AppWidgetHostView(context); + mLauncher = Launcher.getLauncher(context); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + addView(mHostView); + } + + @Override + public void applySearchTarget(SearchTarget searchTarget) { + if (searchTarget.getExtras() == null + || searchTarget.getExtras().getParcelable("provider") == null) { + setVisibility(GONE); + return; + } + AppWidgetProviderInfo providerInfo = searchTarget.getExtras().getParcelable("provider"); + if (mProviderInfo != null && providerInfo.provider.equals(mProviderInfo.provider) + && providerInfo.getProfile().equals(mProviderInfo.getProfile())) { + return; + } + removeListener(); + + mSearchTarget = searchTarget; + mProviderInfo = providerInfo; + + mInfoContainer = mLauncher.getLiveSearchManager().getPlaceHolderWidget(providerInfo); + if (mInfoContainer == null) { + setVisibility(GONE); + return; + } + setVisibility(VISIBLE); + mInfoContainer.attachWidget(mHostView); + PendingAddWidgetInfo info = (PendingAddWidgetInfo) mHostView.getTag(); + int[] size = mLauncher.getWorkspace().estimateItemSize(info); + mHostView.getLayoutParams().width = size[0]; + mHostView.getLayoutParams().height = size[1]; + AppWidgetResizeFrame.updateWidgetSizeRanges(mHostView, mLauncher, info.spanX, + info.spanY); + mHostView.requestLayout(); + + + } + + /** + * Stops hostView from getting updates on a widget provider + */ + public void removeListener() { + if (mInfoContainer != null) { + mInfoContainer.detachWidget(mHostView); + } + } + + @Override + public void handleSelection(int eventType) { + SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent( + new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + handleSelection(SearchTargetEvent.CHILD_SELECT); + } + return super.onInterceptTouchEvent(ev); + } +} From 003859cdd02abf061f0f2b84b2a0092925fa02fc Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Mon, 9 Nov 2020 01:38:05 -0800 Subject: [PATCH 10/27] Show remote action's description in SearchResultIconRow Validated against Pixel Tips corpus Change-Id: I3c3f8eb69007a97e55e2ef08e7f4e0b800bfe247 --- res/layout/search_result_icon_row.xml | 2 ++ src/com/android/launcher3/views/SearchResultIconRow.java | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/res/layout/search_result_icon_row.xml b/res/layout/search_result_icon_row.xml index 280bbc992c..81c23e4c90 100644 --- a/res/layout/search_result_icon_row.xml +++ b/res/layout/search_result_icon_row.xml @@ -38,6 +38,7 @@ android:id="@id/title" android:layout_height="wrap_content" android:gravity="start|center_vertical" + android:maxLines="1" android:textAlignment="viewStart" android:textColor="?android:attr/textColorPrimary" android:textSize="@dimen/search_hero_title_size" /> @@ -45,6 +46,7 @@ diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java index 03332c1501..ecf5054d8b 100644 --- a/src/com/android/launcher3/views/SearchResultIconRow.java +++ b/src/com/android/launcher3/views/SearchResultIconRow.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.ShortcutInfo; import android.os.UserHandle; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Pair; import android.view.View; @@ -125,6 +126,12 @@ public class SearchResultIconRow extends LinearLayout implements shortcutInfo.getUserHandle()); } else if (itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) { showInlineShortcuts(mSearchTarget.getShortcutInfos()); + } else if (itemType.equals(SearchResultIcon.TARGET_TYPE_REMOTE_ACTION)) { + CharSequence desc = mSearchTarget.getRemoteAction().getContentDescription(); + if (!TextUtils.isEmpty(desc)) { + mDescriptionView.setVisibility(VISIBLE); + mDescriptionView.setText(desc); + } } if (!itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) { showInlineShortcuts(new ArrayList<>()); From 20d99c5229ab51f1c04014b5cdebc83a10fae8fd Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 9 Nov 2020 16:16:26 -0600 Subject: [PATCH 11/27] Support drag/drop on live widget search results Preview: https://drive.google.com/file/d/1p0BMoQTaatwcslzPWoD5W_ZWQms5GpSp/view?usp=sharing Bug: 168321831 Test: Manual Change-Id: I237546a884083372d5d1ca80f500504b9fd9ba86 --- .../launcher3/views/SearchResultWidget.java | 77 +++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/src/com/android/launcher3/views/SearchResultWidget.java b/src/com/android/launcher3/views/SearchResultWidget.java index 65131be1a7..7d539553f0 100644 --- a/src/com/android/launcher3/views/SearchResultWidget.java +++ b/src/com/android/launcher3/views/SearchResultWidget.java @@ -18,18 +18,25 @@ package com.android.launcher3.views; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; +import android.graphics.Rect; import android.util.AttributeSet; +import android.view.GestureDetector; import android.view.MotionEvent; +import android.view.View; import android.widget.RelativeLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.AppWidgetResizeFrame; +import com.android.launcher3.CheckLongPressHelper; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.allapps.search.SearchEventTracker; import com.android.launcher3.allapps.search.SearchWidgetInfoContainer; +import com.android.launcher3.dragndrop.DraggableView; +import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.systemui.plugins.shared.SearchTarget; import com.android.systemui.plugins.shared.SearchTargetEvent; @@ -39,14 +46,19 @@ import com.android.systemui.plugins.shared.SearchTargetEvent; * provider */ public class SearchResultWidget extends RelativeLayout implements - AllAppsSearchBarController.SearchTargetHandler { + AllAppsSearchBarController.SearchTargetHandler, DraggableView, View.OnLongClickListener { private static final String TAG = "SearchResultWidget"; public static final String TARGET_TYPE_WIDGET_LIVE = "widget"; + private final Rect mWidgetOffset = new Rect(); + private final Launcher mLauncher; + private final CheckLongPressHelper mLongPressHelper; + private final GestureDetector mClickDetector; private final AppWidgetHostView mHostView; + private final float mScaleToFit; private SearchTarget mSearchTarget; private AppWidgetProviderInfo mProviderInfo; @@ -65,8 +77,18 @@ public class SearchResultWidget extends RelativeLayout implements public SearchResultWidget(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mHostView = new AppWidgetHostView(context); mLauncher = Launcher.getLauncher(context); + mHostView = new AppWidgetHostView(context); + DeviceProfile grid = mLauncher.getDeviceProfile(); + mScaleToFit = Math.min(grid.appWidgetScale.x, grid.appWidgetScale.y); + + // detect tap event on widget container for search target event reporting + mClickDetector = new GestureDetector(context, + new ClickListener(() -> handleSelection(SearchTargetEvent.CHILD_SELECT))); + + mLongPressHelper = new CheckLongPressHelper(this); + mLongPressHelper.setLongPressTimeoutFactor(1); + setOnLongClickListener(this); } @Override @@ -106,8 +128,7 @@ public class SearchResultWidget extends RelativeLayout implements AppWidgetResizeFrame.updateWidgetSizeRanges(mHostView, mLauncher, info.spanX, info.spanY); mHostView.requestLayout(); - - + setTag(info); } /** @@ -127,9 +148,55 @@ public class SearchResultWidget extends RelativeLayout implements @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mLongPressHelper.onTouchEvent(ev); + mClickDetector.onTouchEvent(ev); + if (ev.getAction() == MotionEvent.ACTION_UP && !mLongPressHelper.hasPerformedLongPress()) { handleSelection(SearchTargetEvent.CHILD_SELECT); } return super.onInterceptTouchEvent(ev); } + + + @Override + public void cancelLongPress() { + super.cancelLongPress(); + mLongPressHelper.cancelLongPress(); + } + + @Override + public int getViewType() { + return DraggableView.DRAGGABLE_WIDGET; + } + + @Override + public void getSourceVisualDragBounds(Rect bounds) { + mHostView.getHitRect(mWidgetOffset); + int width = (int) (mHostView.getMeasuredWidth() * mScaleToFit); + int height = (int) (mHostView.getMeasuredHeight() * mScaleToFit); + bounds.set(mWidgetOffset.left, + mWidgetOffset.top, + width + mWidgetOffset.left, + height + mWidgetOffset.top); + } + + @Override + public boolean onLongClick(View view) { + ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view); + handleSelection(SearchTargetEvent.LONG_PRESS); + return false; + } + + static class ClickListener extends GestureDetector.SimpleOnGestureListener { + private final Runnable mCb; + + ClickListener(Runnable cb) { + mCb = cb; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + mCb.run(); + return super.onSingleTapConfirmed(e); + } + } } From 1818fb7a6efc2993b74f951dc0868a680334f02f Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 9 Nov 2020 14:32:48 -0800 Subject: [PATCH 12/27] Dismissing popup if the underlying icon no longer supports it Bug: 168641545 Change-Id: Ia4f44819af02397efaead935db3fc8e305e47ba7 --- src/com/android/launcher3/Launcher.java | 3 +++ .../launcher3/popup/PopupContainerWithArrow.java | 11 +++++++++++ .../secondarydisplay/SecondaryDisplayLauncher.java | 2 ++ 3 files changed, 16 insertions(+) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 38ebe14d01..aa123f6b26 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2497,6 +2497,7 @@ public class Launcher extends StatefulActivity implements Launche @Override public void bindAllApplications(AppInfo[] apps, int flags) { mAppsView.getAppsStore().setApps(apps, flags); + PopupContainerWithArrow.dismissInvalidPopup(this); } /** @@ -2528,6 +2529,7 @@ public class Launcher extends StatefulActivity implements Launche public void bindWorkspaceItemsChanged(List updated) { if (!updated.isEmpty()) { mWorkspace.updateShortcuts(updated); + PopupContainerWithArrow.dismissInvalidPopup(this); } } @@ -2552,6 +2554,7 @@ public class Launcher extends StatefulActivity implements Launche public void bindWorkspaceComponentsRemoved(final ItemInfoMatcher matcher) { mWorkspace.removeItemsByMatcher(matcher); mDragController.onAppsRemoved(matcher); + PopupContainerWithArrow.dismissInvalidPopup(this); } @Override diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 6d92b8b627..59930ff850 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -599,6 +599,17 @@ public class PopupContainerWithArrow extends Arr } } + /** + * Dismisses the popup if it is no longer valid + */ + public static void dismissInvalidPopup(BaseDraggingActivity activity) { + PopupContainerWithArrow popup = getOpen(activity); + if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow() + || !canShow(popup.mOriginalIcon, (ItemInfo) popup.mOriginalIcon.getTag()))) { + popup.animateClose(); + } + } + /** * Handler to control drag-and-drop for popup items */ diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index 2b04365ddc..2adf8ce25a 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -39,6 +39,7 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.PromiseAppInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; @@ -296,6 +297,7 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity @Override public void bindAllApplications(AppInfo[] apps, int flags) { mAppsView.getAppsStore().setApps(apps, flags); + PopupContainerWithArrow.dismissInvalidPopup(this); } public PopupDataProvider getPopupDataProvider() { From 3116dc918fa9b1a318596d1caaf40ece03bd00a1 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 9 Nov 2020 16:30:07 -0600 Subject: [PATCH 13/27] Resolve search scroll hightlight issue After getting the index of the first item that should be highlighted, Section decorator should not assume getChildAt(index) will return item at position. Instead use findViewHolderForAdapterPosition. Bug: 170665892 Change-Id: Id81d021bde8868184013e8d51e49af7842b64125 --- res/layout/search_result_settings_row.xml | 1 + src/com/android/launcher3/allapps/AllAppsGridAdapter.java | 3 +-- src/com/android/launcher3/allapps/AllAppsSectionDecorator.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/res/layout/search_result_settings_row.xml b/res/layout/search_result_settings_row.xml index 05f7561628..22c08bf58f 100644 --- a/res/layout/search_result_settings_row.xml +++ b/res/layout/search_result_settings_row.xml @@ -44,6 +44,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/search_line_spacing" + android:maxLines="1" android:textColor="?android:attr/textColorPrimary" android:textSize="@dimen/search_hero_title_size" /> diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 5b4268111b..d04935223b 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -184,8 +184,7 @@ public class AllAppsGridAdapter extends || viewType == VIEW_TYPE_SEARCH_THUMBNAIL || viewType == VIEW_TYPE_SEARCH_ICON_ROW || viewType == VIEW_TYPE_SEARCH_ICON - || viewType == VIEW_TYPE_SEARCH_SUGGEST - || viewType == VIEW_TYPE_SEARCH_WIDGET_LIVE; + || viewType == VIEW_TYPE_SEARCH_SUGGEST; } } diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java index 3c81811e16..1d31975114 100644 --- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java +++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java @@ -98,7 +98,8 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration { mAppsView.getActiveRecyclerView().getLayoutManager(); if (layoutManager.findFirstVisibleItemPosition() <= index && index < parent.getChildCount()) { - decorationHandler.onFocusDraw(c, parent.getChildAt(index)); + RecyclerView.ViewHolder vh = parent.findViewHolderForAdapterPosition(index); + if (vh != null) decorationHandler.onFocusDraw(c, vh.itemView); } } decorationHandler.reset(); From 1cd6a5f30e66d12bdebaa3a4357cf5cd123494d8 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 5 Nov 2020 13:00:56 -0800 Subject: [PATCH 14/27] Fixing app-to-overview animation in 3 button mode Instead of copying orientation state information from RecentsView to taskViewSimulator, using the same state object, so that new properties (like RecentsRotation and flags) are automatically covered Bug: 162881482 Change-Id: I02d0a710a6ecc4a3cebd0fcc4c49032127df1ce3 --- .../util/RecentsOrientedStateTest.java | 89 +++++++++++++++++++ .../quickstep/util/TaskViewSimulatorTest.java | 2 +- .../android/quickstep/AbsSwipeUpHandler.java | 3 +- .../AppToOverviewAnimationProvider.java | 3 +- .../quickstep/SwipeUpAnimationLogic.java | 2 +- .../com/android/quickstep/TaskViewUtils.java | 11 +-- .../quickstep/util/RecentsOrientedState.java | 54 ++++++----- .../quickstep/util/TaskViewSimulator.java | 28 +++--- .../android/quickstep/views/RecentsView.java | 26 +++--- 9 files changed, 156 insertions(+), 62 deletions(-) create mode 100644 quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java new file mode 100644 index 0000000000..656379f10f --- /dev/null +++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep.util; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_90; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.content.Context; + +import com.android.quickstep.FallbackActivityInterface; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; + +/** + * Tests for {@link RecentsOrientedState} + */ +@RunWith(RobolectricTestRunner.class) +@LooperMode(Mode.PAUSED) +public class RecentsOrientedStateTest { + + private RecentsOrientedState mR1, mR2; + + @Before + public void setup() { + Context context = RuntimeEnvironment.application; + mR1 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { }); + mR2 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { }); + assertEquals(mR1.getStateId(), mR2.getStateId()); + } + + @Test + public void stateId_changesWithFlags() { + mR1.setGestureActive(true); + mR2.setGestureActive(false); + assertNotEquals(mR1.getStateId(), mR2.getStateId()); + + mR2.setGestureActive(true); + assertEquals(mR1.getStateId(), mR2.getStateId()); + } + + @Test + public void stateId_changesWithRecentsRotation() { + mR1.setRecentsRotation(ROTATION_90); + mR2.setRecentsRotation(ROTATION_180); + assertNotEquals(mR1.getStateId(), mR2.getStateId()); + + mR2.setRecentsRotation(ROTATION_90); + assertEquals(mR1.getStateId(), mR2.getStateId()); + } + + @Test + public void stateId_changesWithDisplayRotation() { + mR1.update(ROTATION_0, ROTATION_90); + mR2.update(ROTATION_0, ROTATION_180); + assertNotEquals(mR1.getStateId(), mR2.getStateId()); + + mR2.update(ROTATION_90, ROTATION_90); + assertNotEquals(mR1.getStateId(), mR2.getStateId()); + + mR2.update(ROTATION_90, ROTATION_0); + assertNotEquals(mR1.getStateId(), mR2.getStateId()); + + mR2.update(ROTATION_0, ROTATION_90); + assertEquals(mR1.getStateId(), mR2.getStateId()); + } +} diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java index 6c88e55225..688f323d75 100644 --- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java +++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java @@ -148,7 +148,7 @@ public class TaskViewSimulatorTest { if (mAppRotation < 0) { mAppRotation = launcherRotation; } - tvs.setLayoutRotation(launcherRotation, mAppRotation); + tvs.getOrientationState().update(launcherRotation, mAppRotation); if (mAppInsets == null) { mAppInsets = new Rect(mLauncherInsets); } diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 43c088b585..041f67981d 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -378,7 +378,7 @@ public abstract class AbsSwipeUpHandler, Q extends if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { return; } - mTaskViewSimulator.setRecentsRotation(mActivity.getDisplay().getRotation()); + mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState()); // If we've already ended the gesture and are going home, don't prepare recents UI, // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL. @@ -686,6 +686,7 @@ public abstract class AbsSwipeUpHandler, Q extends dp.updateInsets(targets.homeContentInsets); dp.updateIsSeascape(mContext); initTransitionEndpoints(dp); + mTaskViewSimulator.getOrientationState().setMultiWindowMode(dp.isMultiWindowMode); } // Notify when the animation starts diff --git a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 55f542461c..efd4530c14 100644 --- a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -132,9 +132,8 @@ final class AppToOverviewAnimationProvider> extend TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy()); tsv.setDp(mActivity.getDeviceProfile()); + tsv.setOrientationState(mRecentsView.getPagedViewOrientedState()); tsv.setPreview(runningTaskTarget); - tsv.setLayoutRotation(mRecentsView.getPagedViewOrientedState().getTouchRotation(), - mRecentsView.getPagedViewOrientedState().getDisplayRotation()); TransformParams params = new TransformParams() .setTargetSet(targets) diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index 8f7ec3bfa0..4bb1bb565e 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -79,7 +79,7 @@ public abstract class SwipeUpAnimationLogic { mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); mTransformParams = transformParams; - mTaskViewSimulator.setLayoutRotation( + mTaskViewSimulator.getOrientationState().update( mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(), mDeviceState.getRotationTouchHelper().getDisplayRotation()); mTaskViewSimulator.setDrawsBelowRecents(true); diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index ed761cf640..2aed76afcb 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -179,16 +179,17 @@ public final class TaskViewUtils { Context context = v.getContext(); DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); - // RecentsView never updates the display rotation until swipe-up so the value may be stale. - // Use the display value instead. - int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation; - TaskViewSimulator topMostSimulator = null; if (tsv == null && targets.apps.length > 0) { tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy()); tsv.setDp(dp); - tsv.setLayoutRotation(displayRotation, displayRotation); + + // RecentsView never updates the display rotation until swipe-up so the value may + // be stale. Use the display value instead. + int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation; + tsv.getOrientationState().update(displayRotation, displayRotation); + tsv.setPreview(targets.apps[targets.apps.length - 1]); tsv.fullScreenProgress.value = 0; tsv.recentsViewScale.value = 1; diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index bb84380edd..e273aeb81f 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -72,7 +72,6 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private static final String TAG = "RecentsOrientedState"; private static final boolean DEBUG = false; - private static final String DELIMITER_DOT = "\\."; private ContentObserver mSystemAutoRotateObserver = newContentObserver(new Handler(), t -> updateAutoRotateSetting()); @@ -129,6 +128,9 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private int mFlags; private int mPreviousRotation = ROTATION_0; + // Combined int which encodes the full state. + private int mStateId = 0; + /** * @param rotationChangeListener Callback for receiving rotation events when rotation watcher * is enabled @@ -169,7 +171,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre */ public boolean setRecentsRotation(@SurfaceRotation int recentsRotation) { mRecentsRotation = recentsRotation; - return update(mTouchRotation, mDisplayRotation); + return updateHandler(); } /** @@ -183,8 +185,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre * Sets if the swipe up gesture is currently running or not */ public boolean setGestureActive(boolean isGestureActive) { - setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive); - return update(mTouchRotation, mDisplayRotation); + return setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive); } /** @@ -201,14 +202,13 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mDisplayRotation = displayRotation; mTouchRotation = touchRotation; mPreviousRotation = touchRotation; + return updateHandler(); + } - PagedOrientationHandler oldHandler = mOrientationHandler; + private boolean updateHandler() { if (mRecentsActivityRotation == mTouchRotation || (canRecentsActivityRotate() && (mFlags & FLAG_SWIPE_UP_NOT_RUNNING) != 0)) { mOrientationHandler = PagedOrientationHandler.PORTRAIT; - if (DEBUG) { - Log.d(TAG, "current RecentsOrientedState: " + this); - } } else if (mTouchRotation == ROTATION_90) { mOrientationHandler = PagedOrientationHandler.LANDSCAPE; } else if (mTouchRotation == ROTATION_270) { @@ -219,19 +219,26 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre if (DEBUG) { Log.d(TAG, "current RecentsOrientedState: " + this); } - return oldHandler != mOrientationHandler; + + int oldStateId = mStateId; + // Each SurfaceRotation value takes two bits + mStateId = (((((mFlags << 2) + | mDisplayRotation) << 2) + | mTouchRotation) << 3) + | (mRecentsRotation < 0 ? 7 : mRecentsRotation); + return mStateId != oldStateId; } @SurfaceRotation private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) { if (isRecentsActivityRotationAllowed()) { - return mRecentsRotation < ROTATION_0 ? displayRotation : mRecentsRotation; + return mRecentsRotation < 0 ? displayRotation : mRecentsRotation; } else { return ROTATION_0; } } - private void setFlag(int mask, boolean enabled) { + private boolean setFlag(int mask, boolean enabled) { boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED && !canRecentsActivityRotate(); @@ -253,6 +260,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre } }); } + return updateHandler(); } @Override @@ -327,6 +335,13 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre return mRecentsActivityRotation; } + /** + * Returns an id that can be used to tracking internal changes + */ + public int getStateId() { + return mStateId; + } + public boolean isMultipleOrientationSupportedByDevice() { return (mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE) == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE; @@ -509,14 +524,15 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre public String toString() { boolean systemRotationOn = (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0; return "[" - + "this=" + extractObjectNameAndAddress(super.toString()) - + " mOrientationHandler=" + - extractObjectNameAndAddress(mOrientationHandler.toString()) + + "this=" + nameAndAddress(this) + + " mOrientationHandler=" + nameAndAddress(mOrientationHandler) + " mDisplayRotation=" + mDisplayRotation + " mTouchRotation=" + mTouchRotation + " mRecentsActivityRotation=" + mRecentsActivityRotation + + " mRecentsRotation=" + mRecentsRotation + " isRecentsActivityRotationAllowed=" + isRecentsActivityRotationAllowed() + " mSystemRotation=" + systemRotationOn + + " mStateId=" + mStateId + " mFlags=" + mFlags + "]"; } @@ -533,13 +549,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre : idp.portraitProfile; } - /** - * String conversion for only the helpful parts of {@link Object#toString()} method - * @param stringToExtract "foo.bar.baz.MyObject@1234" - * @return "MyObject@1234" - */ - private static String extractObjectNameAndAddress(String stringToExtract) { - int index = stringToExtract.lastIndexOf(DELIMITER_DOT); - return index >= 0 ? stringToExtract.substring(index) : ""; + private static String nameAndAddress(Object obj) { + return obj.getClass().getSimpleName() + "@" + obj.hashCode(); } } diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java index 61ba411363..2f4bb8ed9d 100644 --- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java @@ -23,7 +23,6 @@ import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_ import android.animation.TimeInterpolator; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; @@ -31,6 +30,8 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.IntProperty; +import androidx.annotation.NonNull; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; @@ -67,10 +68,12 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final RectF mTempRectF = new RectF(); private final float[] mTempPoint = new float[2]; - private final RecentsOrientedState mOrientationState; private final Context mContext; private final BaseActivityInterface mSizeStrategy; + @NonNull + private RecentsOrientedState mOrientationState; + private final Rect mTaskRect = new Rect(); private boolean mDrawsBelowRecents; private final PointF mPivot = new PointF(); @@ -100,6 +103,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { // Cached calculations private boolean mLayoutValid = false; private boolean mScrollValid = false; + private int mOrientationStateId; public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) { mContext = context; @@ -107,8 +111,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mOrientationState = new RecentsOrientedState(context, sizeStrategy, i -> { }); mOrientationState.setGestureActive(true); - mCurrentFullscreenParams = new FullscreenDrawParams(context); + mOrientationStateId = mOrientationState.getStateId(); } /** @@ -116,23 +120,14 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { */ public void setDp(DeviceProfile dp) { mDp = dp; - mOrientationState.setMultiWindowMode(mDp.isMultiWindowMode); mLayoutValid = false; } /** - * @see com.android.quickstep.views.RecentsView#setLayoutRotation(int, int) + * Sets the orientation state used for this animation */ - public void setLayoutRotation(int touchRotation, int displayRotation) { - mOrientationState.update(touchRotation, displayRotation); - mLayoutValid = false; - } - - /** - * @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration) - */ - public void setRecentsRotation(int recentsRotation) { - mOrientationState.setRecentsRotation(recentsRotation); + public void setOrientationState(@NonNull RecentsOrientedState orientationState) { + mOrientationState = orientationState; mLayoutValid = false; } @@ -251,8 +246,9 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { if (mDp == null || mThumbnailPosition.isEmpty()) { return; } - if (!mLayoutValid) { + if (!mLayoutValid || mOrientationStateId != mOrientationState.getStateId()) { mLayoutValid = true; + mOrientationStateId = mOrientationState.getStateId(); getFullScreenScale(); mThumbnailData.rotation = mOrientationState.getDisplayRotation(); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 7efa5bd8c4..f281296ee2 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -89,6 +89,7 @@ import android.widget.ListView; import androidx.annotation.Nullable; import com.android.launcher3.BaseActivity; +import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.InvariantDeviceProfile; @@ -239,7 +240,7 @@ public abstract class RecentsView extends PagedView } }; - protected RecentsOrientedState mOrientationState; + protected final RecentsOrientedState mOrientationState; protected final BaseActivityInterface mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; protected SurfaceTransactionApplier mSyncTransactionApplier; @@ -408,17 +409,18 @@ public abstract class RecentsView extends PagedView private int mTaskViewStartIndex = 0; private OverviewActionsView mActionsView; - private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener = - (inMultiWindowMode) -> { - if (mOrientationState != null) { + private MultiWindowModeChangedListener mMultiWindowModeChangedListener = + new MultiWindowModeChangedListener() { + @Override + public void onMultiWindowModeChanged(boolean inMultiWindowMode) { mOrientationState.setMultiWindowMode(inMultiWindowMode); setLayoutRotation(mOrientationState.getTouchRotation(), mOrientationState.getDisplayRotation()); updateChildTaskOrientations(); - } - if (!inMultiWindowMode && mOverviewStateEnabled) { - // TODO: Re-enable layout transitions for addition of the unpinned task - reloadIfNeeded(); + if (!inMultiWindowMode && mOverviewStateEnabled) { + // TODO: Re-enable layout transitions for addition of the unpinned task + reloadIfNeeded(); + } } }; @@ -476,9 +478,7 @@ public abstract class RecentsView extends PagedView mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy()); mLiveTileTaskViewSimulator.recentsViewScale.value = 1; - mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(), - getPagedViewOrientedState().getDisplayRotation()); - mLiveTileTaskViewSimulator.setRecentsRotation(rotation); + mLiveTileTaskViewSimulator.setOrientationState(mOrientationState); mLiveTileTaskViewSimulator.setDrawsBelowRecents(true); } @@ -1763,7 +1763,7 @@ public abstract class RecentsView extends PagedView if (mOrientationState.setRecentsRotation(rotation)) { updateOrientationHandler(); } - mLiveTileTaskViewSimulator.setRecentsRotation(rotation); + // If overview is in modal state when rotate, reset it to overview state without running // animation. if (mActivity.isInState(OVERVIEW_MODAL_TASK)) { @@ -1798,8 +1798,6 @@ public abstract class RecentsView extends PagedView requestLayout(); // Reapply the current page to update page scrolls. setCurrentPage(mCurrentPage); - mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(), - getPagedViewOrientedState().getDisplayRotation()); } public RecentsOrientedState getPagedViewOrientedState() { From 81de07e405bdab1a694c140cfd2a2b572201367a Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 9 Nov 2020 15:58:21 -0600 Subject: [PATCH 15/27] Polish search result icons SearchResultIconRow consumes a loaded ItemInfoWithIcon from its child SearchResult and updates other siblings with itemInfo. preview: https://drive.google.com/file/d/1ijZsR5Q6M0DeZdlRJWpRb4AKmmTLBElY/view?usp=sharing Change-Id: Ibb3fb697494749fd2e886dcdaa93241767a65944 --- .../launcher3/views/SearchResultIcon.java | 73 ++++++++++--------- .../launcher3/views/SearchResultIconRow.java | 13 +++- .../views/SearchResultSuggestion.java | 6 +- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/com/android/launcher3/views/SearchResultIcon.java b/src/com/android/launcher3/views/SearchResultIcon.java index 0851b5432f..51c741b342 100644 --- a/src/com/android/launcher3/views/SearchResultIcon.java +++ b/src/com/android/launcher3/views/SearchResultIcon.java @@ -30,8 +30,6 @@ import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import androidx.annotation.UiThread; - import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; @@ -49,6 +47,8 @@ import com.android.launcher3.util.ComponentKey; import com.android.systemui.plugins.shared.SearchTarget; import com.android.systemui.plugins.shared.SearchTargetEvent; +import java.util.function.Consumer; + /** * A {@link BubbleTextView} representing a single cell result in AllApps */ @@ -61,7 +61,6 @@ public class SearchResultIcon extends BubbleTextView implements public static final String TARGET_TYPE_HERO_APP = "hero_app"; public static final String TARGET_TYPE_SHORTCUT = "shortcut"; public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action"; - public static final String TARGET_TYPE_SUGGEST = "suggest"; public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result"; public static final String REMOTE_ACTION_TOKEN = "action_token"; @@ -73,6 +72,7 @@ public class SearchResultIcon extends BubbleTextView implements private final Launcher mLauncher; private SearchTarget mSearchTarget; + private Consumer mOnItemInfoChanged; public SearchResultIcon(Context context) { this(context, null, 0); @@ -98,6 +98,15 @@ public class SearchResultIcon extends BubbleTextView implements mLauncher.getDeviceProfile().allAppsCellHeightPx)); } + /** + * Applies search target with a ItemInfoWithIcon consumer to be called after itemInfo is + * constructed + */ + public void applySearchTarget(SearchTarget searchTarget, Consumer cb) { + mOnItemInfoChanged = cb; + applySearchTarget(searchTarget); + } + @Override public void applySearchTarget(SearchTarget searchTarget) { mSearchTarget = searchTarget; @@ -111,7 +120,6 @@ public class SearchResultIcon extends BubbleTextView implements prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0)); break; case TARGET_TYPE_REMOTE_ACTION: - case TARGET_TYPE_SUGGEST: prepareUsingRemoteAction(searchTarget.getRemoteAction(), searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN), searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START), @@ -124,53 +132,45 @@ public class SearchResultIcon extends BubbleTextView implements AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore(); AppInfo appInfo = appsStore.getApp(new ComponentKey(componentName, userHandle)); applyFromApplicationInfo(appInfo); + notifyItemInfoChanged(appInfo); } private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) { WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()); - applyFromWorkspaceItem(workspaceItemInfo); + notifyItemInfoChanged(workspaceItemInfo); LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext()); MODEL_EXECUTOR.execute(() -> { launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo); - reapplyItemInfoAsync(workspaceItemInfo); + MAIN_EXECUTOR.post(() -> applyFromWorkspaceItem(workspaceItemInfo)); }); } private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start, boolean useIconToBadge) { RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start); + notifyItemInfoChanged(itemInfo); + UI_HELPER_EXECUTOR.post(() -> { + // If the Drawable from the remote action is not AdaptiveBitmap, styling will not + // work. + try (LauncherIcons li = LauncherIcons.obtain(getContext())) { + Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext()); + BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user, + Build.VERSION.SDK_INT); - applyFromRemoteActionInfo(itemInfo); - if (!loadIconFromResource()) { - UI_HELPER_EXECUTOR.post(() -> { - // If the Drawable from the remote action is not AdaptiveBitmap, styling will not - // work. - try (LauncherIcons li = LauncherIcons.obtain(getContext())) { - Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext()); - BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user, - Build.VERSION.SDK_INT); - - if (useIconToBadge) { - BitmapInfo placeholder = li.createIconBitmap( - itemInfo.getRemoteAction().getTitle().toString().substring(0, 1), - bitmap.color); - itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap); - } else { - itemInfo.bitmap = bitmap; - } - reapplyItemInfoAsync(itemInfo); + if (useIconToBadge) { + BitmapInfo placeholder = li.createIconBitmap( + itemInfo.getRemoteAction().getTitle().toString().substring(0, 1), + bitmap.color); + itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap); + } else { + itemInfo.bitmap = bitmap; } - }); - } + } + MAIN_EXECUTOR.post(() -> applyFromRemoteActionInfo(itemInfo)); + }); } - @UiThread - void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) { - MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon)); - } - - @Override public void handleSelection(int eventType) { mLauncher.getItemOnClickListener().onClick(this); @@ -208,7 +208,10 @@ public class SearchResultIcon extends BubbleTextView implements return false; } - protected boolean loadIconFromResource() { - return false; + private void notifyItemInfoChanged(ItemInfoWithIcon itemInfoWithIcon) { + if (mOnItemInfoChanged != null) { + mOnItemInfoChanged.accept(itemInfoWithIcon); + mOnItemInfoChanged = null; + } } } diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java index ecf5054d8b..e3c7661166 100644 --- a/src/com/android/launcher3/views/SearchResultIconRow.java +++ b/src/com/android/launcher3/views/SearchResultIconRow.java @@ -46,6 +46,7 @@ import com.android.systemui.plugins.shared.SearchTargetEvent; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /** * A full width representation of {@link SearchResultIcon} with a secondary label and inline @@ -53,7 +54,7 @@ import java.util.List; */ public class SearchResultIconRow extends LinearLayout implements AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener, - View.OnLongClickListener { + View.OnLongClickListener, Consumer { public static final int MAX_SHORTCUTS_COUNT = 2; @@ -95,6 +96,7 @@ public class SearchResultIconRow extends LinearLayout implements mShortcutViews[1] = findViewById(R.id.shortcut_1); mResultIcon.getLayoutParams().height = iconSize; mResultIcon.getLayoutParams().width = iconSize; + mResultIcon.setTextVisibility(false); for (BubbleTextView bubbleTextView : mShortcutViews) { ViewGroup.LayoutParams lp = bubbleTextView.getLayoutParams(); lp.width = iconSize; @@ -113,9 +115,7 @@ public class SearchResultIconRow extends LinearLayout implements @Override public void applySearchTarget(SearchTarget searchTarget) { mSearchTarget = searchTarget; - mResultIcon.applySearchTarget(searchTarget); - mResultIcon.setTextVisibility(false); - mTitleView.setText(mResultIcon.getText()); + mResultIcon.applySearchTarget(searchTarget, this); String itemType = searchTarget.getItemType(); boolean showDesc = itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT); mDescriptionView.setVisibility(showDesc ? VISIBLE : GONE); @@ -138,6 +138,11 @@ public class SearchResultIconRow extends LinearLayout implements } } + @Override + public void accept(ItemInfoWithIcon itemInfoWithIcon) { + mTitleView.setText(itemInfoWithIcon.title); + } + private void showInlineShortcuts(List infos) { if (infos == null) return; ArrayList> shortcuts = new ArrayList<>(); diff --git a/src/com/android/launcher3/views/SearchResultSuggestion.java b/src/com/android/launcher3/views/SearchResultSuggestion.java index ab94bf0342..c67b1cf7f3 100644 --- a/src/com/android/launcher3/views/SearchResultSuggestion.java +++ b/src/com/android/launcher3/views/SearchResultSuggestion.java @@ -28,6 +28,7 @@ import com.android.launcher3.R; */ public class SearchResultSuggestion extends SearchResultIcon { + public static final String TARGET_TYPE_SUGGEST = "suggest"; private final Drawable mCustomIcon; public SearchResultSuggestion(Context context) { @@ -55,8 +56,7 @@ public class SearchResultSuggestion extends SearchResultIcon { } @Override - protected boolean loadIconFromResource() { - setIcon(mCustomIcon); - return true; + protected void setIcon(Drawable icon) { + super.setIcon(mCustomIcon); } } From 3a4caed1f3df5b4b56b1e0d0271f8dd341620842 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Mon, 9 Nov 2020 22:19:37 -0800 Subject: [PATCH 16/27] Fix NPE inside getFocusdChild Bug: 172883035 Change-Id: Iea3e07165cdaa45031ee5bbc11add5c115cab366 --- src/com/android/launcher3/allapps/AlphabeticalAppsList.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 2450787e49..354f97c0a9 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -132,6 +132,9 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { * Returns the child adapter item with IME launch focus. */ public AdapterItem getFocusedChild() { + if (mAdapterItems.size() == 0) { + return null; + } return mAdapterItems.get(getFocusedChildIndex()); } From 90a43a5f00d19bb8f81d76ff3154a555db8f832a Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Mon, 9 Nov 2020 23:46:09 -0800 Subject: [PATCH 17/27] IME stability Bug: 170739594 Change-Id: Ib667c6dfb321411d59e728a1f34d4803f7293f05 --- .../AllAppsInsetTransitionController.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java index 01d01ea35e..93da1c0dd6 100644 --- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java @@ -57,7 +57,8 @@ public class AllAppsInsetTransitionController { // Only purpose of these states is to keep track of fast fling transition enum State { - RESET, DRAG_START_BOTTOM, FLING_END_TOP, + RESET, DRAG_START_BOTTOM, DRAG_START_BOTTOM_IME_CANCELLED, + FLING_END_TOP, FLING_END_TOP_IME_CANCELLED, DRAG_START_TOP, FLING_END_BOTTOM } private State mState; @@ -149,13 +150,12 @@ public class AllAppsInsetTransitionController { + " mAnimationController=" + mAnimationController); } if (mState == State.DRAG_START_BOTTOM) { - mApps.getWindowInsetsController().show(WindowInsets.Type.ime()); + mState = State.DRAG_START_BOTTOM_IME_CANCELLED; } mAnimationController = null; if (controller != null) { controller.finish(true); } - } }); } @@ -200,7 +200,7 @@ public class AllAppsInsetTransitionController { final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom; inset = Math.max(inset, mHiddenInsetBottom); inset = Math.min(inset, mShownInsetBottom); - if (DEBUG || false) { + if (DEBUG && false) { Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown=" + mDown + " hidden=" + mHiddenInsetBottom + " shown=" + mShownInsetBottom @@ -228,8 +228,14 @@ public class AllAppsInsetTransitionController { // only called when launcher restarting. UiThreadHelper.hideKeyboardAsync(mApps.getContext(), mApps.getWindowToken()); } + setState(false, true, progress); + + if (mAnimationController == null) { + if (mState == State.FLING_END_TOP_IME_CANCELLED) { + mApps.getWindowInsetsController().show(WindowInsets.Type.ime()); + } return; } @@ -268,8 +274,12 @@ public class AllAppsInsetTransitionController { } else if (end) { if (Float.compare(progress, 1f) == 0 && mState == State.DRAG_START_TOP) { state = State.FLING_END_BOTTOM; - } else if (Float.compare(progress, 0f) == 0 && mState == State.DRAG_START_BOTTOM) { - state = State.FLING_END_TOP; + } else if (Float.compare(progress, 0f) == 0) { + if (mState == State.DRAG_START_BOTTOM) { + state = State.FLING_END_TOP; + } else if (mState == State.DRAG_START_BOTTOM_IME_CANCELLED) { + state = State.FLING_END_TOP_IME_CANCELLED; + } } } if (DEBUG) { From ffd2b6d25eccac2ec9f69ccfac3883d4c80a8c1b Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 9 Nov 2020 19:04:43 -0600 Subject: [PATCH 18/27] Search support for widgets with config activity Shows widget preview in search if widget requires config. preview: https://drive.google.com/file/d/1q1ROu7-OUGfskDMRxXPNQMdr3T-WMMkv/view?usp=sharing Bug: 168321831 Test: Manual Change-Id: I6c1c168ebac4ce33a4234e8a417eba789f664f43 --- res/layout/search_result_widget_preview.xml | 8 + .../launcher3/allapps/AllAppsGridAdapter.java | 9 +- .../views/SearchResultWidgetPreview.java | 141 ++++++++++++++++++ .../launcher3/widget/BaseWidgetSheet.java | 30 ++-- 4 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 res/layout/search_result_widget_preview.xml create mode 100644 src/com/android/launcher3/views/SearchResultWidgetPreview.java diff --git a/res/layout/search_result_widget_preview.xml b/res/layout/search_result_widget_preview.xml new file mode 100644 index 0000000000..942b199b7f --- /dev/null +++ b/res/layout/search_result_widget_preview.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index d04935223b..3c88288160 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -92,6 +92,8 @@ public class AllAppsGridAdapter extends public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15; + public static final int VIEW_TYPE_SEARCH_WIDGET_PREVIEW = 1 << 16; + // Common view type masks public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER; public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_SEARCH_ICON; @@ -287,7 +289,8 @@ public class AllAppsGridAdapter extends int viewType = mApps.getAdapterItems().get(position).viewType; if (isIconViewType(viewType)) { return 1 * SPAN_MULTIPLIER; - } else if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL) { + } else if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL + || viewType == VIEW_TYPE_SEARCH_WIDGET_PREVIEW) { return mAppsPerRow; } else { // Section breaks span the full width @@ -433,6 +436,9 @@ public class AllAppsGridAdapter extends case VIEW_TYPE_SEARCH_WIDGET_LIVE: return new ViewHolder(mLayoutInflater.inflate( R.layout.search_result_widget_live, parent, false)); + case VIEW_TYPE_SEARCH_WIDGET_PREVIEW: + return new ViewHolder(mLayoutInflater.inflate( + R.layout.search_result_widget_preview, parent, false)); default: throw new RuntimeException("Unexpected view type"); } @@ -476,6 +482,7 @@ public class AllAppsGridAdapter extends case VIEW_TYPE_SEARCH_THUMBNAIL: case VIEW_TYPE_SEARCH_SUGGEST: case VIEW_TYPE_SEARCH_WIDGET_LIVE: + case VIEW_TYPE_SEARCH_WIDGET_PREVIEW: SearchAdapterItem item = (SearchAdapterItem) mApps.getAdapterItems().get(position); SearchTargetHandler payloadResultView = (SearchTargetHandler) holder.itemView; diff --git a/src/com/android/launcher3/views/SearchResultWidgetPreview.java b/src/com/android/launcher3/views/SearchResultWidgetPreview.java new file mode 100644 index 0000000000..c11c232307 --- /dev/null +++ b/src/com/android/launcher3/views/SearchResultWidgetPreview.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.views; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.Context; +import android.graphics.Point; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.Toast; + +import androidx.annotation.Nullable; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.R; +import com.android.launcher3.allapps.search.AllAppsSearchBarController; +import com.android.launcher3.allapps.search.SearchEventTracker; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.widget.BaseWidgetSheet; +import com.android.launcher3.widget.PendingItemDragHelper; +import com.android.launcher3.widget.WidgetCell; +import com.android.launcher3.widget.WidgetImageView; +import com.android.systemui.plugins.shared.SearchTarget; +import com.android.systemui.plugins.shared.SearchTargetEvent; + +/** + * displays preview of a widget upon receiving {@link AppWidgetProviderInfo} from Search provider + */ +public class SearchResultWidgetPreview extends LinearLayout implements + AllAppsSearchBarController.SearchTargetHandler, View.OnLongClickListener, + View.OnClickListener { + + public static final String TARGET_TYPE_WIDGET_PREVIEW = "widget_preview"; + private final Launcher mLauncher; + private final LauncherAppState mAppState; + private WidgetCell mWidgetCell; + private Toast mWidgetToast; + + private SearchTarget mSearchTarget; + + + public SearchResultWidgetPreview(Context context) { + this(context, null, 0); + } + + public SearchResultWidgetPreview(Context context, + @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchResultWidgetPreview(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(context); + mAppState = LauncherAppState.getInstance(context); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mWidgetCell = findViewById(R.id.widget_cell); + mWidgetCell.setOnLongClickListener(this); + mWidgetCell.setOnClickListener(this); + } + + @Override + public void applySearchTarget(SearchTarget searchTarget) { + if (searchTarget.getExtras() == null + || searchTarget.getExtras().getParcelable("provider") == null) { + setVisibility(GONE); + return; + } + mSearchTarget = searchTarget; + AppWidgetProviderInfo providerInfo = searchTarget.getExtras().getParcelable("provider"); + LauncherAppWidgetProviderInfo pInfo = LauncherAppWidgetProviderInfo.fromProviderInfo( + getContext(), providerInfo); + MODEL_EXECUTOR.post(() -> { + WidgetItem widgetItem = new WidgetItem(pInfo, mLauncher.getDeviceProfile().inv, + mAppState.getIconCache()); + MAIN_EXECUTOR.post(() -> { + mWidgetCell.applyFromCellItem(widgetItem, mAppState.getWidgetCache()); + mWidgetCell.ensurePreview(); + }); + }); + + } + + @Override + public boolean onLongClick(View view) { + view.cancelLongPress(); + if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; + if (mWidgetCell.getTag() == null) return false; + + WidgetImageView imageView = mWidgetCell.getWidgetView(); + if (imageView.getBitmap() == null) { + return false; + } + + int[] loc = new int[2]; + mLauncher.getDragLayer().getLocationInDragLayer(imageView, loc); + + new PendingItemDragHelper(mWidgetCell).startDrag( + imageView.getBitmapBounds(), imageView.getBitmap().getWidth(), imageView.getWidth(), + new Point(loc[0], loc[1]), mLauncher.getAppsView(), new DragOptions()); + handleSelection(SearchTargetEvent.LONG_PRESS); + return true; + } + + @Override + public void onClick(View view) { + mWidgetToast = BaseWidgetSheet.showWidgetToast(getContext(), mWidgetToast); + handleSelection(SearchTargetEvent.SELECT); + } + + @Override + public void handleSelection(int eventType) { + SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent( + new SearchTargetEvent.Builder(mSearchTarget, eventType).build()); + } +} diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java index 01af96cb33..a38e90dfd8 100644 --- a/src/com/android/launcher3/widget/BaseWidgetSheet.java +++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java @@ -42,7 +42,7 @@ import com.android.launcher3.views.AbstractSlideInView; /** * Base class for various widgets popup */ -abstract class BaseWidgetSheet extends AbstractSlideInView +public abstract class BaseWidgetSheet extends AbstractSlideInView implements OnClickListener, OnLongClickListener, DragSource, PopupDataProvider.PopupDataChangeListener { @@ -74,16 +74,7 @@ abstract class BaseWidgetSheet extends AbstractSlideInView @Override public final void onClick(View v) { - // Let the user know that they have to long press to add a widget - if (mWidgetInstructionToast != null) { - mWidgetInstructionToast.cancel(); - } - - CharSequence msg = Utilities.wrapForTts( - getContext().getText(R.string.long_press_widget_to_add), - getContext().getString(R.string.long_accessible_way_to_add)); - mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT); - mWidgetInstructionToast.show(); + mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast); } @Override @@ -146,4 +137,21 @@ abstract class BaseWidgetSheet extends AbstractSlideInView protected SystemUiController getSystemUiController() { return mLauncher.getSystemUiController(); } + + /** + * Show Widget tap toast prompting user to drag instead + */ + public static Toast showWidgetToast(Context context, Toast toast) { + // Let the user know that they have to long press to add a widget + if (toast != null) { + toast.cancel(); + } + + CharSequence msg = Utilities.wrapForTts( + context.getText(R.string.long_press_widget_to_add), + context.getString(R.string.long_accessible_way_to_add)); + toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); + toast.show(); + return toast; + } } From 5278e85c4c207c937949cfb2cef99077475c8ce9 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Tue, 10 Nov 2020 10:39:54 -0800 Subject: [PATCH 19/27] People row text size fix Change-Id: I912f8d41bf49215988f37a2405086bf4d737af99 --- res/layout/search_result_people_item.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/layout/search_result_people_item.xml b/res/layout/search_result_people_item.xml index a603941dfa..7526f6f10f 100644 --- a/res/layout/search_result_people_item.xml +++ b/res/layout/search_result_people_item.xml @@ -29,6 +29,7 @@ android:layout_width="0dp" android:textColor="?android:attr/textColorPrimary" android:id="@+id/title" + android:textSize="@dimen/search_hero_title_size" android:layout_height="wrap_content" android:layout_weight="1" /> From ae1997b9d51f996dff41bd792a33176a112b1ef2 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Tue, 10 Nov 2020 16:53:25 -0800 Subject: [PATCH 20/27] Include more error logging for widget restore Bug: 171774227 Test: manually tested complete backup & restore flow Change-Id: Ieb4b39d80a24b4dff377ee74d24d9010acd0daca --- src/com/android/launcher3/Launcher.java | 4 +++- src/com/android/launcher3/LauncherProvider.java | 6 ++++++ src/com/android/launcher3/model/LoaderTask.java | 6 +++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index aa123f6b26..058eca8293 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2340,7 +2340,9 @@ public class Launcher extends StatefulActivity implements Launche if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { // Verify that we own the widget if (appWidgetInfo == null) { - FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId); + FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId + + ",title=" + item.title + + ",providerName=" + item.providerName.toShortString()); getModelWriter().deleteWidgetInfo(item, getAppWidgetHost()); return null; } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index aeed16aa28..6af248cd78 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -85,6 +85,7 @@ import java.util.Arrays; import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.stream.Collectors; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; @@ -931,6 +932,11 @@ public class LauncherProvider extends ContentProvider { final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, Favorites.APPWIDGET_ID, "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null)); + final String allWidgetIds = Arrays.stream(allWidgets).mapToObj(String::valueOf) + .collect(Collectors.joining(",")); + final String validWidgetIds = validWidgets.getArray().toConcatString(); + FileLog.d(TAG, "All widget ids: " + allWidgetIds); + FileLog.d(TAG, "Valid widget ids: " + validWidgetIds); for (int widgetId : allWidgets) { if (!validWidgets.contains(widgetId)) { try { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index b108788061..80a684d86e 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -745,7 +745,11 @@ public class LoaderTask implements Runnable { + "span=" + appWidgetInfo.spanX + "x" + appWidgetInfo.spanY + " minSpan=" + widgetProviderInfo.minSpanX + "x" - + widgetProviderInfo.minSpanY); + + widgetProviderInfo.minSpanY + + ", appWidgetInfo.provider=" + + appWidgetInfo.providerName.toShortString() + + ", widgetProviderInfo.provider=" + + widgetProviderInfo.provider.toShortString()); continue; } if (!c.isOnWorkspaceOrHotseat()) { From c25bc0335b29a0e36d5e8586cb61781e91aff36e Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 11 Nov 2020 10:24:34 -0600 Subject: [PATCH 21/27] Fix OutOfBounds issue on getFocusedChild In cases where search only shows items that don't require focus highlighting,getFocusedChildIndex returns -1. Bug: 13023194 Change-Id: I6145ae26e3953c5fc4fd1ed1696eb931017cac0f --- src/com/android/launcher3/allapps/AlphabeticalAppsList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 354f97c0a9..f235199d12 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -132,7 +132,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { * Returns the child adapter item with IME launch focus. */ public AdapterItem getFocusedChild() { - if (mAdapterItems.size() == 0) { + if (mAdapterItems.size() == 0 || getFocusedChildIndex() != -1) { return null; } return mAdapterItems.get(getFocusedChildIndex()); From 437513d5ef7ae1b57bb874e7225587b0cfdc48e3 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 11 Nov 2020 11:42:03 -0600 Subject: [PATCH 22/27] Federate SliveLiveData for Search results Instead of every slice result creating its own live data, we should manage a single SliceLiveData per url that SliceViews can observe. Bug: 173022319 Test: Manual Change-Id: Ieaf599aadfac40450be4f39657d3cefb70644e0c --- .../allapps/search/LiveSearchManager.java | 27 ++++++++++++++++++- .../views/SearchResultSettingsSlice.java | 5 ++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java index e8a00d958a..ec33908c75 100644 --- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java +++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java @@ -23,9 +23,14 @@ import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; +import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; +import androidx.lifecycle.LiveData; +import androidx.slice.Slice; +import androidx.slice.widget.SliceLiveData; + import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.util.ComponentKey; @@ -42,7 +47,9 @@ public class LiveSearchManager { private final Launcher mLauncher; private final AppWidgetManager mAppWidgetManger; - private HashMap mWidgetPlaceholders = new HashMap<>(); + private final HashMap mWidgetPlaceholders = + new HashMap<>(); + private final HashMap> mUriSliceMap = new HashMap<>(); private SearchWidgetHost mSearchWidgetHost; public LiveSearchManager(Launcher launcher) { @@ -87,6 +94,20 @@ public class LiveSearchManager { return view; } + /** + * Creates {@link LiveData} from Slice Uri. Caches created live data to be reused + * within the same search session. Removes previous observers when new SliceView request a + * live data for observation. + */ + public LiveData getSliceForUri(Uri sliceUri) { + LiveData sliceLiveData = mUriSliceMap.getOrDefault(sliceUri, null); + if (sliceLiveData == null) { + sliceLiveData = SliceLiveData.fromUri(mLauncher, sliceUri); + mUriSliceMap.put(sliceUri, sliceLiveData); + } + return sliceLiveData; + } + /** * Start search session */ @@ -109,6 +130,10 @@ public class LiveSearchManager { mWidgetPlaceholders.clear(); mSearchWidgetHost = null; } + for (LiveData liveData : mUriSliceMap.values()) { + liveData.removeObservers(mLauncher); + } + mUriSliceMap.clear(); } static class SearchWidgetHost extends AppWidgetHost { diff --git a/src/com/android/launcher3/views/SearchResultSettingsSlice.java b/src/com/android/launcher3/views/SearchResultSettingsSlice.java index 2d726e79d4..29e6c1b8cf 100644 --- a/src/com/android/launcher3/views/SearchResultSettingsSlice.java +++ b/src/com/android/launcher3/views/SearchResultSettingsSlice.java @@ -28,7 +28,6 @@ import androidx.lifecycle.LiveData; import androidx.slice.Slice; import androidx.slice.SliceItem; import androidx.slice.widget.EventInfo; -import androidx.slice.widget.SliceLiveData; import androidx.slice.widget.SliceView; import com.android.launcher3.Launcher; @@ -54,7 +53,7 @@ public class SearchResultSettingsSlice extends LinearLayout implements private View mIcon; private LiveData mSliceLiveData; private SearchTarget mSearchTarget; - private Launcher mLauncher; + private final Launcher mLauncher; public SearchResultSettingsSlice(Context context) { this(context, null, 0); @@ -84,7 +83,7 @@ public class SearchResultSettingsSlice extends LinearLayout implements reset(); mSearchTarget = searchTarget; try { - mSliceLiveData = SliceLiveData.fromUri(mLauncher, getSliceUri()); + mSliceLiveData = mLauncher.getLiveSearchManager().getSliceForUri(getSliceUri()); mSliceLiveData.observe(mLauncher, mSliceView); } catch (Exception ex) { Log.e(TAG, "unable to bind slice", ex); From 0112a3d4341f82269f6e5e4a4f2f10dd9b5cb59d Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 11 Nov 2020 12:20:59 -0800 Subject: [PATCH 23/27] Fixing animation cancelled not called if gesture was not initiated Previously, in case of a tap in the gesture region, swipeHandler was not explicitly cancelling the launcher state animation. It was getting reset bucause launcher gets onStop immediately after that. But in some cases, it may not get onStop (if the top activity finishes), which leaves the Launcher UI in a broken state Bug: 172842997 Change-Id: Iff7b1e5b8f67b2f4dd8096b88c4cd97115ec912a --- quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 041f67981d..5ba3f6a96d 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -123,7 +123,7 @@ public abstract class AbsSwipeUpHandler, Q extends RecentsAnimationCallbacks.RecentsAnimationListener { private static final String TAG = "AbsSwipeUpHandler"; - private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; + private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null; protected final BaseActivityInterface mActivityInterface; protected final InputConsumerProxy mInputConsumerProxy; @@ -185,6 +185,8 @@ public abstract class AbsSwipeUpHandler, Q extends getFlagForIndex(14, "STATE_START_NEW_TASK"); private static final int STATE_CURRENT_TASK_FINISHED = getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED"); + private static final int STATE_FINISH_WITH_NO_END = + getFlagForIndex(16, "STATE_FINISH_WITH_NO_END"); private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; @@ -306,6 +308,8 @@ public abstract class AbsSwipeUpHandler, Q extends this::invalidateHandlerWithLauncher); mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, this::notifyTransitionCancelled); + mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END, + this::notifyTransitionCancelled); if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT @@ -1257,6 +1261,7 @@ public abstract class AbsSwipeUpHandler, Q extends if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) { cancelCurrentAnimation(); } else { + mStateCallback.setStateOnUiThread(STATE_FINISH_WITH_NO_END); reset(); } } From 986ea0da6037ed9f3c0e2dcba95358bad6ff7778 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Thu, 5 Nov 2020 17:07:35 -0800 Subject: [PATCH 24/27] Fixing Robolectic model tests Some tests were broken because of refactoring in LauncherModel. Change-Id: I55aa32d75a4d16338796a1b1765717fd72ba4b33 --- .../launcher3/BaseQuickstepLauncher.java | 4 +++- robolectric_tests/Android.mk | 2 +- .../folder/FolderNameProviderTest.java | 12 +++++++++--- .../android/launcher3/logging/FileLogTest.java | 17 +++-------------- .../model/ModelMultiCallbacksTest.java | 6 ++++-- .../secondarydisplay/SDWorkModeTest.java | 5 +---- .../launcher3/shadows/ShadowLooperExecutor.java | 11 +++++++++++ .../launcher3/util/LauncherModelHelper.java | 8 ++++++-- 8 files changed, 38 insertions(+), 27 deletions(-) diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 456727c069..c724318c2d 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -326,6 +326,8 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override public void onAttachedToWindow() { super.onAttachedToWindow(); - InteractionJankMonitorWrapper.init(getWindow().getDecorView()); + if (Utilities.ATLEAST_R) { + InteractionJankMonitorWrapper.init(getWindow().getDecorView()); + } } } diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk index 3fa9b0ab63..836ded5d25 100644 --- a/robolectric_tests/Android.mk +++ b/robolectric_tests/Android.mk @@ -59,4 +59,4 @@ LOCAL_INSTRUMENT_SOURCE_DIRS := packages/apps/Launcher3/src LOCAL_ROBOTEST_TIMEOUT := 36000 -include prebuilts/misc/common/robolectric/4.3.1/run_robotests.mk +include prebuilts/misc/common/robolectric/4.4/run_robotests.mk diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java index b7ba10696f..2a94d9bc73 100644 --- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java +++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java @@ -25,16 +25,20 @@ import android.os.UserHandle; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.Executors; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; import java.util.ArrayList; @RunWith(RobolectricTestRunner.class) +@LooperMode(Mode.PAUSED) public final class FolderNameProviderTest { private Context mContext; private WorkspaceItemInfo mItem1; @@ -58,18 +62,20 @@ public final class FolderNameProviderTest { } @Test - public void getSuggestedFolderName_workAssignedToEnd() { + public void getSuggestedFolderName_workAssignedToEnd() throws Exception { ArrayList list = new ArrayList<>(); list.add(mItem1); list.add(mItem2); FolderNameInfos nameInfos = new FolderNameInfos(); - new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos); + Executors.MODEL_EXECUTOR.submit(() -> + new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos)).get(); assertEquals("Work", nameInfos.getLabels()[0]); nameInfos.setLabel(0, "candidate1", 1.0f); nameInfos.setLabel(1, "candidate2", 1.0f); nameInfos.setLabel(2, "candidate3", 1.0f); - new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos); + Executors.MODEL_EXECUTOR.submit(() -> + new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos)).get(); assertEquals("Work", nameInfos.getLabels()[3]); assertTrue(nameInfos.hasSuggestions()); assertTrue(nameInfos.hasPrimary()); diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java index c892618a9d..01b23ba966 100644 --- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java +++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java @@ -9,8 +9,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; -import org.robolectric.util.Scheduler; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; import java.io.File; import java.io.PrintWriter; @@ -21,11 +21,10 @@ import java.util.Calendar; * Tests for {@link FileLog} */ @RunWith(RobolectricTestRunner.class) +@LooperMode(Mode.PAUSED) public class FileLogTest { private File mTempDir; - private boolean mTestActive; - @Before public void setUp() { int count = 0; @@ -35,14 +34,6 @@ public class FileLogTest { } while (!mTempDir.mkdir()); FileLog.setDir(mTempDir); - - mTestActive = true; - Scheduler scheduler = Shadows.shadowOf(FileLog.getHandler().getLooper()).getScheduler(); - new Thread(() -> { - while (mTestActive) { - scheduler.advanceToLastPostedRunnable(); - } - }).start(); } @After @@ -52,8 +43,6 @@ public class FileLogTest { new File(mTempDir, "log-" + i).delete(); } mTempDir.delete(); - - mTestActive = false; } @Test diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java index 87fe3c086b..aab6c25f1c 100644 --- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java @@ -30,6 +30,7 @@ import com.android.launcher3.PagedView; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.shadows.ShadowLooperExecutor; import com.android.launcher3.util.Executors; import com.android.launcher3.util.LauncherLayoutBuilder; import com.android.launcher3.util.LauncherModelHelper; @@ -43,8 +44,8 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.LooperMode; import org.robolectric.annotation.LooperMode.Mode; +import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowPackageManager; -import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.Arrays; @@ -74,7 +75,8 @@ public class ModelMultiCallbacksTest { // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread, // so that we can wait appropriately for the loader to complete. mTempMainExecutor = new LooperExecutor(createAndStartNewForegroundLooper("tempMain")); - ReflectionHelpers.setField(mModelHelper.getModel(), "mMainExecutor", mTempMainExecutor); + ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR); + sle.setHandler(mTempMainExecutor.getHandler()); } @Test diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java index baae2a6a80..e3694aeddc 100644 --- a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java +++ b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java @@ -59,8 +59,6 @@ public class SDWorkModeTest { private InvariantDeviceProfile mIdp; private LauncherModelHelper mModelHelper; - private LauncherLayoutBuilder mLayoutBuilder; - @Before public void setup() throws Exception { mModelHelper = new LauncherModelHelper(); @@ -70,7 +68,6 @@ public class SDWorkModeTest { Settings.Global.WINDOW_ANIMATION_SCALE, 0); mModelHelper.installApp(TEST_PACKAGE); - mLayoutBuilder = new LauncherLayoutBuilder(); } @Test @@ -86,7 +83,7 @@ public class SDWorkModeTest { public void testAllAppsList_workProfile() throws Exception { ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class)); sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM); - sum.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE); + sum.addProfile(SYSTEM_USER, WORK_PROFILE_ID, "work", FLAG_PROFILE); SecondaryDisplayLauncher launcher = loadLauncher(); launcher.showAppDrawer(true); diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java index a3b7dc706f..57eda7eae8 100644 --- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java +++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java @@ -23,6 +23,8 @@ import static org.robolectric.util.ReflectionHelpers.setField; import android.os.Handler; +import androidx.annotation.Nullable; + import com.android.launcher3.util.LooperExecutor; import org.robolectric.annotation.Implementation; @@ -37,8 +39,13 @@ public class ShadowLooperExecutor { @RealObject private LooperExecutor mRealExecutor; + private Handler mOverriddenHandler; + @Implementation protected Handler getHandler() { + if (mOverriddenHandler != null) { + return mOverriddenHandler; + } Handler handler = directlyOn(mRealExecutor, LooperExecutor.class, "getHandler"); Thread thread = handler.getLooper().getThread(); if (!thread.isAlive()) { @@ -49,4 +56,8 @@ public class ShadowLooperExecutor { } return directlyOn(mRealExecutor, LooperExecutor.class, "getHandler"); } + + public void setHandler(@Nullable Handler handler) { + mOverriddenHandler = handler; + } } diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java index f2b3071c95..849f98b8ff 100644 --- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java +++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -48,10 +48,12 @@ import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.pm.UserCache; +import com.android.launcher3.shadows.ShadowLooperExecutor; import org.mockito.ArgumentCaptor; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowContentResolver; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.util.ReflectionHelpers; @@ -403,14 +405,16 @@ public class LauncherModelHelper { public void loadModelSync() throws ExecutionException, InterruptedException { // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread, // so that we can wait appropriately for the loader to complete. - ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.UI_HELPER_EXECUTOR); + ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR); + sle.setHandler(Executors.UI_HELPER_EXECUTOR.getHandler()); Callbacks mockCb = mock(Callbacks.class); getModel().addCallbacksAndLoad(mockCb); Executors.MODEL_EXECUTOR.submit(() -> { }).get(); Executors.UI_HELPER_EXECUTOR.submit(() -> { }).get(); - ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.MAIN_EXECUTOR); + + sle.setHandler(null); getModel().removeCallbacks(mockCb); } From 7bbec50031ed4f482d615e9516ec5be5432cb565 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Thu, 29 Oct 2020 11:39:24 -0400 Subject: [PATCH 25/27] Fix AllAppsSearchPlugin icon redrawing without special-casing. Preventing icon animations in deep shortcuts fixed the icon redrawing issue in the all apps search page, however other icons could still be redrawn, yet could not be special-cased without removing icon update animations altogether. Added logic to only animate icon updates outside of the all apps search page. Demo: https://drive.google.com/file/d/1ReT2O_1tV20terY0Jr1NJGxIxPblHCK4/view?usp=sharing Change-Id: Ibe0b43801ec5340c3551125aef13170d569d2c55 --- src/com/android/launcher3/BubbleTextView.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 5cd63721d3..3eb52adf1a 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -171,6 +171,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private IconLoadRequest mIconLoadRequest; + private boolean mEnableIconUpdateAnimation = false; + public BubbleTextView(Context context) { this(context, null, 0); } @@ -697,6 +699,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, applyCompoundDrawables(icon); } + protected boolean iconUpdateAnimationEnabled() { + return mEnableIconUpdateAnimation; + } + protected void applyCompoundDrawables(Drawable icon) { // If we had already set an icon before, disable relayout as the icon size is the // same as before. @@ -706,13 +712,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, updateIcon(icon); - ItemInfo itemInfo = (ItemInfo) getTag(); - // If the current icon is a placeholder color, animate its update. if (mIcon != null && mIcon instanceof PlaceHolderIconDrawable - && (itemInfo == null - || itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) { + && iconUpdateAnimationEnabled()) { animateIconUpdate((PlaceHolderIconDrawable) mIcon, icon); } @@ -734,6 +737,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, if (getTag() == info) { mIconLoadRequest = null; mDisableRelayout = true; + mEnableIconUpdateAnimation = true; // Optimization: Starting in N, pre-uploads the bitmap to RenderThread. info.bitmap.icon.prepareToDraw(); @@ -750,6 +754,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } mDisableRelayout = false; + mEnableIconUpdateAnimation = false; } } From 64154ead741628755b08395b5403e4c0da9735d8 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Fri, 6 Nov 2020 13:09:09 -0500 Subject: [PATCH 26/27] Fix hotseat predicted app update regression. Hotseat predicted app updates were only being allowed then the hotseat was invisible or when the workspace was loading. This was too strict. Changed the logic to ignore hotseat visibility when the workspace is loading, when there are empty spots in the hotseat and when the predicted items are unchanged. This ensures the hotseat always has the intended number of icons and that icons can be updated in-place. Fixes: 172597819 Demo: https://drive.google.com/file/d/100svnqX048Da7vues2p-vvQNN6sIF8v4/view?usp=sharing Change-Id: I2f93ff44618c0282a6411918c34d70f75ec73b73 --- .../launcher3/hybridhotseat/HotseatPredictionController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index d3b7e22299..4953138bee 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -251,7 +251,10 @@ public class HotseatPredictionController implements DragController.DragListener, * Sets or updates the predicted items */ public void setPredictedItems(FixedContainerItems items) { - if (!mLauncher.isWorkspaceLoading() + boolean shouldIgnoreVisibility = mLauncher.isWorkspaceLoading() + || mPredictedItems.equals(items.items) + || mHotseat.getShortcutsAndWidgets().getChildCount() < mHotSeatItemsCount; + if (!shouldIgnoreVisibility && mHotseat.isShown() && mHotseat.getWindowVisibility() == View.VISIBLE) { mHotseat.setOnVisibilityAggregatedCallback((isVisible) -> { From 4dd0fb173bde07fd057bc1f9fdf89417ddaec148 Mon Sep 17 00:00:00 2001 From: vadimt Date: Wed, 11 Nov 2020 19:25:09 -0800 Subject: [PATCH 27/27] Removing blocking call during cuj_LAUNCHER_APP_LAUNCH_FROM_ICON This is a part of Blue Chip de-janking effort and is a P0 item in our CUJ list. In the last frame of the app-open animation, we have a android.view.IRemoteAnimationFinishedCallback$Stub$Proxy.onAnimationFinished call which is blocking and prone to lock contention in WindowManager. Moving the call to after the animation. Bug: 173072373 Change-Id: If5eb6469900d1892b861eda6d6b4807d90630b6b --- .../src/com/android/launcher3/LauncherAnimationRunner.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java index 75355c9b4e..069fe2dcd1 100644 --- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java +++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java @@ -17,6 +17,7 @@ package com.android.launcher3; import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.util.DisplayController.getSingleFrameMs; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously; import android.animation.Animator; @@ -62,7 +63,7 @@ public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCo Runnable r = () -> { finishExistingAnimation(); mAnimationResult = new AnimationResult(() -> { - runnable.run(); + UI_HELPER_EXECUTOR.execute(runnable); mAnimationResult = null; }); onCreateAnimation(appTargets, wallpaperTargets, mAnimationResult);