Merge "Ensuring setQsbSearchBar is called whenever the QSB is created" into ub-launcher3-burnaby
diff --git a/Android.mk b/Android.mk
index 632dd09..5267469 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,6 +23,8 @@
LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-java-files-under, WallpaperPicker/src) \
$(call all-renderscript-files-under, src) \
diff --git a/res/drawable-xxhdpi/ic_pageindicator_current_dark.png b/res/drawable-xxhdpi/ic_pageindicator_current_dark.png
new file mode 100644
index 0000000..d5c4c8d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_current_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_pageindicator_default_dark.png b/res/drawable-xxhdpi/ic_pageindicator_default_dark.png
new file mode 100644
index 0000000..79d307b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_pageindicator_default_dark.png
Binary files differ
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index ed8d43e..7a4d5e8 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
+<!--
+ Copyright (C) 2008 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.
@@ -14,42 +15,49 @@
limitations under the License.
-->
-<com.android.launcher3.Folder
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.Folder xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@drawable/quantum_panel">
+ android:background="@drawable/quantum_panel"
+ android:orientation="vertical" >
- <ScrollView
- android:id="@+id/scroll_view"
+ <FrameLayout
+ android:id="@+id/folder_content_wrapper"
android:layout_width="match_parent"
- android:layout_height="match_parent">
- <com.android.launcher3.CellLayout
- android:id="@+id/folder_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:cacheColorHint="#ff333333"
- android:hapticFeedbackEnabled="false" />
- </ScrollView>
+ android:layout_height="match_parent" >
+
+ <!-- Actual size of the indicator doesn't matter as it is scaled to match the view size -->
+
+ <com.android.launcher3.FocusIndicatorView
+ android:id="@+id/focus_indicator"
+ android:layout_width="20dp"
+ android:layout_height="20dp" />
+
+ <com.android.launcher3.FolderCellLayout
+ android:id="@+id/folder_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:cacheColorHint="#ff333333"
+ android:hapticFeedbackEnabled="false" />
+ </FrameLayout>
<com.android.launcher3.FolderEditText
android:id="@+id/folder_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:paddingTop="@dimen/folder_name_padding"
- android:paddingBottom="@dimen/folder_name_padding"
android:background="#00000000"
- android:hint="@string/folder_hint_text"
- android:textSize="14sp"
- android:textColor="#ff777777"
- android:textColorHint="#ff808080"
- android:textColorHighlight="#ffCCCCCC"
- android:textCursorDrawable="@null"
+ android:fontFamily="sans-serif-condensed"
android:gravity="center_horizontal"
- android:singleLine="true"
+ android:hint="@string/folder_hint_text"
android:imeOptions="flagNoExtractUi"
- android:fontFamily="sans-serif-condensed"/>
-</com.android.launcher3.Folder>
+ android:paddingBottom="@dimen/folder_name_padding"
+ android:paddingTop="@dimen/folder_name_padding"
+ android:singleLine="true"
+ android:textColor="#ff777777"
+ android:textColorHighlight="#ffCCCCCC"
+ android:textColorHint="#ff808080"
+ android:textCursorDrawable="@null"
+ android:textSize="14sp" />
+
+</com.android.launcher3.Folder>
\ No newline at end of file
diff --git a/res/layout/user_folder_scroll.xml b/res/layout/user_folder_scroll.xml
new file mode 100644
index 0000000..421e426
--- /dev/null
+++ b/res/layout/user_folder_scroll.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.Folder xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/quantum_panel"
+ android:orientation="vertical" >
+
+ <FrameLayout
+ android:id="@+id/folder_content_wrapper"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <!-- Actual size of the indicator doesn't matter as it is scaled to match the view size -->
+
+ <com.android.launcher3.FocusIndicatorView
+ android:id="@+id/focus_indicator"
+ android:layout_width="20dp"
+ android:layout_height="20dp" />
+
+ <com.android.launcher3.FolderPagedView
+ android:id="@+id/folder_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ launcher:pageIndicator="@+id/folder_page_indicator" />
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/folder_footer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <com.android.launcher3.FolderEditText
+ android:id="@+id/folder_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:background="#00000000"
+ android:fontFamily="sans-serif-condensed"
+ android:gravity="center_horizontal"
+ android:hint="@string/folder_hint_text"
+ android:imeOptions="flagNoExtractUi"
+ android:paddingBottom="@dimen/folder_name_padding"
+ android:paddingTop="@dimen/folder_name_padding"
+ android:singleLine="true"
+ android:textColor="#ff777777"
+ android:textColorHighlight="#ffCCCCCC"
+ android:textColorHint="#ff808080"
+ android:textCursorDrawable="@null"
+ android:textSize="14sp" />
+
+ <include
+ android:id="@+id/folder_page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="12dp"
+ android:layout_gravity="center_vertical"
+ layout="@layout/page_indicator" />
+ </LinearLayout>
+
+</com.android.launcher3.Folder>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index cbec512..1acace6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -104,4 +104,5 @@
<item type="id" name="action_uninstall" />
<item type="id" name="action_info" />
<item type="id" name="action_add_to_workspace" />
+ <item type="id" name="action_move" />
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8b7e6c1..74b8814 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -305,4 +305,32 @@
<!-- Strings for accessibility actions -->
<!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
<string name="action_add_to_workspace">Add To Workspace</string>
+
+ <!-- Accessibility confirmation for item added to workspace -->
+ <string name="item_added_to_workspace">Item added to workspace</string>
+
+ <!-- Accessibility confirmation for item removed-->
+ <string name="item_removed_from_workspace">Item removed from workspace</string>
+
+ <!-- Accessibility action to move an item on the workspace. [CHAR_LIMIT=30] -->
+ <string name="action_move">Move Item</string>
+
+ <!-- Accessibility description to move item to empty cell. -->
+ <string name="move_to_empty_cell">Move to empty cell <xliff:g id="number" example="1">%1$s</xliff:g>, <xliff:g id="number" example="1">%2$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for item move -->
+ <string name="item_moved">Item moved</string>
+
+ <!-- Accessibility description to move item into an existing folder. -->
+ <string name="add_to_folder">Add to folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for item added to folder-->
+ <string name="added_to_folder">Item added to folder</string>
+
+ <!-- Accessibility description to create folder with another item. -->
+ <string name="create_folder_with">Create folder with: <xliff:g id="name" example="Game">%1$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for folder created -->
+ <string name="folder_created">Folder created</string>
+
</resources>
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index c1aa19a..9f8d499 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -36,7 +36,6 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -45,6 +44,7 @@
import android.widget.Toast;
import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.FocusHelper.PagedViewKeyListener;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import java.util.ArrayList;
@@ -139,7 +139,7 @@
* The Apps/Customize page that displays all the applications, widgets, and shortcuts.
*/
public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
- View.OnClickListener, View.OnKeyListener, DragSource,
+ View.OnClickListener, DragSource,
PagedViewWidget.ShortPressListener, LauncherTransitionable {
static final String TAG = "AppsCustomizePagedView";
@@ -182,6 +182,8 @@
ArrayList<AppsCustomizeAsyncTask> mRunningTasks;
private static final int sPageSleepDelay = 200;
+ private final PagedViewKeyListener mKeyListener = new PagedViewKeyListener();
+
private Runnable mInflateWidgetRunnable = null;
private Runnable mBindWidgetRunnable = null;
static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
@@ -449,10 +451,6 @@
mWidgetInstructionToast.show();
}
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- return FocusHelper.handleAppsCustomizeKeyEvent(v, keyCode, event);
- }
-
/*
* PagedViewWithDraggableItems implementation
*/
@@ -959,7 +957,7 @@
icon.setOnClickListener(mLauncher);
icon.setOnLongClickListener(this);
icon.setOnTouchListener(this);
- icon.setOnKeyListener(this);
+ icon.setOnKeyListener(mKeyListener);
icon.setOnFocusChangeListener(layout.mFocusHandlerView);
int index = i - startIndex;
@@ -1141,7 +1139,7 @@
widget.setOnClickListener(this);
widget.setOnLongClickListener(this);
widget.setOnTouchListener(this);
- widget.setOnKeyListener(this);
+ widget.setOnKeyListener(mKeyListener);
// Layout each widget
int ix = i % mWidgetCountX;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index a3500aa..c57090d 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -22,6 +22,7 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -33,25 +34,34 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LayoutAnimationController;
import com.android.launcher3.FolderIcon.FolderRingAnimator;
+import com.android.launcher3.LauncherAccessibilityDelegate.DragType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.List;
import java.util.Stack;
public class CellLayout extends ViewGroup {
@@ -169,6 +179,14 @@
private final static Paint sPaint = new Paint();
+ // Related to accessible drag and drop
+ DragAndDropAccessibilityDelegate mTouchHelper = new DragAndDropAccessibilityDelegate(this);
+ private boolean mUseTouchHelper = false;
+ OnClickListener mOldClickListener = null;
+ OnClickListener mOldWorkspaceListener = null;
+ private int mDownX = 0;
+ private int mDownY = 0;
+
public CellLayout(Context context) {
this(context, null);
}
@@ -294,6 +312,282 @@
addView(mShortcutsAndWidgets);
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public void enableAccessibleDrag(boolean enable) {
+ mUseTouchHelper = enable;
+ if (!enable) {
+ ViewCompat.setAccessibilityDelegate(this, null);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ setOnClickListener(mLauncher);
+ } else {
+ ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ setOnClickListener(mTouchHelper);
+ }
+
+ // Invalidate the accessibility hierarchy
+ if (getParent() != null) {
+ getParent().notifySubtreeAccessibilityStateChanged(
+ this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ }
+ }
+
+ @Override
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ // Always attempt to dispatch hover events to accessibility first.
+ if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mDownX = (int) event.getX();
+ mDownY = (int) event.getY();
+ }
+ return super.dispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mUseTouchHelper ||
+ (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
+ return true;
+ }
+ return false;
+ }
+
+ class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper implements OnClickListener {
+ private final Rect mTempRect = new Rect();
+
+ public DragAndDropAccessibilityDelegate(View forView) {
+ super(forView);
+ }
+
+ private int getViewIdAt(float x, float y) {
+ if (x < 0 || y < 0 || x > getMeasuredWidth() || y > getMeasuredHeight()) {
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ // Map coords to cell
+ int cellX = (int) Math.floor(x / (mCellWidth + mWidthGap));
+ int cellY = (int) Math.floor(y / (mCellHeight + mHeightGap));
+
+ // Map cell to id
+ int id = cellX * mCountY + cellY;
+ return id;
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ return nearestDropLocation(getViewIdAt(x, y));
+ }
+
+ protected int nearestDropLocation(int id) {
+ int count = mCountX * mCountY;
+ for (int delta = 0; delta < count; delta++) {
+ if (id + delta <= (count - 1)) {
+ int target = intersectsValidDropTarget(id + delta);
+ if (target >= 0) {
+ return target;
+ }
+ } else if (id - delta >= 0) {
+ int target = intersectsValidDropTarget(id - delta);
+ if (target >= 0) {
+ return target;
+ }
+ }
+ }
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ /**
+ * Find the virtual view id corresponding to the top left corner of any drop region by which
+ * the passed id is contained. For an icon, this is simply
+ *
+ * @param id the id we're interested examining (ie. does it fit there?)
+ * @return the view id of the top left corner of a valid drop region or -1 if there is no
+ * such valid region. For the icon, this can just be -1 or id.
+ */
+ protected int intersectsValidDropTarget(int id) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+ int y = id % mCountY;
+ int x = id / mCountY;
+
+ if (dragInfo.dragType == DragType.WIDGET) {
+ // For a widget, every cell must be vacant. In addition, we will return any valid
+ // drop target by which the passed id is contained.
+ boolean fits = false;
+
+ // These represent the amount that we can back off if we hit a problem. They
+ // get consumed as we move up and to the right, trying new regions.
+ int spanX = dragInfo.info.spanX;
+ int spanY = dragInfo.info.spanY;
+
+ for (int m = 0; m < spanX; m++) {
+ for (int n = 0; n < spanY; n++) {
+
+ fits = true;
+ int x0 = x - m;
+ int y0 = y - n;
+
+ if (x0 < 0 || y0 < 0) continue;
+
+ for (int i = x0; i < x0 + spanX; i++) {
+ if (!fits) break;
+ for (int j = y0; j < y0 + spanY; j++) {
+ if (i >= mCountX || j >= mCountY || mOccupied[i][j]) {
+ fits = false;
+ break;
+ }
+ }
+ }
+ if (fits) {
+ return x0 * mCountY + y0;
+ }
+ }
+ }
+ return -1;
+ } else {
+ // For an icon, we simply check the view directly below
+ View child = getChildAt(x, y);
+ if (child == null || child == dragInfo.item) {
+ // Empty cell. Good for an icon or folder.
+ return id;
+ } else if (dragInfo.dragType != DragType.FOLDER) {
+ // For icons, we can consider cells that have another icon or a folder.
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (info instanceof AppInfo || info instanceof FolderInfo ||
+ info instanceof ShortcutInfo) {
+ return id;
+ }
+ }
+ return -1;
+ }
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List<Integer> virtualViews) {
+ // We create a virtual view for each cell of the grid
+ // The cell ids correspond to cells in reading order.
+ int nCells = mCountX * mCountY;
+
+ for (int i = 0; i < nCells; i++) {
+ if (intersectsValidDropTarget(i) >= 0) {
+ virtualViews.add(i);
+ }
+ }
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
+ if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+ String confirmation = getConfirmationForIconDrop(viewId);
+ LauncherAppState.getInstance().getAccessibilityDelegate()
+ .handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onClick(View arg0) {
+ int viewId = getViewIdAt(mDownX, mDownY);
+
+ String confirmation = getConfirmationForIconDrop(viewId);
+ LauncherAppState.getInstance().getAccessibilityDelegate()
+ .handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) {
+ if (id == ExploreByTouchHelper.INVALID_ID) {
+ throw new IllegalArgumentException("Invalid virtual view id");
+ }
+ // We're required to set something here.
+ event.setContentDescription("");
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
+ if (id == ExploreByTouchHelper.INVALID_ID) {
+ throw new IllegalArgumentException("Invalid virtual view id");
+ }
+
+ node.setContentDescription(getLocationDescriptionForIconDrop(id));
+ node.setBoundsInParent(getItemBounds(id));
+
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+ node.setClickable(true);
+ node.setFocusable(true);
+ }
+
+ private String getLocationDescriptionForIconDrop(int id) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+ int y = id % mCountY;
+ int x = id / mCountY;
+
+ Resources res = getContext().getResources();
+ View child = getChildAt(x, y);
+ if (child == null || child == dragInfo.item) {
+ return res.getString(R.string.move_to_empty_cell, x, y);
+ } else {
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+ return res.getString(R.string.create_folder_with, info.title);
+ } else if (info instanceof FolderInfo) {
+ return res.getString(R.string.add_to_folder, info.title);
+ }
+ }
+ return "";
+ }
+
+ private String getConfirmationForIconDrop(int id) {
+ LauncherAccessibilityDelegate delegate =
+ LauncherAppState.getInstance().getAccessibilityDelegate();
+ LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
+
+ int y = id % mCountY;
+ int x = id / mCountY;
+
+ Resources res = getContext().getResources();
+ View child = getChildAt(x, y);
+ if (child == null || child == dragInfo.item) {
+ return res.getString(R.string.item_moved);
+ } else {
+ ItemInfo info = (ItemInfo) child.getTag();
+ if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+ return res.getString(R.string.folder_created);
+
+ } else if (info instanceof FolderInfo) {
+ return res.getString(R.string.added_to_folder);
+ }
+ }
+ return "";
+ }
+
+ private Rect getItemBounds(int id) {
+ int cellY = id % mCountY;
+ int cellX = id / mCountY;
+ int x = getPaddingLeft() + (int) (cellX * (mCellWidth + mWidthGap));
+ int y = getPaddingTop() + (int) (cellY * (mCellHeight + mHeightGap));
+
+ Rect bounds = mTempRect;
+ bounds.set(x, y, x + mCellWidth, y + mCellHeight);
+ return bounds;
+ }
+ }
+
public void enableHardwareLayer(boolean hasLayer) {
mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
}
@@ -679,18 +973,6 @@
mShortcutsAndWidgets.removeViewsInLayout(start, count);
}
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // First we clear the tag to ensure that on every touch down we start with a fresh slate,
- // even in the case where we return early. Not clearing here was causing bugs whereby on
- // long-press we'd end up picking up an item from a previous drag operation.
- if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
- return true;
- }
-
- return false;
- }
-
/**
* Given a point, return the cell that strictly encloses that point
* @param x X coordinate of the point
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b97f0f2..7d02e10 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -766,8 +766,7 @@
lp.gravity = Gravity.BOTTOM;
lp.width = LayoutParams.MATCH_PARENT;
lp.height = hotseatBarHeightPx;
- hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
- 2 * edgeMarginPx, 0);
+ hotseat.setPadding(2 * edgeMarginPx, 0, 2 * edgeMarginPx, 0);
}
hotseat.setLayoutParams(lp);
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index 480dce9..09c59a0 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -73,6 +73,9 @@
/** Whether or not we're dragging. */
private boolean mDragging;
+ /** Whether or not this is an accessible drag operation */
+ private boolean mIsAccessibleDrag;
+
/** X coordinate of the down event. */
private int mMotionDownX;
@@ -182,7 +185,7 @@
(int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
- null, initialDragViewScale);
+ null, initialDragViewScale, false);
if (dragAction == DRAG_ACTION_MOVE) {
v.setVisibility(View.GONE);
@@ -202,10 +205,11 @@
* {@link #DRAG_ACTION_COPY}
* @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
* Makes dragging feel more precise, e.g. you can clip out a transparent border
+ * @param accessible whether this drag should occur in accessibility mode
*/
public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY,
DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion,
- float initialDragViewScale) {
+ float initialDragViewScale, boolean accessible) {
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
@@ -228,12 +232,21 @@
final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
mDragging = true;
+ mIsAccessibleDrag = accessible;
mDragObject = new DropTarget.DragObject();
mDragObject.dragComplete = false;
- mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
- mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+ if (mIsAccessibleDrag) {
+ // For an accessible drag, we assume the view is being dragged from the center.
+ mDragObject.xOffset = b.getWidth() / 2;
+ mDragObject.yOffset = b.getHeight() / 2;
+ mDragObject.accessibleDrag = true;
+ } else {
+ mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
+ mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
+ }
+
mDragObject.dragSource = source;
mDragObject.dragInfo = dragInfo;
@@ -349,6 +362,7 @@
private void endDrag() {
if (mDragging) {
mDragging = false;
+ mIsAccessibleDrag = false;
clearScrollRunnable();
boolean isDeferred = false;
if (mDragObject.dragView != null) {
@@ -421,6 +435,10 @@
+ mDragging);
}
+ if (mIsAccessibleDrag) {
+ return false;
+ }
+
// Update the velocity tracker
acquireVelocityTrackerAndAddMovement(ev);
@@ -560,7 +578,7 @@
* Call this from a drag source view.
*/
public boolean onTouchEvent(MotionEvent ev) {
- if (!mDragging) {
+ if (!mDragging || mIsAccessibleDrag) {
return false;
}
@@ -617,6 +635,34 @@
}
/**
+ * Since accessible drag and drop won't cause the same sequence of touch events, we manually
+ * inject the appropriate state.
+ */
+ public void prepareAccessibleDrag(int x, int y) {
+ mMotionDownX = x;
+ mMotionDownY = y;
+ mLastDropTarget = null;
+ }
+
+ /**
+ * As above, since accessible drag and drop won't cause the same sequence of touch events,
+ * we manually ensure appropriate drag and drop events get emulated for accessible drag.
+ */
+ public void completeAccessibleDrag(int[] location) {
+ final int[] coordinates = mCoordinatesTemp;
+
+ // We make sure that we prime the target for drop.
+ DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates);
+ mDragObject.x = coordinates[0];
+ mDragObject.y = coordinates[1];
+ checkTouchMove(dropTarget);
+
+ // Perform the drop
+ drop(location[0], location[1]);
+ endDrag();
+ }
+
+ /**
* Determines whether the user flung the current item to delete it.
*
* @return the vector at which the item was flung, or null if no fling was detected.
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 7ede427..94ae82b 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -54,6 +54,9 @@
/** Where the drag originated */
public DragSource dragSource = null;
+ /** The object is part of an accessible drag operation */
+ public boolean accessibleDrag;
+
/** Post drag animation runnable */
public Runnable postAnimationRunnable = null;
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index bdfd7b2..b090a7c 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,8 +22,8 @@
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ScrollView;
+import com.android.launcher3.FocusHelper.PagedViewKeyListener;
import com.android.launcher3.util.FocusLogic;
/**
@@ -66,9 +66,32 @@
//
/**
+ * A keyboard listener for scrollable folders
+ */
+ public static class PagedFolderKeyEventListener extends PagedViewKeyListener {
+
+ private final Folder mFolder;
+
+ public PagedFolderKeyEventListener(Folder folder) {
+ mFolder = folder;
+ }
+
+ @Override
+ public void handleNoopKey(int keyCode, View v) {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ mFolder.mFolderName.requestFocus();
+ playSoundEffect(keyCode, v);
+ }
+ }
+ }
+
+ /**
* Handles key events in the all apps screen.
*/
- static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
+ public static class PagedViewKeyListener implements View.OnKeyListener {
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent e) {
boolean consume = FocusLogic.shouldConsume(keyCode);
if (e.getAction() == KeyEvent.ACTION_UP) {
return consume;
@@ -88,35 +111,47 @@
parentLayout = (ViewGroup) itemContainer.getParent();
countX = ((CellLayout) parentLayout).getCountX();
countY = ((CellLayout) parentLayout).getCountY();
- } else if (v.getParent() instanceof ViewGroup) {
- itemContainer = parentLayout = (ViewGroup) v.getParent();
- countX = ((PagedViewGridLayout) parentLayout).getCellCountX();
- countY = ((PagedViewGridLayout) parentLayout).getCellCountY();
} else {
- throw new IllegalStateException(
- "Parent of the focused item inside all apps screen is not a supported type.");
+ if (LauncherAppState.isDogfoodBuild()) {
+ throw new IllegalStateException("Parent of the focused item is not supported.");
+ } else {
+ return false;
+ }
}
+
final int iconIndex = itemContainer.indexOfChild(v);
final PagedView container = (PagedView) parentLayout.getParent();
final int pageIndex = container.indexToPage(container.indexOfChild(parentLayout));
final int pageCount = container.getChildCount();
ViewGroup newParent = null;
View child = null;
+ // TODO(hyunyoungs): this matrix is not applicable on the last page.
int[][] matrix = FocusLogic.createFullMatrix(countX, countY, true);
// Process focus.
int newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX, countY, matrix,
iconIndex, pageIndex, pageCount);
if (newIconIndex == FocusLogic.NOOP) {
+ handleNoopKey(keyCode, v);
return consume;
}
switch (newIconIndex) {
+ case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+ newParent = getAppsCustomizePage(container, pageIndex -1);
+ if (newParent != null) {
+ int row = FocusLogic.findRow(matrix, iconIndex);
+ container.snapToPage(pageIndex - 1);
+ // no need to create a new matrix.
+ child = newParent.getChildAt(matrix[countX-1][row]);
+ }
+ break;
case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
newParent = getAppsCustomizePage(container, pageIndex - 1);
if (newParent != null) {
container.snapToPage(pageIndex - 1);
child = newParent.getChildAt(0);
}
+ break;
case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
newParent = getAppsCustomizePage(container, pageIndex - 1);
if (newParent != null) {
@@ -131,6 +166,14 @@
child = newParent.getChildAt(0);
}
break;
+ case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+ newParent = getAppsCustomizePage(container, pageIndex + 1);
+ if (newParent != null) {
+ container.snapToPage(pageIndex + 1);
+ int row = FocusLogic.findRow(matrix, iconIndex);
+ child = newParent.getChildAt(matrix[0][row]);
+ }
+ break;
case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
child = container.getChildAt(0);
break;
@@ -144,10 +187,15 @@
if (child != null) {
child.requestFocus();
playSoundEffect(keyCode, v);
+ } else {
+ handleNoopKey(keyCode, v);
}
return consume;
}
+ public void handleNoopKey(int keyCode, View v) { }
+ }
+
/**
* Handles key events in the workspace hot seat (bottom of the screen).
* <p>Currently we don't special case for the phone UI in different orientations, even though
@@ -256,7 +304,7 @@
// Initialize the variables.
ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
- final CellLayout iconLayout = (CellLayout) parent.getParent();
+ CellLayout iconLayout = (CellLayout) parent.getParent();
final Workspace workspace = (Workspace) iconLayout.getParent();
final ViewGroup launcher = (ViewGroup) workspace.getParent();
final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.search_drop_target_bar);
@@ -301,10 +349,23 @@
newIcon = tabs;
}
break;
+ case FocusLogic.PREVIOUS_PAGE_RIGHT_COLUMN:
+ int row = FocusLogic.findRow(matrix, iconIndex);
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
+ if (parent != null) {
+ iconLayout = (CellLayout) parent.getParent();
+ matrix = FocusLogic.createSparseMatrix(iconLayout,
+ iconLayout.getCountX(), row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+ FocusLogic.PIVOT, pageIndex - 1, pageCount);
+ newIcon = parent.getChildAt(newIconIndex);
+ }
+ break;
case FocusLogic.PREVIOUS_PAGE_FIRST_ITEM:
parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
newIcon = parent.getChildAt(0);
workspace.snapToPage(pageIndex - 1);
+ break;
case FocusLogic.PREVIOUS_PAGE_LAST_ITEM:
parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1);
newIcon = parent.getChildAt(parent.getChildCount() - 1);
@@ -315,6 +376,17 @@
newIcon = parent.getChildAt(0);
workspace.snapToPage(pageIndex + 1);
break;
+ case FocusLogic.NEXT_PAGE_LEFT_COLUMN:
+ row = FocusLogic.findRow(matrix, iconIndex);
+ parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1);
+ if (parent != null) {
+ iconLayout = (CellLayout) parent.getParent();
+ matrix = FocusLogic.createSparseMatrix(iconLayout, -1, row);
+ newIconIndex = FocusLogic.handleKeyEvent(keyCode, countX + 1, countY, matrix,
+ FocusLogic.PIVOT, pageIndex, pageCount);
+ newIcon = parent.getChildAt(newIconIndex);
+ }
+ break;
case FocusLogic.CURRENT_PAGE_FIRST_ITEM:
newIcon = parent.getChildAt(0);
break;
@@ -354,8 +426,7 @@
// Initialize the variables.
ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) v.getParent();
final CellLayout layout = (CellLayout) parent.getParent();
- final ScrollView scrollView = (ScrollView) layout.getParent();
- final Folder folder = (Folder) scrollView.getParent();
+ final Folder folder = (Folder) layout.getParent().getParent();
View title = folder.mFolderName;
Workspace workspace = (Workspace) v.getRootView().findViewById(R.id.workspace);
final int countX = layout.getCountX();
diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java
index 7d4664a..af3b976 100644
--- a/src/com/android/launcher3/FocusIndicatorView.java
+++ b/src/com/android/launcher3/FocusIndicatorView.java
@@ -23,7 +23,6 @@
import android.util.AttributeSet;
import android.util.Pair;
import android.view.View;
-import android.view.ViewParent;
public class FocusIndicatorView extends View implements View.OnFocusChangeListener {
@@ -32,9 +31,6 @@
private static final float MIN_VISIBLE_ALPHA = 0.2f;
private static final long ANIM_DURATION = 150;
- private static final int[] sTempPos = new int[2];
- private static final int[] sTempShift = new int[2];
-
private final int[] mIndicatorPos = new int[2];
private final int[] mTargetViewPos = new int[2];
@@ -80,7 +76,8 @@
}
if (!mInitiated) {
- getLocationRelativeToParentPagedView(this, mIndicatorPos);
+ // The parent view should always the a parent of the target view.
+ computeLocationRelativeToParent(this, (View) getParent(), mIndicatorPos);
mInitiated = true;
}
@@ -93,7 +90,7 @@
nextState.scaleX = v.getScaleX() * v.getWidth() / indicatorWidth;
nextState.scaleY = v.getScaleY() * v.getHeight() / indicatorHeight;
- getLocationRelativeToParentPagedView(v, mTargetViewPos);
+ computeLocationRelativeToParent(v, (View) getParent(), mTargetViewPos);
nextState.x = mTargetViewPos[0] - mIndicatorPos[0] - (1 - nextState.scaleX) * indicatorWidth / 2;
nextState.y = mTargetViewPos[1] - mIndicatorPos[1] - (1 - nextState.scaleY) * indicatorHeight / 2;
@@ -150,27 +147,32 @@
}
/**
- * Gets the location of a view relative in the window, off-setting any shift due to
- * page view scroll
+ * Computes the location of a view relative to {@link #mCommonParent}, off-setting
+ * any shift due to page view scroll.
+ * @param pos an array of two integers in which to hold the coordinates
*/
- private static void getLocationRelativeToParentPagedView(View v, int[] pos) {
- getPagedViewScrollShift(v, sTempShift);
- v.getLocationInWindow(sTempPos);
- pos[0] = sTempPos[0] + sTempShift[0];
- pos[1] = sTempPos[1] + sTempShift[1];
+ private static void computeLocationRelativeToParent(View v, View parent, int[] pos) {
+ pos[0] = pos[1] = 0;
+ computeLocationRelativeToParentHelper(v, parent, pos);
+
+ // If a view is scaled, its position will also shift accordingly. For optimization, only
+ // consider this for the last node.
+ pos[0] += (1 - v.getScaleX()) * v.getWidth() / 2;
+ pos[1] += (1 - v.getScaleY()) * v.getHeight() / 2;
}
- private static void getPagedViewScrollShift(View child, int[] shift) {
- ViewParent parent = child.getParent();
+ private static void computeLocationRelativeToParentHelper(View child,
+ View commonParent, int[] shift) {
+ View parent = (View) child.getParent();
if (parent instanceof PagedView) {
- View parentView = (View) parent;
- child.getLocationInWindow(sTempPos);
- shift[0] = parentView.getPaddingLeft() - sTempPos[0];
- shift[1] = -(int) child.getTranslationY();
- } else if (parent instanceof View) {
- getPagedViewScrollShift((View) parent, shift);
- } else {
- shift[0] = shift[1] = 0;
+ child = ((PagedView) parent).getPageAt(0);
+ }
+
+ shift[0] += child.getLeft();
+ shift[1] += child.getTop();
+
+ if (parent != commonParent) {
+ computeLocationRelativeToParentHelper(parent, commonParent, shift);
}
}
diff --git a/src/com/android/launcher3/FocusOnlyTabWidget.java b/src/com/android/launcher3/FocusOnlyTabWidget.java
deleted file mode 100644
index 08fc311..0000000
--- a/src/com/android/launcher3/FocusOnlyTabWidget.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2011 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.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TabWidget;
-
-public class FocusOnlyTabWidget extends TabWidget {
- public FocusOnlyTabWidget(Context context) {
- super(context);
- }
-
- public FocusOnlyTabWidget(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public FocusOnlyTabWidget(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public View getSelectedTab() {
- final int count = getTabCount();
- for (int i = 0; i < count; ++i) {
- View v = getChildTabViewAt(i);
- if (v.isSelected()) {
- return v;
- }
- }
- return null;
- }
-
- public int getChildTabIndex(View v) {
- final int tabCount = getTabCount();
- for (int i = 0; i < tabCount; ++i) {
- if (getChildTabViewAt(i) == v) {
- return i;
- }
- }
- return -1;
- }
-
- public void setCurrentTabToFocusedTab() {
- View tab = null;
- int index = -1;
- final int count = getTabCount();
- for (int i = 0; i < count; ++i) {
- View v = getChildTabViewAt(i);
- if (v.hasFocus()) {
- tab = v;
- index = i;
- break;
- }
- }
- if (index > -1) {
- super.setCurrentTab(index);
- super.onFocusChange(tab, true);
- }
- }
- public void superOnFocusChange(View v, boolean hasFocus) {
- super.onFocusChange(v, hasFocus);
- }
-
- @Override
- public void onFocusChange(android.view.View v, boolean hasFocus) {
- if (v == this && hasFocus && getTabCount() > 0) {
- getSelectedTab().requestFocus();
- return;
- }
- }
-}
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 11e9835..0a65579 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -45,10 +45,10 @@
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
-import android.widget.ScrollView;
import android.widget.TextView;
import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.Workspace.ItemOperator;
import java.util.ArrayList;
import java.util.Collections;
@@ -61,64 +61,66 @@
View.OnFocusChangeListener {
private static final String TAG = "Launcher.Folder";
- protected DragController mDragController;
- protected Launcher mLauncher;
- protected FolderInfo mInfo;
+ /**
+ * We avoid measuring {@link #mContentWrapper} with a 0 width or height, as this
+ * results in CellLayout being measured as UNSPECIFIED, which it does not support.
+ */
+ private static final int MIN_CONTENT_DIMEN = 5;
+ private static final boolean ALLOW_FOLDER_SCROLL = true;
static final int STATE_NONE = -1;
static final int STATE_SMALL = 0;
static final int STATE_ANIMATING = 1;
static final int STATE_OPEN = 2;
- private int mExpandDuration;
- private int mMaterialExpandDuration;
- private int mMaterialExpandStagger;
- protected CellLayout mContent;
- private ScrollView mScrollView;
- private final LayoutInflater mInflater;
- private final IconCache mIconCache;
- private int mState = STATE_NONE;
- private static final int REORDER_ANIMATION_DURATION = 230;
private static final int REORDER_DELAY = 250;
private static final int ON_EXIT_CLOSE_DELAY = 400;
- private boolean mRearrangeOnClose = false;
+ private static final Rect sTempRect = new Rect();
+
+ private static String sDefaultFolderName;
+ private static String sHintText;
+
+ private final Alarm mReorderAlarm = new Alarm();
+ private final Alarm mOnExitAlarm = new Alarm();
+
+ private final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+
+ private final int mExpandDuration;
+ private final int mMaterialExpandDuration;
+ private final int mMaterialExpandStagger;
+
+ private final InputMethodManager mInputMethodManager;
+
+ protected final Launcher mLauncher;
+ protected DragController mDragController;
+ protected FolderInfo mInfo;
+
private FolderIcon mFolderIcon;
- private int mMaxCountX;
- private int mMaxCountY;
- private int mMaxNumItems;
- private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
+
+ private FolderContent mContent;
+ private View mContentWrapper;
+ FolderEditText mFolderName;
+
+ private View mFooter;
+ private int mFooterHeight;
+
+ // Cell ranks used for drag and drop
+ private int mTargetRank, mPrevTargetRank, mEmptyCellRank;
+
+ private int mState = STATE_NONE;
+ private boolean mRearrangeOnClose = false;
boolean mItemsInvalidated = false;
private ShortcutInfo mCurrentDragInfo;
private View mCurrentDragView;
private boolean mIsExternalDrag;
boolean mSuppressOnAdd = false;
- private int[] mTargetCell = new int[2];
- private int[] mPreviousTargetCell = new int[2];
- private int[] mEmptyCell = new int[2];
- private Alarm mReorderAlarm = new Alarm();
- private Alarm mOnExitAlarm = new Alarm();
- private int mFolderNameHeight;
- private Rect mTempRect = new Rect();
private boolean mDragInProgress = false;
private boolean mDeleteFolderOnDropCompleted = false;
private boolean mSuppressFolderDeletion = false;
private boolean mItemAddedBackToSelfViaIcon = false;
- FolderEditText mFolderName;
private float mFolderIconPivotX;
private float mFolderIconPivotY;
-
private boolean mIsEditingName = false;
- private InputMethodManager mInputMethodManager;
-
- private static String sDefaultFolderName;
- private static String sHintText;
-
- private FocusIndicatorView mFocusIndicatorHandler;
-
- // We avoid measuring the scroll view with a 0 width or height, as this
- // results in CellLayout being measured as UNSPECIFIED, which it does
- // not support.
- private static final int MIN_CONTENT_DIMEN = 5;
private boolean mDestroyed;
@@ -130,25 +132,15 @@
* Used to inflate the Workspace from XML.
*
* @param context The application's context.
- * @param attrs The attribtues set containing the Workspace's customization values.
+ * @param attrs The attributes set containing the Workspace's customization values.
*/
public Folder(Context context, AttributeSet attrs) {
super(context, attrs);
-
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
setAlwaysDrawnWithCacheEnabled(false);
- mInflater = LayoutInflater.from(context);
- mIconCache = app.getIconCache();
-
- Resources res = getResources();
- mMaxCountX = (int) grid.numColumns;
- mMaxCountY = (int) grid.numRows;
- mMaxNumItems = mMaxCountX * mMaxCountY;
-
mInputMethodManager = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ Resources res = getResources();
mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration);
mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger);
@@ -162,44 +154,34 @@
mLauncher = (Launcher) context;
// We need this view to be focusable in touch mode so that when text editing of the folder
// name is complete, we have something to focus on, thus hiding the cursor and giving
- // reliable behvior when clicking the text field (since it will always gain focus on click).
+ // reliable behavior when clicking the text field (since it will always gain focus on click).
setFocusableInTouchMode(true);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mScrollView = (ScrollView) findViewById(R.id.scroll_view);
- mContent = (CellLayout) findViewById(R.id.folder_content);
+ mContentWrapper = findViewById(R.id.folder_content_wrapper);
+ mContent = (FolderContent) findViewById(R.id.folder_content);
+ mContent.setFolder(this);
- mFocusIndicatorHandler = new FocusIndicatorView(getContext());
- mContent.addView(mFocusIndicatorHandler, 0);
- mFocusIndicatorHandler.getLayoutParams().height = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
- mFocusIndicatorHandler.getLayoutParams().width = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
-
- LauncherAppState app = LauncherAppState.getInstance();
- DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
- mContent.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
- mContent.setGridSize(0, 0);
- mContent.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
- mContent.setInvertIfRtl(true);
mFolderName = (FolderEditText) findViewById(R.id.folder_name);
mFolderName.setFolder(this);
mFolderName.setOnFocusChangeListener(this);
- // We find out how tall the text view wants to be (it is set to wrap_content), so that
- // we can allocate the appropriate amount of space for it.
- int measureSpec = MeasureSpec.UNSPECIFIED;
- mFolderName.measure(measureSpec, measureSpec);
- mFolderNameHeight = mFolderName.getMeasuredHeight();
-
// We disable action mode for now since it messes up the view on phones
mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback);
mFolderName.setOnEditorActionListener(this);
mFolderName.setSelectAllOnFocus(true);
mFolderName.setInputType(mFolderName.getInputType() |
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+ mFooter = ALLOW_FOLDER_SCROLL ? findViewById(R.id.folder_footer) : mFolderName;
+ // We find out how tall footer wants to be (it is set to wrap_content), so that
+ // we can allocate the appropriate amount of space for it.
+ int measureSpec = MeasureSpec.UNSPECIFIED;
+ mFooter.measure(measureSpec, measureSpec);
+ mFooterHeight = mFooter.getMeasuredHeight();
}
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
@@ -240,11 +222,10 @@
mLauncher.getWorkspace().beginDragShared(v, this);
mCurrentDragInfo = item;
- mEmptyCell[0] = item.cellX;
- mEmptyCell[1] = item.cellY;
+ mEmptyCellRank = item.rank;
mCurrentDragView = v;
- mContent.removeView(mCurrentDragView);
+ mContent.removeItem(mCurrentDragView);
mInfo.remove(mCurrentDragInfo);
mDragInProgress = true;
mItemAddedBackToSelfViaIcon = false;
@@ -298,10 +279,6 @@
return mFolderName;
}
- public CellLayout getContent() {
- return mContent;
- }
-
/**
* We need to handle touch events to prevent them from falling through to the workspace below.
*/
@@ -314,7 +291,7 @@
mDragController = dragController;
}
- void setFolderIcon(FolderIcon icon) {
+ public void setFolderIcon(FolderIcon icon) {
mFolderIcon = icon;
}
@@ -334,30 +311,9 @@
void bind(FolderInfo info) {
mInfo = info;
ArrayList<ShortcutInfo> children = info.contents;
- ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>();
-
- final int totalChildren = children.size();
- setupContentForNumItems(totalChildren);
-
- // Arrange children in the grid based on the rank.
Collections.sort(children, Utilities.RANK_COMPARATOR);
- final int countX = mContent.getCountX();
- int visibleChildren = 0;
- for (int i = 0; i < children.size(); i++) {
- ShortcutInfo child = children.get(i);
- child.cellX = i % countX;
- child.cellY = i / countX;
-
- if (createAndAddShortcut(child) == null) {
- overflow.add(child);
- } else {
- visibleChildren++;
- }
- }
-
- // We rearrange the items in case there are any empty gaps
- setupContentForNumItems(visibleChildren);
+ ArrayList<ShortcutInfo> overflow = mContent.bindItems(children);
// If our folder has too many items we prune them from the list. This is an issue
// when upgrading from the old Folders implementation which could contain an unlimited
@@ -367,6 +323,14 @@
LauncherModel.deleteItemFromDatabase(mLauncher, item);
}
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ if (lp == null) {
+ lp = new DragLayer.LayoutParams(0, 0);
+ lp.customPosition = true;
+ setLayoutParams(lp);
+ }
+ centerAboutIcon();
+
mItemsInvalidated = true;
updateTextViewFocus();
mInfo.addListener(this);
@@ -395,7 +359,8 @@
* @return A new UserFolder.
*/
static Folder fromXml(Context context) {
- return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null);
+ return (Folder) LayoutInflater.from(context).inflate(
+ ALLOW_FOLDER_SCROLL ? R.layout.user_folder_scroll : R.layout.user_folder, null);
}
/**
@@ -464,14 +429,14 @@
reveal.setDuration(mMaterialExpandDuration);
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
- mContent.setAlpha(0f);
- Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContent, "alpha", 0f, 1f);
+ mContentWrapper.setAlpha(0f);
+ Animator iconsAlpha = LauncherAnimUtils.ofFloat(mContentWrapper, "alpha", 0f, 1f);
iconsAlpha.setDuration(mMaterialExpandDuration);
iconsAlpha.setStartDelay(mMaterialExpandStagger);
iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
- mFolderName.setAlpha(0f);
- Animator textAlpha = LauncherAnimUtils.ofFloat(mFolderName, "alpha", 0f, 1f);
+ mFooter.setAlpha(0f);
+ Animator textAlpha = LauncherAnimUtils.ofFloat(mFooter, "alpha", 0f, 1f);
textAlpha.setDuration(mMaterialExpandDuration);
textAlpha.setStartDelay(mMaterialExpandStagger);
textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
@@ -488,11 +453,11 @@
openFolderAnim = anim;
- mContent.setLayerType(LAYER_TYPE_HARDWARE, null);
+ mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null);
onCompleteRunnable = new Runnable() {
@Override
public void run() {
- mContent.setLayerType(LAYER_TYPE_NONE, null);
+ mContentWrapper.setLayerType(LAYER_TYPE_NONE, null);
}
};
}
@@ -500,8 +465,7 @@
@Override
public void onAnimationStart(Animator animation) {
sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- String.format(getContext().getString(R.string.folder_opened),
- mContent.getCountX(), mContent.getCountY()));
+ mContent.getAccessibilityDescription());
mState = STATE_ANIMATING;
}
@Override
@@ -512,7 +476,7 @@
onCompleteRunnable.run();
}
- setFocusOnFirstChild();
+ mContent.setFocusOnFirstChild();
}
});
openFolderAnim.start();
@@ -524,14 +488,9 @@
}
public void beginExternalDrag(ShortcutInfo item) {
- setupContentForNumItems(getItemCount() + 1);
- findAndSetEmptyCells(item);
-
mCurrentDragInfo = item;
- mEmptyCell[0] = item.cellX;
- mEmptyCell[1] = item.cellY;
+ mEmptyCellRank = mContent.allocateNewLastItemRank();
mIsExternalDrag = true;
-
mDragInProgress = true;
}
@@ -546,13 +505,6 @@
}
}
- private void setFocusOnFirstChild() {
- View firstChild = mContent.getChildAt(0, 0);
- if (firstChild != null) {
- firstChild.requestFocus();
- }
- }
-
public void animateClosed() {
if (!(getParent() instanceof DragLayer)) return;
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
@@ -588,132 +540,34 @@
!isFull());
}
- protected boolean findAndSetEmptyCells(ShortcutInfo item) {
- int[] emptyCell = new int[2];
- if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
- item.cellX = emptyCell[0];
- item.cellY = emptyCell[1];
- return true;
- } else {
- return false;
- }
- }
-
- protected View createAndAddShortcut(ShortcutInfo item) {
- final BubbleTextView textView =
- (BubbleTextView) mInflater.inflate(R.layout.folder_application, this, false);
- textView.applyFromShortcutInfo(item, mIconCache, false);
-
- textView.setOnClickListener(this);
- textView.setOnLongClickListener(this);
- textView.setOnFocusChangeListener(mFocusIndicatorHandler);
-
- // We need to check here to verify that the given item's location isn't already occupied
- // by another item.
- if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
- || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
- // This shouldn't happen, log it.
- Log.e(TAG, "Folder order not properly persisted during bind");
- if (!findAndSetEmptyCells(item)) {
- return null;
- }
- }
-
- CellLayout.LayoutParams lp =
- new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY);
- boolean insert = false;
- textView.setOnKeyListener(new FolderKeyEventListener());
- mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true);
- return textView;
- }
-
public void onDragEnter(DragObject d) {
- mPreviousTargetCell[0] = -1;
- mPreviousTargetCell[1] = -1;
+ mPrevTargetRank = -1;
mOnExitAlarm.cancelAlarm();
}
OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
public void onAlarm(Alarm alarm) {
- realTimeReorder(mEmptyCell, mTargetCell);
+ mContent.realTimeReorder(mEmptyCellRank, mTargetRank);
+ mEmptyCellRank = mTargetRank;
}
};
- boolean readingOrderGreaterThan(int[] v1, int[] v2) {
- if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
- return true;
- } else {
- return false;
- }
- }
-
- private void realTimeReorder(int[] empty, int[] target) {
- boolean wrap;
- int startX;
- int endX;
- int startY;
- int delay = 0;
- float delayAmount = 30;
- if (readingOrderGreaterThan(target, empty)) {
- wrap = empty[0] >= mContent.getCountX() - 1;
- startY = wrap ? empty[1] + 1 : empty[1];
- for (int y = startY; y <= target[1]; y++) {
- startX = y == empty[1] ? empty[0] + 1 : 0;
- endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
- for (int x = startX; x <= endX; x++) {
- View v = mContent.getChildAt(x,y);
- if (mContent.animateChildToPosition(v, empty[0], empty[1],
- REORDER_ANIMATION_DURATION, delay, true, true)) {
- empty[0] = x;
- empty[1] = y;
- delay += delayAmount;
- delayAmount *= 0.9;
- }
- }
- }
- } else {
- wrap = empty[0] == 0;
- startY = wrap ? empty[1] - 1 : empty[1];
- for (int y = startY; y >= target[1]; y--) {
- startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
- endX = y > target[1] ? 0 : target[0];
- for (int x = startX; x >= endX; x--) {
- View v = mContent.getChildAt(x,y);
- if (mContent.animateChildToPosition(v, empty[0], empty[1],
- REORDER_ANIMATION_DURATION, delay, true, true)) {
- empty[0] = x;
- empty[1] = y;
- delay += delayAmount;
- delayAmount *= 0.9;
- }
- }
- }
- }
- }
-
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public boolean isLayoutRtl() {
return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
public void onDragOver(DragObject d) {
- final int scrollOffset = mScrollView.getScrollY();
final float[] r = d.getVisualCenter(null);
r[0] -= getPaddingLeft();
r[1] -= getPaddingTop();
- mTargetCell = mContent.findNearestArea(
- (int) r[0], (int) r[1] + scrollOffset, 1, 1, mTargetCell);
- if (isLayoutRtl()) {
- mTargetCell[0] = mContent.getCountX() - mTargetCell[0] - 1;
- }
- if (mTargetCell[0] != mPreviousTargetCell[0]
- || mTargetCell[1] != mPreviousTargetCell[1]) {
+ mTargetRank = mContent.findNearestArea((int) r[0], (int) r[1]);
+ if (mTargetRank != mPrevTargetRank) {
mReorderAlarm.cancelAlarm();
mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
mReorderAlarm.setAlarm(REORDER_DELAY);
- mPreviousTargetCell[0] = mTargetCell[0];
- mPreviousTargetCell[1] = mTargetCell[1];
+ mPrevTargetRank = mTargetRank;
}
}
@@ -764,7 +618,7 @@
replaceFolderWithFinalItem();
}
} else {
- setupContentForNumItems(getItemCount());
+ rearrangeChildren();
// The drag failed, we need to return the item to the folder
mFolderIcon.onDrop(d);
}
@@ -865,37 +719,8 @@
return true;
}
- private void setupContentDimensions(int count) {
- ArrayList<View> list = getItemsInReadingOrder();
-
- int countX = mContent.getCountX();
- int countY = mContent.getCountY();
- boolean done = false;
-
- while (!done) {
- int oldCountX = countX;
- int oldCountY = countY;
- if (countX * countY < count) {
- // Current grid is too small, expand it
- if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
- countX++;
- } else if (countY < mMaxCountY) {
- countY++;
- }
- if (countY == 0) countY++;
- } else if ((countY - 1) * countX >= count && countY >= countX) {
- countY = Math.max(0, countY - 1);
- } else if ((countX - 1) * countY >= count) {
- countX = Math.max(0, countX - 1);
- }
- done = countX == oldCountX && countY == oldCountY;
- }
- mContent.setGridSize(countX, countY);
- arrangeChildren(list);
- }
-
public boolean isFull() {
- return getItemCount() >= mMaxNumItems;
+ return mContent.isFull();
}
private void centerAboutIcon() {
@@ -905,13 +730,13 @@
int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
int height = getFolderHeight();
- float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect);
+ float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect);
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
- int centerX = (int) (mTempRect.left + mTempRect.width() * scale / 2);
- int centerY = (int) (mTempRect.top + mTempRect.height() * scale / 2);
+ int centerX = (int) (sTempRect.left + sTempRect.width() * scale / 2);
+ int centerY = (int) (sTempRect.top + sTempRect.height() * scale / 2);
int centeredLeft = centerX - width / 2;
int centeredTop = centerY - height / 2;
int currentPage = mLauncher.getWorkspace().getNextPage();
@@ -964,18 +789,6 @@
return mFolderIconPivotY;
}
- private void setupContentForNumItems(int count) {
- setupContentDimensions(count);
-
- DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
- if (lp == null) {
- lp = new DragLayer.LayoutParams(0, 0);
- lp.customPosition = true;
- setLayoutParams(lp);
- }
- centerAboutIcon();
- }
-
private int getContentAreaHeight() {
LauncherAppState app = LauncherAppState.getInstance();
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
@@ -983,7 +796,7 @@
CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
int maxContentAreaHeight = grid.availableHeightPx -
workspacePadding.top - workspacePadding.bottom -
- mFolderNameHeight;
+ mFooterHeight;
int height = Math.min(maxContentAreaHeight,
mContent.getDesiredHeight());
return Math.max(height, MIN_CONTENT_DIMEN);
@@ -994,58 +807,50 @@
}
private int getFolderHeight() {
- int height = getPaddingTop() + getPaddingBottom()
- + getContentAreaHeight() + mFolderNameHeight;
- return height;
+ return getFolderHeight(getContentAreaHeight());
+ }
+
+ private int getFolderHeight(int contentAreaHeight) {
+ return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth();
- int height = getFolderHeight();
- int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(getContentAreaWidth(),
- MeasureSpec.EXACTLY);
- int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(getContentAreaHeight(),
- MeasureSpec.EXACTLY);
+ int contentWidth = getContentAreaWidth();
+ int contentHeight = getContentAreaHeight();
- mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight());
- mScrollView.measure(contentAreaWidthSpec, contentAreaHeightSpec);
- mFolderName.measure(contentAreaWidthSpec,
- MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY));
- setMeasuredDimension(width, height);
+ int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
+ int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
+
+ mContent.setFixedSize(contentWidth, contentHeight);
+ mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec);
+ mFooter.measure(contentAreaWidthSpec,
+ MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY));
+
+ int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth;
+ int folderHeight = getFolderHeight(contentHeight);
+ setMeasuredDimension(folderWidth, folderHeight);
}
- private void arrangeChildren(ArrayList<View> list) {
- int[] vacant = new int[2];
- if (list == null) {
- list = getItemsInReadingOrder();
- }
- mContent.removeAllViews();
+ /**
+ * Rearranges the children based on their rank.
+ */
+ public void rearrangeChildren() {
+ rearrangeChildren(-1);
+ }
- for (int i = 0; i < list.size(); i++) {
- View v = list.get(i);
- mContent.getVacantCell(vacant, 1, 1);
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
- lp.cellX = vacant[0];
- lp.cellY = vacant[1];
- ItemInfo info = (ItemInfo) v.getTag();
- if (info.cellX != vacant[0] || info.cellY != vacant[1]) {
- info.cellX = vacant[0];
- info.cellY = vacant[1];
- LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0,
- info.cellX, info.cellY);
- }
- boolean insert = false;
- mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true);
- }
+ /**
+ * Rearranges the children based on their rank.
+ * @param itemCount if greater than the total children count, empty spaces are left at the end,
+ * otherwise it is ignored.
+ */
+ public void rearrangeChildren(int itemCount) {
+ ArrayList<View> views = getItemsInReadingOrder();
+ mContent.arrangeChildren(views, Math.max(itemCount, views.size()));
mItemsInvalidated = true;
}
public int getItemCount() {
- return mContent.getShortcutsAndWidgets().getChildCount();
- }
-
- public View getItemAt(int index) {
- return mContent.getShortcutsAndWidgets().getChildAt(index);
+ return mContent.getItemCount();
}
private void onCloseComplete() {
@@ -1058,7 +863,7 @@
mFolderIcon.requestFocus();
if (mRearrangeOnClose) {
- setupContentForNumItems(getItemCount());
+ rearrangeChildren();
mRearrangeOnClose = false;
}
if (getItemCount() <= 1) {
@@ -1108,7 +913,7 @@
}
}
};
- View finalChild = getItemAt(0);
+ View finalChild = mContent.getLastItem();
if (finalChild != null) {
mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable);
} else {
@@ -1123,9 +928,8 @@
// This method keeps track of the last item in the folder for the purposes
// of keyboard focus
- private void updateTextViewFocus() {
- View lastChild = getItemAt(getItemCount() - 1);
- getItemAt(getItemCount() - 1);
+ public void updateTextViewFocus() {
+ View lastChild = mContent.getLastItem();
if (lastChild != null) {
mFolderName.setNextFocusDownId(lastChild.getId());
mFolderName.setNextFocusRightId(lastChild.getId());
@@ -1153,9 +957,7 @@
View currentDragView;
ShortcutInfo si = mCurrentDragInfo;
if (mIsExternalDrag) {
- si.cellX = mEmptyCell[0];
- si.cellY = mEmptyCell[1];
-
+ currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
// Actually move the item in the database if it was an external drag. Call this
// before creating the view, so that ShortcutInfo is updated appropriately.
LauncherModel.addOrMoveItemInDatabase(
@@ -1166,14 +968,9 @@
updateItemLocationsInDatabaseBatch();
}
mIsExternalDrag = false;
-
- currentDragView = createAndAddShortcut(si);
} else {
currentDragView = mCurrentDragView;
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams();
- si.cellX = lp.cellX = mEmptyCell[0];
- si.cellX = lp.cellY = mEmptyCell[1];
- mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true);
+ mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
}
if (d.dragView.hasDrawn()) {
@@ -1192,7 +989,7 @@
currentDragView.setVisibility(VISIBLE);
}
mItemsInvalidated = true;
- setupContentDimensions(getItemCount());
+ rearrangeChildren();
// Temporarily suppress the listener, as we did all the work already here.
mSuppressOnAdd = true;
@@ -1219,12 +1016,7 @@
// If the item was dropped onto this open folder, we have done the work associated
// with adding the item to the folder, as indicated by mSuppressOnAdd being set
if (mSuppressOnAdd) return;
- if (!findAndSetEmptyCells(item)) {
- // The current layout is full, can we expand it?
- setupContentForNumItems(getItemCount() + 1);
- findAndSetEmptyCells(item);
- }
- createAndAddShortcut(item);
+ mContent.createAndAddViewForRank(item, mContent.allocateNewLastItemRank());
LauncherModel.addOrMoveItemInDatabase(
mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
}
@@ -1235,27 +1027,25 @@
// the work associated with removing the item, so we don't have to do anything here.
if (item == mCurrentDragInfo) return;
View v = getViewForInfo(item);
- mContent.removeView(v);
+ mContent.removeItem(v);
if (mState == STATE_ANIMATING) {
mRearrangeOnClose = true;
} else {
- setupContentForNumItems(getItemCount());
+ rearrangeChildren();
}
if (getItemCount() <= 1) {
replaceFolderWithFinalItem();
}
}
- private View getViewForInfo(ShortcutInfo item) {
- for (int j = 0; j < mContent.getCountY(); j++) {
- for (int i = 0; i < mContent.getCountX(); i++) {
- View v = mContent.getChildAt(i, j);
- if (v.getTag() == item) {
- return v;
- }
+ private View getViewForInfo(final ShortcutInfo item) {
+ return mContent.iterateOverItems(new ItemOperator() {
+
+ @Override
+ public boolean evaluate(ItemInfo info, View view, View parent) {
+ return info == item;
}
- }
- return null;
+ });
}
public void onItemsChanged() {
@@ -1268,14 +1058,14 @@
public ArrayList<View> getItemsInReadingOrder() {
if (mItemsInvalidated) {
mItemsInReadingOrder.clear();
- for (int j = 0; j < mContent.getCountY(); j++) {
- for (int i = 0; i < mContent.getCountX(); i++) {
- View v = mContent.getChildAt(i, j);
- if (v != null) {
- mItemsInReadingOrder.add(v);
- }
+ mContent.iterateOverItems(new ItemOperator() {
+
+ @Override
+ public boolean evaluate(ItemInfo info, View view, View parent) {
+ mItemsInReadingOrder.add(view);
+ return false;
}
- }
+ });
mItemsInvalidated = false;
}
return mItemsInReadingOrder;
@@ -1295,4 +1085,69 @@
public void getHitRectRelativeToDragLayer(Rect outRect) {
getHitRect(outRect);
}
+
+ public static interface FolderContent {
+ void setFolder(Folder f);
+
+ void removeItem(View v);
+
+ boolean isFull();
+ int getItemCount();
+
+ int getDesiredWidth();
+ int getDesiredHeight();
+ void setFixedSize(int width, int height);
+
+ /**
+ * Iterates over all its items in a reading order.
+ * @return the view for which the operator returned true.
+ */
+ View iterateOverItems(ItemOperator op);
+ View getLastItem();
+
+ String getAccessibilityDescription();
+
+ /**
+ * Binds items to the layout.
+ * @return list of items that could not be bound, probably because we hit the max size limit.
+ */
+ ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> children);
+
+ /**
+ * Create space for a new item at the end, and returns the rank for that item.
+ * Resizes the content if necessary.
+ */
+ int allocateNewLastItemRank();
+
+ View createAndAddViewForRank(ShortcutInfo item, int rank);
+
+ /**
+ * Adds the {@param view} to the layout based on {@param rank} and updated the position
+ * related attributes. It assumes that {@param item} is already attached to the view.
+ */
+ void addViewForRank(View view, ShortcutInfo item, int rank);
+
+ /**
+ * Reorders the items such that the {@param empty} spot moves to {@param target}
+ */
+ void realTimeReorder(int empty, int target);
+
+ /**
+ * @return the rank of the cell nearest to the provided pixel position.
+ */
+ int findNearestArea(int pixelX, int pixelY);
+
+ /**
+ * Updates position and rank of all the children in the view based.
+ * @param list the ordered list of children.
+ * @param itemCount if greater than the total children count, empty spaces are left
+ * at the end.
+ */
+ void arrangeChildren(ArrayList<View> list, int itemCount);
+
+ /**
+ * Sets the focus on the first visible child.
+ */
+ void setFocusOnFirstChild();
+ }
}
diff --git a/src/com/android/launcher3/FolderCellLayout.java b/src/com/android/launcher3/FolderCellLayout.java
new file mode 100644
index 0000000..1566912
--- /dev/null
+++ b/src/com/android/launcher3/FolderCellLayout.java
@@ -0,0 +1,330 @@
+/**
+ * Copyright (C) 2015 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.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.Workspace.ItemOperator;
+
+import java.util.ArrayList;
+
+public class FolderCellLayout extends CellLayout implements Folder.FolderContent {
+
+ private static final int REORDER_ANIMATION_DURATION = 230;
+ private static final int START_VIEW_REORDER_DELAY = 30;
+ private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
+
+ private static final int[] sTempPosArray = new int[2];
+
+ private final FolderKeyEventListener mKeyListener = new FolderKeyEventListener();
+ private final LayoutInflater mInflater;
+ private final IconCache mIconCache;
+
+ private final int mMaxCountX;
+ private final int mMaxCountY;
+ private final int mMaxNumItems;
+
+ // Indicates the last number of items used to set up the grid size
+ private int mAllocatedContentSize;
+
+ private Folder mFolder;
+ private FocusIndicatorView mFocusIndicatorView;
+
+ public FolderCellLayout(Context context) {
+ this(context, null);
+ }
+
+ public FolderCellLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FolderCellLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mMaxCountX = (int) grid.numColumns;
+ mMaxCountY = (int) grid.numRows;
+ mMaxNumItems = mMaxCountX * mMaxCountY;
+
+ mInflater = LayoutInflater.from(context);
+ mIconCache = app.getIconCache();
+
+ setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
+ getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+ setInvertIfRtl(true);
+ }
+
+ @Override
+ public void setFolder(Folder folder) {
+ mFolder = folder;
+ mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
+ }
+
+ /**
+ * Sets up the grid size such that {@param count} items can fit in the grid.
+ * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
+ * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}.
+ */
+ private void setupContentDimensions(int count) {
+ mAllocatedContentSize = count;
+ int countX = getCountX();
+ int countY = getCountY();
+ boolean done = false;
+
+ while (!done) {
+ int oldCountX = countX;
+ int oldCountY = countY;
+ if (countX * countY < count) {
+ // Current grid is too small, expand it
+ if ((countX <= countY || countY == mMaxCountY) && countX < mMaxCountX) {
+ countX++;
+ } else if (countY < mMaxCountY) {
+ countY++;
+ }
+ if (countY == 0) countY++;
+ } else if ((countY - 1) * countX >= count && countY >= countX) {
+ countY = Math.max(0, countY - 1);
+ } else if ((countX - 1) * countY >= count) {
+ countX = Math.max(0, countX - 1);
+ }
+ done = countX == oldCountX && countY == oldCountY;
+ }
+ setGridSize(countX, countY);
+ }
+
+ @Override
+ public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
+ ArrayList<ShortcutInfo> extra = new ArrayList<ShortcutInfo>();
+ setupContentDimensions(Math.min(items.size(), mMaxNumItems));
+
+ int countX = getCountX();
+ int rank = 0;
+ for (ShortcutInfo item : items) {
+ if (rank >= mMaxNumItems) {
+ extra.add(item);
+ continue;
+ }
+
+ item.rank = rank;
+ item.cellX = rank % countX;
+ item.cellY = rank / countX;
+ addNewView(item);
+ rank++;
+ }
+ return extra;
+ }
+
+ @Override
+ public int allocateNewLastItemRank() {
+ int rank = getItemCount();
+ mFolder.rearrangeChildren(rank + 1);
+ return rank;
+ }
+
+ @Override
+ public View createAndAddViewForRank(ShortcutInfo item, int rank) {
+ updateItemXY(item, rank);
+ return addNewView(item);
+ }
+
+ @Override
+ public void addViewForRank(View view, ShortcutInfo item, int rank) {
+ updateItemXY(item, rank);
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+ lp.cellX = item.cellX;
+ lp.cellY = item.cellY;
+ addViewToCellLayout(view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+ }
+
+ @Override
+ public void removeItem(View v) {
+ removeView(v);
+ }
+
+ /**
+ * Updates the item cellX and cellY position
+ */
+ private void updateItemXY(ShortcutInfo item, int rank) {
+ item.rank = rank;
+ int countX = getCountX();
+ item.cellX = rank % countX;
+ item.cellY = rank / countX;
+ }
+
+ private View addNewView(ShortcutInfo item) {
+ final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
+ R.layout.folder_application, getShortcutsAndWidgets(), false);
+ textView.applyFromShortcutInfo(item, mIconCache, false);
+ textView.setOnClickListener(mFolder);
+ textView.setOnLongClickListener(mFolder);
+ textView.setOnFocusChangeListener(mFocusIndicatorView);
+ textView.setOnKeyListener(mKeyListener);
+
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(
+ item.cellX, item.cellY, item.spanX, item.spanY);
+ addViewToCellLayout(textView, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+ return textView;
+ }
+
+ /**
+ * Refer {@link #findNearestArea(int, int, int, int, View, boolean, int[])}
+ */
+ @Override
+ public int findNearestArea(int pixelX, int pixelY) {
+ findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray);
+ if (mFolder.isLayoutRtl()) {
+ sTempPosArray[0] = getCountX() - sTempPosArray[0] - 1;
+ }
+
+ // Convert this position to rank.
+ return Math.min(mAllocatedContentSize - 1,
+ sTempPosArray[1] * getCountX() + sTempPosArray[0]);
+ }
+
+ @Override
+ public boolean isFull() {
+ return getItemCount() >= mMaxNumItems;
+ }
+
+ @Override
+ public int getItemCount() {
+ return getShortcutsAndWidgets().getChildCount();
+ }
+
+ @Override
+ public void arrangeChildren(ArrayList<View> list, int itemCount) {
+ setupContentDimensions(itemCount);
+ removeAllViews();
+
+ int newX, newY;
+ int rank = 0;
+ int countX = getCountX();
+ for (View v : list) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+ newX = rank % countX;
+ newY = rank / countX;
+ ItemInfo info = (ItemInfo) v.getTag();
+ if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
+ info.cellX = newX;
+ info.cellY = newY;
+ info.rank = rank;
+ LauncherModel.addOrMoveItemInDatabase(getContext(), info,
+ mFolder.mInfo.id, 0, info.cellX, info.cellY);
+ }
+ lp.cellX = info.cellX;
+ lp.cellY = info.cellY;
+ rank ++;
+ addViewToCellLayout(v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+ }
+ }
+
+ @Override
+ public View iterateOverItems(ItemOperator op) {
+ for (int j = 0; j < getCountY(); j++) {
+ for (int i = 0; i < getCountX(); i++) {
+ View v = getChildAt(i, j);
+ if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v, this)) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getAccessibilityDescription() {
+ return String.format(getContext().getString(R.string.folder_opened),
+ getCountX(), getCountY());
+ }
+
+ @Override
+ public void setFocusOnFirstChild() {
+ View firstChild = getChildAt(0, 0);
+ if (firstChild != null) {
+ firstChild.requestFocus();
+ }
+ }
+
+ @Override
+ public View getLastItem() {
+ int lastRank = getShortcutsAndWidgets().getChildCount() - 1;
+ // count can be zero if the folder is not yet laid out.
+ int count = getCountX();
+ if (count > 0) {
+ return getShortcutsAndWidgets().getChildAt(lastRank % count, lastRank / count);
+ } else {
+ return getShortcutsAndWidgets().getChildAt(lastRank);
+ }
+ }
+
+ @Override
+ public void realTimeReorder(int empty, int target) {
+ boolean wrap;
+ int startX;
+ int endX;
+ int startY;
+ int delay = 0;
+ float delayAmount = START_VIEW_REORDER_DELAY;
+
+ int countX = getCountX();
+ int emptyX = empty % getCountX();
+ int emptyY = empty / countX;
+
+ int targetX = target % countX;
+ int targetY = target / countX;
+
+ if (target > empty) {
+ wrap = emptyX == countX - 1;
+ startY = wrap ? emptyY + 1 : emptyY;
+ for (int y = startY; y <= targetY; y++) {
+ startX = y == emptyY ? emptyX + 1 : 0;
+ endX = y < targetY ? countX - 1 : targetX;
+ for (int x = startX; x <= endX; x++) {
+ View v = getChildAt(x,y);
+ if (animateChildToPosition(v, emptyX, emptyY,
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
+ emptyX = x;
+ emptyY = y;
+ delay += delayAmount;
+ delayAmount *= VIEW_REORDER_DELAY_FACTOR;
+ }
+ }
+ }
+ } else {
+ wrap = emptyX == 0;
+ startY = wrap ? emptyY - 1 : emptyY;
+ for (int y = startY; y >= targetY; y--) {
+ startX = y == emptyY ? emptyX - 1 : countX - 1;
+ endX = y > targetY ? 0 : targetX;
+ for (int x = startX; x >= endX; x--) {
+ View v = getChildAt(x,y);
+ if (animateChildToPosition(v, emptyX, emptyY,
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
+ emptyX = x;
+ emptyY = y;
+ delay += delayAmount;
+ delayAmount *= VIEW_REORDER_DELAY_FACTOR;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
new file mode 100644
index 0000000..60c94e0
--- /dev/null
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -0,0 +1,581 @@
+/**
+ * Copyright (C) 2015 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.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
+import com.android.launcher3.PageIndicator.PageMarkerResources;
+import com.android.launcher3.Workspace.ItemOperator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+public class FolderPagedView extends PagedView implements Folder.FolderContent {
+
+ private static final String TAG = "FolderPagedView";
+
+ private static final int REORDER_ANIMATION_DURATION = 230;
+ private static final int[] sTempPosArray = new int[2];
+
+ // TODO: Remove this restriction
+ private static final int MAX_ITEMS_PER_PAGE = 3;
+
+ private final LayoutInflater mInflater;
+ private final IconCache mIconCache;
+ private final HashMap<View, Runnable> mPageChangingViews = new HashMap<>();
+
+ private final CellLayout mFirstPage;
+
+ final int mMaxCountX;
+ final int mMaxCountY;
+ final int mMaxItemsPerPage;
+
+ private int mAllocatedContentSize;
+ private int mGridCountX;
+ private int mGridCountY;
+
+ private Folder mFolder;
+ private FocusIndicatorView mFocusIndicatorView;
+ private PagedFolderKeyEventListener mKeyListener;
+
+ public FolderPagedView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ LauncherAppState app = LauncherAppState.getInstance();
+
+ mFirstPage = newCellLayout();
+ addFullScreenPage(mFirstPage);
+ setCurrentPage(0);
+ setDataIsReady();
+
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+ mMaxCountX = Math.min((int) grid.numColumns, MAX_ITEMS_PER_PAGE);
+ mMaxCountY = Math.min((int) grid.numRows, MAX_ITEMS_PER_PAGE);
+ mMaxItemsPerPage = mMaxCountX * mMaxCountY;
+
+ mInflater = LayoutInflater.from(context);
+ mIconCache = app.getIconCache();
+ }
+
+ @Override
+ public void setFolder(Folder folder) {
+ mFolder = folder;
+ mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
+ mKeyListener = new PagedFolderKeyEventListener(folder);
+ }
+
+ /**
+ * Sets up the grid size such that {@param count} items can fit in the grid.
+ * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
+ * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}.
+ */
+ private void setupContentDimensions(int count) {
+ mAllocatedContentSize = count;
+ boolean done;
+ if (count >= mMaxItemsPerPage) {
+ mGridCountX = mMaxCountX;
+ mGridCountY = mMaxCountY;
+ done = true;
+ } else {
+ mGridCountX = mFirstPage.getCountX();
+ mGridCountY = mFirstPage.getCountY();
+ done = false;
+ }
+
+ while (!done) {
+ int oldCountX = mGridCountX;
+ int oldCountY = mGridCountY;
+ if (mGridCountX * mGridCountY < count) {
+ // Current grid is too small, expand it
+ if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) {
+ mGridCountX++;
+ } else if (mGridCountY < mMaxCountY) {
+ mGridCountY++;
+ }
+ if (mGridCountY == 0) mGridCountY++;
+ } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) {
+ mGridCountY = Math.max(0, mGridCountY - 1);
+ } else if ((mGridCountX - 1) * mGridCountY >= count) {
+ mGridCountX = Math.max(0, mGridCountX - 1);
+ }
+ done = mGridCountX == oldCountX && mGridCountY == oldCountY;
+ }
+
+ setGridSize(mGridCountX, mGridCountY);
+ }
+
+ public void setGridSize(int countX, int countY) {
+ mGridCountX = countX;
+ mGridCountY = countY;
+ mFirstPage.setGridSize(mGridCountX, mGridCountY);
+ for (int i = getPageCount() - 1; i > 0; i--) {
+ getPageAt(i).setGridSize(mGridCountX, mGridCountY);
+ }
+ }
+
+ @Override
+ public ArrayList<ShortcutInfo> bindItems(ArrayList<ShortcutInfo> items) {
+ final int count = items.size();
+
+ if (getPageCount() > 1) {
+ Log.d(TAG, "Binding items to an non-empty view");
+ removeAllViews();
+ addView(mFirstPage);
+ mFirstPage.removeAllViews();
+ }
+
+ setupContentDimensions(count);
+ CellLayout page = mFirstPage;
+ int pagePosition = 0;
+ int rank = 0;
+
+ for (ShortcutInfo item : items) {
+ if (pagePosition >= mMaxItemsPerPage) {
+ // This page is full, add a new page.
+ pagePosition = 0;
+ page = newCellLayout();
+ addFullScreenPage(page);
+ }
+
+ item.cellX = pagePosition % mGridCountX;
+ item.cellY = pagePosition / mGridCountX;
+ item.rank = rank;
+ addNewView(item, page);
+
+ rank++;
+ pagePosition++;
+ }
+ return new ArrayList<ShortcutInfo>();
+ }
+
+ /**
+ * Create space for a new item at the end, and returns the rank for that item.
+ * Also sets the current page to the last page.
+ */
+ @Override
+ public int allocateNewLastItemRank() {
+ int rank = getItemCount();
+ int total = rank + 1;
+ if (rank < mMaxItemsPerPage) {
+ // Rearrange the items as the grid size might change.
+ mFolder.rearrangeChildren(total);
+ } else {
+ setupContentDimensions(total);
+ }
+
+ // Add a new page if last page is full
+ if (getPageAt(getChildCount() - 1).getShortcutsAndWidgets().getChildCount()
+ >= mMaxItemsPerPage) {
+ addFullScreenPage(newCellLayout());
+ }
+ setCurrentPage(getChildCount() - 1);
+ return rank;
+ }
+
+ @Override
+ public View createAndAddViewForRank(ShortcutInfo item, int rank) {
+ int pageNo = updateItemXY(item, rank);
+ CellLayout page = getPageAt(pageNo);
+ return addNewView(item, page);
+ }
+
+ @Override
+ public void addViewForRank(View view, ShortcutInfo item, int rank) {
+ int pageNo = updateItemXY(item, rank);
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
+ lp.cellX = item.cellX;
+ lp.cellY = item.cellY;
+ getPageAt(pageNo).addViewToCellLayout(
+ view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+ }
+
+ /**
+ * Updates the item cellX and cellY position and return the page number for that item.
+ */
+ private int updateItemXY(ShortcutInfo item, int rank) {
+ item.rank = rank;
+
+ int pagePos = item.rank % mMaxItemsPerPage;
+ item.cellX = pagePos % mGridCountX;
+ item.cellY = pagePos / mGridCountX;
+
+ return item.rank / mMaxItemsPerPage;
+ }
+
+ private View addNewView(ShortcutInfo item, CellLayout target) {
+ final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
+ R.layout.folder_application, target.getShortcutsAndWidgets(), false);
+ textView.applyFromShortcutInfo(item, mIconCache, false);
+ textView.setOnClickListener(mFolder);
+ textView.setOnLongClickListener(mFolder);
+ textView.setOnFocusChangeListener(mFocusIndicatorView);
+ textView.setOnKeyListener(mKeyListener);
+
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(
+ item.cellX, item.cellY, item.spanX, item.spanY);
+ target.addViewToCellLayout(
+ textView, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true);
+ return textView;
+ }
+
+ @Override
+ public CellLayout getPageAt(int index) {
+ return (CellLayout) getChildAt(index);
+ }
+
+ public void removeCellLayoutView(View view) {
+ for (int i = getChildCount() - 1; i >= 0; i --) {
+ getPageAt(i).removeView(view);
+ }
+ }
+
+ public CellLayout getCurrentCellLayout() {
+ return getPageAt(getNextPage());
+ }
+
+ @Override
+ public void addFullScreenPage(View page) {
+ LayoutParams lp = generateDefaultLayoutParams();
+ lp.isFullScreenPage = true;
+ super.addView(page, -1, lp);
+ }
+
+ private CellLayout newCellLayout() {
+ DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
+
+ CellLayout layout = new CellLayout(getContext());
+ layout.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
+ layout.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
+ layout.setInvertIfRtl(true);
+
+ if (mFirstPage != null) {
+ layout.setGridSize(mFirstPage.getCountX(), mFirstPage.getCountY());
+ }
+
+ return layout;
+ }
+
+ @Override
+ public void setFixedSize(int width, int height) {
+ for (int i = getChildCount() - 1; i >= 0; i --) {
+ ((CellLayout) getChildAt(i)).setFixedSize(width, height);
+ }
+ }
+
+ @Override
+ public void removeItem(View v) {
+ for (int i = getChildCount() - 1; i >= 0; i --) {
+ getPageAt(i).removeView(v);
+ }
+ }
+
+ /**
+ * Updates position and rank of all the children in the view.
+ * It essentially removes all views from all the pages and then adds them again in appropriate
+ * page.
+ *
+ * @param list the ordered list of children.
+ * @param itemCount if greater than the total children count, empty spaces are left
+ * at the end, otherwise it is ignored.
+ *
+ */
+ @Override
+ public void arrangeChildren(ArrayList<View> list, int itemCount) {
+ ArrayList<CellLayout> pages = new ArrayList<CellLayout>();
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout page = (CellLayout) getChildAt(i);
+ page.removeAllViews();
+ pages.add(page);
+ }
+ setupContentDimensions(itemCount);
+
+ Iterator<CellLayout> pageItr = pages.iterator();
+ CellLayout currentPage = null;
+
+ int position = 0;
+ int newX, newY, rank;
+
+ rank = 0;
+ for (View v : list) {
+ if (currentPage == null || position >= mMaxItemsPerPage) {
+ // Next page
+ if (pageItr.hasNext()) {
+ currentPage = pageItr.next();
+ } else {
+ currentPage = newCellLayout();
+ addFullScreenPage(currentPage);
+ }
+ position = 0;
+ }
+
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
+ newX = position % mGridCountX;
+ newY = position / mGridCountX;
+ ItemInfo info = (ItemInfo) v.getTag();
+ if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
+ info.cellX = newX;
+ info.cellY = newY;
+ info.rank = rank;
+ LauncherModel.addOrMoveItemInDatabase(getContext(), info,
+ mFolder.mInfo.id, 0, info.cellX, info.cellY);
+ }
+ lp.cellX = info.cellX;
+ lp.cellY = info.cellY;
+ rank ++;
+ position++;
+ currentPage.addViewToCellLayout(
+ v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+ }
+
+ boolean removed = false;
+ while (pageItr.hasNext()) {
+ CellLayout layout = pageItr.next();
+ if (layout != mFirstPage) {
+ removeView(layout);
+ removed = true;
+ }
+ }
+ if (removed) {
+ setCurrentPage(0);
+ }
+ }
+
+ @Override
+ protected void loadAssociatedPages(int page, boolean immediateAndOnly) { }
+
+ @Override
+ public void syncPages() { }
+
+ @Override
+ public void syncPageItems(int page, boolean immediate) { }
+
+ public int getDesiredWidth() {
+ return mFirstPage.getDesiredWidth();
+ }
+
+ public int getDesiredHeight() {
+ return mFirstPage.getDesiredHeight();
+ }
+
+ @Override
+ public int getItemCount() {
+ int lastPage = getChildCount() - 1;
+ return getPageAt(lastPage).getShortcutsAndWidgets().getChildCount()
+ + lastPage * mMaxItemsPerPage;
+ }
+
+ @Override
+ public int findNearestArea(int pixelX, int pixelY) {
+ int pageIndex = getNextPage();
+ CellLayout page = getPageAt(pageIndex);
+ page.findNearestArea(pixelX, pixelY, 1, 1, null, false, sTempPosArray);
+ if (mFolder.isLayoutRtl()) {
+ sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1;
+ }
+ return Math.min(mAllocatedContentSize - 1,
+ pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]);
+ }
+
+ @Override
+ protected PageMarkerResources getPageIndicatorMarker(int pageIndex) {
+ return new PageMarkerResources(R.drawable.ic_pageindicator_current_dark, R.drawable.ic_pageindicator_default_dark);
+ }
+
+ @Override
+ public boolean isFull() {
+ return false;
+ }
+
+ @Override
+ public View getLastItem() {
+ if (getChildCount() < 1) {
+ return null;
+ }
+ ShortcutAndWidgetContainer lastContainer = getCurrentCellLayout().getShortcutsAndWidgets();
+ int lastRank = lastContainer.getChildCount() - 1;
+ if (mGridCountX > 0) {
+ return lastContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
+ } else {
+ return lastContainer.getChildAt(lastRank);
+ }
+ }
+
+ @Override
+ public View iterateOverItems(ItemOperator op) {
+ for (int k = 0 ; k < getChildCount(); k++) {
+ CellLayout page = getPageAt(k);
+ for (int j = 0; j < page.getCountY(); j++) {
+ for (int i = 0; i < page.getCountX(); i++) {
+ View v = page.getChildAt(i, j);
+ if ((v != null) && op.evaluate((ItemInfo) v.getTag(), v, this)) {
+ return v;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getAccessibilityDescription() {
+ return String.format(getContext().getString(R.string.folder_opened),
+ mGridCountX, mGridCountY);
+ }
+
+ @Override
+ public void setFocusOnFirstChild() {
+ View firstChild = getCurrentCellLayout().getChildAt(0, 0);
+ if (firstChild != null) {
+ firstChild.requestFocus();
+ }
+ }
+
+ @Override
+ protected void notifyPageSwitchListener() {
+ super.notifyPageSwitchListener();
+ if (mFolder != null) {
+ mFolder.updateTextViewFocus();
+ }
+ }
+
+ @Override
+ public void realTimeReorder(int empty, int target) {
+ int delay = 0;
+ float delayAmount = 30;
+
+ // Animation only happens on the current page.
+ int pageToAnimate = getNextPage();
+
+ int pageT = target / mMaxItemsPerPage;
+ int pagePosT = target % mMaxItemsPerPage;
+
+ if (pageT != pageToAnimate) {
+ Log.e(TAG, "Cannot animate when the target cell is invisible");
+ }
+ int pagePosE = empty % mMaxItemsPerPage;
+ int pageE = empty / mMaxItemsPerPage;
+
+ int startPos, endPos;
+ int moveStart, moveEnd;
+ int direction;
+
+ if (target == empty) {
+ // No animation
+ return;
+ } else if (target > empty) {
+ // Items will move backwards to make room for the empty cell.
+ direction = 1;
+
+ // If empty cell is in a different page, move them instantly.
+ if (pageE < pageToAnimate) {
+ moveStart = empty;
+ // Instantly move the first item in the current page.
+ moveEnd = pageToAnimate * mMaxItemsPerPage;
+ // Animate the 2nd item in the current page, as the first item was already moved to
+ // the last page.
+ startPos = 0;
+ } else {
+ moveStart = moveEnd = -1;
+ startPos = pagePosE;
+ }
+
+ endPos = pagePosT;
+ } else {
+ // The items will move forward.
+ direction = -1;
+
+ if (pageE > pageToAnimate) {
+ // Move the items immediately.
+ moveStart = empty;
+ // Instantly move the last item in the current page.
+ moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
+
+ // Animations start with the second last item in the page
+ startPos = mMaxItemsPerPage - 1;
+ } else {
+ moveStart = moveEnd = -1;
+ startPos = pagePosE;
+ }
+
+ endPos = pagePosT;
+ }
+
+ // Instant moving views.
+ while (moveStart != moveEnd) {
+ int rankToMove = moveStart + direction;
+ int p = rankToMove / mMaxItemsPerPage;
+ int pagePos = rankToMove % mMaxItemsPerPage;
+ int x = pagePos % mGridCountX;
+ int y = pagePos / mGridCountX;
+
+ final CellLayout page = getPageAt(p);
+ final View v = page.getChildAt(x, y);
+ if (v != null) {
+ if (pageToAnimate != p) {
+ page.removeView(v);
+ addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart);
+ } else {
+ // Do a fake animation before removing it.
+ final int newRank = moveStart;
+ final float oldTranslateX = v.getTranslationX();
+
+ Runnable endAction = new Runnable() {
+
+ @Override
+ public void run() {
+ mPageChangingViews.remove(v);
+ v.setTranslationX(oldTranslateX);
+ ((CellLayout) v.getParent().getParent()).removeView(v);
+ addViewForRank(v, (ShortcutInfo) v.getTag(), newRank);
+ }
+ };
+ v.animate()
+ .translationXBy(direction > 0 ? -v.getWidth() : v.getWidth())
+ .setDuration(REORDER_ANIMATION_DURATION)
+ .setStartDelay(0)
+ .withEndAction(endAction);
+ mPageChangingViews.put(v, endAction);
+ }
+ }
+ moveStart = rankToMove;
+ }
+
+ if ((endPos - startPos) * direction <= 0) {
+ // No animation
+ return;
+ }
+
+ CellLayout page = getPageAt(pageToAnimate);
+ for (int i = startPos; i != endPos; i += direction) {
+ int nextPos = i + direction;
+ View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
+ if (v != null) {
+ ((ItemInfo) v.getTag()).rank -= direction;
+ }
+ if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
+ REORDER_ANIMATION_DURATION, delay, true, true)) {
+ delay += delayAmount;
+ delayAmount *= 0.9;
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e3a2d7c..984d536 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2448,6 +2448,10 @@
return;
}
+ if (LauncherAppState.getInstance().getAccessibilityDelegate().onBackPressed()) {
+ return;
+ }
+
if (isAllAppsVisible()) {
if (mAppsCustomizeContent.getContentType() ==
AppsCustomizePagedView.ContentType.Applications) {
@@ -3166,7 +3170,7 @@
View itemUnderLongClick = null;
if (v.getTag() instanceof ItemInfo) {
ItemInfo info = (ItemInfo) v.getTag();
- longClickCellInfo = new CellLayout.CellInfo(v, info);;
+ longClickCellInfo = new CellLayout.CellInfo(v, info);
itemUnderLongClick = longClickCellInfo.cell;
resetAddInfo();
}
@@ -3207,7 +3211,7 @@
/**
* Returns the CellLayout of the specified container at the specified screen.
*/
- CellLayout getCellLayout(long container, long screenId) {
+ public CellLayout getCellLayout(long container, long screenId) {
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
if (mHotseat != null) {
return mHotseat.getLayout();
diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
index c9e277e..0ae1c0e 100644
--- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
@@ -1,11 +1,16 @@
package com.android.launcher3;
import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.util.SparseArray;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -20,6 +25,21 @@
public static final int INFO = R.id.action_info;
public static final int UNINSTALL = R.id.action_uninstall;
public static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
+ public static final int MOVE = R.id.action_move;
+
+ enum DragType {
+ ICON,
+ FOLDER,
+ WIDGET
+ }
+
+ public static class DragInfo {
+ DragType dragType;
+ ItemInfo info;
+ View item;
+ }
+
+ private DragInfo mDragInfo = null;
private final SparseArray<AccessibilityAction> mActions =
new SparseArray<AccessibilityAction>();
@@ -36,6 +56,9 @@
launcher.getText(R.string.delete_target_uninstall_label)));
mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
launcher.getText(R.string.action_add_to_workspace)));
+ mActions.put(MOVE, new AccessibilityAction(MOVE,
+ launcher.getText(R.string.action_move)));
+
}
@Override
@@ -49,6 +72,7 @@
|| (item instanceof FolderInfo)) {
// Workspace shortcut / widget
info.addAction(mActions.get(REMOVE));
+ info.addAction(mActions.get(MOVE));
} else if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
// App or Widget from customization tray
if (item instanceof AppInfo) {
@@ -69,14 +93,21 @@
}
public boolean performAction(View host, ItemInfo item, int action) {
+ Resources res = mLauncher.getResources();
if (action == REMOVE) {
- return DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host);
+ if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) {
+ announceConfirmation(R.string.item_removed_from_workspace);
+ return true;
+ }
+ return false;
} else if (action == INFO) {
InfoDropTarget.startDetailsActivityForInfo(item, mLauncher);
return true;
} else if (action == UNINSTALL) {
DeleteDropTarget.uninstallApp(mLauncher, (AppInfo) item);
return true;
+ } else if (action == MOVE) {
+ beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
final int preferredPage = mLauncher.getWorkspace().getCurrentPage();
final ScreenPosProvider screenProvider = new ScreenPosProvider() {
@@ -90,20 +121,92 @@
final ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>();
addShortcuts.add(((AppInfo) item).makeShortcut());
mLauncher.showWorkspace(true, new Runnable() {
-
@Override
public void run() {
mLauncher.getModel().addAndBindAddedWorkspaceApps(
mLauncher, addShortcuts, screenProvider, 0, true);
+ announceConfirmation(R.string.item_added_to_workspace);
}
});
return true;
} else if (item instanceof PendingAddItemInfo) {
mLauncher.getModel().addAndBindPendingItem(
mLauncher, (PendingAddItemInfo) item, screenProvider, 0);
+ announceConfirmation(R.string.item_added_to_workspace);
return true;
}
}
return false;
}
+
+ private void announceConfirmation(int resId) {
+ announceConfirmation(mLauncher.getResources().getString(resId));
+ }
+
+ private void announceConfirmation(String confirmation) {
+ mLauncher.getDragLayer().announceForAccessibility(confirmation);
+
+ }
+
+ public boolean isInAccessibleDrag() {
+ return mDragInfo != null;
+ }
+
+ public DragInfo getDragInfo() {
+ return mDragInfo;
+ }
+
+ public void handleAccessibleDrop(CellLayout targetContainer, Rect dropLocation,
+ String confirmation) {
+ if (!isInAccessibleDrag()) return;
+
+ int[] loc = new int[2];
+ loc[0] = dropLocation.centerX();
+ loc[1] = dropLocation.centerY();
+
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(targetContainer, loc);
+ mLauncher.getDragController().completeAccessibleDrag(loc);
+
+ endAccessibleDrag();
+ announceConfirmation(confirmation);
+ }
+
+ public void beginAccessibleDrag(View item, ItemInfo info) {
+ mDragInfo = new DragInfo();
+ mDragInfo.info = info;
+ mDragInfo.item = item;
+ mDragInfo.dragType = DragType.ICON;
+ if (info instanceof FolderInfo) {
+ mDragInfo.dragType = DragType.FOLDER;
+ } else if (info instanceof LauncherAppWidgetInfo) {
+ mDragInfo.dragType = DragType.WIDGET;
+ }
+
+ CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info);
+
+ Rect pos = new Rect();
+ mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
+
+ mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
+ mLauncher.getWorkspace().enableAccessibleDrag(true);
+ mLauncher.getWorkspace().startDrag(cellInfo, true);
+ }
+
+ public boolean onBackPressed() {
+ if (isInAccessibleDrag()) {
+ cancelAccessibleDrag();
+ return true;
+ }
+ return false;
+ }
+
+ private void cancelAccessibleDrag() {
+ mLauncher.getDragController().cancelDrag();
+ endAccessibleDrag();
+ }
+
+ private void endAccessibleDrag() {
+ mDragInfo = null;
+ mLauncher.getWorkspace().enableAccessibleDrag(false);
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 8e6557f..d8896cc 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -62,7 +62,7 @@
private static LauncherAppState INSTANCE;
private DynamicGrid mDynamicGrid;
- private AccessibilityDelegate mAccessibilityDelegate;
+ private LauncherAccessibilityDelegate mAccessibilityDelegate;
public static LauncherAppState getInstance() {
if (INSTANCE == null) {
@@ -168,7 +168,7 @@
return mModel;
}
- AccessibilityDelegate getAccessibilityDelegate() {
+ LauncherAccessibilityDelegate getAccessibilityDelegate() {
return mAccessibilityDelegate;
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index b618444..b7a271e 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -297,6 +297,10 @@
if (loader == null) {
loader = getDefaultLayoutParser();
}
+
+ // There might be some partially restored DB items, due to buggy restore logic in
+ // previous versions of launcher.
+ createEmptyDB();
// Populate favorites table with initial favorites
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
@@ -733,23 +737,7 @@
}
private long initializeMaxItemId(SQLiteDatabase db) {
- Cursor c = db.rawQuery("SELECT MAX(_id) FROM favorites", null);
-
- // get the result
- final int maxIdIndex = 0;
- long id = -1;
- if (c != null && c.moveToNext()) {
- id = c.getLong(maxIdIndex);
- }
- if (c != null) {
- c.close();
- }
-
- if (id == -1) {
- throw new RuntimeException("Error: could not query max item id");
- }
-
- return id;
+ return getMaxId(db, TABLE_FAVORITES);
}
// Generates a new ID to use for an workspace screen in your database. This method
@@ -768,25 +756,7 @@
}
private long initializeMaxScreenId(SQLiteDatabase db) {
- Cursor c = db.rawQuery("SELECT MAX(" + LauncherSettings.WorkspaceScreens._ID + ") FROM " + TABLE_WORKSPACE_SCREENS, null);
-
- // get the result
- final int maxIdIndex = 0;
- long id = -1;
- if (c != null && c.moveToNext()) {
- id = c.getLong(maxIdIndex);
- }
- if (c != null) {
- c.close();
- }
-
- if (id == -1) {
- throw new RuntimeException("Error: could not query max screen id");
- }
-
- // Log to disk
- Launcher.addDumpLog(TAG, "11683562 - initializeMaxScreenId(): " + id, true);
- return id;
+ return getMaxId(db, TABLE_WORKSPACE_SCREENS);
}
private boolean initializeExternalAdd(ContentValues values) {
@@ -1199,6 +1169,27 @@
}
}
+ /**
+ * @return the max _id in the provided table.
+ */
+ private static long getMaxId(SQLiteDatabase db, String table) {
+ Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null);
+ // get the result
+ long id = -1;
+ if (c != null && c.moveToNext()) {
+ id = c.getLong(0);
+ }
+ if (c != null) {
+ c.close();
+ }
+
+ if (id == -1) {
+ throw new RuntimeException("Error: could not query max id in " + table);
+ }
+
+ return id;
+ }
+
static class SqlArguments {
public final String table;
public final String where;
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 99c2e08..cc17820 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -197,6 +197,10 @@
*/
@Override
public void onDragStart(DragSource source, Object info, int dragAction) {
+ showDeleteTarget();
+ }
+
+ public void showDeleteTarget() {
// Animate out the QSB search bar, and animate in the drop target bar
prepareStartAnimation(mDropTargetBar);
mDropTargetBarAnim.start();
@@ -206,6 +210,16 @@
}
}
+ public void hideDeleteTarget() {
+ // Restore the QSB search bar, and animate out the drop target bar
+ prepareStartAnimation(mDropTargetBar);
+ mDropTargetBarAnim.reverse();
+ if (!mIsSearchBarHidden) {
+ prepareStartAnimation(mQSBSearchBar);
+ mQSBSearchBarAnim.reverse();
+ }
+ }
+
public void deferOnDragEnd() {
mDeferOnDragEnd = true;
}
@@ -213,13 +227,7 @@
@Override
public void onDragEnd() {
if (!mDeferOnDragEnd) {
- // Restore the QSB search bar, and animate out the drop target bar
- prepareStartAnimation(mDropTargetBar);
- mDropTargetBarAnim.reverse();
- if (!mIsSearchBarHidden) {
- prepareStartAnimation(mQSBSearchBar);
- mQSBSearchBarAnim.reverse();
- }
+ hideDeleteTarget();
} else {
mDeferOnDragEnd = false;
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index da893b6..3b293f9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -26,6 +26,7 @@
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
import android.app.WallpaperManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
@@ -44,6 +45,7 @@
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcelable;
@@ -572,6 +574,10 @@
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);
+
+ if (LauncherAppState.getInstance().getAccessibilityDelegate().isInAccessibleDrag()) {
+ newScreen.enableAccessibleDrag(true);
+ }
return screenId;
}
@@ -1621,7 +1627,6 @@
float scrollProgress = getScrollProgress(screenCenter, child, i);
float alpha = 1 - Math.abs(scrollProgress);
child.getShortcutsAndWidgets().setAlpha(alpha);
- //child.setBackgroundAlphaMultiplier(1 - alpha);
}
}
}
@@ -1634,6 +1639,23 @@
}
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public void enableAccessibleDrag(boolean enable) {
+ for (int i = 0; i < getChildCount(); i++) {
+ CellLayout child = (CellLayout) getChildAt(i);
+ child.enableAccessibleDrag(enable);
+ }
+
+ if (enable) {
+ // We need to allow our individual children to become click handlers in this case
+ setOnClickListener(null);
+ } else {
+ // Reset our click listener
+ setOnClickListener(mLauncher);
+ }
+ mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable);
+ }
+
public boolean hasCustomContent() {
return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
}
@@ -2184,7 +2206,7 @@
private void updateAccessibilityFlags() {
int accessible = mState == State.NORMAL ?
- IMPORTANT_FOR_ACCESSIBILITY_YES :
+ IMPORTANT_FOR_ACCESSIBILITY_NO :
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
setImportantForAccessibility(accessible);
}
@@ -2674,7 +2696,11 @@
return b;
}
- void startDrag(CellLayout.CellInfo cellInfo) {
+ public void startDrag(CellLayout.CellInfo cellInfo) {
+ startDrag(cellInfo, false);
+ }
+
+ public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) {
View child = cellInfo.cell;
// Make sure the drag was started by a long press as opposed to a long click.
@@ -2687,10 +2713,14 @@
CellLayout layout = (CellLayout) child.getParent().getParent();
layout.prepareChildForDrag(child);
- beginDragShared(child, this);
+ beginDragShared(child, this, accessible);
}
public void beginDragShared(View child, DragSource source) {
+ beginDragShared(child, source, false);
+ }
+
+ public void beginDragShared(View child, DragSource source, boolean accessible) {
child.clearFocus();
child.setPressed(false);
@@ -2744,7 +2774,7 @@
}
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
- DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
if (child.getParent() instanceof ShortcutAndWidgetContainer) {
@@ -2794,7 +2824,7 @@
// Start the drag
DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
- DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
+ DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, false);
dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
// Recycle temporary bitmaps
@@ -3149,7 +3179,8 @@
final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
- if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
+ if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
+ && !d.accessibleDrag) {
final Runnable addResizeFrame = new Runnable() {
public void run() {
DragLayer dragLayer = mLauncher.getDragLayer();
@@ -3638,7 +3669,7 @@
mTargetCell[1]);
manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
- targetCellDistance, dragOverView);
+ targetCellDistance, dragOverView, d.accessibleDrag);
boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
@@ -3676,15 +3707,21 @@
}
private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
- int[] targetCell, float distance, View dragOverView) {
+ int[] targetCell, float distance, View dragOverView, boolean accessibleDrag) {
boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
false);
-
if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
!mFolderCreationAlarm.alarmPending()) {
- mFolderCreationAlarm.setOnAlarmListener(new
- FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
- mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+
+ FolderCreationAlarmListener listener = new
+ FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
+
+ if (!accessibleDrag) {
+ mFolderCreationAlarm.setOnAlarmListener(listener);
+ mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
+ } else {
+ listener.onAlarm(mFolderCreationAlarm);
+ }
return;
}
@@ -4173,19 +4210,14 @@
removeWorkspaceItem(mDragInfo.cell);
}
} else if (mDragInfo != null) {
- CellLayout cellLayout;
- if (mLauncher.isHotseatLayout(target)) {
- cellLayout = mLauncher.getHotseat().getLayout();
- } else {
- cellLayout = getScreenWithId(mDragInfo.screenId);
- }
- if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
- throw new RuntimeException("Invalid state: cellLayout == null in "
- + "Workspace#onDropCompleted. Please file a bug. ");
- }
+ final CellLayout cellLayout = mLauncher.getCellLayout(
+ mDragInfo.container, mDragInfo.screenId);
if (cellLayout != null) {
cellLayout.onDropChild(mDragInfo.cell);
- }
+ } else if (LauncherAppState.isDogfoodBuild()) {
+ throw new RuntimeException("Invalid state: cellLayout == null in "
+ + "Workspace#onDropCompleted. Please file a bug. ");
+ };
}
if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
&& mDragInfo.cell != null) {
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index c0730d9..0c6bfbf 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -48,16 +48,19 @@
// Item and page index related constant used by {@link #handleKeyEvent}.
public static final int NOOP = -1;
- public static final int PREVIOUS_PAGE_FIRST_ITEM = -2;
- public static final int PREVIOUS_PAGE_LAST_ITEM = -3;
+ public static final int PREVIOUS_PAGE_RIGHT_COLUMN = -2;
+ public static final int PREVIOUS_PAGE_FIRST_ITEM = -3;
+ public static final int PREVIOUS_PAGE_LAST_ITEM = -4;
- public static final int CURRENT_PAGE_FIRST_ITEM = -4;
- public static final int CURRENT_PAGE_LAST_ITEM = -5;
+ public static final int CURRENT_PAGE_FIRST_ITEM = -5;
+ public static final int CURRENT_PAGE_LAST_ITEM = -6;
- public static final int NEXT_PAGE_FIRST_ITEM = -6;
+ public static final int NEXT_PAGE_FIRST_ITEM = -7;
+ public static final int NEXT_PAGE_LEFT_COLUMN = -8;
// Matrix related constant.
public static final int EMPTY = -1;
+ public static final int PIVOT = 100;
/**
* Returns true only if this utility class handles the key code.
@@ -87,13 +90,13 @@
case KeyEvent.KEYCODE_DPAD_LEFT:
newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, -1 /*increment*/);
if (newIndex == NOOP && pageIndex > 0) {
- return PREVIOUS_PAGE_LAST_ITEM;
+ newIndex = PREVIOUS_PAGE_RIGHT_COLUMN;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
newIndex = handleDpadHorizontal(iconIdx, cntX, cntY, map, 1 /*increment*/);
if (newIndex == NOOP && pageIndex < pageCount - 1) {
- return NEXT_PAGE_FIRST_ITEM;
+ newIndex = NEXT_PAGE_LEFT_COLUMN;
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -169,7 +172,7 @@
matrix[cx][cy] = i;
}
if (DEBUG) {
- printMatrix(matrix, m, n);
+ printMatrix(matrix);
}
return matrix;
}
@@ -231,7 +234,47 @@
}
}
if (DEBUG) {
- printMatrix(matrix, m, n);
+ printMatrix(matrix);
+ }
+ return matrix;
+ }
+
+ /**
+ * Creates a sparse matrix that merges the icon of previous/next page and last column of
+ * current page. When left key is triggered on the leftmost column, sparse matrix is created
+ * that combines previous page matrix and an extra column on the right. Likewise, when right
+ * key is triggered on the rightmost column, sparse matrix is created that combines this column
+ * on the 0th column and the next page matrix.
+ *
+ * @param pivotX x coordinate of the focused item in the current page
+ * @param pivotY y coordinate of the focused item in the current page
+ */
+ // TODO: get rid of the dynamic matrix creation
+ public static int[][] createSparseMatrix(CellLayout iconLayout, int pivotX, int pivotY) {
+
+ ViewGroup iconParent = iconLayout.getShortcutsAndWidgets();
+
+ int[][] matrix = createFullMatrix(iconLayout.getCountX() + 1, iconLayout.getCountY(),
+ false /* set all cell to empty */);
+
+ // Iterate thru the children of the top parent.
+ for (int i = 0; i < iconParent.getChildCount(); i++) {
+ int cx = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellX;
+ int cy = ((CellLayout.LayoutParams) iconParent.getChildAt(i).getLayoutParams()).cellY;
+ if (pivotX < 0) {
+ matrix[cx - pivotX][cy] = i;
+ } else {
+ matrix[cx][cy] = i;
+ }
+ }
+
+ if (pivotX < 0) {
+ matrix[0][pivotY] = PIVOT;
+ } else {
+ matrix[pivotX][pivotY] = PIVOT;
+ }
+ if (DEBUG) {
+ printMatrix(matrix);
}
return matrix;
}
@@ -407,21 +450,32 @@
return newIconIndex;
}
+ /**
+ * Only used for debugging.
+ */
private static String getStringIndex(int index) {
switch(index) {
case NOOP: return "NOOP";
case PREVIOUS_PAGE_FIRST_ITEM: return "PREVIOUS_PAGE_FIRST";
case PREVIOUS_PAGE_LAST_ITEM: return "PREVIOUS_PAGE_LAST";
+ case PREVIOUS_PAGE_RIGHT_COLUMN:return "PREVIOUS_PAGE_RIGHT_COLUMN";
case CURRENT_PAGE_FIRST_ITEM: return "CURRENT_PAGE_FIRST";
case CURRENT_PAGE_LAST_ITEM: return "CURRENT_PAGE_LAST";
case NEXT_PAGE_FIRST_ITEM: return "NEXT_PAGE_FIRST";
+ case NEXT_PAGE_LEFT_COLUMN: return "NEXT_PAGE_LEFT_COLUMN";
default:
return Integer.toString(index);
}
}
- private static void printMatrix(int[][] matrix, int m, int n) {
+ /**
+ * Only used for debugging.
+ */
+ private static void printMatrix(int[][] matrix) {
Log.v(TAG, "\tprintMap:");
+ int m = matrix.length;
+ int n = matrix[0].length;
+
for (int j=0; j < n; j++) {
String colY = "\t\t";
for (int i=0; i < m; i++) {
@@ -430,4 +484,24 @@
Log.v(TAG, colY);
}
}
+
+ /**
+ * Figure out the location of the icon.
+ *
+ */
+ //TODO(hyunyoungs): this helper method should move to CellLayout class while removing the
+ // dynamic matrix creation all together.
+ public static int findRow(int[][] matrix, int iconIndex) {
+ int cntX = matrix.length;
+ int cntY = matrix[0].length;
+
+ for (int i = 0; i < cntX; i++) {
+ for (int j = 0; j < cntY; j++) {
+ if (matrix[i][j] == iconIndex) {
+ return j;
+ }
+ }
+ }
+ return -1;
+ }
}
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index fd2e2a8..ea87014 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -18,6 +18,9 @@
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import com.android.launcher3.util.FocusLogic;
/**
* Tests the {@link FocusLogic} class that handles key event based focus handling.
@@ -37,6 +40,21 @@
}
public void testShouldConsume() {
- // write tests.
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_LEFT));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_RIGHT));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_UP));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DPAD_DOWN));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_HOME));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
+ assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
+ }
+
+ public void testCreateSparseMatrix() {
+ // Either, 1) create a helper method to generate/instantiate all possible cell layout that
+ // may get created in real world to test this method. OR 2) Move all the matrix
+ // management routine to celllayout and write tests for them.
}
}