From f0c7966a77f0a62896921b2174e18516fd02a15a Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Tue, 12 May 2020 16:20:04 -0500 Subject: [PATCH 01/25] Only set spring end value if animation wasn't canceled Animations should only be canceled (as opposed to ended) if the caller is intending to update the state from there. In that case, we shouldn't jump to the end value. Bug: 155335494 Change-Id: I93a21f0e2e1923ccdfab890adcb58c49ceb98a28 --- src/com/android/launcher3/anim/SpringAnimationBuilder.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java index 770df034fb..a9702b4784 100644 --- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java +++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java @@ -18,7 +18,6 @@ package com.android.launcher3.anim; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.util.FloatProperty; @@ -197,9 +196,9 @@ public class SpringAnimationBuilder { animator.setDuration(getDuration()).setInterpolator(LINEAR); animator.addUpdateListener(anim -> property.set(target, getInterpolatedValue(anim.getAnimatedFraction()))); - animator.addListener(new AnimatorListenerAdapter() { + animator.addListener(new AnimationSuccessListener() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationSuccess(Animator animation) { property.set(target, mEndValue); } }); From 6524cc72376ee4907ced2ca4fee757e85c1c8933 Mon Sep 17 00:00:00 2001 From: thiruram Date: Fri, 8 May 2020 11:04:32 -0700 Subject: [PATCH 02/25] Adds LAUNCHER_FOLDER_LABEL_CHANGED event. Sample Log: https://docs.google.com/document/d/1CBP2yTcXdFhPdNG5ZmWFKSgd8mDbMevY-akVlUXPLDo/edit#bookmark=id.qwjknn6acmx6 Bug: 155410872 Bug: 152978018 Change-Id: Ib7641d3d42a3f4fd002d1dbb36dc4b9ea0f885fc --- protos/launcher_atom.proto | 78 ++++++++ src/com/android/launcher3/folder/Folder.java | 170 +----------------- .../android/launcher3/folder/FolderIcon.java | 17 +- .../launcher3/logging/StatsLogManager.java | 4 + .../launcher3/model/data/FolderInfo.java | 140 +++++++++++++++ 5 files changed, 235 insertions(+), 174 deletions(-) diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto index cac2d8f5a7..19f72132c8 100644 --- a/protos/launcher_atom.proto +++ b/protos/launcher_atom.proto @@ -95,7 +95,17 @@ message Task { // Represents folder in a closed state. message FolderIcon { + // Number of items inside folder. optional int32 cardinality = 1; + + // State of the folder label before the event. + optional FromState from_state = 2; + + // State of the folder label after the event. + optional ToState to_state = 3; + + // Populated only when folder label was suggested. + optional string label = 4; } ////////////////////////////////////////////// @@ -120,3 +130,71 @@ message FolderContainer { HotseatContainer hotseat = 5; } } + +// Represents state of FolderLabel before editing. +enum FromState { + // Default value. + FROM_STATE_UNSPECIFIED = 0; + + // FolderLabel was empty. + FROM_EMPTY = 1; + + // FolderLabel was non-empty and manually entered by the user. + FROM_CUSTOM = 2; + + // FolderLabel was non-empty and one of the suggestions. + FROM_SUGGESTED = 3; +} + +// Represents state of FolderLabel after editing. +enum ToState { + // Default value. + TO_STATE_UNSPECIFIED = 0; + // User attempted to change the folder label, but was not changed. + UNCHANGED = 1; + + // New label matches with primary(aka top) suggestion. + TO_SUGGESTION0 = 2; + + // New label matches with second top suggestion even though the top suggestion was non-empty. + TO_SUGGESTION1_WITH_VALID_PRIMARY = 3; + + // New label matches with second top suggestion given that top suggestion was empty. + TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 4; + + // New label matches with third top suggestion even though the top suggestion was non-empty. + TO_SUGGESTION2_WITH_VALID_PRIMARY = 5; + + // New label matches with third top suggestion given that top suggestion was empty. + TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 6; + + // New label matches with 4th top suggestion even though the top suggestion was non-empty. + TO_SUGGESTION3_WITH_VALID_PRIMARY = 7; + + // New label matches with 4th top suggestion given that top suggestion was empty. + TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 8; + + // New label is empty even though the top suggestion was non-empty. + TO_EMPTY_WITH_VALID_PRIMARY = 9; + + // New label is empty given that top suggestion was empty. + TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 10; + + // New label is empty given that no suggestions were provided. + TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 11; + + // New label is empty given that suggestions feature was disabled. + TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 12; + + // New label is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty. + TO_CUSTOM_WITH_VALID_PRIMARY = 13; + + // New label is non-empty and not match with any suggestions given that top suggestion was empty. + TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 14; + + // New label is non-empty and also no suggestions were provided. + TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 15; + + // New label is non-empty and also suggestions feature was disable. + TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 16; +} diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 9a36b3ed7c..29a737c227 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -18,23 +18,15 @@ package com.android.launcher3.folder; import static android.text.TextUtils.isEmpty; -import static androidx.core.util.Preconditions.checkNotNull; - import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; -import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED; import static java.util.Arrays.asList; -import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; import android.animation.Animator; @@ -94,12 +86,6 @@ import com.android.launcher3.model.data.FolderInfo.FolderListener; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; -import com.android.launcher3.userevent.LauncherLogProto.Action; -import com.android.launcher3.userevent.LauncherLogProto.ContainerType; -import com.android.launcher3.userevent.LauncherLogProto.ItemType; -import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent; -import com.android.launcher3.userevent.LauncherLogProto.Target; -import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Executors; import com.android.launcher3.util.Thunk; @@ -111,10 +97,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; -import java.util.Optional; -import java.util.OptionalInt; import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * Represents a set of icons chosen by the user or generated by the system. @@ -213,8 +196,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Thunk int mScrollHintDir = SCROLL_NONE; @Thunk int mCurrentScrollDir = SCROLL_NONE; - private String mPreviousLabel; - private boolean mIsPreviousLabelSuggested; /** * Used to inflate the Workspace from XML. @@ -348,9 +329,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (DEBUG) { Log.d(TAG, "onBackKey newTitle=" + newTitle); } - + mInfo.previousTitle = mInfo.title; mInfo.title = newTitle; - mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !getAcceptedSuggestionIndex().isPresent(), + mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(), mLauncher.getModelWriter()); mFolderIcon.onTitleChanged(newTitle); mLauncher.getModelWriter().updateItemInDatabase(mInfo); @@ -441,8 +422,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } mItemsInvalidated = true; mInfo.addListener(this); - Optional.ofNullable(mInfo.title).ifPresent(title -> mPreviousLabel = title.toString()); - mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME); if (!isEmpty(mInfo.title)) { mFolderName.setText(mInfo.title); @@ -1455,7 +1434,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (hasFocus) { startEditingFolderName(); } else { - logCurrentFolderLabelState(); mFolderName.dispatchBackKey(); } } @@ -1653,148 +1631,4 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public FolderPagedView getContent() { return mContent; } - - protected void logCurrentFolderLabelState() { - LauncherEvent launcherEvent = LauncherEvent.newBuilder() - .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD)) - .addSrcTarget(newEditTextTargetBuilder() - .setFromFolderLabelState(getFromFolderLabelState()) - .setToFolderLabelState(getToFolderLabelState())) - .addSrcTarget(newFolderTargetBuilder()) - .addSrcTarget(newParentContainerTarget()) - .build(); - mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent); - mPreviousLabel = mFolderName.getText().toString(); - mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME); - } - - private Target.FromFolderLabelState getFromFolderLabelState() { - return mPreviousLabel == null - ? FROM_FOLDER_LABEL_STATE_UNSPECIFIED - : mPreviousLabel.isEmpty() - ? FROM_EMPTY - : mIsPreviousLabelSuggested - ? FROM_SUGGESTED - : FROM_CUSTOM; - } - - private Target.ToFolderLabelState getToFolderLabelState() { - String newLabel = - checkNotNull(mFolderName.getText().toString(), - "Expected valid folder label, but found null"); - if (newLabel.equals(mPreviousLabel)) { - return Target.ToFolderLabelState.UNCHANGED; - } - - if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) { - return newLabel.isEmpty() - ? ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED - : ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED; - } - - Optional suggestedLabels = getSuggestedLabels(); - boolean isEmptySuggestions = suggestedLabels - .map(labels -> stream(labels).allMatch(TextUtils::isEmpty)) - .orElse(true); - if (isEmptySuggestions) { - return newLabel.isEmpty() - ? ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS - : ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS; - } - - boolean hasValidPrimary = suggestedLabels - .map(labels -> !isEmpty(labels[0])) - .orElse(false); - if (newLabel.isEmpty()) { - return hasValidPrimary ? ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; - } - - OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex(); - if (!accepted_suggestion_index.isPresent()) { - return hasValidPrimary ? ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; - } - - switch (accepted_suggestion_index.getAsInt()) { - case 0: - return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY; - case 1: - return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY; - case 2: - return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY; - case 3: - return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY; - default: - // fall through - } - return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED; - - } - - private Optional getSuggestedLabels() { - return ofNullable(mInfo) - .map(info -> info.suggestedFolderNames) - .map( - folderNames -> - (FolderNameInfo[]) - folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS)) - .map( - folderNameInfoArray -> - stream(folderNameInfoArray) - .filter(Objects::nonNull) - .map(FolderNameInfo::getLabel) - .filter(Objects::nonNull) - .map(CharSequence::toString) - .toArray(String[]::new)); - } - - private OptionalInt getAcceptedSuggestionIndex() { - String newLabel = checkNotNull(mFolderName.getText().toString(), - "Expected valid folder label, but found null"); - return getSuggestedLabels() - .map(suggestionsArray -> - IntStream.range(0, suggestionsArray.length) - .filter( - index -> !isEmpty(suggestionsArray[index]) - && newLabel.equalsIgnoreCase(suggestionsArray[index])) - .sequential() - .findFirst() - ).orElse(OptionalInt.empty()); - - } - - - private Target.Builder newEditTextTargetBuilder() { - return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT); - } - - private Target.Builder newFolderTargetBuilder() { - return Target.newBuilder() - .setType(Target.Type.CONTAINER) - .setContainerType(ContainerType.FOLDER) - .setPageIndex(mInfo.screenId) - .setGridX(mInfo.cellX) - .setGridY(mInfo.cellY) - .setCardinality(mInfo.contents.size()); - } - - private Target.Builder newParentContainerTarget() { - Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER); - switch (mInfo.container) { - case CONTAINER_HOTSEAT: - return builder.setContainerType(ContainerType.HOTSEAT); - case CONTAINER_DESKTOP: - return builder.setContainerType(ContainerType.WORKSPACE); - default: - throw new AssertionError(String - .format("Expected container to be either %s or %s but found %s.", - CONTAINER_HOTSEAT, - CONTAINER_DESKTOP, - mInfo.container)); - } - } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 93208d4a09..bb358abf89 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -20,6 +20,7 @@ import static android.text.TextUtils.isEmpty; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_CHANGED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -62,6 +63,8 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.icons.DotRenderer; +import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.FolderInfo.FolderListener; @@ -410,10 +413,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel Executors.UI_HELPER_EXECUTOR.post(() -> { d.folderNameProvider.getSuggestedFolderName( getContext(), mInfo.contents, nameInfos); - showFinalView(finalIndex, item, nameInfos); + showFinalView(finalIndex, item, nameInfos, d.logInstanceId); }); } else { - showFinalView(finalIndex, item, nameInfos); + showFinalView(finalIndex, item, nameInfos, d.logInstanceId); } } else { addItem(item); @@ -421,12 +424,11 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } private void showFinalView(int finalIndex, final WorkspaceItemInfo item, - FolderNameInfo[] nameInfos) { + FolderNameInfo[] nameInfos, InstanceId instanceId) { postDelayed(() -> { mPreviewItemManager.hidePreviewItem(finalIndex, false); mFolder.showItem(item); - setLabelSuggestion(nameInfos); - mFolder.logCurrentFolderLabelState(); + setLabelSuggestion(nameInfos, instanceId); invalidate(); }, DROP_IN_ANIMATION_DURATION); } @@ -434,7 +436,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel /** * Set the suggested folder name. */ - public void setLabelSuggestion(FolderNameInfo[] nameInfos) { + public void setLabelSuggestion(FolderNameInfo[] nameInfos, InstanceId instanceId) { if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) { return; } @@ -445,7 +447,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) { return; } + mInfo.previousTitle = mInfo.title; mInfo.title = nameInfos[0].getLabel(); + StatsLogManager.newInstance(getContext()) + .log(LAUNCHER_FOLDER_LABEL_CHANGED, instanceId, mInfo.getFolderIconAtom()); onTitleChanged(mInfo.title); mFolder.mFolderName.setText(mInfo.title); mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo); diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 9455bd3fa0..309263db10 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -61,6 +61,10 @@ public class StatsLogManager implements ResourceBasedOverride { + "resulting in a new folder creation") LAUNCHER_ITEM_DROP_FOLDER_CREATED(386), + @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped on another item " + + "resulting in new folder creation") + LAUNCHER_FOLDER_LABEL_CHANGED(460), + @LauncherUiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar") LAUNCHER_ITEM_DROPPED_ON_REMOVE(465), diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java index 3ac6a2213b..fd024f46f8 100644 --- a/src/com/android/launcher3/model/data/FolderInfo.java +++ b/src/com/android/launcher3/model/data/FolderInfo.java @@ -16,16 +16,31 @@ package com.android.launcher3.model.data; +import static android.text.TextUtils.isEmpty; + +import static androidx.core.util.Preconditions.checkNotNull; + +import static java.util.Arrays.stream; +import static java.util.Optional.ofNullable; + import android.content.Intent; import android.os.Process; +import android.text.TextUtils; import com.android.launcher3.LauncherSettings; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.folder.FolderNameInfo; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.util.ContentWriter; import java.util.ArrayList; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.IntStream; + /** * Represents a folder containing shortcuts or apps. @@ -57,6 +72,10 @@ public class FolderInfo extends ItemInfo { public Intent suggestedFolderNames; + // When title changes, previous title is stored. + // Primarily used for logging purpose. + public CharSequence previousTitle; + /** * The apps and shortcuts */ @@ -172,4 +191,125 @@ public class FolderInfo extends ItemInfo { folderInfo.contents = this.contents; return folderInfo; } + + /** + * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging + * into Westworld. + * + */ + public LauncherAtom.ItemInfo getFolderIconAtom() { + LauncherAtom.ToState toFolderLabelState = getToFolderLabelState(); + LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder() + .setCardinality(contents.size()) + .setFromState(getFromFolderLabelState()) + .setToState(toFolderLabelState); + if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) { + folderIconBuilder.setLabel(title.toString()); + } + return getDefaultItemInfoBuilder() + .setFolderIcon(folderIconBuilder) + .setContainerInfo(getContainerInfo()) + .build(); + } + + /** + * Returns index of the accepted suggestion. + */ + public OptionalInt getAcceptedSuggestionIndex() { + String newLabel = checkNotNull(title, + "Expected valid folder label, but found null").toString(); + return getSuggestedLabels() + .map(suggestionsArray -> + IntStream.range(0, suggestionsArray.length) + .filter( + index -> !isEmpty(suggestionsArray[index]) + && newLabel.equalsIgnoreCase( + suggestionsArray[index])) + .sequential() + .findFirst() + ).orElse(OptionalInt.empty()); + + } + + private LauncherAtom.ToState getToFolderLabelState() { + if (title == null) { + return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; + } + + if (title.equals(previousTitle)) { + return LauncherAtom.ToState.UNCHANGED; + } + + if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) { + return title.length() > 0 + ? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED + : LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED; + } + + Optional suggestedLabels = getSuggestedLabels(); + boolean isEmptySuggestions = suggestedLabels + .map(labels -> stream(labels).allMatch(TextUtils::isEmpty)) + .orElse(true); + if (isEmptySuggestions) { + return title.length() > 0 + ? LauncherAtom.ToState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS + : LauncherAtom.ToState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS; + } + + boolean hasValidPrimary = suggestedLabels + .map(labels -> !isEmpty(labels[0])) + .orElse(false); + if (title.length() == 0) { + return hasValidPrimary ? LauncherAtom.ToState.TO_EMPTY_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + } + + OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex(); + if (!accepted_suggestion_index.isPresent()) { + return hasValidPrimary ? LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + } + + switch (accepted_suggestion_index.getAsInt()) { + case 0: + return LauncherAtom.ToState.TO_SUGGESTION0; + case 1: + return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION1_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY; + case 2: + return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION2_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY; + case 3: + return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION3_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY; + default: + // fall through + } + return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; + + } + + private LauncherAtom.FromState getFromFolderLabelState() { + return previousTitle == null + ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED + : previousTitle.toString().isEmpty() + ? LauncherAtom.FromState.FROM_EMPTY + : hasOption(FLAG_MANUAL_FOLDER_NAME) + ? LauncherAtom.FromState.FROM_CUSTOM + : LauncherAtom.FromState.FROM_SUGGESTED; + } + + private Optional getSuggestedLabels() { + return ofNullable(suggestedFolderNames) + .map(folderNames -> + (FolderNameInfo[]) + folderNames.getParcelableArrayExtra(EXTRA_FOLDER_SUGGESTIONS)) + .map(folderNameInfoArray -> + stream(folderNameInfoArray) + .filter(Objects::nonNull) + .map(FolderNameInfo::getLabel) + .filter(Objects::nonNull) + .map(CharSequence::toString) + .toArray(String[]::new)); + } } From 7087a7bd91c8c136722b6b515568f1c9f0aa1f46 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Tue, 12 May 2020 21:34:13 -0700 Subject: [PATCH 03/25] Don't continue rendering preview screen when binder is dead Fixes: 155365052 Test: manually quick switches between different grid options Change-Id: I31dc14b0a50b5e3a5c81272b693303034fcf8b40 --- .../graphics/PreviewSurfaceRenderer.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index c62f308367..350f221c97 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -81,10 +81,12 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { binderDied(); } + SurfaceControlViewHost.SurfacePackage surfacePackage; try { mSurfaceControlViewHost = MAIN_EXECUTOR .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken)) .get(5, TimeUnit.SECONDS); + surfacePackage = mSurfaceControlViewHost.getSurfacePackage(); mHostToken.linkToDeath(this, 0); } catch (Exception e) { e.printStackTrace(); @@ -92,6 +94,14 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { } MAIN_EXECUTOR.execute(() -> { + // If mSurfaceControlViewHost is null due to any reason (e.g. binder died, + // happening when user leaves the preview screen before preview rendering finishes), + // we should return here. + SurfaceControlViewHost host = mSurfaceControlViewHost; + if (host == null) { + return; + } + View view = new LauncherPreviewRenderer(mContext, mIdp).getRenderedView(); // This aspect scales the view to fit in the surface and centers it final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), @@ -107,14 +117,14 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { .setInterpolator(new AccelerateDecelerateInterpolator()) .setDuration(FADE_IN_ANIMATION_DURATION) .start(); - mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), + host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight()); }); Bundle result = new Bundle(); - result.putParcelable(KEY_SURFACE_PACKAGE, mSurfaceControlViewHost.getSurfacePackage()); + result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage); - Handler handler = new Handler(Looper.getMainLooper(), Loopermessage -> { + Handler handler = new Handler(Looper.getMainLooper(), message -> { binderDied(); return true; }); @@ -128,8 +138,10 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { @Override public void binderDied() { if (mSurfaceControlViewHost != null) { - mSurfaceControlViewHost.release(); - mSurfaceControlViewHost = null; + MAIN_EXECUTOR.execute(() -> { + mSurfaceControlViewHost.release(); + mSurfaceControlViewHost = null; + }); } mHostToken.unlinkToDeath(this, 0); } From b9080c2a1555cb8e1bc6665d7fe8f3c02b80e06c Mon Sep 17 00:00:00 2001 From: thiruram Date: Tue, 12 May 2020 14:41:03 -0700 Subject: [PATCH 04/25] Add support to log LauncherEvent without ItemInfo. Change-Id: I145a9500b0442c2b5d9f7f691460465b2bb13a07 --- .../logging/StatsLogCompatManager.java | 32 ++++++++++++++++--- .../launcher3/logging/StatsLogManager.java | 32 ++++++++++++------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index a98aad191e..88895602dc 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -64,24 +64,46 @@ public class StatsLogCompatManager extends StatsLogManager { } /** - * Logs an event and accompanying {@link ItemInfo} + * Logs a {@link LauncherEvent}. */ + @Override + public void log(LauncherEvent event) { + log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance()); + } + + /** + * Logs an event and accompanying {@link InstanceId}. + */ + @Override + public void log(LauncherEvent event, InstanceId instanceId) { + log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance()); + } + + /** + * Logs an event and accompanying {@link ItemInfo}. + */ + @Override public void log(LauncherEvent event, LauncherAtom.ItemInfo itemInfo) { log(event, DEFAULT_INSTANCE_ID, itemInfo); } /** - * Logs an event and accompanying {@link LauncherAtom.ItemInfo} + * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. */ @Override public void log(LauncherEvent event, InstanceId instanceId, LauncherAtom.ItemInfo itemInfo) { if (IS_VERBOSE) { - Log.d(TAG, String.format("\n%s\n%s", event.name(), itemInfo)); + Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID + ? String.format("\n%s\n%s", event.name(), itemInfo) + : String.format("%s(InstanceId:%s)\n%s", event.name(), instanceId, itemInfo)); } + if (!Utilities.ATLEAST_R) { return; } - SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, + + SysUiStatsLog.write( + SysUiStatsLog.LAUNCHER_EVENT, SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */, SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME /* TODO */, SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND /* TODO */, @@ -118,6 +140,7 @@ public class StatsLogCompatManager extends StatsLogManager { } private class SnapshotWorker extends BaseModelUpdateTask { + @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { IntSparseArrayMap folders = dataModel.folders.clone(); @@ -140,6 +163,7 @@ public class StatsLogCompatManager extends StatsLogManager { } } } + private static void writeSnapshot(LauncherAtom.ItemInfo itemInfo) { if (IS_VERBOSE) { Log.d(TAG, "\nwriteSnapshot:" + itemInfo); diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 9455bd3fa0..3131636123 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -16,10 +16,8 @@ package com.android.launcher3.logging; import android.content.Context; -import android.util.Log; import com.android.launcher3.R; -import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ItemInfo; import com.android.launcher3.logging.StatsLogUtils.LogStateProvider; import com.android.launcher3.util.ResourceBasedOverride; @@ -32,8 +30,6 @@ import com.android.launcher3.util.ResourceBasedOverride; */ public class StatsLogManager implements ResourceBasedOverride { - private static final String TAG = "StatsLogManager"; - interface EventEnum { int getId(); } @@ -84,9 +80,11 @@ public class StatsLogManager implements ResourceBasedOverride { // ADD MORE private final int mId; + LauncherEvent(int id) { mId = id; } + public int getId() { return mId; } @@ -109,22 +107,32 @@ public class StatsLogManager implements ResourceBasedOverride { } /** - * Logs an event and accompanying {@link ItemInfo} + * Logs a {@link LauncherEvent}. */ - public void log(LauncherEvent event, InstanceId instanceId) { - Log.d(TAG, String.format("%s(InstanceId:%s)", event.name(), instanceId)); - // Call StatsLog method + public void log(LauncherEvent event) { } /** - * Logs an event and accompanying {@link LauncherAtom.ItemInfo} + * Logs an event and accompanying {@link InstanceId}. */ - public void log(LauncherEvent event, InstanceId instanceId, LauncherAtom.ItemInfo itemInfo) { } - public void log(LauncherEvent event, LauncherAtom.ItemInfo itemInfo) { } + public void log(LauncherEvent event, InstanceId instanceId) { + } + /** + * Logs an event and accompanying {@link ItemInfo}. + */ + public void log(LauncherEvent event, ItemInfo itemInfo) { + } + + /** + * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}. + */ + public void log(LauncherEvent event, InstanceId instanceId, ItemInfo itemInfo) { + } /** * Logs snapshot, or impression of the current workspace. */ - public void logSnapshot() { } + public void logSnapshot() { + } } From f95f152971cdc40b82c3b941c72bd9bc2806c001 Mon Sep 17 00:00:00 2001 From: thiruram Date: Fri, 8 May 2020 17:53:19 -0700 Subject: [PATCH 05/25] Add LAUNCHER_FOLDER_LABEL_CHANGED events for folder label updates. Sample Logs for all update combinations: https://docs.google.com/document/d/1CBP2yTcXdFhPdNG5ZmWFKSgd8mDbMevY-akVlUXPLDo/edit#bookmark=id.7y1p8n2dz8ge Bug: 155410872 Bug: 152978018 Change-Id: I296b124b16aa07878f2cf7b74ab91f13b8e6cfbf --- protos/launcher_atom.proto | 56 ++--- .../hybridhotseat/HotseatEduController.java | 2 +- src/com/android/launcher3/folder/Folder.java | 28 ++- .../android/launcher3/folder/FolderIcon.java | 8 +- .../launcher3/logging/StatsLogManager.java | 6 +- .../launcher3/model/data/FolderInfo.java | 196 ++++++++++++++++-- .../launcher3/model/data/ItemInfo.java | 11 + 7 files changed, 248 insertions(+), 59 deletions(-) diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto index 19f72132c8..f1b71e8868 100644 --- a/protos/launcher_atom.proto +++ b/protos/launcher_atom.proto @@ -99,13 +99,14 @@ message FolderIcon { optional int32 cardinality = 1; // State of the folder label before the event. - optional FromState from_state = 2; + optional FromState from_label_state = 2; // State of the folder label after the event. - optional ToState to_state = 3; + optional ToState to_label_state = 3; - // Populated only when folder label was suggested. - optional string label = 4; + // Details about actual folder label. + // Populated when folder label is not a PII. + optional string label_info = 4; } ////////////////////////////////////////////// @@ -131,70 +132,77 @@ message FolderContainer { } } -// Represents state of FolderLabel before editing. +// Represents state of EditText field before update. enum FromState { // Default value. + // Used when a FromState is not applicable, for example, during folder creation. FROM_STATE_UNSPECIFIED = 0; - // FolderLabel was empty. + // EditText was empty. + // Eg: When a folder label is updated from empty string. FROM_EMPTY = 1; - // FolderLabel was non-empty and manually entered by the user. + // EditText was non-empty and manually entered by the user. + // Eg: When a folder label is updated from a user-entered value. FROM_CUSTOM = 2; - // FolderLabel was non-empty and one of the suggestions. + // EditText was non-empty and one of the suggestions. + // Eg: When a folder label is updated from a suggested value. FROM_SUGGESTED = 3; } -// Represents state of FolderLabel after editing. +// Represents state of EditText field after update. enum ToState { // Default value. + // Used when ToState is not applicable, for example, when folder label is updated to a different + // value when folder label suggestion feature is disabled. TO_STATE_UNSPECIFIED = 0; - // User attempted to change the folder label, but was not changed. + + // User attempted to change the EditText, but was not changed. UNCHANGED = 1; // New label matches with primary(aka top) suggestion. TO_SUGGESTION0 = 2; - // New label matches with second top suggestion even though the top suggestion was non-empty. + // New value matches with second top suggestion even though the top suggestion was non-empty. TO_SUGGESTION1_WITH_VALID_PRIMARY = 3; - // New label matches with second top suggestion given that top suggestion was empty. + // New value matches with second top suggestion given that top suggestion was empty. TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 4; - // New label matches with third top suggestion even though the top suggestion was non-empty. + // New value matches with third top suggestion even though the top suggestion was non-empty. TO_SUGGESTION2_WITH_VALID_PRIMARY = 5; - // New label matches with third top suggestion given that top suggestion was empty. + // New value matches with third top suggestion given that top suggestion was empty. TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 6; - // New label matches with 4th top suggestion even though the top suggestion was non-empty. + // New value matches with 4th top suggestion even though the top suggestion was non-empty. TO_SUGGESTION3_WITH_VALID_PRIMARY = 7; - // New label matches with 4th top suggestion given that top suggestion was empty. + // New value matches with 4th top suggestion given that top suggestion was empty. TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 8; - // New label is empty even though the top suggestion was non-empty. + // New value is empty even though the top suggestion was non-empty. TO_EMPTY_WITH_VALID_PRIMARY = 9; - // New label is empty given that top suggestion was empty. + // New value is empty given that top suggestion was empty. TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 10; - // New label is empty given that no suggestions were provided. + // New value is empty given that no suggestions were provided. TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 11; - // New label is empty given that suggestions feature was disabled. + // New value is empty given that suggestions feature was disabled. TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 12; - // New label is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty. + // New value is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty. TO_CUSTOM_WITH_VALID_PRIMARY = 13; - // New label is non-empty and not match with any suggestions given that top suggestion was empty. + // New value is non-empty and not match with any suggestions given that top suggestion was empty. TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 14; - // New label is non-empty and also no suggestions were provided. + // New value is non-empty and also no suggestions were provided. TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 15; - // New label is non-empty and also suggestions feature was disable. + // New value is non-empty and also suggestions feature was disable. TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 16; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index 7f8f0a0576..e4d0adf0c0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -121,7 +121,7 @@ public class HotseatEduController { if (!putIntoFolder.isEmpty()) { ItemInfo firstItem = putIntoFolder.get(0); FolderInfo folderInfo = new FolderInfo(); - folderInfo.title = ""; + folderInfo.setTitle(""); mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container, firstItem.screenId, firstItem.cellX, firstItem.cellY); folderInfo.contents.addAll(putIntoFolder); diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 29a737c227..f7fe535a48 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -24,6 +24,8 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME; import static java.util.Arrays.asList; @@ -196,6 +198,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Thunk int mScrollHintDir = SCROLL_NONE; @Thunk int mCurrentScrollDir = SCROLL_NONE; + private StatsLogManager mStatsLogManager; /** * Used to inflate the Workspace from XML. @@ -208,10 +211,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo setAlwaysDrawnWithCacheEnabled(false); mLauncher = Launcher.getLauncher(context); + mStatsLogManager = StatsLogManager.newInstance(context); // We need this view to be focusable in touch mode so that when text editing of the folder // name is complete, we have something to focus on, thus hiding the cursor and giving // reliable behavior when clicking the text field (since it will always gain focus on click). setFocusableInTouchMode(true); + } @Override @@ -329,8 +334,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (DEBUG) { Log.d(TAG, "onBackKey newTitle=" + newTitle); } - mInfo.previousTitle = mInfo.title; - mInfo.title = newTitle; + mInfo.setTitle(newTitle); + mInfo.fromCustom = mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME); mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(), mLauncher.getModelWriter()); mFolderIcon.onTitleChanged(newTitle); @@ -1326,10 +1331,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (d.stateAnnouncer != null) { d.stateAnnouncer.completeAction(R.string.item_moved); } - StatsLogManager.newInstance(getContext()) - .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, - d.logInstanceId, - d.dragInfo.buildProto(mInfo)); + mStatsLogManager + .log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo.buildProto(mInfo)); } // This is used so the item doesn't immediately appear in the folder when added. In one case @@ -1434,6 +1437,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (hasFocus) { startEditingFolderName(); } else { + mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo.buildProto()); + logFolderLabelState(); mFolderName.dispatchBackKey(); } } @@ -1631,4 +1636,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public FolderPagedView getContent() { return mContent; } + + /** + * Logs current folder label info. + * + * @deprecated This method is only used for log validation and soon will be removed. + */ + @Deprecated + public void logFolderLabelState() { + mLauncher.getUserEventDispatcher() + .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent()); + } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index bb358abf89..153d6bceb1 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -20,7 +20,7 @@ import static android.text.TextUtils.isEmpty; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_CHANGED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -429,6 +429,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mPreviewItemManager.hidePreviewItem(finalIndex, false); mFolder.showItem(item); setLabelSuggestion(nameInfos, instanceId); + mFolder.logFolderLabelState(); invalidate(); }, DROP_IN_ANIMATION_DURATION); } @@ -447,10 +448,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) { return; } - mInfo.previousTitle = mInfo.title; - mInfo.title = nameInfos[0].getLabel(); + mInfo.setTitle(nameInfos[0].getLabel()); StatsLogManager.newInstance(getContext()) - .log(LAUNCHER_FOLDER_LABEL_CHANGED, instanceId, mInfo.getFolderIconAtom()); + .log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo.buildProto()); onTitleChanged(mInfo.title); mFolder.mFolderName.setText(mInfo.title); mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo); diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index ee9322bca6..b240f0bef2 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -57,9 +57,9 @@ public class StatsLogManager implements ResourceBasedOverride { + "resulting in a new folder creation") LAUNCHER_ITEM_DROP_FOLDER_CREATED(386), - @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped on another item " - + "resulting in new folder creation") - LAUNCHER_FOLDER_LABEL_CHANGED(460), + @LauncherUiEvent(doc = "User action resulted in or manually updated the folder label to " + + "new/same value.") + LAUNCHER_FOLDER_LABEL_UPDATED(460), @LauncherUiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar") LAUNCHER_ITEM_DROPPED_ON_REMOVE(465), diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java index fd024f46f8..096743a8c8 100644 --- a/src/com/android/launcher3/model/data/FolderInfo.java +++ b/src/com/android/launcher3/model/data/FolderInfo.java @@ -20,6 +20,13 @@ import static android.text.TextUtils.isEmpty; import static androidx.core.util.Preconditions.checkNotNull; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED; + import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; @@ -32,13 +39,20 @@ import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderNameInfo; import com.android.launcher3.logger.LauncherAtom; +import com.android.launcher3.logger.LauncherAtom.FromState; +import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.model.ModelWriter; +import com.android.launcher3.userevent.LauncherLogProto; +import com.android.launcher3.userevent.LauncherLogProto.Target; +import com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState; +import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState; import com.android.launcher3.util.ContentWriter; import java.util.ArrayList; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; +import java.util.StringJoiner; import java.util.stream.IntStream; @@ -72,9 +86,19 @@ public class FolderInfo extends ItemInfo { public Intent suggestedFolderNames; - // When title changes, previous title is stored. + // Represents the title before current. // Primarily used for logging purpose. - public CharSequence previousTitle; + private CharSequence mPreviousTitle; + + // True if the title before was manually entered, suggested otherwise. + // Primarily used for logging purpose. + public boolean fromCustom; + + /** + * Used for separating {@link #mPreviousTitle} and {@link #title} when concatenating them + * for logging. + */ + private static final CharSequence FOLDER_LABEL_DELIMITER = "=>"; /** * The apps and shortcuts @@ -179,9 +203,20 @@ public class FolderInfo extends ItemInfo { @Override public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) { return getDefaultItemInfoBuilder() - .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size())) - .setContainerInfo(getContainerInfo()) - .build(); + .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size())) + .setRank(rank) + .setContainerInfo(getContainerInfo()) + .build(); + } + + @Override + public void setTitle(CharSequence title) { + mPreviousTitle = this.title; + this.title = title; + } + + public CharSequence getPreviousTitle() { + return mPreviousTitle; } @Override @@ -193,19 +228,30 @@ public class FolderInfo extends ItemInfo { } /** - * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging - * into Westworld. - * + * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging. */ - public LauncherAtom.ItemInfo getFolderIconAtom() { - LauncherAtom.ToState toFolderLabelState = getToFolderLabelState(); + @Override + public LauncherAtom.ItemInfo buildProto() { + FromState fromFolderLabelState = getFromFolderLabelState(); + ToState toFolderLabelState = getToFolderLabelState(); LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder() .setCardinality(contents.size()) - .setFromState(getFromFolderLabelState()) - .setToState(toFolderLabelState); - if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) { - folderIconBuilder.setLabel(title.toString()); + .setFromLabelState(fromFolderLabelState) + .setToLabelState(toFolderLabelState); + + // If the folder label is suggested, it is logged to improve prediction model. + // When both old and new labels are logged together delimiter is used. + StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER); + if (fromFolderLabelState.equals(FromState.FROM_SUGGESTED)) { + labelInfoBuilder.add(mPreviousTitle); } + if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) { + labelInfoBuilder.add(title); + } + if (labelInfoBuilder.length() > 0) { + folderIconBuilder.setLabelInfo(labelInfoBuilder.toString()); + } + return getDefaultItemInfoBuilder() .setFolderIcon(folderIconBuilder) .setContainerInfo(getContainerInfo()) @@ -236,7 +282,7 @@ public class FolderInfo extends ItemInfo { return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; } - if (title.equals(previousTitle)) { + if (title.equals(mPreviousTitle)) { return LauncherAtom.ToState.UNCHANGED; } @@ -290,13 +336,13 @@ public class FolderInfo extends ItemInfo { } private LauncherAtom.FromState getFromFolderLabelState() { - return previousTitle == null + return mPreviousTitle == null ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED - : previousTitle.toString().isEmpty() - ? LauncherAtom.FromState.FROM_EMPTY - : hasOption(FLAG_MANUAL_FOLDER_NAME) - ? LauncherAtom.FromState.FROM_CUSTOM - : LauncherAtom.FromState.FROM_SUGGESTED; + : mPreviousTitle.length() == 0 + ? LauncherAtom.FromState.FROM_EMPTY + : fromCustom + ? LauncherAtom.FromState.FROM_CUSTOM + : LauncherAtom.FromState.FROM_SUGGESTED; } private Optional getSuggestedLabels() { @@ -312,4 +358,112 @@ public class FolderInfo extends ItemInfo { .map(CharSequence::toString) .toArray(String[]::new)); } + + /** + * Returns {@link LauncherLogProto.LauncherEvent} to log current folder label info. + * + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent() { + return LauncherLogProto.LauncherEvent.newBuilder() + .setAction(LauncherLogProto.Action + .newBuilder() + .setType(LauncherLogProto.Action.Type.SOFT_KEYBOARD)) + .addSrcTarget(Target + .newBuilder() + .setType(Target.Type.ITEM) + .setItemType(LauncherLogProto.ItemType.EDITTEXT) + .setFromFolderLabelState(convertFolderLabelState(getFromFolderLabelState())) + .setToFolderLabelState(convertFolderLabelState(getToFolderLabelState()))) + .addSrcTarget(Target.newBuilder() + .setType(Target.Type.CONTAINER) + .setContainerType(LauncherLogProto.ContainerType.FOLDER) + .setPageIndex(screenId) + .setGridX(cellX) + .setGridY(cellY) + .setCardinality(contents.size())) + .addSrcTarget(newParentContainerTarget()) + .build(); + } + + /** + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + private Target.Builder newParentContainerTarget() { + Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER); + switch (container) { + case CONTAINER_HOTSEAT: + return builder.setContainerType(LauncherLogProto.ContainerType.HOTSEAT); + case CONTAINER_DESKTOP: + return builder.setContainerType(LauncherLogProto.ContainerType.WORKSPACE); + default: + throw new AssertionError(String + .format("Expected container to be either %s or %s but found %s.", + CONTAINER_HOTSEAT, + CONTAINER_DESKTOP, + container)); + } + } + + /** + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + private static FromFolderLabelState convertFolderLabelState(FromState fromState) { + switch (fromState) { + case FROM_EMPTY: + return FROM_EMPTY; + case FROM_SUGGESTED: + return FROM_SUGGESTED; + case FROM_CUSTOM: + return FROM_CUSTOM; + default: + return FROM_FOLDER_LABEL_STATE_UNSPECIFIED; + } + } + + /** + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + private static ToFolderLabelState convertFolderLabelState(ToState toState) { + switch (toState) { + case UNCHANGED: + return ToFolderLabelState.UNCHANGED; + case TO_SUGGESTION0: + return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY; + case TO_SUGGESTION1_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY; + case TO_SUGGESTION1_WITH_EMPTY_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY; + case TO_SUGGESTION2_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY; + case TO_SUGGESTION2_WITH_EMPTY_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY; + case TO_SUGGESTION3_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY; + case TO_SUGGESTION3_WITH_EMPTY_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY; + case TO_EMPTY_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY; + case TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY: + return ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + case TO_EMPTY_WITH_EMPTY_SUGGESTIONS: + return ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS; + case TO_EMPTY_WITH_SUGGESTIONS_DISABLED: + return ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED; + case TO_CUSTOM_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY; + case TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY: + return ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + case TO_CUSTOM_WITH_EMPTY_SUGGESTIONS: + return ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS; + case TO_CUSTOM_WITH_SUGGESTIONS_DISABLED: + return ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED; + default: + return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED; + } + } } diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 7611ee74b4..f2b7e54a12 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -249,6 +249,13 @@ public class ItemInfo { public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) { } + /** + * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. + */ + public LauncherAtom.ItemInfo buildProto() { + return buildProto(null); + } + /** * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. */ @@ -345,4 +352,8 @@ public class ItemInfo { itemInfo.copyFrom(this); return itemInfo; } + + public void setTitle(CharSequence title) { + this.title = title; + } } From 24e3b6598e432af3b987a52a27f950165fbbda64 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 13 May 2020 20:41:35 -0700 Subject: [PATCH 06/25] Prevent AllApps scroller activation in NORMAL state Bug: 153663434 Test: Manual Change-Id: I859d63fff08091961970d72f56853b50433f6d44 --- .../launcher3/allapps/LauncherAllAppsContainerView.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java index 80b6a5ab96..a6bc6cf59f 100644 --- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java @@ -59,6 +59,14 @@ public class LauncherAllAppsContainerView extends AllAppsContainerView { return super.onInterceptTouchEvent(ev); } + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mLauncher.isInState(LauncherState.ALL_APPS)) { + return false; + } + return super.onTouchEvent(ev); + } + @Override public void setInsets(Rect insets) { super.setInsets(insets); From d9c0349c1afb9e2071e44e971082a65b8aa3a539 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 13 May 2020 10:29:47 -0700 Subject: [PATCH 07/25] Merging WindowSizeStrategy with BaseActivityInterface Also converting BaseActivityInterface to an abstract class instead of interface so that it can hold member variables Change-Id: I04cab934137eb1d6f470c6a7618c50a2ed2c71c1 --- .../states/OverviewModalTaskState.java | 3 - .../AppToOverviewAnimationProvider.java | 9 +- .../android/quickstep/BaseSwipeUpHandler.java | 12 +- .../quickstep/FallbackActivityInterface.java | 43 ++-- .../quickstep/FallbackSwipeHandler.java | 3 +- .../quickstep/LauncherActivityInterface.java | 120 ++++----- .../quickstep/LauncherSwipeHandler.java | 7 +- .../quickstep/OverviewCommandHelper.java | 11 +- .../android/quickstep/RecentsActivity.java | 4 - .../quickstep/TouchInteractionService.java | 5 +- .../fallback/FallbackRecentsView.java | 4 +- .../inputconsumers/OverviewInputConsumer.java | 6 +- .../quickstep/util/TaskViewSimulator.java | 7 +- .../quickstep/views/LauncherRecentsView.java | 4 +- .../android/quickstep/views/RecentsView.java | 6 +- .../quickstep/BaseActivityInterface.java | 227 +++++++++++++++--- .../com/android/quickstep/GestureState.java | 7 +- .../quickstep/OverviewComponentObserver.java | 4 +- .../android/quickstep/util/LayoutUtils.java | 4 +- .../quickstep/util/RecentsOrientedState.java | 5 +- .../quickstep/util/WindowSizeStrategy.java | 185 -------------- 21 files changed, 327 insertions(+), 349 deletions(-) delete mode 100644 quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java index 0be248691e..d5b06871b9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java @@ -16,7 +16,6 @@ package com.android.launcher3.uioverrides.states; import android.content.Context; -import android.content.res.Resources; import android.graphics.Rect; import com.android.launcher3.BaseDraggingActivity; @@ -58,8 +57,6 @@ public class OverviewModalTaskState extends OverviewState { } public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) { - Resources res = activity.getResources(); - Rect out = new Rect(); activity.getOverviewPanel().getTaskSize(out); int taskHeight = out.height(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 1dd5fb7a57..f38ff10cc2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -31,9 +31,9 @@ import android.util.Log; import android.view.View; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.statehandlers.DepthController; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.TransformParams; @@ -47,20 +47,21 @@ import com.android.systemui.shared.system.TransactionCompat; * * @param activity that contains the overview */ -final class AppToOverviewAnimationProvider extends +final class AppToOverviewAnimationProvider> extends RemoteAnimationProvider { private static final long RECENTS_LAUNCH_DURATION = 250; private static final String TAG = "AppToOverviewAnimationProvider"; - private final BaseActivityInterface mActivityInterface; + private final BaseActivityInterface mActivityInterface; // The id of the currently running task that is transitioning to overview. private final int mTargetTaskId; private T mActivity; private RecentsView mRecentsView; - AppToOverviewAnimationProvider(BaseActivityInterface activityInterface, int targetTaskId) { + AppToOverviewAnimationProvider( + BaseActivityInterface activityInterface, int targetTaskId) { mActivityInterface = activityInterface; mTargetTaskId = targetTaskId; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index 76c6060e8b..bbee67ce94 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -44,12 +44,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.views.FloatingIconView; @@ -60,7 +60,6 @@ import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.TransformParams.BuilderProxy; -import com.android.quickstep.util.WindowSizeStrategy; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -76,7 +75,7 @@ import java.util.function.Consumer; * Base class for swipe up handler with some utility methods */ @TargetApi(Build.VERSION_CODES.Q) -public abstract class BaseSwipeUpHandler +public abstract class BaseSwipeUpHandler, Q extends RecentsView> implements RecentsAnimationListener { private static final String TAG = "BaseSwipeUpHandler"; @@ -97,7 +96,7 @@ public abstract class BaseSwipeUpHandler mActivityInterface; + protected final BaseActivityInterface mActivityInterface; protected final InputConsumerController mInputConsumer; protected final TaskViewSimulator mTaskViewSimulator; @@ -132,15 +131,14 @@ public abstract class BaseSwipeUpHandler { +public final class FallbackActivityInterface extends + BaseActivityInterface { - public FallbackActivityInterface() { } + public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface(); - @Override - public void onTransitionCancelled(boolean activityVisible) { - // TODO: + private FallbackActivityInterface() { + super(false); } @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) { - FALLBACK_RECENTS_SIZE_STRATEGY.calculateTaskSize(context, dp, outRect); + calculateTaskSize(context, dp, outRect); if (dp.isVerticalBarLayout() && SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) { Rect targetInsets = dp.getInsets(); @@ -69,17 +70,6 @@ public final class FallbackActivityInterface implements } } - @Override - public void onSwipeUpToRecentsComplete() { - RecentsActivity activity = getCreatedActivity(); - if (activity == null) { - return; - } - RecentsView recentsView = activity.getOverviewPanel(); - recentsView.getClearAllButton().setVisibilityAlpha(1); - recentsView.setDisallowScrollToClearAll(false); - } - @Override public void onAssistantVisibilityChanged(float visibility) { // This class becomes active when the screen is locked. @@ -198,11 +188,14 @@ public final class FallbackActivityInterface implements } @Override - public void onLaunchTaskSuccess() { - RecentsActivity activity = getCreatedActivity(); - if (activity == null) { - return; - } - activity.onTaskLaunched(); + public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) { + out.set(dp.widthPx, dp.heightPx); + } + + @Override + protected float getExtraSpace(Context context, DeviceProfile dp) { + return showOverviewActions(context) + ? context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height) + : 0; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java index 77e50caae7..db41bd6fd4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -24,7 +24,6 @@ import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID; import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL; -import static com.android.quickstep.util.WindowSizeStrategy.FALLBACK_RECENTS_SIZE_STRATEGY; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import android.animation.Animator; @@ -114,7 +113,7 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler { +public final class LauncherActivityInterface extends + BaseActivityInterface { + + public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface(); + + private LauncherActivityInterface() { + super(true); + } @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) { - LAUNCHER_ACTIVITY_SIZE_STRATEGY.calculateTaskSize(context, dp, outRect); + calculateTaskSize(context, dp, outRect); if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) { Rect targetInsets = dp.getInsets(); int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right; @@ -83,25 +95,13 @@ public final class LauncherActivityInterface implements BaseActivityInterface implements Runnable { + private class RecentsActivityCommand> implements Runnable { - protected final BaseActivityInterface mActivityInterface; + protected final BaseActivityInterface mActivityInterface; private final long mCreateTime; private final AppToOverviewAnimationProvider mAnimationProvider; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index 03d522e85d..a4670fd40b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -261,10 +261,6 @@ public final class RecentsActivity extends StatefulActivity { AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL); } - public void onTaskLaunched() { - mFallbackRecentsView.resetTaskVisuals(); - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 258d60c208..acc7794631 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -63,6 +63,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.provider.RestoreDbTask; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.tracing.nano.LauncherTraceProto; @@ -705,7 +706,7 @@ public class TouchInteractionService extends Service implements PluginListener activityInterface = + final BaseActivityInterface activityInterface = mOverviewComponentObserver.getActivityInterface(); final Intent overviewIntent = new Intent( mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState()); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java index 1ab317b9eb..f958e6d734 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -17,7 +17,6 @@ package com.android.quickstep.fallback; import static com.android.quickstep.fallback.RecentsState.DEFAULT; import static com.android.quickstep.fallback.RecentsState.MODAL_TASK; -import static com.android.quickstep.util.WindowSizeStrategy.FALLBACK_RECENTS_SIZE_STRATEGY; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; @@ -26,6 +25,7 @@ import android.os.Build; import android.util.AttributeSet; import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.quickstep.FallbackActivityInterface; import com.android.quickstep.RecentsActivity; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; @@ -45,7 +45,7 @@ public class FallbackRecentsView extends RecentsView } public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr, FALLBACK_RECENTS_SIZE_STRATEGY); + super(context, attrs, defStyleAttr, FallbackActivityInterface.INSTANCE); mActivity.getStateManager().addStateListener(this); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java index c82d4b5edd..11fee2f32a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java @@ -24,8 +24,8 @@ import android.view.MotionEvent; import androidx.annotation.Nullable; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.Utilities; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.views.BaseDragLayer; @@ -41,11 +41,11 @@ import java.util.function.Predicate; /** * Input consumer for handling touch on the recents/Launcher activity. */ -public class OverviewInputConsumer +public class OverviewInputConsumer> implements InputConsumer { private final T mActivity; - private final BaseActivityInterface mActivityInterface; + private final BaseActivityInterface mActivityInterface; private final BaseDragLayer mTarget; private final InputMonitorCompat mInputMonitor; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java index b8f0f4ddf7..a3db940211 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java @@ -15,8 +15,6 @@ */ package com.android.quickstep.util; -import static android.view.Surface.ROTATION_0; - import static com.android.launcher3.states.RotationHelper.deltaRotation; import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE; import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; @@ -33,6 +31,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.views.RecentsView.ScrollState; import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper; import com.android.quickstep.views.TaskView; @@ -52,7 +51,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final RecentsOrientedState mOrientationState; private final Context mContext; - private final WindowSizeStrategy mSizeStrategy; + private final BaseActivityInterface mSizeStrategy; private final Rect mTaskRect = new Rect(); private final PointF mPivot = new PointF(); @@ -81,7 +80,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private boolean mLayoutValid = false; private boolean mScrollValid = false; - public TaskViewSimulator(Context context, WindowSizeStrategy sizeStrategy) { + public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) { mContext = context; mSizeStrategy = sizeStrategy; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 250c78bdac..3d894033d1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -25,7 +25,6 @@ import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; @@ -47,6 +46,7 @@ import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.ScrimView; +import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.util.TransformParams; import com.android.systemui.plugins.PluginListener; @@ -89,7 +89,7 @@ public class LauncherRecentsView extends RecentsView } public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr, LAUNCHER_ACTIVITY_SIZE_STRATEGY); + super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE); mActivity.getStateManager().addStateListener(this); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 54ed40f3e9..253e83cab4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -115,6 +115,7 @@ import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.Themes; import com.android.launcher3.util.ViewPool; +import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; @@ -128,7 +129,6 @@ import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.SplitScreenBounds; import com.android.quickstep.util.TransformParams; -import com.android.quickstep.util.WindowSizeStrategy; import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.model.Task; @@ -209,7 +209,7 @@ public abstract class RecentsView extends PagedView impl }; protected final RecentsOrientedState mOrientationState; - protected final WindowSizeStrategy mSizeStrategy; + protected final BaseActivityInterface mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; protected RecentsAnimationTargets mRecentsAnimationTargets; protected AppWindowAnimationHelper mAppWindowAnimationHelper; @@ -381,7 +381,7 @@ public abstract class RecentsView extends PagedView impl }; public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, - WindowSizeStrategy sizeStrategy) { + BaseActivityInterface sizeStrategy) { super(context, attrs, defStyleAttr); setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing)); setEnableFreeScroll(true); diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 43328b6885..2699a916a0 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -15,23 +15,34 @@ */ package com.android.quickstep; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; +import static com.android.quickstep.SysUINavigationMode.getMode; +import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; + import android.annotation.TargetApi; import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; import android.view.MotionEvent; -import android.view.View; import android.view.animation.Interpolator; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.statehandlers.DepthController; +import com.android.launcher3.statemanager.BaseState; +import com.android.launcher3.statemanager.StatefulActivity; +import com.android.launcher3.util.WindowBounds; +import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.SplitScreenBounds; +import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -42,84 +53,232 @@ import java.util.function.Predicate; * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. */ @TargetApi(Build.VERSION_CODES.P) -public interface BaseActivityInterface { +public abstract class BaseActivityInterface, + ACTIVITY_TYPE extends StatefulActivity> { - void onTransitionCancelled(boolean activityVisible); + private final PointF mTempPoint = new PointF(); + public final boolean rotationSupportedByActivity; - int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect); + protected BaseActivityInterface(boolean rotationSupportedByActivity) { + this.rotationSupportedByActivity = rotationSupportedByActivity; + } - void onSwipeUpToRecentsComplete(); + public void onTransitionCancelled(boolean activityVisible) { + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + STATE_TYPE startState = activity.getStateManager().getRestState(); + activity.getStateManager().goToState(startState, activityVisible); + } - default void onSwipeUpToHomeComplete() { } - void onAssistantVisibilityChanged(float visibility); + public abstract int getSwipeUpDestinationAndLength( + DeviceProfile dp, Context context, Rect outRect); - AnimationFactory prepareRecentsUI( + public void onSwipeUpToRecentsComplete() { + // Re apply state in case we did something funky during the transition. + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + activity.getStateManager().reapplyState(); + } + + public void onSwipeUpToHomeComplete() { } + + public abstract void onAssistantVisibilityChanged(float visibility); + + public abstract AnimationFactory prepareRecentsUI( boolean activityVisible, Consumer callback); - ActivityInitListener createActivityInitListener(Predicate onInitListener); + public abstract ActivityInitListener createActivityInitListener( + Predicate onInitListener); /** * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. */ - default void setOnDeferredActivityLaunchCallback(Runnable r) {} + public void setOnDeferredActivityLaunchCallback(Runnable r) {} @Nullable - T getCreatedActivity(); + public abstract ACTIVITY_TYPE getCreatedActivity(); @Nullable - default DepthController getDepthController() { + public DepthController getDepthController() { return null; } - default boolean isResumed() { - BaseDraggingActivity activity = getCreatedActivity(); + public final boolean isResumed() { + ACTIVITY_TYPE activity = getCreatedActivity(); return activity != null && activity.hasBeenResumed(); } - default boolean isStarted() { - BaseDraggingActivity activity = getCreatedActivity(); + public final boolean isStarted() { + ACTIVITY_TYPE activity = getCreatedActivity(); return activity != null && activity.isStarted(); } @UiThread @Nullable - T getVisibleRecentsView(); + public abstract T getVisibleRecentsView(); @UiThread - boolean switchToRecentsIfVisible(Runnable onCompleteCallback); + public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback); - Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target); + public abstract Rect getOverviewWindowBounds( + Rect homeBounds, RemoteAnimationTargetCompat target); - boolean allowMinimizeSplitScreen(); + public abstract boolean allowMinimizeSplitScreen(); - default boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { + public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { return true; } /** * Updates the prediction state to the overview state. */ - default void updateOverviewPredictionState() { - // By default overview predictions are not supported + public void updateOverviewPredictionState() { + // By public overview predictions are not supported } /** * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher} */ - int getContainerType(); + public abstract int getContainerType(); - boolean isInLiveTileMode(); + public abstract boolean isInLiveTileMode(); - void onLaunchTaskFailed(); + public abstract void onLaunchTaskFailed(); - void onLaunchTaskSuccess(); + public void onLaunchTaskSuccess() { + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + activity.getStateManager().moveToRestState(); + } - default void closeOverlay() { } + public void closeOverlay() { } - default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, - Runnable runnable) {} + public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, Runnable runnable) { + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + RecentsView recentsView = activity.getOverviewPanel(); + if (recentsView == null) { + if (runnable != null) { + runnable.run(); + } + return; + } + recentsView.switchToScreenshot(thumbnailData, runnable); + } - interface AnimationFactory { + /** + * Sets the expected window size in multi-window mode + */ + public abstract void getMultiWindowSize(Context context, DeviceProfile dp, PointF out); + + /** + * Calculates the taskView size for the provided device configuration + */ + public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) { + calculateTaskSize(context, dp, getExtraSpace(context, dp), outRect); + } + + protected abstract float getExtraSpace(Context context, DeviceProfile dp); + + private void calculateTaskSize( + Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect) { + Resources res = context.getResources(); + final boolean showLargeTaskSize = showOverviewActions(context); + + final int paddingResId; + if (dp.isMultiWindowMode) { + paddingResId = R.dimen.multi_window_task_card_horz_space; + } else if (dp.isVerticalBarLayout()) { + paddingResId = R.dimen.landscape_task_card_horz_space; + } else if (showLargeTaskSize) { + paddingResId = R.dimen.portrait_task_card_horz_space_big_overview; + } else { + paddingResId = R.dimen.portrait_task_card_horz_space; + } + float paddingHorz = res.getDimension(paddingResId); + float paddingVert = showLargeTaskSize + ? 0 : res.getDimension(R.dimen.task_card_vert_space); + + calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, + res.getDimension(R.dimen.task_thumbnail_top_margin), outRect); + } + + private void calculateTaskSizeInternal(Context context, DeviceProfile dp, + float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin, + Rect outRect) { + float taskWidth, taskHeight; + Rect insets = dp.getInsets(); + if (dp.isMultiWindowMode) { + WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context); + taskWidth = bounds.availableSize.x; + taskHeight = bounds.availableSize.y; + } else { + taskWidth = dp.availableWidthPx; + taskHeight = dp.availableHeightPx; + } + + // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless + // we override the insets ourselves. + int launcherVisibleWidth = dp.widthPx - insets.left - insets.right; + int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom; + + float availableHeight = launcherVisibleHeight + - topIconMargin - extraVerticalSpace - paddingVert; + float availableWidth = launcherVisibleWidth - paddingHorz; + + float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight); + float outWidth = scale * taskWidth; + float outHeight = scale * taskHeight; + + // Center in the visible space + float x = insets.left + (launcherVisibleWidth - outWidth) / 2; + float y = insets.top + Math.max(topIconMargin, + (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2); + outRect.set(Math.round(x), Math.round(y), + Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight)); + } + + /** + * Calculates the modal taskView size for the provided device configuration + */ + public void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) { + float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode + ? R.dimen.multi_window_task_card_horz_space + : dp.isVerticalBarLayout() + ? R.dimen.landscape_task_card_horz_space + : R.dimen.portrait_modal_task_card_horz_space); + float extraVerticalSpace = getOverviewActionsHeight(context); + float paddingVert = 0; + float topIconMargin = 0; + calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, + topIconMargin, outRect); + } + + /** Gets the space that the overview actions will take, including margins. */ + public float getOverviewActionsHeight(Context context) { + Resources res = context.getResources(); + float actionsBottomMargin = 0; + if (getMode(context) == Mode.THREE_BUTTONS) { + actionsBottomMargin = res.getDimensionPixelSize( + R.dimen.overview_actions_bottom_margin_three_button); + } else { + actionsBottomMargin = res.getDimensionPixelSize( + R.dimen.overview_actions_bottom_margin_gesture); + } + float overviewActionsHeight = actionsBottomMargin + + res.getDimensionPixelSize(R.dimen.overview_actions_height); + return overviewActionsHeight; + } + + public interface AnimationFactory { default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { } @@ -137,4 +296,8 @@ public interface BaseActivityInterface { */ default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { } } + + protected static boolean showOverviewActions(Context context) { + return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context); + } } diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index 9b515ae788..295b465120 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -17,10 +17,12 @@ package com.android.quickstep; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; +import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Intent; +import android.os.Build; -import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -32,6 +34,7 @@ import java.util.ArrayList; * Manages the state for an active system gesture, listens for events from the system and Launcher, * and fires events when the states change. */ +@TargetApi(Build.VERSION_CODES.R) public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener { /** @@ -189,7 +192,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL /** * @return the interface to the activity handing the UI updates for this gesture. */ - public BaseActivityInterface getActivityInterface() { + public > BaseActivityInterface getActivityInterface() { return mActivityInterface; } diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java index 231ee72f1c..0449d0ca2a 100644 --- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java +++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java @@ -140,7 +140,7 @@ public final class OverviewComponentObserver { if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) { // User default home is same as out home app. Use Overview integrated in Launcher. - mActivityInterface = new LauncherActivityInterface(); + mActivityInterface = LauncherActivityInterface.INSTANCE; mIsHomeAndOverviewSame = true; mOverviewIntent = mMyHomeIntent; mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent()); @@ -150,7 +150,7 @@ public final class OverviewComponentObserver { } else { // The default home app is a different launcher. Use the fallback Overview instead. - mActivityInterface = new FallbackActivityInterface(); + mActivityInterface = FallbackActivityInterface.INSTANCE; mIsHomeAndOverviewSame = false; mOverviewIntent = mFallbackIntent; mCurrentHomeIntent.setComponent(defaultHome); diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index fa53be2b08..c1b276aa5a 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -17,7 +17,6 @@ package com.android.quickstep.util; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; -import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY; import android.content.Context; import android.graphics.Rect; @@ -26,6 +25,7 @@ import android.view.ViewGroup; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; +import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.SysUINavigationMode; public class LayoutUtils { @@ -45,7 +45,7 @@ public class LayoutUtils { // Track the bottom of the window. if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) { Rect taskSize = new Rect(); - LAUNCHER_ACTIVITY_SIZE_STRATEGY.calculateTaskSize(context, dp, taskSize); + LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize); return (dp.heightPx - taskSize.height()) / 2; } int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom; diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index e7ff48f52b..e03f4b80ea 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -52,6 +52,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.WindowBounds; +import com.android.quickstep.BaseActivityInterface; import java.lang.annotation.Retention; import java.util.function.IntConsumer; @@ -121,7 +122,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre private final ContentResolver mContentResolver; private final SharedPreferences mSharedPrefs; private final OrientationEventListener mOrientationListener; - private final WindowSizeStrategy mSizeStrategy; + private final BaseActivityInterface mSizeStrategy; private final Matrix mTmpMatrix = new Matrix(); @@ -133,7 +134,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre * is enabled * @see #setRotationWatcherEnabled(boolean) */ - public RecentsOrientedState(Context context, WindowSizeStrategy sizeStrategy, + public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy, IntConsumer rotationChangeListener) { mContext = context; mContentResolver = context.getContentResolver(); diff --git a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java b/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java deleted file mode 100644 index b088ba8e58..0000000000 --- a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java +++ /dev/null @@ -1,185 +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.quickstep.util; - -import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; -import static com.android.quickstep.SysUINavigationMode.getMode; -import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; -import static com.android.quickstep.util.LayoutUtils.getDefaultSwipeHeight; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.os.Build; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.R; -import com.android.launcher3.util.WindowBounds; -import com.android.quickstep.SysUINavigationMode.Mode; - -/** - * Utility class to wrap different layout behavior for Launcher and RecentsView - * TODO: Merge is with {@link com.android.quickstep.BaseActivityInterface} once we remove the - * state dependent members from {@link com.android.quickstep.LauncherActivityInterface} - */ -@TargetApi(Build.VERSION_CODES.R) -public abstract class WindowSizeStrategy { - - public final boolean rotationSupportedByActivity; - - private WindowSizeStrategy(boolean rotationSupportedByActivity) { - this.rotationSupportedByActivity = rotationSupportedByActivity; - } - - /** - * Calculates the taskView size for the provided device configuration - */ - public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) { - calculateTaskSize(context, dp, getExtraSpace(context, dp), outRect); - } - - abstract float getExtraSpace(Context context, DeviceProfile dp); - - private void calculateTaskSize( - Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect) { - Resources res = context.getResources(); - final boolean showLargeTaskSize = showOverviewActions(context); - - final int paddingResId; - if (dp.isMultiWindowMode) { - paddingResId = R.dimen.multi_window_task_card_horz_space; - } else if (dp.isVerticalBarLayout()) { - paddingResId = R.dimen.landscape_task_card_horz_space; - } else if (showLargeTaskSize) { - paddingResId = R.dimen.portrait_task_card_horz_space_big_overview; - } else { - paddingResId = R.dimen.portrait_task_card_horz_space; - } - float paddingHorz = res.getDimension(paddingResId); - float paddingVert = showLargeTaskSize - ? 0 : res.getDimension(R.dimen.task_card_vert_space); - - calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, - res.getDimension(R.dimen.task_thumbnail_top_margin), outRect); - } - - private void calculateTaskSizeInternal(Context context, DeviceProfile dp, - float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin, - Rect outRect) { - float taskWidth, taskHeight; - Rect insets = dp.getInsets(); - if (dp.isMultiWindowMode) { - WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context); - taskWidth = bounds.availableSize.x; - taskHeight = bounds.availableSize.y; - } else { - taskWidth = dp.availableWidthPx; - taskHeight = dp.availableHeightPx; - } - - // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless - // we override the insets ourselves. - int launcherVisibleWidth = dp.widthPx - insets.left - insets.right; - int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom; - - float availableHeight = launcherVisibleHeight - - topIconMargin - extraVerticalSpace - paddingVert; - float availableWidth = launcherVisibleWidth - paddingHorz; - - float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight); - float outWidth = scale * taskWidth; - float outHeight = scale * taskHeight; - - // Center in the visible space - float x = insets.left + (launcherVisibleWidth - outWidth) / 2; - float y = insets.top + Math.max(topIconMargin, - (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2); - outRect.set(Math.round(x), Math.round(y), - Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight)); - } - - /** - * Calculates the modal taskView size for the provided device configuration - */ - public void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) { - float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode - ? R.dimen.multi_window_task_card_horz_space - : dp.isVerticalBarLayout() - ? R.dimen.landscape_task_card_horz_space - : R.dimen.portrait_modal_task_card_horz_space); - float extraVerticalSpace = getOverviewActionsHeight(context); - float paddingVert = 0; - float topIconMargin = 0; - calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, - topIconMargin, outRect); - } - - /** Gets the space that the overview actions will take, including margins. */ - public float getOverviewActionsHeight(Context context) { - Resources res = context.getResources(); - float actionsBottomMargin = 0; - if (getMode(context) == Mode.THREE_BUTTONS) { - actionsBottomMargin = res.getDimensionPixelSize( - R.dimen.overview_actions_bottom_margin_three_button); - } else { - actionsBottomMargin = res.getDimensionPixelSize( - R.dimen.overview_actions_bottom_margin_gesture); - } - float overviewActionsHeight = actionsBottomMargin - + res.getDimensionPixelSize(R.dimen.overview_actions_height); - return overviewActionsHeight; - } - - public static final WindowSizeStrategy LAUNCHER_ACTIVITY_SIZE_STRATEGY = - new WindowSizeStrategy(true) { - - @Override - float getExtraSpace(Context context, DeviceProfile dp) { - if (dp.isVerticalBarLayout()) { - return 0; - } else { - Resources res = context.getResources(); - if (showOverviewActions(context)) { - //TODO: this needs to account for the swipe gesture height and accessibility - // UI when shown. - return getOverviewActionsHeight(context); - } else { - return getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight - + res.getDimensionPixelSize( - R.dimen.dynamic_grid_hotseat_extra_vertical_size) - + res.getDimensionPixelSize( - R.dimen.dynamic_grid_hotseat_bottom_padding); - } - } - } - }; - - public static final WindowSizeStrategy FALLBACK_RECENTS_SIZE_STRATEGY = - new WindowSizeStrategy(false) { - - @Override - float getExtraSpace(Context context, DeviceProfile dp) { - return showOverviewActions(context) - ? context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height) - : 0; - } - }; - - static boolean showOverviewActions(Context context) { - return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context); - } -} From 1801d19bcd212d71a9504e991b976542324338c0 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 13 May 2020 20:06:09 -0700 Subject: [PATCH 08/25] Remove LockscreenRecentsActivity - This activity would create another task (no longer in the same stack since we don't have activity type stacks) which was causing the camera to be occluded sometimes, which then cancels the recents animation - Tweaking the animation slightly to match the movement of the closing the activity back to the lockscreen Bug: 156514461 Test: Launch camera on the lockscreen and swipe up to dismiss (repeatedly) Change-Id: I8aaac22767288d7fbf183683276c50acaaf831d2 --- quickstep/AndroidManifest.xml | 6 -- .../quickstep/LockScreenRecentsActivity.java | 31 ---------- .../DeviceLockedInputConsumer.java | 62 +++++++++++++------ quickstep/res/values/dimens.xml | 3 + 4 files changed, 45 insertions(+), 57 deletions(-) delete mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 04506b5647..b2286f1bd7 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -95,12 +95,6 @@ android:clearTaskOnLaunch="true" android:exported="false" /> - - = (1 - MIN_PROGRESS_FOR_OVERVIEW); } - if (dismissTask) { - // For now, just start the home intent so user is prompted to unlock the device. - mContext.startActivity(new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_HOME) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } + + // Animate back to fullscreen before finishing + ValueAnimator animator = ValueAnimator.ofFloat(mTransformParams.getProgress(), 0f); + animator.setDuration(100); + animator.setInterpolator(Interpolators.ACCEL); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + mTransformParams.setProgress((float) valueAnimator.getAnimatedValue()); + mAppWindowAnimationHelper.applyTransform(mTransformParams); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (dismissTask) { + // For now, just start the home intent so user is prompted to unlock the device. + mContext.startActivity(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + mHomeLaunched = true; + } + mStateCallback.setState(STATE_HANDLER_INVALIDATED); + } + }); + animator.start(); + } else { + mStateCallback.setState(STATE_HANDLER_INVALIDATED); } mVelocityTracker.recycle(); mVelocityTracker = null; @@ -205,13 +226,11 @@ public class DeviceLockedInputConsumer implements InputConsumer, private void startRecentsTransition() { mThresholdCrossed = true; + mHomeLaunched = false; TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); mInputMonitorCompat.pilferPointers(); - Intent intent = new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_DEFAULT) - .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) + Intent intent = mGestureState.getHomeIntent() .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this); } @@ -229,8 +248,9 @@ public class DeviceLockedInputConsumer implements InputConsumer, mAppWindowAnimationHelper.updateSource(displaySize, targetCompat); } - Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN); - displaySize.offsetTo(displaySize.left, 0); + // Offset the surface slightly + displaySize.offset(0, mContext.getResources().getDimensionPixelSize( + R.dimen.device_locked_y_offset)); mTransformParams.setTargetSet(mRecentsAnimationTargets); mAppWindowAnimationHelper.updateTargetRect(displaySize); mAppWindowAnimationHelper.applyTransform(mTransformParams); @@ -245,7 +265,9 @@ public class DeviceLockedInputConsumer implements InputConsumer, } private void endRemoteAnimation() { - if (mRecentsAnimationController != null) { + if (mHomeLaunched) { + ActivityManagerWrapper.getInstance().cancelRecentsAnimation(false); + } else if (mRecentsAnimationController != null) { mRecentsAnimationController.finishController( false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */); } diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 72275c81b1..ee55e610c7 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -79,6 +79,9 @@ 28dp + + -80dp + 40dp From 034ce6fd945c9c2bfc6f4380619b965a5b1c8f8e Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 14 May 2020 10:49:30 -0700 Subject: [PATCH 09/25] Update shelf offset earlier when in gesture nav - Provide a hint to Launcher to indicate it will be resumed when swiping home, which allows us to notify sysui before any potential pip invocation, reducing the likelyhood of two conflicting animations when entering pip Bug: 156637223 Change-Id: Iae773e1aac88bbea6f74e1d1332417b448126471 --- .../launcher3/uioverrides/QuickstepLauncher.java | 16 ++++++++-------- .../quickstep/LauncherActivityInterface.java | 5 +++++ .../android/quickstep/LauncherSwipeHandler.java | 1 + .../android/launcher3/BaseQuickstepLauncher.java | 4 ++++ .../android/quickstep/BaseActivityInterface.java | 2 ++ src/com/android/launcher3/BaseActivity.java | 8 +++++++- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 6cfc846432..ad6a10bba3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -100,7 +100,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - onStateOrResumeChanged(); + onStateOrResumeChanging(false /* inTransition */); } @Override @@ -115,11 +115,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override protected void onActivityFlagsChanged(int changeBits) { super.onActivityFlagsChanged(changeBits); - if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED - | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0 - && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) { - onStateOrResumeChanged(); + | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { + onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0); } if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0 @@ -164,14 +162,16 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { /** * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. */ - private void onStateOrResumeChanged() { + private void onStateOrResumeChanging(boolean inTransition) { LauncherState state = getStateManager().getState(); DeviceProfile profile = getDeviceProfile(); - boolean visible = (state == NORMAL || state == OVERVIEW) && isUserActive() + boolean willUserBeActive = (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; + boolean visible = (state == NORMAL || state == OVERVIEW) + && (willUserBeActive || isUserActive()) && !profile.isVerticalBarLayout(); UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0, profile.hotseatBarSizePx); - if (state == NORMAL) { + if (state == NORMAL && !inTransition) { ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index d466296a30..4ebfbd6a4c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -290,6 +290,11 @@ public final class LauncherActivityInterface extends return true; } + @Override + public void setHintUserWillBeActive() { + getCreatedActivity().setHintUserWillBeActive(); + } + @Override public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { return deviceState.isInDeferredGestureRegion(ev); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 1830ccb04b..54b4769550 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -939,6 +939,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler Date: Tue, 12 May 2020 12:06:55 -0400 Subject: [PATCH 10/25] Compose gesture integrated fully into Launcher - Support dismissing Compose via the reverse gesture from the appear gesture - Use Tony Wickham's ag/10204761 with some glue code to enable the app below Compose panning in the same direction as the gesture as Compose peeks in - Add feature flag to use Compose hosted in a window (permits underlying app panning) - Use InterpolatingVelocityTracker to fix OtherActivityInputConsumer processing swipes in the wrong direction ~20% of the time due to a bug in VelocityTracker (see go/quirky-bubbles) Change-Id: I3adbaee1763f21557fb628b60d03b0a03e7079ab --- .../OtherActivityInputConsumer.java | 3 +- .../OverscrollInputConsumer.java | 170 +++++++++++------- quickstep/res/values/dimens.xml | 2 + .../com/android/quickstep/GestureState.java | 4 + .../launcher3/config/FeatureFlags.java | 3 + .../systemui/plugins/OverscrollPlugin.java | 44 ++--- 6 files changed, 136 insertions(+), 90 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 1f6c506d3a..6b0d7a3e87 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -26,6 +26,7 @@ import static android.view.MotionEvent.INVALID_POINTER_ID; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS; +import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; @@ -430,6 +431,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC @Override public boolean allowInterceptByParent() { - return !mPassedPilferInputSlop; + return !mPassedPilferInputSlop || mGestureState.hasState(STATE_OVERSCROLL_WINDOW_CREATED); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java index c49b8f2d14..fb420a272a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java @@ -24,12 +24,15 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; +import static java.lang.Math.abs; + import android.content.Context; import android.graphics.PointF; -import android.view.GestureDetector; +import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.BaseDraggingActivity; @@ -44,24 +47,36 @@ import com.android.systemui.shared.system.InputMonitorCompat; * Input consumer for handling events to pass to an {@code OverscrollPlugin}. */ public class OverscrollInputConsumer extends DelegateInputConsumer { - private static final String TAG = "OverscrollInputConsumer"; + private static final boolean DEBUG_LOGS_ENABLED = false; + private static void debugPrint(String log) { + if (DEBUG_LOGS_ENABLED) { + Log.v(TAG, log); + } + } private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); private final PointF mStartDragPos = new PointF(); private final int mAngleThreshold; - private final float mFlingThresholdPx; + private final int mFlingDistanceThresholdPx; + private final int mFlingVelocityThresholdPx; private int mActivePointerId = -1; private boolean mPassedSlop = false; - + // True if we set ourselves as active, meaning we no longer pass events to the delegate. + private boolean mPassedActiveThreshold = false; + // When a gesture crosses this length, this recognizer will attempt to interpret touch events. private final float mSquaredSlop; + // When a gesture crosses this length, this recognizer will become the sole active recognizer. + private final float mSquaredActiveThreshold; + // When a gesture crosses this length, the overscroll view should be shown. + private final float mSquaredFinishThreshold; + private boolean mThisDownIsIgnored = false; private final GestureState mGestureState; @Nullable private final OverscrollPlugin mPlugin; - private final GestureDetector mGestureDetector; @Nullable private RecentsView mRecentsView; @@ -72,15 +87,24 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { mAngleThreshold = context.getResources() .getInteger(R.integer.assistant_gesture_corner_deg_threshold); - mFlingThresholdPx = context.getResources() - .getDimension(R.dimen.gestures_overscroll_fling_threshold); + mFlingDistanceThresholdPx = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_fling_threshold); + mFlingVelocityThresholdPx = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); mGestureState = gestureState; mPlugin = plugin; float slop = ViewConfiguration.get(context).getScaledTouchSlop(); mSquaredSlop = slop * slop; - mGestureDetector = new GestureDetector(context, new FlingGestureListener()); + + + float finishGestureThreshold = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_finish_threshold); + mSquaredFinishThreshold = finishGestureThreshold * finishGestureThreshold; + + float activeThreshold = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_active_threshold); + mSquaredActiveThreshold = activeThreshold * activeThreshold; } @Override @@ -90,12 +114,26 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { @Override public void onMotionEvent(MotionEvent ev) { + if (mPlugin == null) { + return; + } + + debugPrint("got event, underlying activity is " + getUnderlyingActivity()); switch (ev.getActionMasked()) { case ACTION_DOWN: { + debugPrint("ACTION_DOWN"); mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); - + if (mPlugin.blockOtherGestures()) { + debugPrint("mPlugin.blockOtherGestures(), becoming active on ACTION_DOWN"); + // Otherwise, if an appear gesture is performed when the Activity is visible, + // the Activity will dismiss its keyboard. + mPassedActiveThreshold = true; + mPassedSlop = true; + mStartDragPos.set(mLastPos.x, mLastPos.y); + setActive(ev); + } break; } case ACTION_POINTER_DOWN: { @@ -121,57 +159,61 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { if (mState == STATE_DELEGATE_ACTIVE) { break; } + if (!mDelegate.allowInterceptByParent()) { mState = STATE_DELEGATE_ACTIVE; break; } + + // Update last touch position. int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { break; } mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); - if (!mPassedSlop) { - // Normal gesture, ensure we pass the slop before we start tracking the gesture - if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) - > mSquaredSlop) { - - mPassedSlop = true; - mStartDragPos.set(mLastPos.x, mLastPos.y); - if (isOverscrolled()) { - setActive(ev); - - if (mPlugin != null) { - mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity()); - } - } else { - mState = STATE_DELEGATE_ACTIVE; - } - } + float squaredDist = squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y); + if ((!mPassedSlop) && (squaredDist > mSquaredSlop)) { + mPassedSlop = true; + mStartDragPos.set(mLastPos.x, mLastPos.y); + mGestureState.setState(GestureState.STATE_OVERSCROLL_WINDOW_CREATED); } - if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled() - && mPlugin != null) { - mPlugin.onTouchTraveled(getDistancePx()); + boolean becomeActive = mPassedSlop && !mPassedActiveThreshold && isOverscrolled() + && (squaredDist > mSquaredActiveThreshold); + if (becomeActive) { + debugPrint("Past slop and past threshold, set active"); + mPassedActiveThreshold = true; + setActive(ev); + } + + if (mPassedActiveThreshold) { + debugPrint("ACTION_MOVE Relaying touch event"); + mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(), + (int) Math.sqrt(mSquaredFinishThreshold), mFlingDistanceThresholdPx, + mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity()); } break; } case ACTION_CANCEL: case ACTION_UP: - if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) { - mPlugin.onTouchEnd(getDistancePx()); + debugPrint("ACTION_UP"); + if (mPassedActiveThreshold) { + debugPrint("ACTION_UP Relaying touch event"); + + mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(), + (int) Math.sqrt(mSquaredFinishThreshold), mFlingDistanceThresholdPx, + mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity()); } + mPassedSlop = false; + mPassedActiveThreshold = false; mState = STATE_INACTIVE; break; } - if (mState != STATE_DELEGATE_ACTIVE) { - mGestureDetector.onTouchEvent(ev); - } - if (mState != STATE_ACTIVE) { mDelegate.onMotionEvent(ev); } @@ -192,15 +234,20 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { maxIndex = 1; } - boolean atRightMostApp = (mRecentsView == null - || mRecentsView.getRunningTaskIndex() <= maxIndex); + boolean atRightMostApp = mRecentsView == null + || (mRecentsView.getRunningTaskIndex() <= maxIndex); // Check if the gesture is within our angle threshold of horizontal - float deltaY = Math.abs(mLastPos.y - mDownPos.y); - float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left - boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold; + float deltaY = abs(mLastPos.y - mDownPos.y); + float deltaX = mLastPos.x - mDownPos.x; - return atRightMostApp && angleInBounds; + boolean angleInBounds = (Math.toDegrees(Math.atan2(deltaY, abs(deltaX))) < mAngleThreshold); + + boolean overscrollVisible = mPlugin.blockOtherGestures(); + boolean overscrollInvisibleAndLeftSwipe = !overscrollVisible && deltaX < 0; + boolean gestureDirectionMatchesVisibility = overscrollVisible + || overscrollInvisibleAndLeftSwipe; + return atRightMostApp && angleInBounds && gestureDirectionMatchesVisibility; } private String getDeviceState() { @@ -219,35 +266,22 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { return deviceState; } - private int getDistancePx() { - return (int) Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y); + private int getHorizontalDistancePx() { + return (int) (mLastPos.x - mDownPos.x); } - private String getUnderlyingActivity() { + private int getVerticalDistancePx() { + return (int) (mLastPos.y - mDownPos.y); + } + + private @NonNull String getUnderlyingActivity() { + // Overly defensive, got guidance on code review that something in the chain of + // `mGestureState.getRunningTask().topActivity` can be null and thus cause a null pointer + // exception to be thrown, but we aren't sure which part can be null. + if ((mGestureState == null) || (mGestureState.getRunningTask() == null) + || (mGestureState.getRunningTask().topActivity == null)) { + return ""; + } return mGestureState.getRunningTask().topActivity.flattenToString(); } - - private class FlingGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (isValidAngle(velocityX, -velocityY) - && getDistancePx() >= mFlingThresholdPx - && mState != STATE_DELEGATE_ACTIVE) { - - if (mPlugin != null) { - mPlugin.onFling(-velocityX); - } - } - return true; - } - - private boolean isValidAngle(float deltaX, float deltaY) { - float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX)); - // normalize so that angle is measured clockwise from horizontal in the bottom right - // corner and counterclockwise from horizontal in the bottom left corner - - angle = angle > 90 ? 180 - angle : angle; - return (angle < mAngleThreshold); - } - } } diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index b06dc6b01d..85868049c5 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -79,6 +79,8 @@ 40dp + 80dp + 136dp 40dp diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index f06e1a6afc..5adcc2ed70 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -107,6 +107,10 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL public static final int STATE_RECENTS_ANIMATION_ENDED = getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED"); + // Called when we create an overscroll window when swiping right to left on the most recent app + public static final int STATE_OVERSCROLL_WINDOW_CREATED = + getFlagForIndex("STATE_OVERSCROLL_WINDOW_CREATED"); + // Called when RecentsView stops scrolling and settles on a TaskView. public static final int STATE_RECENTS_SCROLLING_FINISHED = getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED"); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 78d194bd98..69193399f7 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -110,6 +110,9 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag( "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture"); + public static final BooleanFlag ENABLE_QUICK_CAPTURE_WINDOW = getDebugFlag( + "ENABLE_QUICK_CAPTURE_WINDOW", false, "Use window to host quick capture"); + public static final BooleanFlag FORCE_LOCAL_OVERSCROLL_PLUGIN = getDebugFlag( "FORCE_LOCAL_OVERSCROLL_PLUGIN", false, "Use a launcher-provided OverscrollPlugin if available"); diff --git a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java index 28a9193bec..a434d078bc 100644 --- a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java +++ b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java @@ -15,6 +15,8 @@ */ package com.android.systemui.plugins; +import android.view.MotionEvent; + import com.android.systemui.plugins.annotations.ProvidesInterface; /** @@ -28,7 +30,7 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; public interface OverscrollPlugin extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL"; - int VERSION = 3; + int VERSION = 4; String DEVICE_STATE_LOCKED = "Locked"; String DEVICE_STATE_LAUNCHER = "Launcher"; @@ -41,33 +43,33 @@ public interface OverscrollPlugin extends Plugin { boolean isActive(); /** - * Called when a touch is down and has been recognized as an overscroll gesture. - * A call of this method will always result in `onTouchUp` being called, and possibly - * `onFling` as well. - * + * Called when a touch has been recognized as an overscroll gesture. + * @param horizontalDistancePx Horizontal distance from the last finger location to the finger + * location when it first touched the screen. + * @param verticalDistancePx Horizontal distance from the last finger location to the finger + * location when it first touched the screen. + * @param thresholdPx Minimum distance for gesture. + * @param flingDistanceThresholdPx Minimum distance for gesture by fling. + * @param flingVelocityThresholdPx Minimum velocity for gesture by fling. * @param deviceState String representing the current device state * @param underlyingActivity String representing the currently active Activity */ - void onTouchStart(String deviceState, String underlyingActivity); + void onTouchEvent(MotionEvent event, + int horizontalDistancePx, + int verticalDistancePx, + int thresholdPx, + int flingDistanceThresholdPx, + int flingVelocityThresholdPx, + String deviceState, + String underlyingActivity); /** - * Called when a touch that was previously recognized has moved. - * - * @param px distance between the position of touch on this update and the position of the - * touch when it was initially recognized. + * @return `true` if overscroll gesture handling should override all other gestures. */ - void onTouchTraveled(int px); + boolean blockOtherGestures(); /** - * Called when a touch that was previously recognized has ended. - * - * @param px distance between the position of touch on this update and the position of the - * touch when it was initially recognized. + * @return `true` if the overscroll gesture can pan the underlying app. */ - void onTouchEnd(int px); - - /** - * Called when the user starts Compose with a fling. `onTouchUp` will also be called. - */ - void onFling(float velocity); + boolean allowsUnderlyingActivityOverscroll(); } From 18aa37ca68bcd6946437dc801b6a185f8f4bbdc5 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Thu, 14 May 2020 11:27:58 -0700 Subject: [PATCH 11/25] Don't require the center task id to adjust status bar when swiping up b/155029378 Change-Id: I3cb65e82ab192b63d54291aad9e2395fa8dedaef --- .../src/com/android/quickstep/LauncherSwipeHandler.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 0bf81efbae..4248cdeca0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -587,9 +587,8 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler Date: Thu, 14 May 2020 14:24:31 -0700 Subject: [PATCH 12/25] Avoid truncation of input trace Bug: 156287114 Change-Id: Iac0f93d4e8082907210bd6312d1c046ff1f49dba --- .../android/launcher3/tapl/LauncherInstrumentation.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 14212be24e..b333100393 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -436,9 +436,12 @@ public final class LauncherInstrumentation { sEventChecker.finishNoWait(); } } - // b/156287114 + try { - log("Input: " + mDevice.executeShellCommand("dumpsys input")); + Log.e("b/156287114", "Input:"); + for (String line : mDevice.executeShellCommand("dumpsys input").split("\\n")) { + Log.d("b/156287114", line); + } } catch (IOException e) { e.printStackTrace(); } From c40352ef8c42267138454a93639bb71293cf25d2 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Thu, 14 May 2020 15:07:31 -0700 Subject: [PATCH 13/25] Override Rect#contains() to include bottom right In landscape, the lowest coordinate touch registered is the actual lowest pixel possible by the screen. For portrait/seascape the lowest coordinate is (lowest pixel possible - 1). The difference of the one pixel makes Rect#contains() return false since only top left Rect points are inclusive and bottom right points are exclusive. Fixes: 156333291 Test: Always able to smoothly go to overview from landscape. Change-Id: I6beddad99ae076a167d2a5f5e5acc6e466bff544 --- .../quickstep/OrientationTouchTransformer.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java index 879fd1d64f..4cf7aab4ab 100644 --- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -317,13 +317,6 @@ class OrientationTouchTransformer { private class OrientationRectF extends RectF { - /** - * Delta to subtract width and height by because if we report the translated touch - * bounds as the width and height, calling {@link RectF#contains(float, float)} will - * be false - */ - private float maxDelta = 0.001f; - private int mRotation; private float mHeight; private float mWidth; @@ -331,8 +324,8 @@ class OrientationTouchTransformer { OrientationRectF(float left, float top, float right, float bottom, int rotation) { super(left, top, right, bottom); this.mRotation = rotation; - mHeight = bottom - maxDelta; - mWidth = right - maxDelta; + mHeight = bottom; + mWidth = right; } @Override @@ -342,6 +335,13 @@ class OrientationTouchTransformer { return s; } + @Override + public boolean contains(float x, float y) { + // Mark bottom right as included in the Rect (copied from Rect src, added "=" in "<=") + return left < right && top < bottom // check for empty first + && x >= left && x <= right && y >= top && y <= bottom; + } + boolean applyTransform(MotionEvent event, boolean forceTransform) { mTmpMatrix.reset(); postDisplayRotation(deltaRotation(mCurrentDisplayRotation, mRotation), From d12d6ab98a6f390c5b4c59b303e35a3f596645be Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 11 May 2020 19:48:59 -0700 Subject: [PATCH 14/25] Switch to new protocol for hybrid hotseat - create predictor from items in bgModel instead of scanning views - Launcher no longer checks for duplicates before sending pin/unpin events - sending cached items from last prediction to reduce UI shuffle - Switch to using UserCache to persist and read ComponentKey Bug: 148814143 Bug: 156413231 Bug: 156200931 Change-Id: Ide6330bed8eb7f0c6fbec1d1ac21e7f67a9b2be2 --- .../res/values/override.xml | 2 + .../HotseatPredictionController.java | 285 +++++------------- .../hybridhotseat/HotseatPredictionModel.java | 136 +++++++++ res/values/config.xml | 1 + src/com/android/launcher3/DropTarget.java | 12 + .../android/launcher3/LauncherAppState.java | 2 +- .../launcher3/model/BaseLoaderResults.java | 4 +- .../android/launcher3/model/LoaderTask.java | 8 +- .../launcher3/model/PredictionModel.java | 96 ++++-- 9 files changed, 292 insertions(+), 254 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml index ed3ba929a8..6aa9619cc9 100644 --- a/quickstep/recents_ui_overrides/res/values/override.xml +++ b/quickstep/recents_ui_overrides/res/values/override.xml @@ -26,5 +26,7 @@ com.android.quickstep.QuickstepProcessInitializer com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension + + com.android.launcher3.hybridhotseat.HotseatPredictionModel diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index e9f35345ca..7a73e50da8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -25,10 +25,7 @@ import android.app.prediction.AppPredictionManager; import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; -import android.app.prediction.AppTargetId; import android.content.ComponentName; -import android.os.Bundle; -import android.os.Process; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -36,8 +33,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.CellLayout; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.Hotseat; @@ -48,7 +43,6 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.Workspace; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.appprediction.ComponentKeyMapper; @@ -57,12 +51,10 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.FileLog; -import com.android.launcher3.model.PredictionModel; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; @@ -77,7 +69,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.OptionalInt; import java.util.stream.IntStream; @@ -93,17 +84,6 @@ public class HotseatPredictionController implements DragController.DragListener, private static final String TAG = "PredictiveHotseat"; private static final boolean DEBUG = false; - //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) - private static final int APPTARGET_ACTION_UNPIN = 4; - - private static final String APP_LOCATION_HOTSEAT = "hotseat"; - private static final String APP_LOCATION_WORKSPACE = "workspace"; - - private static final String BUNDLE_KEY_HOTSEAT = "hotseat_apps"; - private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps"; - - private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; - private static final String PREDICTION_CLIENT = "hotseat"; private DropTarget.DragObject mDragObject; private int mHotSeatItemsCount; @@ -116,13 +96,14 @@ public class HotseatPredictionController implements DragController.DragListener, private DynamicItemCache mDynamicItemCache; - private final PredictionModel mPredictionModel; + private final HotseatPredictionModel mPredictionModel; private AppPredictor mAppPredictor; private AllAppsStore mAllAppsStore; private AnimatorSet mIconRemoveAnimators; private boolean mUIUpdatePaused = false; private boolean mRequiresCacheUpdate = true; private boolean mIsCacheEmpty; + private boolean mIsDestroyed = false; private HotseatEduController mHotseatEduController; @@ -141,13 +122,13 @@ public class HotseatPredictionController implements DragController.DragListener, mLauncher = launcher; mHotseat = launcher.getHotseat(); mAllAppsStore = mLauncher.getAppsView().getAppsStore(); - mPredictionModel = LauncherAppState.INSTANCE.get(launcher).getPredictionModel(); + LauncherAppState appState = LauncherAppState.getInstance(launcher); + mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel(); mAllAppsStore.addUpdateListener(this); mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction); mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); - mIsCacheEmpty = mPredictionModel.getPredictionComponentKeys().isEmpty(); if (mHotseat.isAttachedToWindow()) { onViewAttachedToWindow(mHotseat); } @@ -260,6 +241,7 @@ public class HotseatPredictionController implements DragController.DragListener, * Unregisters callbacks and frees resources */ public void destroy() { + mIsDestroyed = true; mAllAppsStore.removeUpdateListener(this); mLauncher.getDeviceProfile().inv.removeOnChangeListener(this); mHotseat.removeOnAttachStateChangeListener(this); @@ -293,99 +275,52 @@ public class HotseatPredictionController implements DragController.DragListener, if (mAppPredictor != null) { mAppPredictor.destroy(); } - mAppPredictor = apm.createAppPredictionSession( - new AppPredictionContext.Builder(mLauncher) - .setUiSurface(PREDICTION_CLIENT) - .setPredictedTargetCount(mHotSeatItemsCount) - .setExtras(getAppPredictionContextExtra()) - .build()); WeakReference controllerRef = new WeakReference<>(this); - mAppPredictor.registerPredictionUpdates(mLauncher.getApplicationContext().getMainExecutor(), - list -> { - if (controllerRef.get() != null) { - controllerRef.get().setPredictedApps(list); - } - }); + + mPredictionModel.createBundle(bundle -> { + if (mIsDestroyed) return; + mAppPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(mLauncher) + .setUiSurface(PREDICTION_CLIENT) + .setPredictedTargetCount(mHotSeatItemsCount) + .setExtras(bundle) + .build()); + mAppPredictor.registerPredictionUpdates( + mLauncher.getApplicationContext().getMainExecutor(), + list -> { + if (controllerRef.get() != null) { + controllerRef.get().setPredictedApps(list); + } + }); + mAppPredictor.requestPredictionUpdate(); + }); setPauseUIUpdate(false); if (!isEduSeen()) { mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor); } - mAppPredictor.requestPredictionUpdate(); } /** * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items. */ - public void showCachedItems(List apps, IntArray ranks) { + public void showCachedItems(List apps, IntArray ranks) { + mIsCacheEmpty = apps.isEmpty(); int count = Math.min(ranks.size(), apps.size()); List items = new ArrayList<>(count); + mComponentKeyMappers.clear(); for (int i = 0; i < count; i++) { WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i)); + ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user); preparePredictionInfo(item, ranks.get(i)); items.add(item); - } - mComponentKeyMappers.clear(); - for (ComponentKey key : mPredictionModel.getPredictionComponentKeys()) { - mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); + + mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache)); } updateDependencies(); bindItems(items, false, null); } - private Bundle getAppPredictionContextExtra() { - Bundle bundle = new Bundle(); - - //TODO: remove this way of reporting items - bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT, - getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets()))); - bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup( - mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets())); - - ArrayList pinEvents = new ArrayList<>(); - getPinEventsForViewGroup(pinEvents, mHotseat.getShortcutsAndWidgets(), - APP_LOCATION_HOTSEAT); - getPinEventsForViewGroup(pinEvents, mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(), APP_LOCATION_WORKSPACE); - bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, pinEvents); - - return bundle; - } - - private ArrayList getPinEventsForViewGroup(ArrayList pinEvents, - ViewGroup views, String root) { - for (int i = 0; i < views.getChildCount(); i++) { - View child = views.getChildAt(i); - final AppTargetEvent event; - if (child.getTag() instanceof ItemInfo && getAppTargetFromInfo( - (ItemInfo) child.getTag()) != null) { - ItemInfo info = (ItemInfo) child.getTag(); - event = wrapAppTargetWithLocation(getAppTargetFromInfo(info), - AppTargetEvent.ACTION_PIN, info); - } else { - CellLayout.LayoutParams params = (CellLayout.LayoutParams) views.getLayoutParams(); - event = wrapAppTargetWithLocation(getBlockAppTarget(), AppTargetEvent.ACTION_PIN, - root, 0, params.cellX, params.cellY, params.cellHSpan, params.cellVSpan); - } - pinEvents.add(event); - } - return pinEvents; - } - - - private ArrayList getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) { - ArrayList pinnedApps = new ArrayList<>(); - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - if (isPinnedIcon(child)) { - WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag(); - pinnedApps.add(getAppTargetFromItemInfo(itemInfo)); - } - } - return pinnedApps; - } - private void setPredictedApps(List appTargets) { mComponentKeyMappers.clear(); StringBuilder predictionLog = new StringBuilder("predictedApps: [\n"); @@ -443,8 +378,11 @@ public class HotseatPredictionController implements DragController.DragListener, workspaceItemInfo.cellX, workspaceItemInfo.cellY); ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); icon.pin(workspaceItemInfo); - AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo); - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); + AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo); + if (appTarget != null) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_PIN, workspaceItemInfo)); + } mRequiresCacheUpdate = true; } @@ -524,10 +462,9 @@ public class HotseatPredictionController implements DragController.DragListener, mIconRemoveAnimators.start(); } - private void notifyItemAction(AppTarget target, String location, int action) { + private void notifyItemAction(AppTargetEvent event) { if (mAppPredictor != null) { - mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, - action).setLaunchLocation(location).build()); + mAppPredictor.notifyAppTargetEvent(event); } } @@ -545,36 +482,35 @@ public class HotseatPredictionController implements DragController.DragListener, /** * Unpins pinned app when it's converted into a folder */ - public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) { - if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - return; + public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { + AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); + AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); + if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, + AppTargetEvent.ACTION_PIN, folderInfo)); } - AppTarget target = getAppTargetFromItemInfo(info); - ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets(); - ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(); - - if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains( - target)) { - notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); - } else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup( - firstScreenVG).contains(target)) { - notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); + // using folder info with isTrackedForPrediction as itemInfo.container is already changed + // to folder by this point + if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, + AppTargetEvent.ACTION_UNPIN, folderInfo + )); } } /** * Pins workspace item created when all folder items are removed but one */ - public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) { - if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - return; + public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { + AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); + AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); + if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, + AppTargetEvent.ACTION_UNPIN, folderInfo)); } - AppTarget target = getAppTargetFromItemInfo(info); - if (isInHotseat(info)) { - notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); - } else if (isInFirstPage(info)) { - notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); + if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, + AppTargetEvent.ACTION_PIN, itemInfo)); } } @@ -585,29 +521,18 @@ public class HotseatPredictionController implements DragController.DragListener, } ItemInfo dragInfo = mDragObject.dragInfo; - ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets(); - ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(); - - if (dragInfo instanceof WorkspaceItemInfo - && dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && dragInfo.getTargetComponent() != null) { - AppTarget appTarget = getAppTargetFromItemInfo(dragInfo); - if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { - if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) { - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); - } + if (mDragObject.isMoved()) { + AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo); + //always send pin event first to prevent AiAi from predicting an item moved within + // the same page + if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_PIN, dragInfo)); } - if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) { - if (!getPinnedAppTargetsInViewGroup(firstScreenVG).contains(appTarget)) { - notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); - } - } - if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); - } - if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); + if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction( + mDragObject.originalDragInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo)); } } mDragObject = null; @@ -615,6 +540,7 @@ public class HotseatPredictionController implements DragController.DragListener, mRequiresCacheUpdate = true; } + @Nullable @Override public SystemShortcut getShortcut(QuickstepLauncher activity, @@ -711,77 +637,4 @@ public class HotseatPredictionController implements DragController.DragListener, && ((WorkspaceItemInfo) view.getTag()).container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; } - - private static boolean isPinnedIcon(View view) { - if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) { - return false; - } - ItemInfo info = (ItemInfo) view.getTag(); - return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && ( - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION); - } - - private static boolean isInHotseat(ItemInfo itemInfo) { - return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; - } - - private static boolean isInFirstPage(ItemInfo itemInfo) { - return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP - && itemInfo.screenId == Workspace.FIRST_SCREEN_ID; - } - - private static AppTarget getAppTargetFromItemInfo(ItemInfo info) { - if (info.getTargetComponent() == null) return null; - ComponentName cn = info.getTargetComponent(); - return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } - - private AppTarget getAppTargetFromInfo(ItemInfo info) { - if (info == null) return null; - if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET - && info instanceof LauncherAppWidgetInfo - && ((LauncherAppWidgetInfo) info).providerName != null) { - ComponentName cn = ((LauncherAppWidgetInfo) info).providerName; - return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && info.getTargetComponent() != null) { - ComponentName cn = info.getTargetComponent(); - return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT - && info instanceof WorkspaceItemInfo) { - ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info); - //TODO: switch to using full shortcut info - return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()), - shortcutKey.componentName.getPackageName(), shortcutKey.user).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { - return new AppTarget.Builder(new AppTargetId("folder:" + info.id), - mLauncher.getPackageName(), info.user).build(); - } - return null; - } - - private AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) { - return wrapAppTargetWithLocation(target, action, - info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT - ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE, info.screenId, info.cellX, - info.cellY, info.spanX, info.spanY); - } - - private AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, String root, - int screenId, int x, int y, int spanX, int spanY) { - return new AppTargetEvent.Builder(target, action).setLaunchLocation( - String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]", root, screenId, x, y, spanX, - spanY)).build(); - } - - /** - * A helper method to generate an AppTarget that's used to communicate workspace layout - */ - private AppTarget getBlockAppTarget() { - return new AppTarget.Builder(new AppTargetId("block"), - mLauncher.getPackageName(), Process.myUserHandle()).build(); - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java new file mode 100644 index 0000000000..5a038d27af --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java @@ -0,0 +1,136 @@ +/* + * 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.hybridhotseat; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; +import android.content.ComponentName; +import android.content.Context; +import android.os.Bundle; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.Workspace; +import com.android.launcher3.model.AllAppsList; +import com.android.launcher3.model.BaseModelUpdateTask; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.PredictionModel; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.shortcuts.ShortcutKey; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.function.Consumer; + +/** + * Model helper for app predictions in workspace + */ +public class HotseatPredictionModel extends PredictionModel { + private static final String APP_LOCATION_HOTSEAT = "hotseat"; + private static final String APP_LOCATION_WORKSPACE = "workspace"; + + private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; + private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items"; + + + public HotseatPredictionModel(Context context) { } + + /** + * Creates and returns bundle using workspace items and cached items + */ + public void createBundle(Consumer cb) { + LauncherAppState appState = LauncherAppState.getInstance(mContext); + appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + Bundle bundle = new Bundle(); + ArrayList events = new ArrayList<>(); + ArrayList workspaceItems = new ArrayList<>(dataModel.workspaceItems); + workspaceItems.addAll(dataModel.appWidgets); + for (ItemInfo item : workspaceItems) { + AppTarget target = getAppTargetFromInfo(item); + if (target != null && !isTrackedForPrediction(item)) continue; + events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item)); + } + ArrayList currentTargets = new ArrayList<>(); + for (ItemInfo itemInfo : dataModel.cachedPredictedItems) { + AppTarget target = getAppTargetFromInfo(itemInfo); + if (target != null) currentTargets.add(target); + } + bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events); + bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets); + MAIN_EXECUTOR.execute(() -> cb.accept(bundle)); + } + }); + } + + /** + * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null + * if item is not supported prediction + */ + public AppTarget getAppTargetFromInfo(ItemInfo info) { + if (info == null) return null; + if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET + && info instanceof LauncherAppWidgetInfo + && ((LauncherAppWidgetInfo) info).providerName != null) { + ComponentName cn = ((LauncherAppWidgetInfo) info).providerName; + return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()), + cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); + } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && info.getTargetComponent() != null) { + ComponentName cn = info.getTargetComponent(); + return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), + cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); + } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT + && info instanceof WorkspaceItemInfo) { + ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info); + //TODO: switch to using full shortcut info + return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()), + shortcutKey.componentName.getPackageName(), shortcutKey.user).build(); + } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { + return new AppTarget.Builder(new AppTargetId("folder:" + info.id), + mContext.getPackageName(), info.user).build(); + } + return null; + } + + + /** + * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item + * location using {@link ItemInfo} + */ + public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) { + String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]", + info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE, + info.screenId, info.cellX, info.cellY, info.spanX, info.spanY); + return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build(); + } + + /** + * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors + */ + public static boolean isTrackedForPrediction(ItemInfo info) { + return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || ( + info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP + && info.screenId == Workspace.FIRST_SCREEN_ID); + } +} diff --git a/res/values/config.xml b/res/values/config.xml index 603dc91619..4cbc597f6f 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -69,6 +69,7 @@ + diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index 0b0983cbee..c1aed9812c 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -110,6 +110,18 @@ public interface DropTarget { return res; } + + + /** + * This is used to determine if an object is dropped at a different location than it was + * dragged from + */ + public boolean isMoved() { + return dragInfo.cellX != originalDragInfo.cellX + || dragInfo.cellY != originalDragInfo.cellY + || dragInfo.screenId != originalDragInfo.screenId + || dragInfo.container != originalDragInfo.container; + } } /** diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 14e604d9ed..53e5274c0c 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -129,7 +129,7 @@ public class LauncherAppState { mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); - mPredictionModel = new PredictionModel(mContext); + mPredictionModel = PredictionModel.newInstance(mContext); } protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 1465100b83..ab921eaad9 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -253,8 +253,8 @@ public abstract class BaseLoaderResults { } private void bindPredictedItems(IntArray ranks, final Executor executor) { - executeCallbacksTask( - c -> c.bindPredictedItems(mBgDataModel.cachedPredictedItems, ranks), executor); + ArrayList items = new ArrayList<>(mBgDataModel.cachedPredictedItems); + executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor); } protected void executeCallbacksTask(CallbackTask task, Executor executor) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 9e6282e270..d05d70b044 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -45,6 +45,8 @@ import android.util.LongSparseArray; import android.util.MutableInt; import android.util.TimingLogger; +import androidx.annotation.WorkerThread; + import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; @@ -850,12 +852,11 @@ public class LoaderTask implements Runnable { } } - private List loadCachedPredictions() { + @WorkerThread + private void loadCachedPredictions() { synchronized (mBgDataModel) { List componentKeys = mApp.getPredictionModel().getPredictionComponentKeys(); - List results = new ArrayList<>(); - if (componentKeys == null) return results; List l; mBgDataModel.cachedPredictedItems.clear(); for (ComponentKey key : componentKeys) { @@ -866,7 +867,6 @@ public class LoaderTask implements Runnable { mBgDataModel.cachedPredictedItems.add(info); mIconCache.getTitleAndIcon(info, false); } - return results; } } diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java index 6aa41eb760..f8140eb87a 100644 --- a/src/com/android/launcher3/model/PredictionModel.java +++ b/src/com/android/launcher3/model/PredictionModel.java @@ -14,60 +14,86 @@ * limitations under the License. */ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.os.UserHandle; +import androidx.annotation.AnyThread; +import androidx.annotation.WorkerThread; + +import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.ResourceBasedOverride; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** - * Model helper for app predictions in workspace + * Model Helper for app predictions */ -public class PredictionModel { +public class PredictionModel implements ResourceBasedOverride { + private static final String CACHED_ITEMS_KEY = "predicted_item_keys"; private static final int MAX_CACHE_ITEMS = 5; - private final Context mContext; - private final SharedPreferences mDevicePrefs; + protected Context mContext; private ArrayList mCachedComponentKeys; + private SharedPreferences mDevicePrefs; + private UserCache mUserCache; - public PredictionModel(Context context) { - mContext = context; - mDevicePrefs = Utilities.getDevicePrefs(mContext); + + /** + * Retrieve instance of this object that can be overridden in runtime based on the build + * variant of the application. + */ + public static PredictionModel newInstance(Context context) { + PredictionModel model = Overrides.getObject(PredictionModel.class, context, + R.string.prediction_model_class); + model.init(context); + return model; } + protected void init(Context context) { + mContext = context; + mDevicePrefs = Utilities.getDevicePrefs(mContext); + mUserCache = UserCache.INSTANCE.get(mContext); + + } /** * Formats and stores a list of component key in device preferences. */ + @AnyThread public void cachePredictionComponentKeys(List componentKeys) { - StringBuilder builder = new StringBuilder(); - int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS); - for (int i = 0; i < count; i++) { - builder.append(componentKeys.get(i)); - builder.append("\n"); - } - mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); - mCachedComponentKeys = null; + MODEL_EXECUTOR.execute(() -> { + StringBuilder builder = new StringBuilder(); + int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS); + for (int i = 0; i < count; i++) { + builder.append(serializeComponentKeyToString(componentKeys.get(i))); + builder.append("\n"); + } + mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); + mCachedComponentKeys = null; + }); } /** * parses and returns ComponentKeys saved by * {@link PredictionModel#cachePredictionComponentKeys(List)} */ + @WorkerThread public List getPredictionComponentKeys() { + Preconditions.assertWorkerThread(); if (mCachedComponentKeys == null) { mCachedComponentKeys = new ArrayList<>(); - String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, ""); for (String line : cachedBlob.split("\n")) { - ComponentKey key = ComponentKey.fromString(line); + ComponentKey key = getComponentKeyFromSerializedString(line); if (key != null) { mCachedComponentKeys.add(key); } @@ -76,18 +102,26 @@ public class PredictionModel { return mCachedComponentKeys; } - /** - * Remove uninstalled applications from model - */ - public void removePackage(String pkgName, UserHandle user, ArrayList ids) { - for (int i = ids.size() - 1; i >= 0; i--) { - AppInfo info = ids.get(i); - if (info.user.equals(user) && pkgName.equals(info.componentName.getPackageName())) { - ids.remove(i); - } + private String serializeComponentKeyToString(ComponentKey componentKey) { + long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user); + return componentKey.componentName.flattenToString() + "#" + userSerialNumber; + } + + private ComponentKey getComponentKeyFromSerializedString(String str) { + int sep = str.indexOf('#'); + if (sep < 0 || (sep + 1) >= str.length()) { + return null; + } + ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep)); + if (componentName == null) { + return null; + } + try { + long serialNumber = Long.parseLong(str.substring(sep + 1)); + UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber); + return userHandle != null ? new ComponentKey(componentName, userHandle) : null; + } catch (NumberFormatException ex) { + return null; } - cachePredictionComponentKeys(getPredictionComponentKeys().stream() - .filter(cn -> !(cn.user.equals(user) && cn.componentName.getPackageName().equals( - pkgName))).collect(Collectors.toList())); } } From 7e4d19f842a80bb72b107babba6f1e0d9913b073 Mon Sep 17 00:00:00 2001 From: vadimt Date: Thu, 14 May 2020 19:52:01 -0700 Subject: [PATCH 15/25] Rename closeEvents to reduce confusion Change-Id: I73f083c568846ec8f96b4b403afdaf10d75a106e --- .../android/launcher3/tapl/LauncherInstrumentation.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index b333100393..974956770e 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -362,7 +362,7 @@ public final class LauncherInstrumentation { public void checkForAnomaly() { final String systemAnomalyMessage = getSystemAnomalyMessage(); if (systemAnomalyMessage != null) { - Assert.fail(formatSystemHealthMessage(closeEvents( + Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( "http://go/tapl : Tests are broken by a non-Launcher system error: " + systemAnomalyMessage, false))); } @@ -424,7 +424,7 @@ public final class LauncherInstrumentation { return message; } - private String closeEvents(String message, boolean checkEvents) { + private String formatErrorWithEvents(String message, boolean checkEvents) { if (sCheckingEvents) { sCheckingEvents = false; if (checkEvents) { @@ -454,7 +454,7 @@ public final class LauncherInstrumentation { private void fail(String message) { checkForAnomaly(); - Assert.fail(formatSystemHealthMessage(closeEvents( + Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( "http://go/tapl : " + getContextDescription() + message + " (visible state: " + getVisibleStateMessage() + ")", true))); } @@ -1315,7 +1315,8 @@ public final class LauncherInstrumentation { if (mOnLauncherCrashed != null) mOnLauncherCrashed.run(); checkForAnomaly(); Assert.fail( - formatSystemHealthMessage(closeEvents("Launcher crashed", false))); + formatSystemHealthMessage( + formatErrorWithEvents("Launcher crashed", false))); } if (sCheckingEvents) { From 6793dae6920a0410d776ecd7a2daac105405ba8b Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 13 May 2020 21:25:11 -0700 Subject: [PATCH 16/25] Prevent all apps prediction update while visible Test: Manual Bug: 156418702 Change-Id: Iaa5468d7c600e6568ac1e35108c586752d6658b6 --- .../launcher3/appprediction/PredictionUiStateManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index ab3c71ae86..b6a8206ef0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -211,8 +211,11 @@ public class PredictionUiStateManager implements StateListener, } private void dispatchOnChange(boolean changed) { - PredictionState newState = changed ? parseLastState() : - (mPendingState == null ? mCurrentState : mPendingState); + PredictionState newState = changed + ? parseLastState() + : mPendingState != null && canApplyPredictions(mPendingState) + ? mPendingState + : mCurrentState; if (changed && mAppsView != null && !canApplyPredictions(newState)) { scheduleApplyPredictedApps(newState); } else { From 9c62e1866007357c34354f89976c826689465eff Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Fri, 15 May 2020 10:04:51 -0700 Subject: [PATCH 17/25] Remove debug logs, root cause found. Bug: 146964271 Change-Id: I38ed3b4b1a90fa1ff9a3aa4bb5547300ddd92358 --- .../src/com/android/quickstep/TouchInteractionService.java | 2 -- .../src/com/android/quickstep/RecentsAnimationDeviceState.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index acc7794631..82b009784b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -540,8 +540,6 @@ public class TouchInteractionService extends Service implements PluginListener Date: Fri, 15 May 2020 11:01:06 -0700 Subject: [PATCH 18/25] Deference mAppPredictor on destroy + Also remove PredictionModel.mCachedComponentKeys Bug: 156747920 Bug: 156041043 Test: Manual Change-Id: I56dac1c6ac0a1bb93363342006232fc1ba42a3b4 --- .../HotseatPredictionController.java | 1 + .../launcher3/model/PredictionModel.java | 19 ++++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 7a73e50da8..1fe364386e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -274,6 +274,7 @@ public class HotseatPredictionController implements DragController.DragListener, } if (mAppPredictor != null) { mAppPredictor.destroy(); + mAppPredictor = null; } WeakReference controllerRef = new WeakReference<>(this); diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java index f8140eb87a..1429843b7e 100644 --- a/src/com/android/launcher3/model/PredictionModel.java +++ b/src/com/android/launcher3/model/PredictionModel.java @@ -43,7 +43,6 @@ public class PredictionModel implements ResourceBasedOverride { private static final int MAX_CACHE_ITEMS = 5; protected Context mContext; - private ArrayList mCachedComponentKeys; private SharedPreferences mDevicePrefs; private UserCache mUserCache; @@ -78,7 +77,6 @@ public class PredictionModel implements ResourceBasedOverride { builder.append("\n"); } mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); - mCachedComponentKeys = null; }); } @@ -89,17 +87,16 @@ public class PredictionModel implements ResourceBasedOverride { @WorkerThread public List getPredictionComponentKeys() { Preconditions.assertWorkerThread(); - if (mCachedComponentKeys == null) { - mCachedComponentKeys = new ArrayList<>(); - String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, ""); - for (String line : cachedBlob.split("\n")) { - ComponentKey key = getComponentKeyFromSerializedString(line); - if (key != null) { - mCachedComponentKeys.add(key); - } + ArrayList items = new ArrayList<>(); + String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, ""); + for (String line : cachedBlob.split("\n")) { + ComponentKey key = getComponentKeyFromSerializedString(line); + if (key != null) { + items.add(key); } + } - return mCachedComponentKeys; + return items; } private String serializeComponentKeyToString(ComponentKey componentKey) { From bb6f5f5242ecb5c8df64320ff3b2e247ad5528f4 Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 15 May 2020 13:11:36 -0700 Subject: [PATCH 19/25] Add dumpsys input where it was missing Bug: 156287114 Change-Id: Ie0a71e8dc261abfde4eec3ce882786d77a870c32 --- .../launcher3/tapl/LauncherInstrumentation.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index b333100393..4493279782 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -1323,6 +1323,16 @@ public final class LauncherInstrumentation { if (mCheckEventsForSuccessfulGestures) { final String message = sEventChecker.verify(WAIT_TIME_MS, true); if (message != null) { + try { + Log.e("b/156287114", "Input:"); + for (String line : mDevice.executeShellCommand("dumpsys input").split( + "\\n")) { + Log.d("b/156287114", line); + } + } catch (IOException e) { + e.printStackTrace(); + } + checkForAnomaly(); Assert.fail(formatSystemHealthMessage( "http://go/tapl : successful gesture produced " + message)); From b9abae9f0ffd887041374cedb4201c48d28462d2 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 12 May 2020 15:18:49 -0700 Subject: [PATCH 20/25] Using taskView simulator for animation a task to recentsView Also applying transform params in DeviceLockedInputConsumer directly instead of going through AppWindowAnimationHelper Bug: 156398988 Bug: 155816922 Change-Id: I791e1a9feb07c4fb787130f8d040a4f404faf734 --- .../RecentsViewStateController.java | 2 +- .../AppToOverviewAnimationProvider.java | 102 +++++++----------- .../android/quickstep/BaseSwipeUpHandler.java | 33 ++---- .../DeviceLockedInputConsumer.java | 74 ++++++------- .../util/AppWindowAnimationHelper.java | 27 ----- .../util/StaggeredWorkspaceAnim.java | 3 +- .../quickstep/util/TaskViewSimulator.java | 23 ++-- .../quickstep/util/TransformParams.java | 30 ++++-- .../android/quickstep/views/RecentsView.java | 4 + .../quickstep/RemoteAnimationTargets.java | 2 +- .../launcher3/anim/PendingAnimation.java | 32 ++++-- .../launcher3/statemanager/StateManager.java | 8 +- 12 files changed, 158 insertions(+), 182 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index a1cc60ec71..085b9b3af9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -71,7 +71,7 @@ public final class RecentsViewStateController extends builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData); mRecentsView.updateEmptyMessage(); } else { - builder.getAnim().addListener( + builder.addListener( AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals)); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index f38ff10cc2..dc8fb9e124 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -18,29 +18,26 @@ package com.android.quickstep; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; +import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.statehandlers.DepthController.DEPTH; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; -import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.Animator; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.graphics.Rect; import android.util.Log; -import android.view.View; +import android.view.animation.Interpolator; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StatefulActivity; -import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.RemoteAnimationProvider; +import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; -import com.android.systemui.shared.system.TransactionCompat; /** * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview. @@ -97,32 +94,25 @@ final class AppToOverviewAnimationProvider> extend @Override public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { - if (mRecentsView != null) { - mRecentsView.setRunningTaskIconScaledDown(true); + PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); + if (mActivity == null) { + Log.e(TAG, "Animation created, before activity"); + return pa.buildAnim(); } - AnimatorSet anim = new AnimatorSet(); - anim.addListener(new AnimationSuccessListener() { + mRecentsView.setRunningTaskIconScaledDown(true); + pa.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { mActivityInterface.onSwipeUpToRecentsComplete(); - if (mRecentsView != null) { - mRecentsView.animateUpRunningTaskIconScale(); - } + mRecentsView.animateUpRunningTaskIconScale(); } }); - if (mActivity == null) { - Log.e(TAG, "Animation created, before activity"); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)); - return anim; - } DepthController depthController = mActivityInterface.getDepthController(); if (depthController != null) { - anim.play(ObjectAnimator.ofFloat(depthController, DEPTH, - BACKGROUND_APP.getDepth(mActivity), - OVERVIEW.getDepth(mActivity)) - .setDuration(RECENTS_LAUNCH_DURATION)); + pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity), + OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR); } RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets, @@ -132,53 +122,39 @@ final class AppToOverviewAnimationProvider> extend RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId); if (runningTaskTarget == null) { Log.e(TAG, "No closing app"); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)); - return anim; + return pa.buildAnim(); } - final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper( - mRecentsView.getPagedViewOrientedState(), mActivity); - - // At this point, the activity is already started and laid-out. Get the home-bounds - // relative to the screen using the rootView of the activity. - int loc[] = new int[2]; - View rootView = mActivity.getRootView(); - rootView.getLocationOnScreen(loc); - Rect homeBounds = new Rect(loc[0], loc[1], - loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight()); - clipHelper.updateSource(homeBounds, runningTaskTarget); - - Rect targetRect = new Rect(); - mActivityInterface.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity, - targetRect); - clipHelper.updateTargetRect(targetRect); - clipHelper.prepareAnimation(mActivity.getDeviceProfile()); + TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy()); + tsv.setDp(mActivity.getDeviceProfile()); + tsv.setPreview(runningTaskTarget); + tsv.setLayoutRotation(mRecentsView.getPagedViewOrientedState().getTouchRotation(), + mRecentsView.getPagedViewOrientedState().getDisplayRotation()); TransformParams params = new TransformParams() - .setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(rootView)); - ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); - valueAnimator.setDuration(RECENTS_LAUNCH_DURATION); - valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); - valueAnimator.addUpdateListener((v) -> { - params.setProgress((float) v.getAnimatedValue()).setTargetSet(targets); - clipHelper.applyTransform(params); - }); + .setTargetSet(targets) + .setSyncTransactionApplier( + new SyncRtSurfaceTransactionApplierCompat(mActivity.getRootView())); + AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { }); + params.setBaseAlphaCallback((t, a) -> recentsAlpha.value); + + Interpolator taskInterpolator; if (targets.isAnimatingHome()) { - // If we are animating home, fade in the opening targets - RemoteAnimationTargets openingSet = new RemoteAnimationTargets(appTargets, - wallpaperTargets, MODE_OPENING); - - TransactionCompat transaction = new TransactionCompat(); - valueAnimator.addUpdateListener((v) -> { - for (RemoteAnimationTargetCompat app : openingSet.apps) { - transaction.setAlpha(app.leash, (float) v.getAnimatedValue()); - } - transaction.apply(); - }); + taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR; + pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR); + } else { + // When animation from app to recents, the recents layer is drawn on top of the app. To + // prevent the overlap, we animate the task first and then quickly fade in the recents. + taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f); + pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, + clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1)); } - anim.play(valueAnimator); - return anim; + + pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator); + tsv.addAppToOverviewAnim(pa, taskInterpolator); + pa.addOnFrameCallback(() -> tsv.apply(params)); + return pa.buildAnim(); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index bbee67ce94..14b32ac4d2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -15,8 +15,6 @@ */ package com.android.quickstep; -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; @@ -25,8 +23,6 @@ import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; @@ -49,6 +45,7 @@ import com.android.launcher3.InvariantDeviceProfile; 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.statemanager.StatefulActivity; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.VibratorWrapper; @@ -377,18 +374,9 @@ public abstract class BaseSwipeUpHandler, Q extend mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1; } - AnimatorSet anim = new AnimatorSet(); - anim.setDuration(mTransitionDragLength * 2); - anim.setInterpolator(t -> t * mDragLengthFactor); - anim.play(ObjectAnimator.ofFloat(mTaskViewSimulator.recentsViewScale, - AnimatedFloat.VALUE, - mTaskViewSimulator.getFullScreenScale(), 1)); - anim.play(ObjectAnimator.ofFloat(mTaskViewSimulator.fullScreenProgress, - AnimatedFloat.VALUE, - BACKGROUND_APP.getOverviewFullscreenProgress(), - OVERVIEW.getOverviewFullscreenProgress())); - mWindowTransitionController = - AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2); + PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); + mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor); + mWindowTransitionController = pa.createPlaybackController(); } /** @@ -651,14 +639,11 @@ public abstract class BaseSwipeUpHandler, Q extend } @Override - public void onBuildParams(Builder builder, RemoteAnimationTargetCompat app, int targetMode, - TransformParams params) { - if (app.mode == targetMode - && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { - builder.withMatrix(mMatrix) - .withWindowCrop(mCropRect) - .withCornerRadius(params.getCornerRadius()); - } + public void onBuildTargetParams( + Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { + builder.withMatrix(mMatrix) + .withWindowCrop(mCropRect) + .withCornerRadius(params.getCornerRadius()); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index 2dc7f5ff5c..abe4af4705 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -18,6 +18,7 @@ package com.android.quickstep.inputconsumers; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_UP; + import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; @@ -26,21 +27,22 @@ import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; +import android.animation.ObjectAnimator; import android.content.Context; import android.content.Intent; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; -import android.graphics.Rect; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; + import com.android.launcher3.R; -import com.android.launcher3.Utilities; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.DefaultDisplay; +import com.android.quickstep.AnimatedFloat; import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; import com.android.quickstep.MultiStateCallback; @@ -49,18 +51,19 @@ import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.TaskAnimationManager; -import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.TransformParams; +import com.android.quickstep.util.TransformParams.BuilderProxy; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; /** * A dummy input consumer used when the device is still locked, e.g. from secure camera. */ public class DeviceLockedInputConsumer implements InputConsumer, - RecentsAnimationCallbacks.RecentsAnimationListener { + RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy { private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null; private static int getFlagForIndex(int index, String name) { @@ -83,19 +86,20 @@ public class DeviceLockedInputConsumer implements InputConsumer, private final InputMonitorCompat mInputMonitorCompat; private final PointF mTouchDown = new PointF(); - private final AppWindowAnimationHelper mAppWindowAnimationHelper; private final TransformParams mTransformParams; - private final Point mDisplaySize; private final MultiStateCallback mStateCallback; + private final Point mDisplaySize; + private final Matrix mMatrix = new Matrix(); + private final float mMaxTranslationY; + private VelocityTracker mVelocityTracker; - private float mProgress; + private final AnimatedFloat mProgress = new AnimatedFloat(this::applyTransform); private boolean mThresholdCrossed = false; private boolean mHomeLaunched = false; private RecentsAnimationController mRecentsAnimationController; - private RecentsAnimationTargets mRecentsAnimationTargets; public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, @@ -105,9 +109,10 @@ public class DeviceLockedInputConsumer implements InputConsumer, mTaskAnimationManager = taskAnimationManager; mGestureState = gestureState; mTouchSlopSquared = squaredTouchSlop(context); - mAppWindowAnimationHelper = new AppWindowAnimationHelper(context); mTransformParams = new TransformParams(); mInputMonitorCompat = inputMonitorCompat; + mMaxTranslationY = context.getResources().getDimensionPixelSize( + R.dimen.device_locked_y_offset); // Do not use DeviceProfile as the user data might be locked mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize; @@ -158,9 +163,7 @@ public class DeviceLockedInputConsumer implements InputConsumer, } } else { float dy = Math.max(mTouchDown.y - y, 0); - mProgress = dy / mDisplaySize.y; - mTransformParams.setProgress(mProgress); - mAppWindowAnimationHelper.applyTransform(mTransformParams); + mProgress.updateValue(dy / mDisplaySize.y); } break; } @@ -189,20 +192,13 @@ public class DeviceLockedInputConsumer implements InputConsumer, // Is fling dismissTask = velocityY < 0; } else { - dismissTask = mProgress >= (1 - MIN_PROGRESS_FOR_OVERVIEW); + dismissTask = mProgress.value >= (1 - MIN_PROGRESS_FOR_OVERVIEW); } // Animate back to fullscreen before finishing - ValueAnimator animator = ValueAnimator.ofFloat(mTransformParams.getProgress(), 0f); + ObjectAnimator animator = mProgress.animateToValue(mProgress.value, 0); animator.setDuration(100); animator.setInterpolator(Interpolators.ACCEL); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mTransformParams.setProgress((float) valueAnimator.getAnimatedValue()); - mAppWindowAnimationHelper.applyTransform(mTransformParams); - } - }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -239,29 +235,15 @@ public class DeviceLockedInputConsumer implements InputConsumer, public void onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets) { mRecentsAnimationController = controller; - mRecentsAnimationTargets = targets; - - Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y); - RemoteAnimationTargetCompat targetCompat = targets.findTask( - mGestureState.getRunningTaskId()); - if (targetCompat != null) { - mAppWindowAnimationHelper.updateSource(displaySize, targetCompat); - } - - // Offset the surface slightly - displaySize.offset(0, mContext.getResources().getDimensionPixelSize( - R.dimen.device_locked_y_offset)); - mTransformParams.setTargetSet(mRecentsAnimationTargets); - mAppWindowAnimationHelper.updateTargetRect(displaySize); - mAppWindowAnimationHelper.applyTransform(mTransformParams); - + mTransformParams.setTargetSet(targets); + applyTransform(); mStateCallback.setState(STATE_TARGET_RECEIVED); } @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { mRecentsAnimationController = null; - mRecentsAnimationTargets = null; + mTransformParams.setTargetSet(null); } private void endRemoteAnimation() { @@ -273,6 +255,20 @@ public class DeviceLockedInputConsumer implements InputConsumer, } } + private void applyTransform() { + mTransformParams.setProgress(mProgress.value); + if (mTransformParams.getTargetSet() != null) { + mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); + } + } + + @Override + public void onBuildTargetParams( + Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { + mMatrix.setTranslate(0, mProgress.value * mMaxTranslationY); + builder.withMatrix(mMatrix); + } + @Override public void onConsumerAboutToBeSwitched() { mStateCallback.setState(STATE_HANDLER_INVALIDATED); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java index a7979cce96..d22755e727 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java @@ -77,7 +77,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { private final Matrix mTmpMatrix = new Matrix(); private final Rect mTmpRect = new Rect(); private final RectF mTmpRectF = new RectF(); - private final RectF mCurrentRectWithInsets = new RectF(); private RecentsOrientedState mOrientedState; // Corner radius of windows, in pixels private final float mWindowCornerRadius; @@ -88,9 +87,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { // Whether or not to actually use the rounded cornders on windows private boolean mUseRoundedCornersOnWindows; - // Corner radius currently applied to transformed window. - private float mCurrentCornerRadius; - public AppWindowAnimationHelper(RecentsOrientedState orientedState, Context context) { Resources res = context.getResources(); mOrientedState = orientedState; @@ -100,10 +96,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows; } - public AppWindowAnimationHelper(Context context) { - this(null, context); - } - private void updateSourceStack(RemoteAnimationTargetCompat target) { mSourceInsets.set(target.contentInsets); mSourceStackBounds.set(target.screenSpaceBounds); @@ -112,15 +104,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { mSourceStackBounds.offsetTo(target.position.x, target.position.y); } - public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) { - updateSourceStack(target); - updateHomeBounds(homeStackBounds); - } - - public void updateHomeBounds(Rect homeStackBounds) { - mHomeStackBounds.set(homeStackBounds); - } - public void updateTargetRect(Rect targetRect) { mSourceRect.set(mSourceInsets.left, mSourceInsets.top, mSourceStackBounds.width() - mSourceInsets.right, @@ -205,7 +188,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { cornerRadius = mapRange(boundToRange(params.getProgress(), 0, 1), windowCornerRadius, mTaskCornerRadius); } - mCurrentCornerRadius = cornerRadius; } builder.withMatrix(mTmpMatrix) @@ -241,11 +223,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress); } - public RectF getCurrentRectWithInsets() { - mTmpMatrix.mapRect(mCurrentRectWithInsets, mCurrentClipRectF); - return mCurrentRectWithInsets; - } - public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv, @Nullable RemoteAnimationTargetCompat target) { BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext()); @@ -317,8 +294,4 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { return mTargetRect; } - public float getCurrentCornerRadius() { - return mCurrentCornerRadius; - } - } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 32fc0de0a8..3e0daaf2ea 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -212,12 +212,13 @@ public class StaggeredWorkspaceAnim { } private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) { - PendingAnimation builder = new PendingAnimation(duration, mAnimators); + PendingAnimation builder = new PendingAnimation(duration); launcher.getWorkspace().getStateTransitionAnimation().setScrim(builder, state); builder.setFloat( launcher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS, state.getOverviewScrimAlpha(launcher), ACCEL_DEACCEL); + mAnimators.play(builder.buildAnim()); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java index a3db940211..f4f7e9c957 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java @@ -20,6 +20,7 @@ import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TR import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; +import android.animation.TimeInterpolator; import android.content.Context; import android.graphics.Matrix; import android.graphics.PointF; @@ -29,6 +30,7 @@ import android.graphics.RectF; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.BaseActivityInterface; @@ -144,6 +146,14 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } } + /** + * Adds animation for all the components corresponding to transition from an app to overview + */ + public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) { + pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator); + pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator); + } + /** * Returns the current clipped/visible window bounds in the window coordinate space */ @@ -250,14 +260,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } @Override - public void onBuildParams(Builder builder, RemoteAnimationTargetCompat app, - int targetMode, TransformParams params) { - if (app.mode == targetMode - && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { - builder.withMatrix(mMatrix) - .withWindowCrop(mTmpCropRect) - .withCornerRadius(getCurrentCornerRadius()); - } + public void onBuildTargetParams( + Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { + builder.withMatrix(mMatrix) + .withWindowCrop(mTmpCropRect) + .withCornerRadius(getCurrentCornerRadius()); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java index 83b64db024..d837e54835 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java @@ -16,6 +16,7 @@ package com.android.quickstep.util; import android.graphics.RectF; +import android.util.FloatProperty; import androidx.annotation.Nullable; @@ -29,6 +30,19 @@ import com.android.systemui.shared.system.TransactionCompat; public class TransformParams { + public static FloatProperty PROGRESS = + new FloatProperty("progress") { + @Override + public void setValue(TransformParams params, float v) { + params.setProgress(v); + } + + @Override + public Float get(TransformParams params) { + return params.getProgress(); + } + }; + private float mProgress; private @Nullable RectF mCurrentRect; private float mTargetAlpha; @@ -176,10 +190,6 @@ public class TransformParams { return mTargetSet; } - public SyncRtSurfaceTransactionApplierCompat getSyncTransactionApplier() { - return mSyncTransactionApplier; - } - public void applySurfaceParams(SurfaceParams[] params) { if (mSyncTransactionApplier != null) { mSyncTransactionApplier.scheduleApply(params); @@ -199,7 +209,15 @@ public class TransformParams { public interface BuilderProxy { - void onBuildParams(SurfaceParams.Builder builder, - RemoteAnimationTargetCompat app, int targetMode, TransformParams params); + default void onBuildParams(SurfaceParams.Builder builder, + RemoteAnimationTargetCompat app, int targetMode, TransformParams params) { + if (app.mode == targetMode + && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { + onBuildTargetParams(builder, app, params); + } + } + + default void onBuildTargetParams(SurfaceParams.Builder builder, + RemoteAnimationTargetCompat app, TransformParams params) { } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 253e83cab4..3fc235c2f0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -2190,6 +2190,10 @@ public abstract class RecentsView extends PagedView impl */ public void setModalStateEnabled(boolean isModalState) { } + public BaseActivityInterface getSizeStrategy() { + return mSizeStrategy; + } + /** * Used to register callbacks for when our empty message state changes. * diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java index 5fa6bc79e1..f90df4563f 100644 --- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java @@ -68,7 +68,7 @@ public class RemoteAnimationTargets { } public boolean isAnimatingHome() { - for (RemoteAnimationTargetCompat target : apps) { + for (RemoteAnimationTargetCompat target : unfilteredApps) { if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { return true; } diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 740f7f2469..0f04104ca8 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -18,6 +18,7 @@ package com.android.launcher3.anim; import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -51,12 +52,8 @@ public class PendingAnimation implements PropertySetter { private ValueAnimator mProgressAnimator; public PendingAnimation(long duration) { - this(duration, new AnimatorSet()); - } - - public PendingAnimation(long duration, AnimatorSet targetSet) { mDuration = duration; - mAnim = targetSet; + mAnim = new AnimatorSet(); } /** @@ -129,13 +126,32 @@ public class PendingAnimation implements PropertySetter { public void addOnFrameCallback(Runnable runnable) { if (mProgressAnimator == null) { mProgressAnimator = ValueAnimator.ofFloat(0, 1).setDuration(mDuration); - add(mProgressAnimator); } mProgressAnimator.addUpdateListener(anim -> runnable.run()); } - public AnimatorSet getAnim() { + /** + * @see AnimatorSet#addListener(AnimatorListener) + */ + public void addListener(Animator.AnimatorListener listener) { + mAnim.addListener(listener); + } + + /** + * Creates and returns the underlying AnimatorSet + */ + public AnimatorSet buildAnim() { + // Add progress animation to the end, so that frame callback is called after all the other + // animation update. + if (mProgressAnimator != null) { + add(mProgressAnimator); + mProgressAnimator = null; + } + if (mAnimHolders.isEmpty()) { + // Add a dummy animation to that the duration is respected + add(ValueAnimator.ofFloat(0, 1)); + } return mAnim; } @@ -143,7 +159,7 @@ public class PendingAnimation implements PropertySetter { * Creates a controller for this animation */ public AnimatorPlaybackController createPlaybackController() { - return new AnimatorPlaybackController(mAnim, mDuration, mAnimHolders); + return new AnimatorPlaybackController(buildAnim(), mDuration, mAnimHolders); } /** diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java index 44471660af..97dc0524f1 100644 --- a/src/com/android/launcher3/statemanager/StateManager.java +++ b/src/com/android/launcher3/statemanager/StateManager.java @@ -239,7 +239,7 @@ public class StateManager> { ? fromState.getTransitionDuration(mActivity) : state.getTransitionDuration(mActivity); prepareForAtomicAnimation(fromState, state, mConfig); - AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).getAnim(); + AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim(); if (onCompleteRunnable != null) { animation.addListener(AnimationSuccessListener.forRunnable(onCompleteRunnable)); } @@ -267,7 +267,7 @@ public class StateManager> { for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) { handler.setStateWithAnimation(toState, config, builder); } - return builder.getAnim(); + return builder.buildAnim(); } /** @@ -309,7 +309,7 @@ public class StateManager> { for (StateHandler handler : getStateHandlers()) { handler.setStateWithAnimation(state, mConfig, builder); } - builder.getAnim().addListener(new AnimationSuccessListener() { + builder.addListener(new AnimationSuccessListener() { @Override public void onAnimationStart(Animator animation) { @@ -325,7 +325,7 @@ public class StateManager> { onStateTransitionEnd(state); } }); - mConfig.setAnimation(builder.getAnim(), state); + mConfig.setAnimation(builder.buildAnim(), state); return builder; } From 39f2f1029b54a60ed3c70e9c88d5fa378ac020a3 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Fri, 15 May 2020 16:32:04 -0500 Subject: [PATCH 21/25] Log last appeared task id instead of target Might be slightly more useful (e.g. if we need to compare against last started task id) Change-Id: I3617be270bec6841fbd943e681de78ada549ff84 --- quickstep/src/com/android/quickstep/GestureState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index 8e14bbb9cf..016ffea2d7 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -322,7 +322,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL pw.println(" gestureID=" + mGestureId); pw.println(" runningTask=" + mRunningTask); pw.println(" endTarget=" + mEndTarget); - pw.println(" lastAppearedTaskTarget=" + mLastAppearedTaskTarget); + pw.println(" lastAppearedTaskTargetId=" + getLastAppearedTaskId()); pw.println(" lastStartedTaskId=" + mLastStartedTaskId); pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning()); } From 2e7f24c1189243246548a7d4b731ade2b57eecad Mon Sep 17 00:00:00 2001 From: thiruram Date: Fri, 15 May 2020 10:30:31 -0700 Subject: [PATCH 22/25] Rename @LauncherUiEvent to @UiEvent to be consistent with other UiEvents. Change-Id: I049a238f717cc2a6b0bbb4755794bc4de2935b97 --- .../launcher3/logging/StatsLogManager.java | 28 +++++++++---------- .../{LauncherUiEvent.java => UiEvent.java} | 10 +++++-- 2 files changed, 21 insertions(+), 17 deletions(-) rename src/com/android/launcher3/logging/{LauncherUiEvent.java => UiEvent.java} (80%) diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index b240f0bef2..475305f65d 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -35,50 +35,50 @@ public class StatsLogManager implements ResourceBasedOverride { } public enum LauncherEvent implements EventEnum { - @LauncherUiEvent(doc = "App launched from workspace, hotseat or folder in launcher") + @UiEvent(doc = "App launched from workspace, hotseat or folder in launcher") LAUNCHER_APP_LAUNCH_TAP(338), - @LauncherUiEvent(doc = "Task launched from overview using TAP") + @UiEvent(doc = "Task launched from overview using TAP") LAUNCHER_TASK_LAUNCH_TAP(339), - @LauncherUiEvent(doc = "Task launched from overview using SWIPE DOWN") + @UiEvent(doc = "Task launched from overview using SWIPE DOWN") LAUNCHER_TASK_LAUNCH_SWIPE_DOWN(340), - @LauncherUiEvent(doc = "TASK dismissed from overview using SWIPE UP") + @UiEvent(doc = "TASK dismissed from overview using SWIPE UP") LAUNCHER_TASK_DISMISS_SWIPE_UP(341), - @LauncherUiEvent(doc = "User dragged a launcher item") + @UiEvent(doc = "User dragged a launcher item") LAUNCHER_ITEM_DRAG_STARTED(383), - @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped") + @UiEvent(doc = "A dragged launcher item is successfully dropped") LAUNCHER_ITEM_DROP_COMPLETED(385), - @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped on another item " + @UiEvent(doc = "A dragged launcher item is successfully dropped on another item " + "resulting in a new folder creation") LAUNCHER_ITEM_DROP_FOLDER_CREATED(386), - @LauncherUiEvent(doc = "User action resulted in or manually updated the folder label to " + @UiEvent(doc = "User action resulted in or manually updated the folder label to " + "new/same value.") LAUNCHER_FOLDER_LABEL_UPDATED(460), - @LauncherUiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar") + @UiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar") LAUNCHER_ITEM_DROPPED_ON_REMOVE(465), - @LauncherUiEvent(doc = "A dragged item is dropped on 'Cancel' button in the target bar") + @UiEvent(doc = "A dragged item is dropped on 'Cancel' button in the target bar") LAUNCHER_ITEM_DROPPED_ON_CANCEL(466), - @LauncherUiEvent(doc = "A predicted item is dragged and dropped on 'Don't suggest app'" + @UiEvent(doc = "A predicted item is dragged and dropped on 'Don't suggest app'" + " button in the target bar") LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST(467), - @LauncherUiEvent(doc = "A dragged item is dropped on 'Uninstall' button in target bar") + @UiEvent(doc = "A dragged item is dropped on 'Uninstall' button in target bar") LAUNCHER_ITEM_DROPPED_ON_UNINSTALL(468), - @LauncherUiEvent(doc = "User completed uninstalling the package after dropping on " + @UiEvent(doc = "User completed uninstalling the package after dropping on " + "the icon onto 'Uninstall' button in the target bar") LAUNCHER_ITEM_UNINSTALL_COMPLETED(469), - @LauncherUiEvent(doc = "User cancelled uninstalling the package after dropping on " + @UiEvent(doc = "User cancelled uninstalling the package after dropping on " + "the icon onto 'Uninstall' button in the target bar") LAUNCHER_ITEM_UNINSTALL_CANCELLED(470); // ADD MORE diff --git a/src/com/android/launcher3/logging/LauncherUiEvent.java b/src/com/android/launcher3/logging/UiEvent.java similarity index 80% rename from src/com/android/launcher3/logging/LauncherUiEvent.java rename to src/com/android/launcher3/logging/UiEvent.java index 4507ff7d76..20d6c72e41 100644 --- a/src/com/android/launcher3/logging/LauncherUiEvent.java +++ b/src/com/android/launcher3/logging/UiEvent.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.launcher3.logging; import static java.lang.annotation.ElementType.FIELD; @@ -23,8 +24,11 @@ import java.lang.annotation.Target; @Retention(SOURCE) @Target(FIELD) -public @interface LauncherUiEvent { - /** An explanation, suitable for Android analysts, of the UI event that this log represents. */ +// Copy of frameworks/base/core/java/com/android/internal/logging/UiEvent.java +public @interface UiEvent { + + /** + * An explanation, suitable for Android analysts, of the UI event that this log represents. + */ String doc(); } - From 23681083b2f0a145f3d03cabf194774a68f98e66 Mon Sep 17 00:00:00 2001 From: Vinit Nayak Date: Thu, 7 May 2020 15:23:13 -0700 Subject: [PATCH 23/25] Hide shelf in 2-Button Landscape Disable overview rotation in 2-Button. Fixes: 154181816 Test: Tested no button and 2 button, overview actions worked as before. Change-Id: Ic22c791c7a93460840d54aaf0bf32884cc110e19 --- .../uioverrides/QuickstepLauncher.java | 4 ++- .../uioverrides/states/OverviewState.java | 8 ++++- .../android/quickstep/BaseSwipeUpHandler.java | 12 +++++++ .../quickstep/LauncherActivityInterface.java | 4 ++- .../quickstep/LauncherSwipeHandler.java | 2 ++ .../quickstep/TouchInteractionService.java | 15 --------- .../quickstep/util/RecentsOrientedState.java | 33 ++++++++++++++----- .../launcher3/InsettableFrameLayout.java | 3 ++ .../launcher3/allapps/DiscoveryBounce.java | 13 +++++--- 9 files changed, 63 insertions(+), 31 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index ad6a10bba3..95087bafdd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -217,7 +217,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { break; } case OVERVIEW_STATE_ORDINAL: { - DiscoveryBounce.showForOverviewIfNeeded(this); + RecentsView recentsView = getOverviewPanel(); + DiscoveryBounce.showForOverviewIfNeeded(this, + recentsView.getPagedOrientationHandler()); RecentsView rv = getOverviewPanel(); sendCustomAccessibilityEvent( rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 9f31608ad7..9b4e8906a1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -19,6 +19,8 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL_2; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; +import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; +import static com.android.quickstep.SysUINavigationMode.getMode; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import android.content.Context; @@ -127,7 +129,11 @@ public class OverviewState extends LauncherState { @Override public int getVisibleElements(Launcher launcher) { - if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) { + RecentsView recentsView = launcher.getOverviewPanel(); + boolean hideShelfTwoButtonLandscape = getMode(launcher) == TWO_BUTTONS && + !recentsView.getPagedOrientationHandler().isLayoutNaturalToLauncher(); + if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) || + hideShelfTwoButtonLandscape) { return OVERVIEW_BUTTONS; } else if (launcher.getDeviceProfile().isVerticalBarLayout()) { return VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index bbee67ce94..b6ccdc55e1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -36,6 +36,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; +import android.util.Log; import android.view.MotionEvent; import android.view.animation.Interpolator; @@ -50,12 +51,14 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.statemanager.StatefulActivity; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; @@ -398,7 +401,16 @@ public abstract class BaseSwipeUpHandler, Q extend protected boolean onActivityInit(Boolean alreadyOnHome) { T createdActivity = mActivityInterface.getCreatedActivity(); + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1"); + } if (createdActivity != null) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2"); + } + ((RecentsView) createdActivity.getOverviewPanel()) + .setLayoutRotation(mDeviceState.getCurrentActiveRotation(), + mDeviceState.getDisplayRotation()); initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext) .getDeviceProfile(mContext)); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index 4ebfbd6a4c..5dbf199599 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -100,7 +100,9 @@ public final class LauncherActivityInterface extends super.onSwipeUpToRecentsComplete(); Launcher launcher = getCreatedActivity(); if (launcher != null) { - DiscoveryBounce.showForOverviewIfNeeded(launcher); + RecentsView recentsView = launcher.getOverviewPanel(); + DiscoveryBounce.showForOverviewIfNeeded(launcher, + recentsView.getPagedOrientationHandler()); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java index 82a3e79372..a8fa630b7b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java @@ -276,6 +276,8 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler setFlag(FLAG_ROTATION_WATCHER_SUPPORTED, newMode != TWO_BUTTONS); + private final Context mContext; private final ContentResolver mContentResolver; private final SharedPreferences mSharedPrefs; @@ -163,13 +168,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre if (isFixedRotationTransformEnabled(context)) { mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG; } - if (mOrientationListener.canDetectOrientation()) { - mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED; - } - - // initialize external flags - updateAutoRotateSetting(); - updateHomeRotationSetting(); + initFlags(); } /** @@ -273,6 +272,18 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false)); } + private void initFlags() { + SysUINavigationMode.Mode currentMode = SysUINavigationMode.getMode(mContext); + if (mOrientationListener.canDetectOrientation() && + currentMode != TWO_BUTTONS) { + mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED; + } + + // initialize external flags + updateAutoRotateSetting(); + updateHomeRotationSetting(); + } + /** * Initializes any system values and registers corresponding change listeners. It must be * paired with {@link #destroyListeners()} call @@ -283,9 +294,11 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mContentResolver.registerContentObserver( Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, mSystemAutoRotateObserver); + SysUINavigationMode.Mode currentMode = + SysUINavigationMode.INSTANCE.get(mContext) + .addModeChangeListener(mNavModeChangeListener); } - updateAutoRotateSetting(); - updateHomeRotationSetting(); + initFlags(); } /** @@ -295,6 +308,8 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre if (isMultipleOrientationSupportedByDevice()) { mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this); mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver); + SysUINavigationMode.INSTANCE.get(mContext) + .removeModeChangeListener(mNavModeChangeListener); } setRotationWatcherEnabled(false); } diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java index faa18b8050..9a66d32524 100644 --- a/src/com/android/launcher3/InsettableFrameLayout.java +++ b/src/com/android/launcher3/InsettableFrameLayout.java @@ -91,6 +91,9 @@ public class InsettableFrameLayout extends FrameLayout implements Insettable { @Override public void onViewAdded(View child) { super.onViewAdded(child); + if (!isAttachedToWindow()) { + return; + } setFrameLayoutChildInsets(child, mInsets, new Rect()); } diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java index 539794230a..b4ff5ea19a 100644 --- a/src/com/android/launcher3/allapps/DiscoveryBounce.java +++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java @@ -34,6 +34,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.OnboardingPrefs; /** @@ -153,16 +154,19 @@ public class DiscoveryBounce extends AbstractFloatingView { new DiscoveryBounce(launcher, 0).show(HOTSEAT); } - public static void showForOverviewIfNeeded(Launcher launcher) { - showForOverviewIfNeeded(launcher, true); + public static void showForOverviewIfNeeded(Launcher launcher, + PagedOrientationHandler orientationHandler) { + showForOverviewIfNeeded(launcher, true, orientationHandler); } - private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay) { + private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay, + PagedOrientationHandler orientationHandler) { OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs(); if (!launcher.isInState(OVERVIEW) || !launcher.hasBeenResumed() || launcher.isForceInvisible() || launcher.getDeviceProfile().isVerticalBarLayout() + || !orientationHandler.isLayoutNaturalToLauncher() || onboardingPrefs.getBoolean(OnboardingPrefs.SHELF_BOUNCE_SEEN) || launcher.getSystemService(UserManager.class).isDemoUser() || Utilities.IS_RUNNING_IN_TEST_HARNESS) { @@ -170,7 +174,8 @@ public class DiscoveryBounce extends AbstractFloatingView { } if (withDelay) { - new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS); + new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false, + orientationHandler), DELAY_MS); return; } else if (AbstractFloatingView.getTopOpenView(launcher) != null) { // TODO: Move these checks to the top and call this method after invalidate handler. From 2bba25a08df54445774c996c7204726bae67b183 Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 18 May 2020 09:34:24 -0700 Subject: [PATCH 24/25] Remove tracing for a fixed flake Bug: 155926212 Change-Id: Ia847d735d58154bc768b69d3ae3dc0af7fcdbf11 --- .../com/android/launcher3/tapl/LauncherInstrumentation.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index b333100393..b4d6cec908 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -632,8 +632,6 @@ public final class LauncherInstrumentation { * @return the Workspace object. */ public Workspace pressHome() { - mInstrumentation.getUiAutomation().setOnAccessibilityEventListener( - e -> Log.d("b/155926212", e.toString())); try (LauncherInstrumentation.Closable e = eventsCheck()) { waitForLauncherInitialized(); // Click home, then wait for any accessibility event, then wait until accessibility @@ -642,9 +640,7 @@ public final class LauncherInstrumentation { // otherwise waitForIdle may return immediately in case when there was a big enough // pause in accessibility events prior to pressing Home. final String action; - Log.d("b/155926212", "Before isLauncherVisible()"); final boolean launcherWasVisible = isLauncherVisible(); - Log.d("b/155926212", "After isLauncherVisible(): " + launcherWasVisible); if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { checkForAnomaly(); @@ -700,8 +696,6 @@ public final class LauncherInstrumentation { "performed action to switch to Home - " + action)) { return getWorkspace(); } - } finally { - mInstrumentation.getUiAutomation().setOnAccessibilityEventListener(null); } } From 6bf6848951666ed1f5d4779f62ef98643aabb351 Mon Sep 17 00:00:00 2001 From: thiruram Date: Wed, 6 May 2020 22:19:43 -0700 Subject: [PATCH 25/25] Introduces CONTAINER_WIDGETS_TRAY to LauncherSettings.Favorites. This would log LAUNCHER_ITEM_DRAG_STARTED event when an item is dragged from widgets tray. This also fixes empty component with widget logs. Sample Log: https://docs.google.com/document/d/1CBP2yTcXdFhPdNG5ZmWFKSgd8mDbMevY-akVlUXPLDo/edit#bookmark=id.bk5w3n8uwhcl Bug: 152978018 Change-Id: I51d16edae13973d5e62adda0e4efa861fa10dc1b --- protos/launcher_atom.proto | 4 +++ .../android/launcher3/LauncherSettings.java | 4 +++ .../android/launcher3/PendingAddItemInfo.java | 27 ++++++++++++++++--- src/com/android/launcher3/Workspace.java | 13 ++++++--- .../launcher3/model/data/ItemInfo.java | 6 +++++ .../widget/PendingAddShortcutInfo.java | 3 +++ .../widget/PendingAddWidgetInfo.java | 3 +++ 7 files changed, 53 insertions(+), 7 deletions(-) diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto index f1b71e8868..f1db144604 100644 --- a/protos/launcher_atom.proto +++ b/protos/launcher_atom.proto @@ -48,12 +48,16 @@ message ContainerInfo { HotseatContainer hotseat = 2; FolderContainer folder = 3; AllAppsContainer all_apps_container = 4; + WidgetsContainer widgets_container = 5; } } message AllAppsContainer { } +message WidgetsContainer { +} + enum Origin { UNKNOWN = 0; DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 87ead9e7b5..535c9e6c70 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -153,12 +153,16 @@ public class LauncherSettings { public static final int CONTAINER_PREDICTION = -102; public static final int CONTAINER_HOTSEAT_PREDICTION = -103; public static final int CONTAINER_ALL_APPS = -104; + public static final int CONTAINER_WIDGETS_TRAY = -105; + public static final String containerToString(int container) { switch (container) { case CONTAINER_DESKTOP: return "desktop"; case CONTAINER_HOTSEAT: return "hotseat"; case CONTAINER_PREDICTION: return "prediction"; + case CONTAINER_ALL_APPS: return "all_apps"; + case CONTAINER_WIDGETS_TRAY: return "widgets_tray"; default: return String.valueOf(container); } } diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java index 29c9d935ae..be994ee138 100644 --- a/src/com/android/launcher3/PendingAddItemInfo.java +++ b/src/com/android/launcher3/PendingAddItemInfo.java @@ -18,12 +18,15 @@ package com.android.launcher3; import android.content.ComponentName; +import androidx.annotation.Nullable; + import com.android.launcher3.model.data.ItemInfo; +import java.util.Optional; + /** - * Meta data that is used for deferred binding. - * e.g., this object is used to pass information on draggable targets when they are dropped onto - * the workspace from another container. + * Meta data that is used for deferred binding. e.g., this object is used to pass information on + * draggable targets when they are dropped onto the workspace from another container. */ public class PendingAddItemInfo extends ItemInfo { @@ -36,4 +39,22 @@ public class PendingAddItemInfo extends ItemInfo { protected String dumpProperties() { return super.dumpProperties() + " componentName=" + componentName; } + + /** + * Returns shallow copy of the object. + */ + @Override + public ItemInfo makeShallowCopy() { + PendingAddItemInfo itemInfo = new PendingAddItemInfo(); + itemInfo.copyFrom(this); + itemInfo.componentName = this.componentName; + return itemInfo; + } + + @Nullable + @Override + public ComponentName getTargetComponent() { + return Optional.ofNullable(super.getTargetComponent()).orElse(componentName); + } + } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 286b522636..6b660c1ab8 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -2438,6 +2438,10 @@ public class Workspace extends PagedView // widgets/shortcuts/folders in a slightly different way mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell, item.spanX, item.spanY); + mStatsLogManager.log( + LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, + d.logInstanceId, + d.dragInfo.buildProto(null)); } }; boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET @@ -2526,11 +2530,12 @@ public class Workspace extends PagedView mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this); resetTransitionTransform(); } + mStatsLogManager.log( + LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, + d.logInstanceId, + d.dragInfo.buildProto(null)); } - mStatsLogManager.log( - LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, - d.logInstanceId, - d.dragInfo.buildProto(null)); + } public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index f2b7e54a12..a97d52969b 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APP import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; @@ -342,6 +343,11 @@ public class ItemInfo { .setAllAppsContainer( AllAppsContainer.getDefaultInstance()) .build(); + case CONTAINER_WIDGETS_TRAY: + return ContainerInfo.newBuilder() + .setWidgetsContainer( + LauncherAtom.WidgetsContainer.getDefaultInstance()) + .build(); } return ContainerInfo.getDefaultInstance(); } diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java index 6e21a41b27..96016526a7 100644 --- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java +++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; + import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.pm.ShortcutConfigActivityInfo; @@ -32,5 +34,6 @@ public class PendingAddShortcutInfo extends PendingAddItemInfo { componentName = activityInfo.getComponent(); user = activityInfo.getUser(); itemType = activityInfo.getItemType(); + this.container = CONTAINER_WIDGETS_TRAY; } } diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java index bc404842eb..bef9a08f14 100644 --- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java +++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; + import android.appwidget.AppWidgetHostView; import android.os.Bundle; @@ -50,6 +52,7 @@ public class PendingAddWidgetInfo extends PendingAddItemInfo { spanY = i.spanY; minSpanX = i.minSpanX; minSpanY = i.minSpanY; + this.container = CONTAINER_WIDGETS_TRAY; } public WidgetAddFlowHandler getHandler() {