Adds additional folder label states (UNLABELED & EMPTY_LABEL)

UNLABELED -> title==null and EMPTY_LABEL -> title=="". When adding new items for the folder if the folder is in UNLABELED state, auto-labeling will be enabled.
This change also addresses auto-labeling issue due to false edit from UNLABELED to EMPTY.

Bug: 159164315

Change-Id: Ia17cd27b4afb60420dc15c544f544061fc46ad33
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index d1185bd..98ce9af 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -96,8 +96,21 @@
   ADD_TO_HOMESCREEN = 6;    // play install + launcher home setting
   ALLAPPS_PREDICTION = 7;   // from prediction bar in all apps container
   HOTSEAT_PREDICTION = 8;   // from prediction bar in hotseat container
-  SUGGESTED_LABEL = 9;      // folder icon's label was suggested
-  MANUAL_LABEL = 10;        // folder icon's label was manually edited
+
+  // Folder's label is one of the non-empty suggested values.
+  SUGGESTED_LABEL = 9;
+
+  // Folder's label is non-empty, manually entered by the user
+  // and different from any of suggested values.
+  MANUAL_LABEL = 10;
+
+  // Folder's label is not yet assigned( i.e., title == null).
+  // Eligible for auto-labeling.
+  UNLABELED = 11;
+
+  // Folder's label is empty(i.e., title == "").
+  // Not eligible for auto-labeling.
+  EMPTY_LABEL = 12;
 }
 
 // Main app icons
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 c1bf2fd..a1218ae 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
@@ -114,9 +114,9 @@
         if (!putIntoFolder.isEmpty()) {
             ItemInfo firstItem = putIntoFolder.get(0);
             FolderInfo folderInfo = new FolderInfo();
-            folderInfo.setTitle("");
             mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
                     firstItem.screenId, firstItem.cellX, firstItem.cellY);
+            folderInfo.setTitle("", mLauncher.getModelWriter());
             folderInfo.contents.addAll(putIntoFolder);
             for (int i = 0; i < folderInfo.contents.size(); i++) {
                 ItemInfo item = folderInfo.contents.get(i);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 4af3544..d01e189 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,7 +26,6 @@
 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 android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -339,11 +338,8 @@
         if (DEBUG) {
             Log.d(TAG, "onBackKey newTitle=" + newTitle);
         }
-        mInfo.setTitle(newTitle);
-        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(),
-                mLauncher.getModelWriter());
+        mInfo.setTitle(newTitle, mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
-        mLauncher.getModelWriter().updateItemInDatabase(mInfo);
 
         if (TextUtils.isEmpty(mInfo.title)) {
             mFolderName.setHint(R.string.folder_hint_text);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 152fd37..75275b2 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.folder;
 
-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_AUTO_LABELED;
@@ -72,6 +70,7 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.FolderInfo.FolderListener;
+import com.android.launcher3.model.data.FolderInfo.LabelState;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
@@ -443,8 +442,7 @@
         if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
             return;
         }
-        if (!isEmpty(mFolderName.getText().toString())
-                || mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
+        if (!mInfo.getLabelState().equals(LabelState.UNLABELED)) {
             return;
         }
         if (nameInfos == null || !nameInfos.hasSuggestions()) {
@@ -464,10 +462,9 @@
         CharSequence newTitle = nameInfos.getLabels()[0];
         FromState fromState = mInfo.getFromLabelState();
 
-        mInfo.setTitle(newTitle);
+        mInfo.setTitle(newTitle, mFolder.mLauncher.getModelWriter());
         onTitleChanged(mInfo.title);
         mFolder.mFolderName.setText(mInfo.title);
-        mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
 
         // Logging for folder creation flow
         StatsLogManager.newInstance(getContext()).logger()
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index ecd18ce..05ce06a 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.logger.LauncherAtom.Attribute.EMPTY_LABEL;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
 import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
@@ -31,11 +32,14 @@
 
 import android.os.Process;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderNameInfos;
 import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.Attribute;
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.model.ModelWriter;
@@ -74,6 +78,30 @@
 
     public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
 
+    /**
+     * Different states of folder label.
+     */
+    public enum LabelState {
+        // Folder's label is not yet assigned( i.e., title == null). Eligible for auto-labeling.
+        UNLABELED(Attribute.UNLABELED),
+
+        // Folder's label is empty(i.e., title == ""). Not eligible for auto-labeling.
+        EMPTY(EMPTY_LABEL),
+
+        // Folder's label is one of the non-empty suggested values.
+        SUGGESTED(SUGGESTED_LABEL),
+
+        // Folder's label is non-empty, manually entered by the user
+        // and different from any of suggested values.
+        MANUAL(MANUAL_LABEL);
+
+        private final LauncherAtom.Attribute mLogAttribute;
+
+        LabelState(Attribute logAttribute) {
+            this.mLogAttribute = logAttribute;
+        }
+    }
+
     public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
 
     public int options;
@@ -176,8 +204,7 @@
 
     @Override
     protected String dumpProperties() {
-        return super.dumpProperties()
-                + " manuallyTypedTitle=" + hasOption(FLAG_MANUAL_FOLDER_NAME);
+        return String.format("%s; labelState=%s", super.dumpProperties(), getLabelState());
     }
 
     @Override
@@ -185,14 +212,41 @@
         return getDefaultItemInfoBuilder()
                 .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size()))
                 .setRank(rank)
-                .setAttribute(hasOption(FLAG_MANUAL_FOLDER_NAME) ? MANUAL_LABEL : SUGGESTED_LABEL)
+                .setAttribute(getLabelState().mLogAttribute)
                 .setContainerInfo(getContainerInfo())
                 .build();
     }
 
     @Override
-    public void setTitle(CharSequence title) {
+    public void setTitle(@Nullable CharSequence title, ModelWriter modelWriter) {
+        // Updating label from null to empty is considered as false touch.
+        // Retaining null title(ie., UNLABELED state) allows auto-labeling when new items added.
+        if (isEmpty(title) && this.title == null) {
+            return;
+        }
+
+        // Updating title to same value does not change any states.
+        if (title != null && title == this.title) {
+            return;
+        }
+
         this.title = title;
+        LabelState newLabelState =
+                title == null ? LabelState.UNLABELED
+                        : title.length() == 0 ? LabelState.EMPTY :
+                                getAcceptedSuggestionIndex().isPresent() ? LabelState.SUGGESTED
+                                        : LabelState.MANUAL;
+        setOption(FLAG_MANUAL_FOLDER_NAME, newLabelState.equals(LabelState.MANUAL), modelWriter);
+    }
+
+    /**
+     * Returns current state of the current folder label.
+     */
+    public LabelState getLabelState() {
+        return title == null ? LabelState.UNLABELED
+                : title.length() == 0 ? LabelState.EMPTY :
+                        hasOption(FLAG_MANUAL_FOLDER_NAME) ? LabelState.MANUAL
+                                : LabelState.SUGGESTED;
     }
 
     @Override
@@ -233,13 +287,17 @@
      * Returns {@link FromState} based on current {@link #title}.
      */
     public LauncherAtom.FromState getFromLabelState() {
-        return title == null
-                ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED
-                : title.length() == 0
-                        ? LauncherAtom.FromState.FROM_EMPTY
-                        : hasOption(FLAG_MANUAL_FOLDER_NAME)
-                                ? LauncherAtom.FromState.FROM_CUSTOM
-                                : LauncherAtom.FromState.FROM_SUGGESTED;
+        switch (getLabelState()){
+            case EMPTY:
+                return LauncherAtom.FromState.FROM_EMPTY;
+            case MANUAL:
+                return LauncherAtom.FromState.FROM_CUSTOM;
+            case SUGGESTED:
+                return LauncherAtom.FromState.FROM_SUGGESTED;
+            case UNLABELED:
+            default:
+                return LauncherAtom.FromState.FROM_STATE_UNSPECIFIED;
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 66c3cbb..3082b6e 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
 import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
 import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
+import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.Optional;
@@ -405,7 +406,10 @@
         return itemInfo;
     }
 
-    public void setTitle(CharSequence title) {
+    /**
+     * Sets the title of the item and writes to DB model if needed.
+     */
+    public void setTitle(CharSequence title, ModelWriter modelWriter) {
         this.title = title;
     }
 }