Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6524cc72 authored by thiruram's avatar thiruram Committed by Thiru Ramasamy
Browse files

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
parent e0fef877
Loading
Loading
Loading
Loading
+78 −0
Original line number Diff line number Diff line
@@ -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;
}
+2 −168
Original line number Diff line number Diff line
@@ -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<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));
        }
    }
}
+11 −6
Original line number Diff line number Diff line
@@ -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);
+4 −0
Original line number Diff line number Diff line
@@ -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),

+140 −0
Original line number Diff line number Diff line
@@ -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<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));
    }
}