From 6524cc72376ee4907ced2ca4fee757e85c1c8933 Mon Sep 17 00:00:00 2001 From: thiruram Date: Fri, 8 May 2020 11:04:32 -0700 Subject: [PATCH] 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)); + } }