Adding sort switch in FolderPagedView

> Adding options column in DB to store generation purpose flags
> Storing isSorted flag in FolderInfo
> Adding a switch for A-Z sorting (only visible if pageCount > 1)
> When in sorted mode, spring-load snaps to the target location for 1.5 seconds

Change-Id: I8c7c778d2cc3ccbd35a2890a1a705e1c1a7e9a66
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 7ff60de..deb94ca 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -86,6 +86,12 @@
     public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY;
 
     /**
+     * Time in milliseconds for which an icon sticks to the target position
+     * in case of a sorted folder.
+     */
+    private static final int SORTED_STICKY_REORDER_DELAY = 1500;
+
+    /**
      * Fraction of icon width which behave as scroll region.
      */
     private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f;
@@ -423,8 +429,11 @@
         if (!(getParent() instanceof DragLayer)) return;
 
         if (ALLOW_FOLDER_SCROLL) {
-            // Always open on the first page.
-            mPagedView.snapToPageImmediately(0);
+            mPagedView.completePendingPageChanges();
+            if (!(mDragInProgress && mPagedView.mIsSorted)) {
+                // Open on the first page.
+                mPagedView.snapToPageImmediately(0);
+            }
         }
 
         Animator openFolderAnim = null;
@@ -531,9 +540,15 @@
 
     public void beginExternalDrag(ShortcutInfo item) {
         mCurrentDragInfo = item;
-        mEmptyCellRank = mContent.allocateNewLastItemRank();
+        mEmptyCellRank = mContent.allocateRankForNewItem(item);
         mIsExternalDrag = true;
         mDragInProgress = true;
+        if (ALLOW_FOLDER_SCROLL && mPagedView.mIsSorted) {
+            mScrollPauseAlarm.setOnAlarmListener(null);
+            mScrollPauseAlarm.cancelAlarm();
+            mScrollPauseAlarm.setAlarm(SORTED_STICKY_REORDER_DELAY);
+        }
+
     }
 
     private void sendCustomAccessibilityEvent(int type, String text) {
@@ -747,6 +762,7 @@
                 if (!successfulDrop) {
                     mSuppressFolderDeletion = true;
                 }
+                mScrollPauseAlarm.cancelAlarm();
                 completeDragExit();
             }
         }
@@ -1155,7 +1171,7 @@
         // If the item was dropped onto this open folder, we have done the work associated
         // with adding the item to the folder, as indicated by mSuppressOnAdd being set
         if (mSuppressOnAdd) return;
-        mContent.createAndAddViewForRank(item, mContent.allocateNewLastItemRank());
+        mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem(item));
         LauncherModel.addOrMoveItemInDatabase(
                 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
     }
@@ -1304,10 +1320,10 @@
         ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> children);
 
         /**
-         * Create space for a new item at the end, and returns the rank for that item.
+         * Create space for a new item, and returns the rank for that item.
          * Resizes the content if necessary.
          */
-        int allocateNewLastItemRank();
+        int allocateRankForNewItem(ShortcutInfo info);
 
         View createAndAddViewForRank(ShortcutInfo item, int rank);
 
diff --git a/src/com/android/launcher3/FolderCellLayout.java b/src/com/android/launcher3/FolderCellLayout.java
index 1566912..8585add 100644
--- a/src/com/android/launcher3/FolderCellLayout.java
+++ b/src/com/android/launcher3/FolderCellLayout.java
@@ -133,7 +133,7 @@
     }
 
     @Override
-    public int allocateNewLastItemRank() {
+    public int allocateRankForNewItem(ShortcutInfo info) {
         int rank = getItemCount();
         mFolder.rearrangeChildren(rank + 1);
         return rank;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 85a792f..3240cbf 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -29,11 +29,20 @@
  */
 public class FolderInfo extends ItemInfo {
 
+    public static final int NO_FLAGS = 0x00000000;
+
+    /**
+     * The folder is locked in sorted mode
+     */
+    public static final int FLAG_ITEMS_SORTED = 0x00000001;
+
     /**
      * Whether this folder has been opened
      */
     boolean opened;
 
+    public int options;
+
     /**
      * The apps and shortcuts
      */
@@ -83,6 +92,8 @@
     void onAddToDatabase(Context context, ContentValues values) {
         super.onAddToDatabase(context, values);
         values.put(LauncherSettings.Favorites.TITLE, title.toString());
+        values.put(LauncherSettings.Favorites.OPTIONS, options);
+
     }
 
     void addListener(FolderListener listener) {
@@ -121,4 +132,25 @@
                 + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
                 + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")";
     }
+
+    public boolean hasOption(int optionFlag) {
+        return (options & optionFlag) != 0;
+    }
+
+    /**
+     * @param option flag to set or clear
+     * @param isEnabled whether to set or clear the flag
+     * @param context if not null, save changes to the db.
+     */
+    public void setOption(int option, boolean isEnabled, Context context) {
+        int oldOptions = options;
+        if (isEnabled) {
+            options |= option;
+        } else {
+            options &= ~option;
+        }
+        if (context != null && oldOptions != options) {
+            LauncherModel.updateItemInDatabase(context, this);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index b4a7a75..5290644 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -20,15 +20,22 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.Switch;
 
 import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
 import com.android.launcher3.PageIndicator.PageMarkerResources;
 import com.android.launcher3.Workspace.ItemOperator;
 
+import java.text.Collator;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -40,14 +47,19 @@
     private static final int REORDER_ANIMATION_DURATION = 230;
     private static final int START_VIEW_REORDER_DELAY = 30;
     private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
+
+    private static final int SPAN_TO_PAGE_DURATION = 350;
+    private static final int SORT_ANIM_HIDE_DURATION = 130;
+    private static final int SORT_ANIM_SHOW_DURATION = 160;
+
     private static final int[] sTempPosArray = new int[2];
 
     // TODO: Remove this restriction
-    private static final int MAX_ITEMS_PER_PAGE = 3;
+    private static final int MAX_ITEMS_PER_PAGE = 4;
 
     private final LayoutInflater mInflater;
     private final IconCache mIconCache;
-    private final HashMap<View, Runnable> mPageChangingViews = new HashMap<>();
+    private final HashMap<View, Runnable> mPendingAnimations = new HashMap<>();
 
     private final int mMaxCountX;
     private final int mMaxCountY;
@@ -61,6 +73,13 @@
     private FocusIndicatorView mFocusIndicatorView;
     private PagedFolderKeyEventListener mKeyListener;
 
+    private View mSortButton;
+    private Switch mSortSwitch;
+    private View mPageIndicator;
+
+    private boolean mSortOperationPending;
+    boolean mIsSorted;
+
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
         LauncherAppState app = LauncherAppState.getInstance();
@@ -80,6 +99,134 @@
         mFolder = folder;
         mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
         mKeyListener = new PagedFolderKeyEventListener(folder);
+
+        mSortButton = folder.findViewById(R.id.folder_sort);
+        mSortButton.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                onSortClicked();
+            }
+        });
+        mPageIndicator = folder.findViewById(R.id.folder_page_indicator);
+        mSortSwitch = (Switch) folder.findViewById(R.id.folder_sort_switch);
+    }
+
+    private void onSortClicked() {
+        if (mSortOperationPending) {
+            return;
+        }
+        if (mIsSorted) {
+            setIsSorted(false, true);
+        } else {
+            mSortOperationPending = true;
+            doSort();
+        }
+    }
+
+    private void setIsSorted(boolean isSorted, boolean saveChanges) {
+        mIsSorted = isSorted;
+        mSortSwitch.setChecked(isSorted);
+        mFolder.mInfo.setOption(FolderInfo.FLAG_ITEMS_SORTED, isSorted,
+                saveChanges ? mFolder.mLauncher : null);
+    }
+
+    /**
+     * Sorts the contents of the folder and animates the icons on the first page to reflect
+     * the changes.
+     * Steps:
+     *      1. Scroll to first page
+     *      2. Sort all icons in one go
+     *      3. Re-apply the old IconInfos on the first page (so that there is no instant change)
+     *      4. Animate each view individually to reflect the new icon.
+     */
+    private void doSort() {
+        if (!mSortOperationPending) {
+            return;
+        }
+        if (getNextPage() != 0) {
+            snapToPage(0, SPAN_TO_PAGE_DURATION, new DecelerateInterpolator());
+            return;
+        }
+
+        mSortOperationPending = false;
+        ShortcutInfo[][] oldItems = new ShortcutInfo[mGridCountX][mGridCountY];
+        CellLayout currentPage = getCurrentCellLayout();
+        for (int x = 0; x < mGridCountX; x++) {
+            for (int y = 0; y < mGridCountY; y++) {
+                View v = currentPage.getChildAt(x, y);
+                if (v != null) {
+                    oldItems[x][y] = (ShortcutInfo) v.getTag();
+                }
+            }
+        }
+
+        ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder());
+        Collections.sort(views, new ViewComparator());
+        arrangeChildren(views, views.size());
+
+        int delay = 0;
+        float delayAmount = START_VIEW_REORDER_DELAY;
+        final Interpolator hideInterpolator = new DecelerateInterpolator(2);
+        final Interpolator showInterpolator = new OvershootInterpolator(0.8f);
+
+        currentPage = getCurrentCellLayout();
+        for (int x = 0; x < mGridCountX; x++) {
+            for (int y = 0; y < mGridCountY; y++) {
+                final BubbleTextView v = (BubbleTextView) currentPage.getChildAt(x, y);
+                if (v != null) {
+                    final ShortcutInfo info = (ShortcutInfo) v.getTag();
+                    final Runnable clearPending = new Runnable() {
+
+                        @Override
+                        public void run() {
+                            mPendingAnimations.remove(v);
+                            v.setScaleX(1);
+                            v.setScaleY(1);
+                        }
+                    };
+                    if (oldItems[x][y] == null) {
+                        v.setScaleX(0);
+                        v.setScaleY(0);
+                        v.animate().setDuration(SORT_ANIM_SHOW_DURATION)
+                            .setStartDelay(SORT_ANIM_HIDE_DURATION + delay)
+                            .scaleX(1).scaleY(1).setInterpolator(showInterpolator)
+                            .withEndAction(clearPending);
+                        mPendingAnimations.put(v, clearPending);
+                    } else {
+                        // Apply the old iconInfo so that there is no sudden change.
+                        v.applyFromShortcutInfo(oldItems[x][y], mIconCache, false);
+                        v.animate().setStartDelay(delay).setDuration(SORT_ANIM_HIDE_DURATION)
+                            .scaleX(0).scaleY(0)
+                            .setInterpolator(hideInterpolator)
+                            .withEndAction(new Runnable() {
+
+                                @Override
+                                public void run() {
+                                    // Apply the new iconInfo as part of the animation.
+                                    v.applyFromShortcutInfo(info, mIconCache, false);
+                                    v.animate().scaleX(1).scaleY(1)
+                                        .setDuration(SORT_ANIM_SHOW_DURATION).setStartDelay(0)
+                                        .setInterpolator(showInterpolator)
+                                        .withEndAction(clearPending);
+                                }
+                       });
+                       mPendingAnimations.put(v, new Runnable() {
+
+                           @Override
+                           public void run() {
+                               clearPending.run();
+                               v.applyFromShortcutInfo(info, mIconCache, false);
+                           }
+                        });
+                    }
+                    delay += delayAmount;
+                    delayAmount *= VIEW_REORDER_DELAY_FACTOR;
+                }
+            }
+        }
+
+        setIsSorted(true, true);
     }
 
     /**
@@ -125,6 +272,7 @@
 
     @Override
     public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
+        mIsSorted = mFolder.mInfo.hasOption(FolderInfo.FLAG_ITEMS_SORTED);
         ArrayList<View> icons = new ArrayList<View>();
         for (ShortcutInfo item : items) {
             icons.add(createNewView(item));
@@ -138,20 +286,33 @@
      * Also sets the current page to the last page.
      */
     @Override
-    public int allocateNewLastItemRank() {
+    public int allocateRankForNewItem(ShortcutInfo info) {
         int rank = getItemCount();
-        int total = rank + 1;
-        // Rearrange the items as the grid size might change.
-        mFolder.rearrangeChildren(total);
+        ArrayList<View> views = new ArrayList<View>(mFolder.getItemsInReadingOrder());
+        if (mIsSorted) {
+            View tmp = new View(getContext());
+            tmp.setTag(info);
+            int index = Collections.binarySearch(views, tmp, new ViewComparator());
+            if (index < 0) {
+                rank = -index - 1;
+            } else {
+                // Item with same name already exists.
+                // We will just insert it before that item.
+                rank = index;
+            }
 
-        setCurrentPage(getChildCount() - 1);
+        }
+
+        views.add(rank, null);
+        arrangeChildren(views, views.size(), false);
+        setCurrentPage(rank / mMaxItemsPerPage);
         return rank;
     }
 
     @Override
     public View createAndAddViewForRank(ShortcutInfo item, int rank) {
         View icon = createNewView(item);
-        addViewForRank(createNewView(item), item, rank);
+        addViewForRank(icon, item, rank);
         return icon;
     }
 
@@ -259,6 +420,10 @@
         int position = 0;
         int newX, newY, rank;
 
+        boolean isSorted = mIsSorted;
+
+        ViewComparator comparator = new ViewComparator();
+        View lastView = null;
         rank = 0;
         for (int i = 0; i < itemCount; i++) {
             View v = list.size() > i ? list.get(i) : null;
@@ -273,6 +438,10 @@
             }
 
             if (v != null) {
+                if (lastView != null) {
+                    isSorted &= comparator.compare(lastView, v) <= 0;
+                }
+
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
                 newX = position % mGridCountX;
                 newY = position / mGridCountX;
@@ -292,6 +461,7 @@
                         v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
             }
 
+            lastView = v;
             rank ++;
             position++;
         }
@@ -305,6 +475,19 @@
         if (removed) {
             setCurrentPage(0);
         }
+
+        setIsSorted(isSorted, saveChanges);
+
+        // Update footer
+        if (getPageCount() > 1) {
+            mPageIndicator.setVisibility(View.VISIBLE);
+            mSortButton.setVisibility(View.VISIBLE);
+            mFolder.mFolderName.setGravity(Gravity.START);
+        } else {
+            mPageIndicator.setVisibility(View.GONE);
+            mSortButton.setVisibility(View.GONE);
+            mFolder.mFolderName.setGravity(Gravity.CENTER_HORIZONTAL);
+        }
     }
 
     @Override
@@ -407,6 +590,17 @@
         if (mFolder != null) {
             mFolder.updateTextViewFocus();
         }
+        if (mSortOperationPending && getNextPage() == 0) {
+            post(new Runnable() {
+
+                @Override
+                public void run() {
+                    if (mSortOperationPending) {
+                        doSort();
+                    }
+                }
+            });
+        }
     }
 
     /**
@@ -433,8 +627,8 @@
      * Finish animation all the views which are animating across pages
      */
     public void completePendingPageChanges() {
-        if (!mPageChangingViews.isEmpty()) {
-            HashMap<View, Runnable> pendingViews = new HashMap<>(mPageChangingViews);
+        if (!mPendingAnimations.isEmpty()) {
+            HashMap<View, Runnable> pendingViews = new HashMap<>(mPendingAnimations);
             for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) {
                 e.getKey().animate().cancel();
                 e.getValue().run();
@@ -533,7 +727,7 @@
 
                         @Override
                         public void run() {
-                            mPageChangingViews.remove(v);
+                            mPendingAnimations.remove(v);
                             v.setTranslationX(oldTranslateX);
                             ((CellLayout) v.getParent().getParent()).removeView(v);
                             addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
@@ -544,7 +738,7 @@
                         .setDuration(REORDER_ANIMATION_DURATION)
                         .setStartDelay(0)
                         .withEndAction(endAction);
-                    mPageChangingViews.put(v, endAction);
+                    mPendingAnimations.put(v, endAction);
                 }
             }
             moveStart = rankToMove;
@@ -569,4 +763,14 @@
             }
         }
     }
+
+    private static class ViewComparator implements Comparator<View> {
+        private final Collator mCollator = Collator.getInstance();
+
+        @Override
+        public int compare(View lhs, View rhs) {
+            return mCollator.compare( ((ShortcutInfo) lhs.getTag()).title.toString(),
+                    ((ShortcutInfo) rhs.getTag()).title.toString());
+        }
+    }
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 2fd9db2..d80debb 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -961,6 +961,7 @@
                 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
                 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
                 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+                final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
 
                 FolderInfo folderInfo = null;
                 switch (c.getInt(itemTypeIndex)) {
@@ -975,6 +976,7 @@
                 folderInfo.screenId = c.getInt(screenIndex);
                 folderInfo.cellX = c.getInt(cellXIndex);
                 folderInfo.cellY = c.getInt(cellYIndex);
+                folderInfo.options = c.getInt(optionsIndex);
 
                 return folderInfo;
             }
@@ -1862,9 +1864,8 @@
                             LauncherSettings.Favorites.RESTORED);
                     final int profileIdIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.PROFILE_ID);
-                    //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
-                    //final int displayModeIndex = c.getColumnIndexOrThrow(
-                    //        LauncherSettings.Favorites.DISPLAY_MODE);
+                    final int optionsIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.OPTIONS);
 
                     ShortcutInfo info;
                     String intentDescription;
@@ -2114,6 +2115,7 @@
                                 folderInfo.cellY = c.getInt(cellYIndex);
                                 folderInfo.spanX = 1;
                                 folderInfo.spanY = 1;
+                                folderInfo.options = c.getInt(optionsIndex);
 
                                 // check & update map of what's occupied
                                 if (!checkItemPlacement(occupied, folderInfo)) {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index b7a271e..59c8d92 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -57,7 +57,7 @@
     private static final String TAG = "Launcher.LauncherProvider";
     private static final boolean LOGD = false;
 
-    private static final int DATABASE_VERSION = 22;
+    private static final int DATABASE_VERSION = 23;
 
     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
     static final String AUTHORITY = ProviderConfig.AUTHORITY;
@@ -413,7 +413,8 @@
                     "modified INTEGER NOT NULL DEFAULT 0," +
                     "restored INTEGER NOT NULL DEFAULT 0," +
                     "profileId INTEGER DEFAULT " + userSerialNumber + "," +
-                    "rank INTEGER NOT NULL DEFAULT 0" +
+                    "rank INTEGER NOT NULL DEFAULT 0," +
+                    "options INTEGER NOT NULL DEFAULT 0" +
                     ");");
             addWorkspacesTable(db);
 
@@ -524,18 +525,9 @@
                     }
                 }
                 case 15: {
-                    db.beginTransaction();
-                    try {
-                        // Insert new column for holding restore status
-                        db.execSQL("ALTER TABLE favorites " +
-                                "ADD COLUMN restored INTEGER NOT NULL DEFAULT 0;");
-                        db.setTransactionSuccessful();
-                    } catch (SQLException ex) {
-                        Log.e(TAG, ex.getMessage(), ex);
+                    if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
                         // Old version remains, which means we wipe old data
                         break;
-                    } finally {
-                        db.endTransaction();
                     }
                 }
                 case 16: {
@@ -573,6 +565,12 @@
                         break;
                     }
                 case 22: {
+                    if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
+                        // Old version remains, which means we wipe old data
+                        break;
+                    }
+                }
+                case 23: {
                     // DB Upgraded successfully
                     return;
                 }
@@ -682,20 +680,21 @@
         }
 
         private boolean addProfileColumn(SQLiteDatabase db) {
+            UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+            // Default to the serial number of this user, for older
+            // shortcuts.
+            long userSerialNumber = userManager.getSerialNumberForUser(
+                    UserHandleCompat.myUserHandle());
+            return addIntegerColumn(db, Favorites.PROFILE_ID, userSerialNumber);
+        }
+
+        private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
             db.beginTransaction();
             try {
-                UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
-                // Default to the serial number of this user, for older
-                // shortcuts.
-                long userSerialNumber = userManager.getSerialNumberForUser(
-                        UserHandleCompat.myUserHandle());
-                // Insert new column for holding user serial number
-                db.execSQL("ALTER TABLE favorites " +
-                        "ADD COLUMN profileId INTEGER DEFAULT "
-                                        + userSerialNumber + ";");
+                db.execSQL("ALTER TABLE favorites ADD COLUMN "
+                        + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
                 db.setTransactionSuccessful();
             } catch (SQLException ex) {
-                // Old version remains, which means we wipe old data
                 Log.e(TAG, ex.getMessage(), ex);
                 return false;
             } finally {
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 13fd7ee..d161fbb 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -309,5 +309,11 @@
          * <p>Type: INTEGER</p>
          */
         static final String RANK = "rank";
+
+        /**
+         * Stores general flag based options for {@link ItemInfo}s.
+         * <p>Type: INTEGER</p>
+         */
+        static final String OPTIONS = "options";
     }
 }