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
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index cac2d8f..19f7213 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -95,7 +95,17 @@
 
 // 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 @@
     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 9a36b3e..29a737c 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,23 +18,15 @@
 
 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.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.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 @@
     @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 @@
         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 @@
         }
         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 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
-                logCurrentFolderLabelState();
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1653,148 +1631,4 @@
     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<String[]> 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<String[]> 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 93208d4..bb358ab 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -20,6 +20,7 @@
 
 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.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 @@
                 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 @@
     }
 
     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 @@
     /**
      * 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 @@
         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 9455bd3..309263d 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -61,6 +61,10 @@
                 + "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 3ac6a22..fd024f4 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 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 @@
         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<String[]> 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<String[]> 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));
+    }
 }