Adding a DB update path to handle the QSB position change

> Renamed the id of 1st screen to 0 and making space for the QSB
by running the grid migration task.
> Added a feature flag to easily disable the QSB-in-workspace behavior

Change-Id: Ie3369f0d4433d916e9d6215d414770d4333f3e26
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a504250..705cb58 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3593,10 +3593,14 @@
     @Override
     public void bindScreens(ArrayList<Long> orderedScreenIds) {
         // Make sure the first screen is always at the start.
-        if (orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
+                orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
             orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID);
             orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
             mModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
+        } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
+            // If there are no screens, we need to have an empty screen
+            mWorkspace.addExtraEmptyScreen();
         }
         bindAddScreens(orderedScreenIds);
 
@@ -3612,7 +3616,7 @@
         int count = orderedScreenIds.size();
         for (int i = 0; i < count; i++) {
             long screenId = orderedScreenIds.get(i);
-            if (screenId != Workspace.FIRST_SCREEN_ID) {
+            if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
                 // No need to bind the first screen, as its always bound.
                 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
             }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 557a91a..4c80ccf 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.folder.Folder;
@@ -57,6 +58,7 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.FlagOp;
@@ -1246,22 +1248,8 @@
         final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
 
         // Get screens ordered by rank.
-        final Cursor sc = contentResolver.query(screensUri, null, null, null,
-                LauncherSettings.WorkspaceScreens.SCREEN_RANK);
-        ArrayList<Long> screenIds = new ArrayList<Long>();
-        try {
-            final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
-            while (sc.moveToNext()) {
-                try {
-                    screenIds.add(sc.getLong(idIndex));
-                } catch (Exception e) {
-                    FileLog.d(TAG, "Invalid screen id", e);
-                }
-            }
-        } finally {
-            sc.close();
-        }
-        return screenIds;
+        return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
+                screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
     }
 
     /**
@@ -1522,8 +1510,9 @@
             if (!occupied.containsKey(item.screenId)) {
                 GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
                 if (item.screenId == Workspace.FIRST_SCREEN_ID) {
-                    // Mark the first row as occupied in order to account for the QSB.
-                    screen.markCells(0, 0, countX + 1, 1, true);
+                    // Mark the first row as occupied (if the feature is enabled)
+                    // in order to account for the QSB.
+                    screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
                 }
                 occupied.put(item.screenId, screen);
             }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index dfb8ba2..4e7d57b 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -23,7 +23,6 @@
 import android.content.ContentProvider;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
-import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -48,15 +47,16 @@
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.SparseArray;
 
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.WorkspaceScreens;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.dynamicui.ExtractionUtils;
+import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.NoLocaleSqliteContext;
@@ -66,13 +66,12 @@
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
     private static final boolean LOGD = false;
 
-    private static final int DATABASE_VERSION = 26;
+    private static final int DATABASE_VERSION = 27;
 
     public static final String AUTHORITY = ProviderConfig.AUTHORITY;
 
@@ -780,7 +779,13 @@
                     ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mContext);
                 case 25:
                     convertShortcutsToLauncherActivities(db);
-                case 26: {
+                case 26:
+                    // QSB was moved to the grid. Clear the first row on screen 0.
+                    if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
+                            !LauncherDbUtils.prepareScreenZeroToHostQsb(db)) {
+                        break;
+                    }
+                case 27: {
                     // DB Upgraded successfully
                     return;
                 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 85ba57c..60a1c9d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -510,6 +510,9 @@
      * @param qsb an exisitng qsb to recycle or null.
      */
     public void bindAndInitFirstWorkspaceScreen(View qsb) {
+        if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
+            return;
+        }
         // Add the first page
         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
 
@@ -909,7 +912,8 @@
             long id = mWorkspaceScreens.keyAt(i);
             CellLayout cl = mWorkspaceScreens.valueAt(i);
             // FIRST_SCREEN_ID can never be removed.
-            if (id > FIRST_SCREEN_ID && cl.getShortcutsAndWidgets().getChildCount() == 0) {
+            if ((!FeatureFlags.QSB_ON_FIRST_SCREEN || id > FIRST_SCREEN_ID)
+                    && cl.getShortcutsAndWidgets().getChildCount() == 0) {
                 removeScreens.add(id);
             }
         }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index dd11bde..8762fef 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -7,6 +7,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.graphics.Point;
 import android.net.Uri;
@@ -26,6 +27,7 @@
 import com.android.launcher3.backup.nano.BackupProtos;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.LongArrayMap;
 
@@ -66,9 +68,9 @@
 
     private final HashMap<String, Point> mWidgetMinSize = new HashMap<>();
     private final ContentValues mTempValues = new ContentValues();
-    private final ArrayList<Long> mEntryToRemove = new ArrayList<>();
+    protected final ArrayList<Long> mEntryToRemove = new ArrayList<>();
     private final ArrayList<ContentProviderOperation> mUpdateOperations = new ArrayList<>();
-    private final ArrayList<DbEntry> mCarryOver = new ArrayList<>();
+    protected final ArrayList<DbEntry> mCarryOver = new ArrayList<>();
     private final HashSet<String> mValidPackages;
 
     private final int mSrcX, mSrcY;
@@ -269,9 +271,10 @@
      *   3) If all those items from the above list can be placed on this screen, place them
      *      (otherwise they are placed on a new screen).
      */
-    private void migrateScreen(long screenId) {
+    protected void migrateScreen(long screenId) {
         // If we are migrating the first screen, do not touch the first row.
-        int startY = screenId == Workspace.FIRST_SCREEN_ID ? 1 : 0;
+        int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID)
+                ? 1 : 0;
 
         ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
 
@@ -366,7 +369,7 @@
     /**
      * Updates an item in the DB.
      */
-    private void update(DbEntry item) {
+    protected void update(DbEntry item) {
         mTempValues.clear();
         item.addToContentValues(mTempValues);
         mUpdateOperations.add(ContentProviderOperation
@@ -677,8 +680,8 @@
     /**
      * Loads entries for a particular screen id.
      */
-    private ArrayList<DbEntry> loadWorkspaceEntries(long screen) {
-        Cursor c =  mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+    protected ArrayList<DbEntry> loadWorkspaceEntries(long screen) {
+        Cursor c = queryWorkspace(
                 new String[]{
                         Favorites._ID,                  // 0
                         Favorites.ITEM_TYPE,            // 1
@@ -690,7 +693,7 @@
                         Favorites.APPWIDGET_PROVIDER,   // 7
                         Favorites.APPWIDGET_ID},        // 8
                 Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP
-                        + " AND " + Favorites.SCREEN + " = " + screen, null, null, null);
+                        + " AND " + Favorites.SCREEN + " = " + screen);
 
         final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
         final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
@@ -776,9 +779,9 @@
      * @return the number of valid items in the folder.
      */
     private int getFolderItemsCount(long folderId) {
-        Cursor c =  mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+        Cursor c = queryWorkspace(
                 new String[]{Favorites._ID, Favorites.INTENT},
-                Favorites.CONTAINER + " = " + folderId, null, null, null);
+                Favorites.CONTAINER + " = " + folderId);
 
         int total = 0;
         while (c.moveToNext()) {
@@ -793,6 +796,11 @@
         return total;
     }
 
+    protected Cursor queryWorkspace(String[] columns, String where) {
+        return mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                columns, where, null, null, null);
+    }
+
     /**
      * Verifies if the intent should be restored.
      */
@@ -815,7 +823,7 @@
         }
     }
 
-    private static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+    protected static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
 
         public float weight;
 
@@ -913,18 +921,7 @@
         try {
             boolean dbChanged = false;
 
-            // Initialize list of valid packages. This contain all the packages which are already on
-            // the device and packages which are being installed. Any item which doesn't belong to
-            // this set is removed.
-            // Since the loader removes such items anyway, removing these items here doesn't cause
-            // any extra data loss and gives us more free space on the grid for better migration.
-            HashSet validPackages = new HashSet<>();
-            for (PackageInfo info : context.getPackageManager().getInstalledPackages(0)) {
-                validPackages.add(info.packageName);
-            }
-            validPackages.addAll(PackageInstallerCompat.getInstance(context)
-                    .updateAndGetActiveSessionCache().keySet());
-
+            HashSet validPackages = getValidPackages(context);
             // Hotseat
             Point srcHotseatSize = parsePoint(prefs.getString(
                     KEY_MIGRATION_SRC_HOTSEAT_SIZE, hotseatSizeString));
@@ -1022,4 +1019,20 @@
                     .apply();
         }
     }
+
+    protected static HashSet<String> getValidPackages(Context context) {
+        // Initialize list of valid packages. This contain all the packages which are already on
+        // the device and packages which are being installed. Any item which doesn't belong to
+        // this set is removed.
+        // Since the loader removes such items anyway, removing these items here doesn't cause
+        // any extra data loss and gives us more free space on the grid for better migration.
+        HashSet validPackages = new HashSet<>();
+        for (PackageInfo info : context.getPackageManager()
+                .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
+            validPackages.add(info.packageName);
+        }
+        validPackages.addAll(PackageInstallerCompat.getInstance(context)
+                .updateAndGetActiveSessionCache().keySet());
+        return validPackages;
+    }
 }
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
new file mode 100644
index 0000000..faa5fad
--- /dev/null
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.provider;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.WorkspaceScreens;
+import com.android.launcher3.logging.FileLog;
+
+import java.util.ArrayList;
+
+/**
+ * A set of utility methods for Launcher DB used for DB updates and migration.
+ */
+public class LauncherDbUtils {
+
+    private static final String TAG = "LauncherDbUtils";
+
+    /**
+     * Makes the first screen as screen 0 (if screen 0 already exists,
+     * renames it to some other number).
+     * If the first row of screen 0 is non empty, runs a 'lossy' GridMigrationTask to clear
+     * the first row. The items in the first screen are moved and resized but the carry-forward
+     * items are simply deleted.
+     */
+    public static boolean prepareScreenZeroToHostQsb(SQLiteDatabase db) {
+        db.beginTransaction();
+        try {
+            // Get the existing screens
+            ArrayList<Long> screenIds = getScreenIdsFromCursor(db.query(WorkspaceScreens.TABLE_NAME,
+                    null, null, null, null, null, WorkspaceScreens.SCREEN_RANK));
+
+            if (screenIds.isEmpty()) {
+                // No update needed
+                return true;
+            }
+            if (screenIds.get(0) != 0) {
+                // First screen is not 0, we need to rename screens
+                if (screenIds.indexOf(0L) > -1) {
+                    // There is already a screen 0. First rename it to a differen screen.
+                    long newScreenId = 1;
+                    while (screenIds.indexOf(newScreenId) > -1) newScreenId++;
+                    renameScreen(db, 0, newScreenId);
+                }
+
+                // Rename the first screen to 0.
+                renameScreen(db, screenIds.get(0), 0);
+            }
+
+            // Check if the first row is empty
+            try (Cursor c = db.query(Favorites.TABLE_NAME, null,
+                    "container = -100 and screen = 0 and cellY = 0", null, null, null, null)) {
+                if (c.getCount() == 0) {
+                    // First row is empty, no need to migrate.
+                    return true;
+                }
+            }
+
+            LauncherAppState app = LauncherAppState.getInstance();
+            new LossyScreenMigrationTask(app.getContext(), app.getInvariantDeviceProfile(), db)
+                    .migrateScreen0();
+            db.setTransactionSuccessful();
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to update workspace size", e);
+            return false;
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private static void renameScreen(SQLiteDatabase db, long oldScreen, long newScreen) {
+        String[] whereParams = new String[] { Long.toString(oldScreen) };
+
+        ContentValues values = new ContentValues();
+        values.put(WorkspaceScreens._ID, newScreen);
+        db.update(WorkspaceScreens.TABLE_NAME, values, "_id = ?", whereParams);
+
+        values.clear();
+        values.put(Favorites.SCREEN, newScreen);
+        db.update(Favorites.TABLE_NAME, values, "container = -100 and screen = ?", whereParams);
+    }
+
+    /**
+     * Parses the cursor containing workspace screens table and returns the list of screen IDs
+     */
+    public static ArrayList<Long> getScreenIdsFromCursor(Cursor sc) {
+        ArrayList<Long> screenIds = new ArrayList<Long>();
+        try {
+            final int idIndex = sc.getColumnIndexOrThrow(WorkspaceScreens._ID);
+            while (sc.moveToNext()) {
+                try {
+                    screenIds.add(sc.getLong(idIndex));
+                } catch (Exception e) {
+                    FileLog.d(TAG, "Invalid screen id", e);
+                }
+            }
+        } finally {
+            sc.close();
+        }
+        return screenIds;
+    }
+}
diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
new file mode 100644
index 0000000..bb6ed0d
--- /dev/null
+++ b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.provider;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Point;
+import android.util.Log;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.util.LongArrayMap;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * An extension of {@link GridSizeMigrationTask} which migrates only one screen and
+ * deletes all carry-forward items.
+ */
+public class LossyScreenMigrationTask extends GridSizeMigrationTask {
+
+    private final SQLiteDatabase mDb;
+
+    private final LongArrayMap<DbEntry> mOriginalItems;
+    private final LongArrayMap<DbEntry> mUpdates;
+
+    protected LossyScreenMigrationTask(
+            Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
+        // Decrease the rows count by 1
+        super(context, idp, getValidPackages(context), new HashMap<String, Point>(),
+                new Point(idp.numColumns, idp.numRows + 1), new Point(idp.numColumns, idp.numRows));
+
+        mDb = db;
+        mOriginalItems = new LongArrayMap<>();
+        mUpdates = new LongArrayMap<>();
+    }
+
+    @Override
+    protected Cursor queryWorkspace(String[] columns, String where) {
+        return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
+    }
+
+    @Override
+    protected void update(DbEntry item) {
+        mUpdates.put(item.id, item.copy());
+    }
+
+    @Override
+    protected ArrayList<DbEntry> loadWorkspaceEntries(long screen) {
+        ArrayList<DbEntry> result = super.loadWorkspaceEntries(screen);
+        for (DbEntry entry : result) {
+            mOriginalItems.put(entry.id, entry.copy());
+
+            // Shift all items by 1 in y direction and mark them for update.
+            entry.cellY++;
+            mUpdates.put(entry.id, entry.copy());
+        }
+
+        return result;
+    }
+
+    public void migrateScreen0() {
+        migrateScreen(Workspace.FIRST_SCREEN_ID);
+
+        ContentValues tempValues = new ContentValues();
+        for (DbEntry update : mUpdates) {
+            DbEntry org = mOriginalItems.get(update.id);
+
+            if (org.cellX != update.cellX || org.cellY != update.cellY
+                    || org.spanX != update.spanX || org.spanY != update.spanY) {
+                tempValues.clear();
+                update.addToContentValues(tempValues);
+                mDb.update(Favorites.TABLE_NAME, tempValues, "_id = ?",
+                        new String[] {Long.toString(update.id)});
+            }
+        }
+
+        // Delete any carry over items as we are only migration a single screen.
+        for (DbEntry entry : mCarryOver) {
+            mEntryToRemove.add(entry.id);
+        }
+
+        if (!mEntryToRemove.isEmpty()) {
+            mDb.delete(Favorites.TABLE_NAME,
+                    Utilities.createDbSelectionQuery(Favorites._ID, mEntryToRemove), null);
+        }
+    }
+}
diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_config/com/android/launcher3/config/FeatureFlags.java
index 2b9e6ce..9806afd 100644
--- a/src_config/com/android/launcher3/config/FeatureFlags.java
+++ b/src_config/com/android/launcher3/config/FeatureFlags.java
@@ -30,4 +30,7 @@
     public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = false;
     public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false;
     public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true;
+
+    // Feature flag to enable moving the QSB on the 0th screen of the workspace
+    public static final boolean QSB_ON_FIRST_SCREEN = true;
 }