OSDN Git Service

Moving the QSB to the workspace grid.
authorSunny Goyal <sunnygoyal@google.com>
Thu, 26 May 2016 23:05:17 +0000 (16:05 -0700)
committerSunny Goyal <sunnygoyal@google.com>
Sat, 28 May 2016 01:23:29 +0000 (18:23 -0700)
The QSB will only be resent on the first screen of the workspace
covering the full width of the first row. If will not be movable.
The first screen of the workspace will not be movable.
The searchDropTargetBar no longer contains the QSB (it can be
renamed in aseparate cl).

Refactoring all QSB related logic by moving it to a custom view
inflated only using xml.
Change-Id: Icb4fd6eb855df1af15f685961c38351bf4fd4f4a

20 files changed:
res/layout/qsb_container.xml [new file with mode: 0644]
res/layout/qsb_default_view.xml [new file with mode: 0644]
src/com/android/launcher3/CellLayout.java
src/com/android/launcher3/Launcher.java
src/com/android/launcher3/LauncherAppState.java
src/com/android/launcher3/LauncherAppWidgetHost.java
src/com/android/launcher3/LauncherAppWidgetHostView.java
src/com/android/launcher3/LauncherModel.java
src/com/android/launcher3/LauncherProvider.java
src/com/android/launcher3/LauncherProviderChangeListener.java
src/com/android/launcher3/PagedView.java
src/com/android/launcher3/PinchAnimationManager.java
src/com/android/launcher3/QsbContainerView.java [new file with mode: 0644]
src/com/android/launcher3/SearchDropTargetBar.java
src/com/android/launcher3/Utilities.java
src/com/android/launcher3/Workspace.java
src/com/android/launcher3/model/GridSizeMigrationTask.java
src/com/android/launcher3/util/GridOccupancy.java
tests/src/com/android/launcher3/BindWidgetTest.java
tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java

diff --git a/res/layout/qsb_container.xml b/res/layout/qsb_container.xml
new file mode 100644 (file)
index 0000000..3de2876
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<com.android.launcher3.QsbContainerView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/qsb_container"
+        android:padding="0dp" />
\ No newline at end of file
diff --git a/res/layout/qsb_default_view.xml b/res/layout/qsb_default_view.xml
new file mode 100644 (file)
index 0000000..82bdea5
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:layout_margin="16dp"
+        android:layout_gravity="center_vertical"
+        android:background="@drawable/quantum_panel_shape"
+        android:elevation="2dp"
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="0dp"
+            android:id="@+id/btn_qsb_search"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center_vertical"
+            android:paddingStart="16dp"
+            android:text="@string/abandoned_search"
+            android:textColor="@color/quantum_panel_text_color"
+            android:textAppearance="?android:textAppearanceMedium"
+            android:clickable="true"
+            android:background="?android:attr/selectableItemBackground" />
+        <ImageView
+            android:layout_width="48dp"
+            android:id="@+id/btn_qsb_setup"
+            android:clickable="true"
+            android:visibility="gone"
+            android:layout_height="match_parent"
+            android:src="@drawable/ic_setting"
+            android:tint="@color/quantum_panel_text_color"
+            android:contentDescription="@string/gadget_setup_text"
+            android:padding="8dp"
+            android:background="?android:attr/selectableItemBackground" />
+    </LinearLayout>
+</FrameLayout>
\ No newline at end of file
index e3bb5fa..cfaa6a3 100644 (file)
@@ -2708,7 +2708,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler {
 
         /**
          * Indicates whether this item can be reordered. Always true except in the case of the
-         * the AllApps button.
+         * the AllApps button and QSB place holder.
          */
         public boolean canReorder = true;
 
index 13e451a..6a95a90 100644 (file)
@@ -45,20 +45,16 @@ import android.content.IntentSender;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
@@ -113,7 +109,6 @@ import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.WidgetsModel;
-import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.pageindicators.PageIndicatorLine;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
@@ -183,6 +178,9 @@ public class Launcher extends Activity
     static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
             "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
 
+    public static final String ACTION_APPWIDGET_HOST_RESET =
+            "com.android.launcher3.intent.ACTION_APPWIDGET_HOST_RESET";
+
     // Type: int
     private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
     // Type: int
@@ -197,9 +195,6 @@ public class Launcher extends Activity
     static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
     static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
 
-    private static final String QSB_WIDGET_ID = "qsb_widget_id";
-    private static final String QSB_WIDGET_PROVIDER = "qsb_widget_provider";
-
     /** The different states that Launcher can be in. */
     enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
         WIDGETS, WIDGETS_SPRING_LOADED }
@@ -219,8 +214,19 @@ public class Launcher extends Activity
     private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
     @Thunk static int NEW_APPS_ANIMATION_DELAY = 500;
 
-    private final BroadcastReceiver mCloseSystemDialogsReceiver
-            = new CloseSystemDialogsIntentReceiver();
+    private final BroadcastReceiver mUiBroadcastReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                closeSystemDialogs();
+            } else if (ACTION_APPWIDGET_HOST_RESET.equals(intent.getAction())) {
+                if (mAppWidgetHost != null) {
+                    mAppWidgetHost.startListening();
+                }
+            }
+        }
+    };
 
     @Thunk Workspace mWorkspace;
     private View mLauncherView;
@@ -255,8 +261,6 @@ public class Launcher extends Activity
     @Thunk WidgetsContainerView mWidgetsView;
     @Thunk WidgetsModel mWidgetsModel;
 
-    private AppWidgetHostView mQsb;
-
     private Bundle mSavedState;
     // We set the state in both onCreate and then onNewIntent in some cases, which causes both
     // scroll issues (because the workspace may not have been measured yet) and extra work.
@@ -464,7 +468,8 @@ public class Launcher extends Activity
         Selection.setSelection(mDefaultKeySsb, 0);
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        registerReceiver(mCloseSystemDialogsReceiver, filter);
+        filter.addAction(ACTION_APPWIDGET_HOST_RESET);
+        registerReceiver(mUiBroadcastReceiver, filter);
 
         mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
         // In case we are on a device with locked rotation, we should look at preferences to check
@@ -739,10 +744,6 @@ public class Launcher extends Activity
             } else if (resultCode == RESULT_OK) {
                 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
                         mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
-
-                // When the user has granted permission to bind widgets, we should check to see if
-                // we can inflate the default search bar widget.
-                getOrCreateQsbBar();
             }
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -1056,7 +1057,6 @@ public class Launcher extends Activity
         if (!isWorkspaceLoading()) {
             getWorkspace().reinflateWidgetsIfNecessary();
         }
-        reinflateQSBIfNecessary();
 
         if (DEBUG_RESUME_TIME) {
             Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
@@ -1368,6 +1368,7 @@ public class Launcher extends Activity
         mWorkspace.setHapticFeedbackEnabled(false);
         mWorkspace.setOnLongClickListener(this);
         mWorkspace.setup(dragController);
+        mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
         dragController.addDragListener(mWorkspace);
 
         // Get the search/delete/uninstall bar
@@ -1393,7 +1394,6 @@ public class Launcher extends Activity
         dragController.addDropTarget(mWorkspace);
         if (mSearchDropTargetBar != null) {
             mSearchDropTargetBar.setup(this, dragController);
-            mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
         }
         if (mAppInfoDropTargetBar != null) {
             mAppInfoDropTargetBar.setup(this, dragController);
@@ -1999,7 +1999,7 @@ public class Launcher extends Activity
         ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
                 .removeAccessibilityStateChangeListener(this);
 
-        unregisterReceiver(mCloseSystemDialogsReceiver);
+        unregisterReceiver(mUiBroadcastReceiver);
 
         LauncherAnimUtils.onDestroyActivity();
 
@@ -2052,10 +2052,9 @@ public class Launcher extends Activity
             appSearchData = new Bundle();
             appSearchData.putString("source", "launcher-search");
         }
-        Rect sourceBounds = new Rect();
-        if (mSearchDropTargetBar != null) {
-            sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
-        }
+
+        // TODO send proper bounds.
+        Rect sourceBounds = null;
 
         boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
                 appSearchData, sourceBounds);
@@ -2465,19 +2464,6 @@ public class Launcher extends Activity
     }
 
     /**
-     * Re-listen when widget host is reset.
-     */
-    @Override
-    public void onAppWidgetHostReset() {
-        if (mAppWidgetHost != null) {
-            mAppWidgetHost.startListening();
-        }
-
-        // Recreate the QSB, as the widget has been reset.
-        bindSearchProviderChanged();
-    }
-
-    /**
      * Launches the intent referred by the clicked shortcut.
      *
      * @param v The view representing the clicked shortcut.
@@ -3497,105 +3483,6 @@ public class Launcher extends Activity
         return (mLauncherCallbacks != null && mLauncherCallbacks.providesSearch());
     }
 
-    public View getOrCreateQsbBar() {
-        if (launcherCallbacksProvidesSearch()) {
-            return mLauncherCallbacks.getQsbBar();
-        }
-
-        if (mQsb == null) {
-            AppWidgetProviderInfo searchProvider = Utilities.getSearchWidgetProvider(this);
-            if (searchProvider == null) {
-                return null;
-            }
-
-            Bundle opts = new Bundle();
-            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
-                    AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
-
-            // Determine the min and max dimensions of the widget.
-            LauncherAppState app = LauncherAppState.getInstance();
-            DeviceProfile portraitProfile = app.getInvariantDeviceProfile().portraitProfile;
-            DeviceProfile landscapeProfile = app.getInvariantDeviceProfile().landscapeProfile;
-            float density = getResources().getDisplayMetrics().density;
-            Point searchDimens = portraitProfile.getSearchBarDimensForWidgetOpts(getResources());
-            int maxHeight = (int) (searchDimens.y / density);
-            int minHeight = maxHeight;
-            int maxWidth = (int) (searchDimens.x / density);
-            int minWidth = maxWidth;
-            if (!landscapeProfile.isVerticalBarLayout()) {
-                searchDimens = landscapeProfile.getSearchBarDimensForWidgetOpts(getResources());
-                maxHeight = (int) Math.max(maxHeight, searchDimens.y / density);
-                minHeight = (int) Math.min(minHeight, searchDimens.y / density);
-                maxWidth = (int) Math.max(maxWidth, searchDimens.x / density);
-                minWidth = (int) Math.min(minWidth, searchDimens.x / density);
-            }
-            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, maxHeight);
-            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, minHeight);
-            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, maxWidth);
-            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, minWidth);
-            if (LOGD) {
-                Log.d(TAG, "QSB widget options: maxHeight=" + maxHeight + " minHeight=" + minHeight
-                        + " maxWidth=" + maxWidth + " minWidth=" + minWidth);
-            }
-
-            if (mLauncherCallbacks != null) {
-                opts.putAll(mLauncherCallbacks.getAdditionalSearchWidgetOptions());
-            }
-
-            int widgetId = mSharedPrefs.getInt(QSB_WIDGET_ID, -1);
-            AppWidgetProviderInfo widgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
-            if (!searchProvider.provider.flattenToString().equals(
-                    mSharedPrefs.getString(QSB_WIDGET_PROVIDER, null))
-                    || (widgetInfo == null)
-                    || !widgetInfo.provider.equals(searchProvider.provider)) {
-                // A valid widget is not already bound.
-                if (widgetId > -1) {
-                    mAppWidgetHost.deleteAppWidgetId(widgetId);
-                    widgetId = -1;
-                }
-
-                // Try to bind a new widget
-                widgetId = mAppWidgetHost.allocateAppWidgetId();
-
-                if (!AppWidgetManagerCompat.getInstance(this)
-                        .bindAppWidgetIdIfAllowed(widgetId, searchProvider, opts)) {
-                    mAppWidgetHost.deleteAppWidgetId(widgetId);
-                    widgetId = -1;
-                }
-
-                mSharedPrefs.edit()
-                    .putInt(QSB_WIDGET_ID, widgetId)
-                    .putString(QSB_WIDGET_PROVIDER, searchProvider.provider.flattenToString())
-                    .apply();
-            }
-
-            mAppWidgetHost.setQsbWidgetId(widgetId);
-            if (widgetId != -1) {
-                mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
-                mQsb.setId(R.id.qsb_widget);
-                if (!Utilities.containsAll(
-                        AppWidgetManager.getInstance(this).getAppWidgetOptions(widgetId), opts)) {
-                    // Launcher should not be updating the options often.
-                    FileLog.d(TAG, "Options for QSB were not same");
-                    mQsb.updateAppWidgetOptions(opts);
-                }
-                mQsb.setPadding(0, 0, 0, 0);
-                mSearchDropTargetBar.addView(mQsb);
-                mSearchDropTargetBar.setQsbSearchBar(mQsb);
-            }
-        }
-        return mQsb;
-    }
-
-    private void reinflateQSBIfNecessary() {
-        if (mQsb instanceof LauncherAppWidgetHostView &&
-                ((LauncherAppWidgetHostView) mQsb).isReinflateRequired()) {
-            mSearchDropTargetBar.removeView(mQsb);
-            mQsb = null;
-            mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
-        }
-    }
-
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         final boolean result = super.dispatchPopulateAccessibilityEvent(event);
@@ -3615,16 +3502,6 @@ public class Launcher extends Activity
     }
 
     /**
-     * Receives notifications when system dialogs are to be closed.
-     */
-    @Thunk class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            closeSystemDialogs();
-        }
-    }
-
-    /**
      * If the activity is currently paused, signal that we need to run the passed Runnable
      * in onResume.
      *
@@ -3729,12 +3606,13 @@ public class Launcher extends Activity
 
     @Override
     public void bindScreens(ArrayList<Long> orderedScreenIds) {
-        bindAddScreens(orderedScreenIds);
-
-        // If there are no screens, we need to have an empty screen
-        if (orderedScreenIds.size() == 0) {
-            mWorkspace.addExtraEmptyScreen();
+        // Make sure the first screen is always at the start.
+        if (orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
+            orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID);
+            orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
+            mModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
         }
+        bindAddScreens(orderedScreenIds);
 
         // Create the custom content page (this call updates mDefaultScreen which calls
         // setCurrentPage() so ensure that all pages are added before calling this).
@@ -3744,11 +3622,14 @@ public class Launcher extends Activity
         }
     }
 
-    @Override
-    public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
+    private void bindAddScreens(ArrayList<Long> orderedScreenIds) {
         int count = orderedScreenIds.size();
         for (int i = 0; i < count; i++) {
-            mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
+            long screenId = orderedScreenIds.get(i);
+            if (screenId != Workspace.FIRST_SCREEN_ID) {
+                // No need to bind the first screen, as its always bound.
+                mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
+            }
         }
     }
 
@@ -3827,24 +3708,6 @@ public class Launcher extends Activity
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                     ShortcutInfo info = (ShortcutInfo) item;
                     view = createShortcut(info);
-
-                    /*
-                     * TODO: FIX collision case
-                     */
-                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                        CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
-                        if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
-                            View v = cl.getChildAt(item.cellX, item.cellY);
-                            Object tag = v.getTag();
-                            String desc = "Collision while binding workspace item: " + item
-                                    + ". Collides with " + tag;
-                            if (ProviderConfig.IS_DOGFOOD_BUILD) {
-                                throw (new RuntimeException(desc));
-                            } else {
-                                Log.d(TAG, desc);
-                            }
-                        }
-                    }
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                     view = FolderIcon.fromXml(R.layout.folder_icon, this,
@@ -3855,6 +3718,25 @@ public class Launcher extends Activity
                     throw new RuntimeException("Invalid Item Type");
             }
 
+             /*
+             * Remove colliding items.
+             */
+            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
+                if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
+                    View v = cl.getChildAt(item.cellX, item.cellY);
+                    Object tag = v.getTag();
+                    String desc = "Collision while binding workspace item: " + item
+                            + ". Collides with " + tag;
+                    if (ProviderConfig.IS_DOGFOOD_BUILD) {
+                        throw (new RuntimeException(desc));
+                    } else {
+                        Log.d(TAG, desc);
+                        LauncherModel.deleteItemFromDatabase(this, item);
+                        continue;
+                    }
+                }
+            }
             workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
                     item.cellY, 1, 1);
             if (animateIcons) {
@@ -4154,17 +4036,6 @@ public class Launcher extends Activity
         return LauncherCallbacks.SEARCH_BAR_HEIGHT_NORMAL;
     }
 
-    public void bindSearchProviderChanged() {
-        if (mSearchDropTargetBar == null) {
-            return;
-        }
-        if (mQsb != null) {
-            mSearchDropTargetBar.removeView(mQsb);
-            mQsb = null;
-        }
-        mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
-    }
-
     /**
      * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent
      * multiple calls to bind the same list.)
index 9d889e0..ae3abbf 100644 (file)
@@ -105,7 +105,6 @@ public class LauncherAppState {
         // Register intent receivers
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
         // For handling managed profiles
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
index 8c23ff3..3bb7381 100644 (file)
@@ -37,7 +37,6 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
 
     private final ArrayList<Runnable> mProviderChangeListeners = new ArrayList<Runnable>();
 
-    private int mQsbWidgetId = -1;
     private Launcher mLauncher;
 
     public LauncherAppWidgetHost(Launcher launcher, int hostId) {
@@ -45,23 +44,9 @@ public class LauncherAppWidgetHost extends AppWidgetHost {
         mLauncher = launcher;
     }
 
-    public void setQsbWidgetId(int widgetId) {
-        mQsbWidgetId = widgetId;
-    }
-
     @Override
     protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
             AppWidgetProviderInfo appWidget) {
-        if (appWidgetId == mQsbWidgetId) {
-            return new LauncherAppWidgetHostView(context) {
-
-                @Override
-                protected View getErrorView() {
-                    // For the QSB, show an empty view instead of an error view.
-                    return new View(getContext());
-                }
-            };
-        }
         return new LauncherAppWidgetHostView(context);
     }
 
index 28557d0..53d9c04 100644 (file)
@@ -54,6 +54,8 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mChildrenFocused;
 
+    protected int mErrorViewId = R.layout.appwidget_error;
+
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mContext = context;
@@ -68,7 +70,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc
 
     @Override
     protected View getErrorView() {
-        return mInflater.inflate(R.layout.appwidget_error, this, false);
+        return mInflater.inflate(mErrorViewId, this, false);
     }
 
     public void updateLastInflationOrientation() {
index 649f42d..557a91a 100644 (file)
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import android.app.SearchManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -177,7 +176,6 @@ public class LauncherModel extends BroadcastReceiver
         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
                               boolean forceAnimateIcons);
         public void bindScreens(ArrayList<Long> orderedScreenIds);
-        public void bindAddScreens(ArrayList<Long> orderedScreenIds);
         public void finishBindingItems();
         public void bindAppWidget(LauncherAppWidgetInfo info);
         public void bindAllApplications(ArrayList<AppInfo> apps);
@@ -196,7 +194,6 @@ public class LauncherModel extends BroadcastReceiver
         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
         public void notifyWidgetProvidersChanged();
         public void bindWidgetsModel(WidgetsModel model);
-        public void bindSearchProviderChanged();
         public boolean isAllAppsButtonRank(int rank);
         public void onPageBoundSynchronously(int page);
         public void executeOnNextDraw(ViewOnDrawExecutor executor);
@@ -1139,11 +1136,6 @@ public class LauncherModel extends BroadcastReceiver
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
-        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
-            Callbacks callbacks = getCallback();
-            if (callbacks != null) {
-                callbacks.bindSearchProviderChanged();
-            }
         } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
                 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
             UserManagerCompat.getInstance(context).enableAndResetCache();
@@ -1528,7 +1520,12 @@ public class LauncherModel extends BroadcastReceiver
             }
 
             if (!occupied.containsKey(item.screenId)) {
-                occupied.put(item.screenId, new GridOccupancy(countX + 1, countY + 1));
+                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);
+                }
+                occupied.put(item.screenId, screen);
             }
             final GridOccupancy occupancy = occupied.get(item.screenId);
 
index 11d61d0..49ce06a 100644 (file)
@@ -611,8 +611,11 @@ public class LauncherProvider extends ContentProvider {
             // Database was just created, so wipe any previous widgets
             if (mWidgetHostResetHandler != null) {
                 new AppWidgetHost(mContext, Launcher.APPWIDGET_HOST_ID).deleteHost();
-                mWidgetHostResetHandler.sendEmptyMessage(
-                        ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET);
+                mWidgetHostResetHandler.sendMessage(Message.obtain(
+                        mWidgetHostResetHandler,
+                        ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET,
+                        mContext
+                ));
             }
 
             // Set the flag for empty DB
@@ -1100,7 +1103,11 @@ public class LauncherProvider extends ContentProvider {
                         mListener.onExtractedColorsChanged();
                         break;
                     case MSG_APP_WIDGET_HOST_RESET:
-                        mListener.onAppWidgetHostReset();
+                        Context context = (Context) msg.obj;
+                        if (context != null) {
+                            context.sendBroadcast(new Intent(Launcher.ACTION_APPWIDGET_HOST_RESET)
+                                    .setPackage(context.getPackageName()));
+                        }
                         break;
                 }
             }
index 524befc..5998dad 100644 (file)
@@ -10,6 +10,4 @@ public interface LauncherProviderChangeListener {
     public void onLauncherProviderChange();
 
     public void onExtractedColorsChanged();
-
-    public void onAppWidgetHostReset();
 }
index fdca9f2..4af53d2 100644 (file)
@@ -1653,7 +1653,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
                 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
 
                 final int pageUnderPointIndex = getNearestHoverOverPageIndex();
-                if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView)) {
+                // Do not allow any page to be moved to 0th position.
+                if (pageUnderPointIndex > 0 && pageUnderPointIndex != indexOfChild(mDragView)) {
                     mTempVisiblePagesRange[0] = 0;
                     mTempVisiblePagesRange[1] = getPageCount() - 1;
                     getFreeScrollPageRange(mTempVisiblePagesRange);
@@ -2171,7 +2172,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
     public boolean startReordering(View v) {
         int dragViewIndex = indexOfChild(v);
 
-        if (mTouchState != TOUCH_STATE_REST || dragViewIndex == -1) return false;
+        // Do not allow the first page to be moved around
+        if (mTouchState != TOUCH_STATE_REST || dragViewIndex <= 0) return false;
 
         mTempVisiblePagesRange[0] = 0;
         mTempVisiblePagesRange[1] = getPageCount() - 1;
index c8c8fa4..c1c2519 100644 (file)
@@ -150,12 +150,10 @@ public class PinchAnimationManager {
                 animateOverviewPanelButtons(goingTowards == OVERVIEW);
             } else if (startState == NORMAL) {
                 animateHotseatAndPageIndicator(goingTowards == NORMAL);
-                animateQsb(goingTowards == NORMAL);
             }
         } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
             if (startState == OVERVIEW) {
                 animateHotseatAndPageIndicator(goingTowards == NORMAL);
-                animateQsb(goingTowards == NORMAL);
                 animateScrim(goingTowards == OVERVIEW);
             } else if (startState == NORMAL) {
                 animateOverviewPanelButtons(goingTowards == OVERVIEW);
@@ -198,12 +196,6 @@ public class PinchAnimationManager {
         }
     }
 
-    private void animateQsb(boolean show) {
-        SearchDropTargetBar.State searchBarState = show ? SearchDropTargetBar.State.SEARCH_BAR
-                : SearchDropTargetBar.State.INVISIBLE;
-        mLauncher.getSearchDropTargetBar().animateToState(searchBarState, THRESHOLD_ANIM_DURATION);
-    }
-
     private void animateOverviewPanelButtons(boolean show) {
         animateShowHideView(INDEX_OVERVIEW_PANEL_BUTTONS, mLauncher.getOverviewPanel(), show);
     }
diff --git a/src/com/android/launcher3/QsbContainerView.java b/src/com/android/launcher3/QsbContainerView.java
new file mode 100644 (file)
index 0000000..0a112d2
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+
+/**
+ * A frame layout which contains a QSB. This internally uses fragment to bind the view, which
+ * allows it to contain the logic for {@link Fragment#startActivityForResult(Intent, int)}.
+ */
+public class QsbContainerView extends FrameLayout {
+
+    private boolean mBound;
+
+    public QsbContainerView(Context context) {
+        super(context);
+    }
+
+    public QsbContainerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public QsbContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (!mBound) {
+            FragmentManager fm = ((Launcher) getContext()).getFragmentManager();
+            fm.beginTransaction().add(R.id.qsb_container, new QsbFragment()).commit();
+            mBound = true;
+        }
+    }
+
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        super.setPadding(0, 0, 0, 0);
+    }
+
+    /**
+     * A fragment to display the QSB.
+     */
+    public static class QsbFragment extends Fragment implements View.OnClickListener {
+
+        private static final int REQUEST_BIND_QSB = 1;
+        private static final String QSB_WIDGET_ID = "qsb_widget_id";
+
+        private static int sSavedWidgetId = -1;
+
+        private AppWidgetProviderInfo mWidgetInfo;
+        private LauncherAppWidgetHostView mQsb;
+
+        private BroadcastReceiver mRebindReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                rebindFragment();
+            }
+        };
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            IntentFilter filter = new IntentFilter(Launcher.ACTION_APPWIDGET_HOST_RESET);
+            filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+            getContext().registerReceiver(mRebindReceiver, filter);
+        }
+
+        @Override
+        public View onCreateView(
+                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+            if (savedInstanceState != null) {
+                sSavedWidgetId = savedInstanceState.getInt(QSB_WIDGET_ID, -1);
+            }
+
+            Launcher launcher = (Launcher) getActivity();
+            mWidgetInfo = getSearchWidgetProvider(launcher);
+            if (mWidgetInfo == null) {
+                // There is no search provider, just show the default widget.
+                return getDefaultView(inflater, container, false);
+            }
+
+            SharedPreferences prefs = Utilities.getPrefs(launcher);
+            AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(launcher);
+            LauncherAppWidgetHost widgetHost = launcher.getAppWidgetHost();
+            InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+
+            Bundle opts = new Bundle();
+            Rect size = AppWidgetResizeFrame.getWidgetSizeRanges(launcher, idp.numColumns, 1, null);
+            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, size.left);
+            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, size.top);
+            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, size.right);
+            opts.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, size.bottom);
+
+            int widgetId = prefs.getInt(QSB_WIDGET_ID, -1);
+            AppWidgetProviderInfo widgetInfo = widgetManager.getAppWidgetInfo(widgetId);
+            boolean isWidgetBound = (widgetInfo != null) &&
+                    widgetInfo.provider.equals(mWidgetInfo.provider);
+
+            if (!isWidgetBound) {
+                // widgetId is already bound and its not the correct provider.
+                // Delete the widget id.
+                if (widgetId > -1) {
+                    widgetHost.deleteAppWidgetId(widgetId);
+                    widgetId = -1;
+                }
+
+                widgetId = widgetHost.allocateAppWidgetId();
+                isWidgetBound = widgetManager.bindAppWidgetIdIfAllowed(widgetId, mWidgetInfo, opts);
+                if (!isWidgetBound) {
+                    widgetHost.deleteAppWidgetId(widgetId);
+                    widgetId = -1;
+                }
+            }
+
+            if (isWidgetBound) {
+                mQsb = (LauncherAppWidgetHostView)
+                        widgetHost.createView(launcher, widgetId, mWidgetInfo);
+                mQsb.setId(R.id.qsb_widget);
+                mQsb.mErrorViewId = R.layout.qsb_default_view;
+
+                if (!Utilities.containsAll(AppWidgetManager.getInstance(launcher)
+                        .getAppWidgetOptions(widgetId), opts)) {
+                    mQsb.updateAppWidgetOptions(opts);
+                }
+                mQsb.setPadding(0, 0, 0, 0);
+                return mQsb;
+            }
+
+            // Return a default widget with setup icon.
+            return getDefaultView(inflater, container, true);
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (view.getId() == R.id.btn_qsb_search) {
+                getActivity().startSearch("", false, null, true);
+            } else if (view.getId() == R.id.btn_qsb_setup) {
+                // Allocate a new widget id for QSB
+                sSavedWidgetId = ((Launcher) getActivity())
+                        .getAppWidgetHost().allocateAppWidgetId();
+                // Start intent for bind the widget
+                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
+                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, sSavedWidgetId);
+                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, mWidgetInfo.provider);
+                startActivityForResult(intent, REQUEST_BIND_QSB);
+            }
+        }
+
+        @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+            outState.putInt(QSB_WIDGET_ID, sSavedWidgetId);
+        }
+
+        @Override
+        public void onActivityResult(int requestCode, int resultCode, Intent data) {
+            if (requestCode == REQUEST_BIND_QSB) {
+                if (resultCode == Activity.RESULT_OK) {
+                    int widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+                            sSavedWidgetId);
+                    Utilities.getPrefs(getContext()).edit().putInt(QSB_WIDGET_ID, widgetId).apply();
+                    sSavedWidgetId = -1;
+                    rebindFragment();
+                } else if (sSavedWidgetId != -1) {
+                    ((Launcher) getActivity()).getAppWidgetHost().deleteAppWidgetId(sSavedWidgetId);
+                    sSavedWidgetId = -1;
+                }
+            }
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+            if (mQsb != null && mQsb.isReinflateRequired()) {
+                rebindFragment();
+            }
+        }
+
+        @Override
+        public void onDestroy() {
+            getContext().unregisterReceiver(mRebindReceiver);
+            super.onDestroy();
+        }
+
+        private void rebindFragment() {
+            if (getActivity() != null) {
+                // Recreate the fragment. This will cause the qsb to be inflated again.
+                getActivity().getFragmentManager().beginTransaction()
+                        .replace(R.id.qsb_container, new QsbFragment()).commit();
+            }
+        }
+
+        private View getDefaultView(LayoutInflater inflater, ViewGroup parent, boolean showSetup) {
+            View v = inflater.inflate(R.layout.qsb_default_view, parent, false);
+            if (showSetup) {
+                View setupButton = v.findViewById(R.id.btn_qsb_setup);
+                setupButton.setVisibility(View.VISIBLE);
+                setupButton.setOnClickListener(this);
+            }
+            v.findViewById(R.id.btn_qsb_search).setOnClickListener(this);
+            return v;
+        }
+    }
+
+    /**
+     * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
+     * provided by the same package which is set to be global search activity.
+     * If widgetCategory is not supported, or no such widget is found, returns the first widget
+     * provided by the package.
+     */
+    public static AppWidgetProviderInfo getSearchWidgetProvider(Context context) {
+        SearchManager searchManager =
+                (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
+        ComponentName searchComponent = searchManager.getGlobalSearchActivity();
+        if (searchComponent == null) return null;
+        String providerPkg = searchComponent.getPackageName();
+
+        AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
+
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+        for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
+            if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
+                if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
+                    return info;
+                } else if (defaultWidgetForSearchPackage == null) {
+                    defaultWidgetForSearchPackage = info;
+                }
+            }
+        }
+        return defaultWidgetForSearchPackage;
+    }
+}
index 171dd87..e43e96c 100644 (file)
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -26,8 +24,6 @@ import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.dragndrop.DragController;
@@ -39,32 +35,21 @@ import com.android.launcher3.util.Thunk;
  */
 public class SearchDropTargetBar extends BaseDropTargetBar {
 
-    private static final TimeInterpolator MOVE_DOWN_INTERPOLATOR = new DecelerateInterpolator(0.6f);
-    private static final TimeInterpolator MOVE_UP_INTERPOLATOR = new DecelerateInterpolator(1.5f);
-
     /** The different states that the search bar space can be in. */
     public enum State {
-        INVISIBLE             (0f, 0f, 0f),
-        INVISIBLE_TRANSLATED  (0f, 0f, -1f),
-        SEARCH_BAR            (1f, 0f, 0f),
-        DROP_TARGET           (0f, 1f, 0f);
+        INVISIBLE             (0f),
+        DROP_TARGET           (1f);
 
-        private final float mSearchBarAlpha;
         private final float mDropTargetBarAlpha;
-        private final float mTranslation;
 
-        State(float sbAlpha, float dtbAlpha, float translation) {
-            mSearchBarAlpha = sbAlpha;
+        State(float dtbAlpha) {
             mDropTargetBarAlpha = dtbAlpha;
-            mTranslation = translation;
         }
-
     }
 
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private State mState = State.SEARCH_BAR;
-    @Thunk View mQSB;
+    private State mState = State.INVISIBLE;
 
     // Drop targets
     private ButtonDropTarget mDeleteDropTarget;
@@ -113,11 +98,7 @@ public class SearchDropTargetBar extends BaseDropTargetBar {
 
     @Override
     public void hideDropTargets() {
-        animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
-    }
-
-    public void setQsbSearchBar(View qsb) {
-        mQSB = qsb;
+        animateToState(State.INVISIBLE, DEFAULT_DRAG_FADE_DURATION);
     }
 
     /**
@@ -140,30 +121,6 @@ public class SearchDropTargetBar extends BaseDropTargetBar {
                 AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
             }
 
-            if (mQSB != null) {
-                boolean isVertical = ((Launcher) getContext()).getDeviceProfile()
-                        .isVerticalBarLayout();
-                float translation = isVertical ? 0 : mState.mTranslation * getMeasuredHeight();
-
-                if (duration > 0) {
-                    int translationChange = Float.compare(mQSB.getTranslationY(), translation);
-
-                    animateAlpha(mQSB, mState.mSearchBarAlpha,
-                            translationChange == 0 ? DEFAULT_INTERPOLATOR
-                                    : (translationChange < 0 ? MOVE_DOWN_INTERPOLATOR
-                                    : MOVE_UP_INTERPOLATOR));
-
-                    if (translationChange != 0) {
-                        mCurrentAnimation.play(
-                                ObjectAnimator.ofFloat(mQSB, View.TRANSLATION_Y, translation));
-                    }
-                } else {
-                    mQSB.setTranslationY(translation);
-                    mQSB.setAlpha(mState.mSearchBarAlpha);
-                    AlphaUpdateListener.updateVisibility(mQSB, mAccessibilityEnabled);
-                }
-            }
-
             // Start the final animation
             if (duration > 0) {
                 if (animation != null) {
@@ -175,30 +132,8 @@ public class SearchDropTargetBar extends BaseDropTargetBar {
         }
     }
 
-    /**
-     * @return the bounds of the QSB search bar.
-     */
-    public Rect getSearchBarBounds() {
-        if (mQSB != null) {
-            final int[] pos = new int[2];
-            mQSB.getLocationOnScreen(pos);
-
-            final Rect rect = new Rect();
-            rect.left = pos[0];
-            rect.top = pos[1];
-            rect.right = pos[0] + mQSB.getWidth();
-            rect.bottom = pos[1] + mQSB.getHeight();
-            return rect;
-        } else {
-            return null;
-        }
-    }
-
     @Override
     public void enableAccessibleDrag(boolean enable) {
-        if (mQSB != null) {
-            mQSB.setVisibility(enable ? View.GONE : View.VISIBLE);
-        }
         mDeleteDropTarget.enableAccessibleDrag(enable);
         mUninstallDropTarget.enableAccessibleDrag(enable);
     }
index c9d8cce..4aaa02f 100644 (file)
@@ -18,10 +18,7 @@ package com.android.launcher3;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
-import android.app.SearchManager;
 import android.app.WallpaperManager;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -603,39 +600,6 @@ public final class Utilities {
     }
 
     /**
-     * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
-     * provided by the same package which is set to be global search activity.
-     * If widgetCategory is not supported, or no such widget is found, returns the first widget
-     * provided by the package.
-     */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    public static AppWidgetProviderInfo getSearchWidgetProvider(Context context) {
-        SearchManager searchManager =
-                (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
-        ComponentName searchComponent = searchManager.getGlobalSearchActivity();
-        if (searchComponent == null) return null;
-        String providerPkg = searchComponent.getPackageName();
-
-        AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
-
-        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
-        for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
-            if (info.provider.getPackageName().equals(providerPkg)) {
-                if (ATLEAST_JB_MR1) {
-                    if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
-                        return info;
-                    } else if (defaultWidgetForSearchPackage == null) {
-                        defaultWidgetForSearchPackage = info;
-                    }
-                } else {
-                    return info;
-                }
-            }
-        }
-        return defaultWidgetForSearchPackage;
-    }
-
-    /**
      * Compresses the bitmap to a byte array for serialization.
      */
     public static byte[] flattenBitmap(Bitmap bitmap) {
index 0f0673a..6f81f59 100644 (file)
@@ -73,7 +73,6 @@ import com.android.launcher3.dragndrop.SpringLoadedDragController;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.LongArrayMap;
@@ -109,7 +108,10 @@ public class Workspace extends PagedView
     private static final boolean MAP_RECURSE = true;
 
     // The screen id used for the empty screen always present to the right.
-    public final static long EXTRA_EMPTY_SCREEN_ID = -201;
+    public static final long EXTRA_EMPTY_SCREEN_ID = -201;
+    // The is the first screen. It is always present, even if its empty.
+    public static final long FIRST_SCREEN_ID = 0;
+
     private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
 
     private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
@@ -180,8 +182,8 @@ public class Workspace extends PagedView
     // in all apps or customize mode)
 
     enum State {
-        NORMAL          (SearchDropTargetBar.State.SEARCH_BAR, false, false),
-        NORMAL_HIDDEN   (SearchDropTargetBar.State.INVISIBLE_TRANSLATED, false, false),
+        NORMAL          (SearchDropTargetBar.State.INVISIBLE, false, false),
+        NORMAL_HIDDEN   (SearchDropTargetBar.State.INVISIBLE, false, false),
         SPRING_LOADED   (SearchDropTargetBar.State.DROP_TARGET, false, true),
         OVERVIEW        (SearchDropTargetBar.State.INVISIBLE, true, true),
         OVERVIEW_HIDDEN (SearchDropTargetBar.State.INVISIBLE, true, false);
@@ -501,6 +503,32 @@ public class Workspace extends PagedView
         return mTouchState != TOUCH_STATE_REST;
     }
 
+    /**
+     * Initializes and binds the first page
+     * @param qsb an exisitng qsb to recycle or null.
+     */
+    public void bindAndInitFirstWorkspaceScreen(View qsb) {
+        // Add the first page
+        CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
+
+        if (qsb == null) {
+            // Always add a QSB on the first screen.
+            qsb = mLauncher.getLayoutInflater().inflate(R.layout.qsb_container,
+                    firstPage, false);
+        }
+
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) qsb.getLayoutParams();
+        lp.cellX = 0;
+        lp.cellY = 0;
+        lp.cellHSpan = firstPage.getCountX();
+        lp.cellVSpan = 1;
+        lp.canReorder = false;
+
+        if (!firstPage.addViewToCellLayout(qsb, 0, R.id.qsb_container, lp, true)) {
+            Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
+        }
+    }
+
     public void removeAllWorkspaceScreens() {
         // Disable all layout transitions before removing all pages to ensure that we don't get the
         // transition animations competing with us changing the scroll when we add pages or the
@@ -513,30 +541,39 @@ public class Workspace extends PagedView
             removeCustomContentPage();
         }
 
+        // Recycle the QSB widget
+        View qsb = findViewById(R.id.qsb_container);
+        if (qsb != null) {
+            ((ViewGroup) qsb.getParent()).removeView(qsb);
+        }
+
         // Remove the pages and clear the screen models
         removeAllViews();
         mScreenOrder.clear();
         mWorkspaceScreens.clear();
 
+        // Ensure that the first page is always present
+        bindAndInitFirstWorkspaceScreen(qsb);
+
         // Re-enable the layout transitions
         enableLayoutTransitions();
     }
 
-    public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
+    public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
         // Find the index to insert this view into.  If the empty screen exists, then
         // insert it before that.
         int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
         if (insertIndex < 0) {
             insertIndex = mScreenOrder.size();
         }
-        return insertNewWorkspaceScreen(screenId, insertIndex);
+        insertNewWorkspaceScreen(screenId, insertIndex);
     }
 
-    public long insertNewWorkspaceScreen(long screenId) {
-        return insertNewWorkspaceScreen(screenId, getChildCount());
+    public void insertNewWorkspaceScreen(long screenId) {
+        insertNewWorkspaceScreen(screenId, getChildCount());
     }
 
-    public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
+    public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) {
         if (mWorkspaceScreens.containsKey(screenId)) {
             throw new RuntimeException("Screen id " + screenId + " already exists!");
         }
@@ -558,7 +595,8 @@ public class Workspace extends PagedView
         if (delegate != null && delegate.isInAccessibleDrag()) {
             newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
         }
-        return screenId;
+
+        return newScreen;
     }
 
     public void createCustomContentContainer() {
@@ -860,7 +898,8 @@ public class Workspace extends PagedView
         for (int i = 0; i < total; i++) {
             long id = mWorkspaceScreens.keyAt(i);
             CellLayout cl = mWorkspaceScreens.valueAt(i);
-            if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
+            // FIRST_SCREEN_ID can never be removed.
+            if (id > FIRST_SCREEN_ID && cl.getShortcutsAndWidgets().getChildCount() == 0) {
                 removeScreens.add(id);
             }
         }
index f900790..dd11bde 100644 (file)
@@ -22,6 +22,7 @@ import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.backup.nano.BackupProtos;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
@@ -221,7 +222,7 @@ public class GridSizeMigrationTask {
                 // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
                 // break the loop and abort migration by throwing an exception.
                 OptimalPlacementSolution placement = new OptimalPlacementSolution(
-                        new GridOccupancy(mTrgX, mTrgY), deepCopy(mCarryOver), true);
+                        new GridOccupancy(mTrgX, mTrgY), deepCopy(mCarryOver), 0, true);
                 placement.find();
                 if (placement.finalPlacedItems.size() > 0) {
                     long newScreenId = LauncherSettings.Settings.call(
@@ -262,13 +263,16 @@ public class GridSizeMigrationTask {
      * Migrate a particular screen id.
      * Strategy:
      *   1) For all possible combinations of row and column, pick the one which causes the least
-     *      data loss: {@link #tryRemove(int, int, ArrayList, float[])}
+     *      data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
      *   2) Maintain a list of all lost items before this screen, and add any new item lost from
      *      this screen to that list as well.
      *   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) {
+        // If we are migrating the first screen, do not touch the first row.
+        int startY = screenId == Workspace.FIRST_SCREEN_ID ? 1 : 0;
+
         ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
 
         int removedCol = Integer.MAX_VALUE;
@@ -286,10 +290,10 @@ public class GridSizeMigrationTask {
 
         // Try removing all possible combinations
         for (int x = 0; x < mSrcX; x++) {
-            for (int y = 0; y < mSrcY; y++) {
+            for (int y = startY; y < mSrcY; y++) {
                 // Use a deep copy when trying out a particular combination as it can change
                 // the underlying object.
-                ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, deepCopy(items), outLoss);
+                ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items), outLoss);
 
                 if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
                     removeWt = outLoss[0];
@@ -338,12 +342,13 @@ public class GridSizeMigrationTask {
         if (!mCarryOver.isEmpty() && removeWt == 0) {
             // No new items were removed in this step. Try placing all the items on this screen.
             GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
+            occupied.markCells(0, 0, mTrgX, startY, true);
             for (DbEntry item : finalItems) {
                 occupied.markCells(item, true);
             }
 
             OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
-                    deepCopy(mCarryOver), true);
+                    deepCopy(mCarryOver), startY, true);
             placement.find();
             if (placement.lowestWeightLoss == 0) {
                 // All items got placed
@@ -375,9 +380,10 @@ public class GridSizeMigrationTask {
      * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
      * with the overall item movement.
      */
-    private ArrayList<DbEntry> tryRemove(int col, int row, ArrayList<DbEntry> items,
-            float[] outLoss) {
+    private ArrayList<DbEntry> tryRemove(int col, int row, int startY,
+            ArrayList<DbEntry> items, float[] outLoss) {
         GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
+        occupied.markCells(0, 0, mTrgX, startY, true);
 
         col = mShouldRemoveX ? col : Integer.MAX_VALUE;
         row = mShouldRemoveY ? row : Integer.MAX_VALUE;
@@ -399,7 +405,8 @@ public class GridSizeMigrationTask {
             }
         }
 
-        OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied, removedItems);
+        OptimalPlacementSolution placement =
+                new OptimalPlacementSolution(occupied, removedItems, startY);
         placement.find();
         finalItems.addAll(placement.finalPlacedItems);
         outLoss[0] = placement.lowestWeightLoss;
@@ -415,19 +422,24 @@ public class GridSizeMigrationTask {
         // linear placement.
         private final boolean ignoreMove;
 
+        // The first row in the grid from where the placement should start.
+        private final int startY;
+
         float lowestWeightLoss = Float.MAX_VALUE;
         float lowestMoveCost = Float.MAX_VALUE;
         ArrayList<DbEntry> finalPlacedItems;
 
-        public OptimalPlacementSolution(GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace) {
-            this(occupied, itemsToPlace, false);
+        public OptimalPlacementSolution(
+                GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace, int startY) {
+            this(occupied, itemsToPlace, startY, false);
         }
 
         public OptimalPlacementSolution(GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace,
-                boolean ignoreMove) {
+                int startY, boolean ignoreMove) {
             this.occupied = occupied;
             this.itemsToPlace = itemsToPlace;
             this.ignoreMove = ignoreMove;
+            this.startY = startY;
 
             // Sort the items such that larger widgets appear first followed by 1x1 items
             Collections.sort(this.itemsToPlace);
@@ -477,7 +489,7 @@ public class GridSizeMigrationTask {
                 int myW = me.spanX;
                 int myH = me.spanY;
 
-                for (int y = 0; y < mTrgY; y++) {
+                for (int y = startY; y < mTrgY; y++) {
                     for (int x = 0; x < mTrgX; x++) {
                         float newMoveCost = moveCost;
                         if (x != myX) {
@@ -547,7 +559,7 @@ public class GridSizeMigrationTask {
                 int newDistance = Integer.MAX_VALUE;
                 int newX = Integer.MAX_VALUE, newY = Integer.MAX_VALUE;
 
-                for (int y = 0; y < mTrgY; y++) {
+                for (int y = startY; y < mTrgY; y++) {
                     for (int x = 0; x < mTrgX; x++) {
                         if (!occupied.cells[x][y]) {
                             int dist = ignoreMove ? 0 :
index 3f5f0b4..6a10b0a 100644 (file)
@@ -77,7 +77,7 @@ public class GridOccupancy {
     public void markCells(int cellX, int cellY, int spanX, int spanY, boolean value) {
         if (cellX < 0 || cellY < 0) return;
         for (int x = cellX; x < cellX + spanX && x < mCountX; x++) {
-            for (int y = cellY; y < cellY + spanY && y < mCountX; y++) {
+            for (int y = cellY; y < cellY + spanY && y < mCountY; y++) {
                 cells[x][y] = value;
             }
         }
index 81cb8b5..5c5069f 100644 (file)
@@ -205,10 +205,13 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase {
      */
     private void setupAndVerifyContents(
             LauncherAppWidgetInfo item, Class<?> widgetClass, String desc) {
-        // Add new screen
-        long screenId = LauncherSettings.Settings.call(
-                mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+        long screenId = Workspace.FIRST_SCREEN_ID;
+        // Update the screen id counter for the provider.
+        LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+        if (screenId > Workspace.FIRST_SCREEN_ID) {
+            screenId = Workspace.FIRST_SCREEN_ID;
+        }
         ContentValues v = new ContentValues();
         v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
         v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0);
@@ -263,7 +266,7 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase {
         item.minSpanY = info.minSpanY;
         item.user = mWidgetManager.getUser(info);
         item.cellX = 0;
-        item.cellY = 0;
+        item.cellY = 1;
         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
 
         if (bindWidget) {
@@ -319,7 +322,7 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase {
         item.minSpanX = 2;
         item.minSpanY = 2;
         item.cellX = 0;
-        item.cellY = 0;
+        item.cellY = 1;
         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
         return item;
     }
index ccd8f73..ebf06ba 100644 (file)
@@ -208,6 +208,57 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherPro
         }});
     }
 
+    public void testWorkspace_first_row_blocked() throws Exception {
+        // The first screen has one item on the 4th column which needs moving, as the first row
+        // will be kept empty.
+        long[][][] ids = createGrid(new int[][][]{{
+                { -1, -1, -1, -1},
+                {  3,  1,  7,  0},
+                {  8,  7,  7, -1},
+                {  5,  2,  7, -1},
+        }}, 0);
+
+        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, new HashMap<String, Point>(),
+                new Point(4, 4), new Point(3, 4)).migrateWorkspace();
+
+        // Items in the second column of the first screen should get placed on a new screen.
+        verifyWorkspace(new long[][][] {{
+                {          -1,           -1,           -1},
+                {ids[0][1][0], ids[0][1][1], ids[0][1][2]},
+                {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
+                {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
+        }, {
+                {ids[0][1][3]},
+        }});
+    }
+
+    public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
+        // Items will get moved to the next screen to keep the first screen empty.
+        long[][][] ids = createGrid(new int[][][]{{
+                { -1, -1, -1, -1},
+                {  0,  1,  0,  0},
+                {  8,  7,  7, -1},
+                {  5,  6,  7, -1},
+        }}, 0);
+
+        new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, new HashMap<String, Point>(),
+                new Point(4, 4), new Point(3, 3)).migrateWorkspace();
+
+        // Items in the second column of the first screen should get placed on a new screen.
+        verifyWorkspace(new long[][][] {{
+                {          -1,           -1,           -1},
+                {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
+                {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
+        }, {
+                {ids[0][1][1], ids[0][1][0], ids[0][1][2]},
+                {ids[0][1][3]},
+        }});
+    }
+
+    private long[][][] createGrid(int[][][] typeArray) throws Exception {
+        return createGrid(typeArray, 1);
+    }
+
     /**
      * Initializes the DB with dummy elements to represent the provided grid structure.
      * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
@@ -215,14 +266,18 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherPro
      *                  two represent the workspace grid.
      * @return the same grid representation where each entry is the corresponding item id.
      */
-    private long[][][] createGrid(int[][][] typeArray) throws Exception {
+    private long[][][] createGrid(int[][][] typeArray, long startScreen) throws Exception {
+        LauncherSettings.Settings.call(getMockContentResolver(),
+                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
         long[][][] ids = new long[typeArray.length][][];
 
         for (int i = 0; i < typeArray.length; i++) {
             // Add screen to DB
-            long screenId = LauncherSettings.Settings.call(getMockContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                    .getLong(LauncherSettings.Settings.EXTRA_VALUE);
+            long screenId = startScreen + i;
+
+            // Keep the screen id counter up to date
+            LauncherSettings.Settings.call(getMockContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
 
             ContentValues v = new ContentValues();
             v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
@@ -270,7 +325,8 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherPro
                     } else {
                         assertEquals(1, c.getCount());
                         c.moveToNext();
-                        assertEquals(id, c.getLong(0));
+                        assertEquals(String.format("Failed to verify item ad %d %d, %d", i, y, x),
+                                id, c.getLong(0));
                         total++;
                     }
                     c.close();