diff options
Diffstat (limited to 'src/com')
17 files changed, 630 insertions, 655 deletions
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index b63ef788a..8d418f984 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -16,15 +16,28 @@ package com.android.launcher3; +import android.animation.ObjectAnimator; import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + import com.android.launcher3.util.Thunk; /** - * A base {@link RecyclerView}, which will NOT intercept a touch sequence unless the scrolling - * velocity is below a predefined threshold. + * A base {@link RecyclerView}, which does the following: + * <ul> + * <li> NOT intercept a touch unless the scrolling velocity is below a predefined threshold. + * <li> Enable fast scroller. + * </ul> */ public class BaseRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener { @@ -35,6 +48,53 @@ public class BaseRecyclerView extends RecyclerView @Thunk int mDy = 0; private float mDeltaThreshold; + // + // Keeps track of variables required for the second function of this class: fast scroller. + // + + private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f; + + /** + * The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds() + * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so + * that we can calculate what the scroll bar looks like, and where to jump to from the fast + * scroller. + */ + public static class ScrollPositionState { + // The index of the first visible row + public int rowIndex; + // The offset of the first visible row + public int rowTopOffset; + // The height of a given row (they are currently all the same height) + public int rowHeight; + } + // Should be maintained inside overriden method #updateVerticalScrollbarBounds + public ScrollPositionState scrollPosState = new ScrollPositionState(); + public Rect verticalScrollbarBounds = new Rect(); + + private boolean mDraggingFastScroller; + + private Drawable mScrollbar; + private Drawable mFastScrollerBg; + private Rect mTmpFastScrollerInvalidateRect = new Rect(); + private Rect mFastScrollerBounds = new Rect(); + + private String mFastScrollSectionName; + private Paint mFastScrollTextPaint; + private Rect mFastScrollTextBounds = new Rect(); + private float mFastScrollAlpha; + + private int mDownX; + private int mDownY; + private int mLastX; + private int mLastY; + private int mScrollbarWidth; + private int mScrollbarMinHeight; + private int mScrollbarInset; + private Rect mBackgroundPadding = new Rect(); + + + public BaseRecyclerView(Context context) { this(context, null); } @@ -49,6 +109,24 @@ public class BaseRecyclerView extends RecyclerView ScrollListener listener = new ScrollListener(); setOnScrollListener(listener); + + Resources res = context.getResources(); + int fastScrollerSize = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_popup_size); + mScrollbar = res.getDrawable(R.drawable.all_apps_scrollbar_thumb); + mFastScrollerBg = res.getDrawable(R.drawable.all_apps_fastscroll_bg); + mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize); + mFastScrollTextPaint = new Paint(); + mFastScrollTextPaint.setColor(Color.WHITE); + mFastScrollTextPaint.setAntiAlias(true); + mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize( + R.dimen.all_apps_fast_scroll_text_size)); + mScrollbarWidth = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_width); + mScrollbarMinHeight = + res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_min_height); + mScrollbarInset = + res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset); + setFastScrollerAlpha(mFastScrollAlpha); + setOverScrollMode(View.OVER_SCROLL_NEVER); } private class ScrollListener extends OnScrollListener { @@ -68,17 +146,74 @@ public class BaseRecyclerView extends RecyclerView addOnItemTouchListener(this); } + /** + * We intercept the touch handling only to support fast scrolling when initiated from the + * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling. + */ @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { - if (shouldStopScroll(ev)) { - stopScroll(); - } - return false; + return handleTouchEvent(ev); } @Override public void onTouchEvent(RecyclerView rv, MotionEvent ev) { - // Do nothing. + handleTouchEvent(ev); + } + + /** + * Handles the touch event and determines whether to show the fast scroller (or updates it if + * it is already showing). + */ + private boolean handleTouchEvent(MotionEvent ev) { + ViewConfiguration config = ViewConfiguration.get(getContext()); + + int action = ev.getAction(); + int x = (int) ev.getX(); + int y = (int) ev.getY(); + switch (action) { + case MotionEvent.ACTION_DOWN: + // Keep track of the down positions + mDownX = mLastX = x; + mDownY = mLastY = y; + if (shouldStopScroll(ev)) { + stopScroll(); + } + break; + case MotionEvent.ACTION_MOVE: + // Check if we are scrolling + if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) && + Math.abs(y - mDownY) > config.getScaledTouchSlop()) { + getParent().requestDisallowInterceptTouchEvent(true); + mDraggingFastScroller = true; + animateFastScrollerVisibility(true); + } + if (mDraggingFastScroller) { + mLastX = x; + mLastY = y; + + // Scroll to the right position, and update the section name + int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2); + int bottom = getHeight() - getPaddingBottom() - + (mFastScrollerBg.getBounds().height() / 2); + float boundedY = (float) Math.max(top, Math.min(bottom, y)); + mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) / + (bottom - top)); + + // Combine the old and new fast scroller bounds to create the full invalidate + // rect + mTmpFastScrollerInvalidateRect.set(mFastScrollerBounds); + updateFastScrollerBounds(); + mTmpFastScrollerInvalidateRect.union(mFastScrollerBounds); + invalidateFastScroller(mTmpFastScrollerInvalidateRect); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mDraggingFastScroller = false; + animateFastScrollerVisibility(false); + break; + } + return mDraggingFastScroller; } public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { @@ -99,4 +234,127 @@ public class BaseRecyclerView extends RecyclerView } return false; } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + drawVerticalScrubber(canvas); + drawFastScrollerPopup(canvas); + } + + /** + * Draws the vertical scrollbar. + */ + private void drawVerticalScrubber(Canvas canvas) { + updateVerticalScrollbarBounds(); + + // Draw the scroll bar + int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.translate(verticalScrollbarBounds.left, verticalScrollbarBounds.top); + mScrollbar.setBounds(0, 0, mScrollbarWidth, verticalScrollbarBounds.height()); + mScrollbar.draw(canvas); + canvas.restoreToCount(restoreCount); + } + + /** + * Draws the fast scroller popup. + */ + private void drawFastScrollerPopup(Canvas canvas) { + if (mFastScrollAlpha > 0f && mFastScrollSectionName != null && !mFastScrollSectionName.isEmpty()) { + // Draw the fast scroller popup + int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.translate(mFastScrollerBounds.left, mFastScrollerBounds.top); + mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255)); + mFastScrollerBg.draw(canvas); + mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255)); + mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0, + mFastScrollSectionName.length(), mFastScrollTextBounds); + float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName); + canvas.drawText(mFastScrollSectionName, + (mFastScrollerBounds.width() - textWidth) / 2, + mFastScrollerBounds.height() - + (mFastScrollerBounds.height() - mFastScrollTextBounds.height()) / 2, + mFastScrollTextPaint); + canvas.restoreToCount(restoreCount); + } + } + + /** + * Returns the scroll bar width. + */ + public int getScrollbarWidth() { + return mScrollbarWidth; + } + + /** + * Sets the fast scroller alpha. + */ + public void setFastScrollerAlpha(float alpha) { + mFastScrollAlpha = alpha; + invalidateFastScroller(mFastScrollerBounds); + } + + /** + * Maps the touch (from 0..1) to the adapter position that should be visible. + * <p>Override in each subclass of this base class. + */ + public String scrollToPositionAtProgress(float touchFraction) { + return null; + } + + /** + * Updates the bounds for the scrollbar. + * <p>Override in each subclass of this base class. + */ + public void updateVerticalScrollbarBounds() {}; + + /** + * Animates the visibility of the fast scroller popup. + */ + private void animateFastScrollerVisibility(boolean visible) { + ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f); + anim.setDuration(visible ? 200 : 150); + anim.start(); + } + + /** + * Invalidates the fast scroller popup. + */ + protected void invalidateFastScroller(Rect bounds) { + invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + /** + * Returns whether a given point is near the scrollbar. + */ + private boolean isPointNearScrollbar(int x, int y) { + // Check if we are scrolling + updateVerticalScrollbarBounds(); + verticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset); + return verticalScrollbarBounds.contains(x, y); + } + + /** + * Updates the bounds for the fast scroller. + */ + private void updateFastScrollerBounds() { + if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) { + int x; + int y; + + // Calculate the position for the fast scroller popup + Rect bgBounds = mFastScrollerBg.getBounds(); + if (Utilities.isRtl(getResources())) { + x = mBackgroundPadding.left + getScrollBarSize(); + } else { + x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width(); + } + y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height()); + y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() - + bgBounds.height())); + mFastScrollerBounds.set(x, y, x + bgBounds.width(), y + bgBounds.height()); + } else { + mFastScrollerBounds.setEmpty(); + } + } }
\ No newline at end of file diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 2b1cfe0e4..61567ac00 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -168,7 +168,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private int[] mDirectionVector = new int[2]; int[] mPreviousReorderDirection = new int[2]; private static final int INVALID_DIRECTION = -100; - private DropTarget.DragEnforcer mDragEnforcer; private final Rect mTempRect = new Rect(); @@ -188,7 +187,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { public CellLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mDragEnforcer = new DropTarget.DragEnforcer(context); // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show // the user where a dragged item will land when dropped. @@ -2637,7 +2635,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * or it may have begun on another layout. */ void onDragEnter() { - mDragEnforcer.onDragEnter(); mDragging = true; } @@ -2645,7 +2642,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * Called when drag has left this CellLayout or has been completed (successfully or not) */ void onDragExit() { - mDragEnforcer.onDragExit(); // This can actually be called when we aren't in a drag, e.g. when adding a new // item to this layout via the customize drawer. // Guard against that case. diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java index 423a9a3d5..41e053eed 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/DragLayer.java @@ -918,7 +918,7 @@ public class DragLayer extends InsettableFrameLayout { void showPageHints() { mShowPageHints = true; Workspace workspace = mLauncher.getWorkspace(); - getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.getChildCount() - 1), + getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()), mScrollChildPosition); invalidate(); } diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index a3828c1d0..c8fac5466 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -16,10 +16,8 @@ package com.android.launcher3; -import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; -import android.util.Log; /** * Interface defining an object that can receive a drag. @@ -93,43 +91,6 @@ public interface DropTarget { } } - public static class DragEnforcer implements DragController.DragListener { - int dragParity = 0; - - public DragEnforcer(Context context) { - Launcher launcher = (Launcher) context; - launcher.getDragController().addDragListener(this); - } - - void onDragEnter() { - dragParity++; - if (dragParity != 1) { - Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); - } - } - - void onDragExit() { - dragParity--; - if (dragParity != 0) { - Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); - } - } - - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - if (dragParity != 0) { - Log.e(TAG, "onDragEnter: Drag contract violated: " + dragParity); - } - } - - @Override - public void onDragEnd() { - if (dragParity != 0) { - Log.e(TAG, "onDragExit: Drag contract violated: " + dragParity); - } - } - } - /** * Used to temporarily disable certain drop targets * diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 0c91a7113..6dfca9ef3 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -49,10 +49,8 @@ import com.android.launcher3.util.Thunk; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.util.Map.Entry; import java.util.Stack; /** @@ -75,8 +73,8 @@ public class IconCache { @Thunk static class CacheEntry { public Bitmap icon; - public CharSequence title; - public CharSequence contentDescription; + public CharSequence title = ""; + public CharSequence contentDescription = ""; public boolean isLowResIcon; } @@ -367,13 +365,6 @@ public class IconCache { } /** - * Empty out the cache. - */ - public synchronized void flush() { - mCache.clear(); - } - - /** * Fetches high-res icon for the provided ItemInfo and updates the caller when done. * @return a request ID that can be used to cancel the request. */ @@ -584,7 +575,7 @@ public class IconCache { CacheEntry entry = mCache.get(cacheKey); if (entry == null || (entry.isLowResIcon && !useLowResIcon)) { entry = new CacheEntry(); - mCache.put(cacheKey, entry); + boolean entryUpdated = true; // Check the DB first. if (!getEntryFromDB(cn, user, entry, useLowResIcon)) { @@ -609,8 +600,14 @@ public class IconCache { } catch (NameNotFoundException e) { if (DEBUG) Log.d(TAG, "Application not installed " + packageName); + entryUpdated = false; } } + + // Only add a filled-out entry to the cache + if (entryUpdated) { + mCache.put(cacheKey, entry); + } } return entry; } diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 92fdbde85..7f34593a9 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -19,12 +19,13 @@ package com.android.launcher3; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Point; -import android.graphics.PointF; import android.os.Build; import android.util.DisplayMetrics; import android.view.Display; import android.view.WindowManager; + import com.android.launcher3.util.Thunk; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -34,61 +35,36 @@ public class InvariantDeviceProfile { // This is a static that we use for the default icon size on a 4/5-inch phone private static float DEFAULT_ICON_SIZE_DP = 60; - private static final ArrayList<InvariantDeviceProfile> sDeviceProfiles = new ArrayList<>(); - static { - sDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby", - 255, 300, 2, 3, 2, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4)); - sDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby", - 255, 400, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4)); - sDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby", - 275, 420, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); - sDeviceProfiles.add(new InvariantDeviceProfile("Stubby", - 255, 450, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); - sDeviceProfiles.add(new InvariantDeviceProfile("Nexus S", - 296, 491.33f, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); - sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4", - 335, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4)); - sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5", - 359, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4)); - sDeviceProfiles.add(new InvariantDeviceProfile("Large Phone", - 406, 694, 5, 5, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5)); - // The tablet profile is odd in that the landscape orientation - // also includes the nav bar on the side - sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7", - 575, 904, 5, 6, 4, 5, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6)); - // Larger tablet profiles always have system bars on the top & bottom - sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10", - 727, 1207, 5, 6, 4, 5, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6)); - sDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet", - 1527, 2527, 7, 7, 6, 6, 100, 20, 7, 72, R.xml.default_workspace_4x4)); - } + // Constants that affects the interpolation curve between statically defined device profile + // buckets. + private static float KNEARESTNEIGHBOR = 3; + private static float WEIGHT_POWER = 5; - private class DeviceProfileQuery { - InvariantDeviceProfile profile; - float widthDps; - float heightDps; - float value; - PointF dimens; - - DeviceProfileQuery(InvariantDeviceProfile p, float v) { - widthDps = p.minWidthDps; - heightDps = p.minHeightDps; - value = v; - dimens = new PointF(widthDps, heightDps); - profile = p; - } - } + // used to offset float not being able to express extremely small weights in extreme cases. + private static float WEIGHT_EFFICIENT = 100000f; // Profile-defining invariant properties String name; float minWidthDps; float minHeightDps; + + /** + * Number of icons per row and column in the workspace. + */ public int numRows; public int numColumns; + + /** + * Number of icons per row and column in the folder. + */ public int numFolderRows; public int numFolderColumns; float iconSize; float iconTextSize; + + /** + * Number of icons inside the hotseat area. + */ float numHotseatIcons; float hotseatIconSize; int defaultLayoutId; @@ -102,6 +78,12 @@ public class InvariantDeviceProfile { InvariantDeviceProfile() { } + public InvariantDeviceProfile(InvariantDeviceProfile p) { + this(p.name, p.minWidthDps, p.minHeightDps, p.numRows, p.numColumns, + p.numFolderRows, p.numFolderColumns, p.iconSize, p.iconTextSize, p.numHotseatIcons, + p.hotseatIconSize, p.defaultLayoutId); + } + InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc, float is, float its, float hs, float his, int dlId) { // Ensure that we have an odd number of hotseat items (since we need to place all apps) @@ -134,21 +116,16 @@ public class InvariantDeviceProfile { Point largestSize = new Point(); display.getCurrentSizeRange(smallestSize, largestSize); + // This guarantees that width < height minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm); minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm); - ArrayList<DeviceProfileQuery> points = - new ArrayList<DeviceProfileQuery>(); + ArrayList<InvariantDeviceProfile> closestProfiles = + findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles()); + InvariantDeviceProfile interpolatedDeviceProfileOut = + invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles); - // Find the closes profile given the width/height - for (InvariantDeviceProfile p : sDeviceProfiles) { - points.add(new DeviceProfileQuery(p, 0f)); - } - - InvariantDeviceProfile closestProfile = - findClosestDeviceProfile(minWidthDps, minHeightDps, points); - - // The following properties are inherited directly from the nearest archetypal profile + InvariantDeviceProfile closestProfile = closestProfiles.get(0); numRows = closestProfile.numRows; numColumns = closestProfile.numColumns; numHotseatIcons = closestProfile.numHotseatIcons; @@ -157,24 +134,9 @@ public class InvariantDeviceProfile { numFolderRows = closestProfile.numFolderRows; numFolderColumns = closestProfile.numFolderColumns; - - // The following properties are interpolated based on proximity to nearby archetypal - // profiles - points.clear(); - for (InvariantDeviceProfile p : sDeviceProfiles) { - points.add(new DeviceProfileQuery(p, p.iconSize)); - } - iconSize = invDistWeightedInterpolate(minWidthDps, minHeightDps, points); - points.clear(); - for (InvariantDeviceProfile p : sDeviceProfiles) { - points.add(new DeviceProfileQuery(p, p.iconTextSize)); - } - iconTextSize = invDistWeightedInterpolate(minWidthDps, minHeightDps, points); - points.clear(); - for (InvariantDeviceProfile p : sDeviceProfiles) { - points.add(new DeviceProfileQuery(p, p.hotseatIconSize)); - } - hotseatIconSize = invDistWeightedInterpolate(minWidthDps, minHeightDps, points); + iconSize = interpolatedDeviceProfileOut.iconSize; + iconTextSize = interpolatedDeviceProfileOut.iconTextSize; + hotseatIconSize = interpolatedDeviceProfileOut.hotseatIconSize; // If the partner customization apk contains any grid overrides, apply them // Supported overrides: numRows, numColumns, iconSize @@ -182,7 +144,7 @@ public class InvariantDeviceProfile { Point realSize = new Point(); display.getRealSize(realSize); - // The real size never changes. smallSide and largeSize will remain the + // The real size never changes. smallSide and largeSide will remain the // same in any orientation. int smallSide = Math.min(realSize.x, realSize.y); int largeSide = Math.max(realSize.x, realSize.y); @@ -193,84 +155,112 @@ public class InvariantDeviceProfile { smallSide, largeSide, false /* isLandscape */); } + ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() { + ArrayList<InvariantDeviceProfile> predefinedDeviceProfiles = new ArrayList<>(); + // width, height, #rows, #columns, #folder rows, #folder columns, + // iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId. + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby", + 255, 300, 2, 3, 2, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby", + 255, 400, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby", + 275, 420, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby", + 255, 450, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S", + 296, 491.33f, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4", + 335, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5", + 359, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone", + 406, 694, 5, 5, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5)); + // The tablet profile is odd in that the landscape orientation + // also includes the nav bar on the side + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7", + 575, 904, 5, 6, 4, 5, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6)); + // Larger tablet profiles always have system bars on the top & bottom + predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10", + 727, 1207, 5, 6, 4, 5, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6)); + predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet", + 1527, 2527, 7, 7, 6, 6, 100, 20, 7, 72, R.xml.default_workspace_4x4)); + return predefinedDeviceProfiles; + } + + /** * Apply any Partner customization grid overrides. * * Currently we support: all apps row / column count. */ - private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) { - Partner p = Partner.get(ctx.getPackageManager()); + private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) { + Partner p = Partner.get(context.getPackageManager()); if (p != null) { p.applyInvariantDeviceProfileOverrides(this, dm); } } - @Thunk float dist(PointF p0, PointF p1) { - return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) + - (p1.y-p0.y)*(p1.y-p0.y)); - } - - private float weight(PointF a, PointF b, - float pow) { - float d = dist(a, b); - if (d == 0f) { - return Float.POSITIVE_INFINITY; - } - return (float) (1f / Math.pow(d, pow)); - } - - /** Returns the closest device profile given the width and height and a list of profiles */ - private InvariantDeviceProfile findClosestDeviceProfile(float width, float height, - ArrayList<DeviceProfileQuery> points) { - return findClosestDeviceProfiles(width, height, points).get(0).profile; + @Thunk float dist(float x0, float y0, float x1, float y1) { + return (float) Math.hypot(x1 - x0, y1 - y0); } - /** Returns the closest device profiles ordered by closeness to the specified width and height */ - private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height, - ArrayList<DeviceProfileQuery> points) { - final PointF xy = new PointF(width, height); + /** + * Returns the closest device profiles ordered by closeness to the specified width and height + */ + // Package private visibility for testing. + ArrayList<InvariantDeviceProfile> findClosestDeviceProfiles( + final float width, final float height, ArrayList<InvariantDeviceProfile> points) { // Sort the profiles by their closeness to the dimensions - ArrayList<DeviceProfileQuery> pointsByNearness = points; - Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() { - public int compare(DeviceProfileQuery a, DeviceProfileQuery b) { - return (int) (dist(xy, a.dimens) - dist(xy, b.dimens)); + ArrayList<InvariantDeviceProfile> pointsByNearness = points; + Collections.sort(pointsByNearness, new Comparator<InvariantDeviceProfile>() { + public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) { + return (int) (dist(width, height, a.minWidthDps, a.minHeightDps) + - dist(width, height, b.minWidthDps, b.minHeightDps)); } }); return pointsByNearness; } - private float invDistWeightedInterpolate(float width, float height, - ArrayList<DeviceProfileQuery> points) { - float sum = 0; + // Package private visibility for testing. + InvariantDeviceProfile invDistWeightedInterpolate(float width, float height, + ArrayList<InvariantDeviceProfile> points) { float weights = 0; - float pow = 5; - float kNearestNeighbors = 3; - final PointF xy = new PointF(width, height); - - ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height, - points); - - for (int i = 0; i < pointsByNearness.size(); ++i) { - DeviceProfileQuery p = pointsByNearness.get(i); - if (i < kNearestNeighbors) { - float w = weight(xy, p.dimens, pow); - if (w == Float.POSITIVE_INFINITY) { - return p.value; - } - weights += w; - } + + InvariantDeviceProfile p = points.get(0); + if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) { + return p; } - for (int i = 0; i < pointsByNearness.size(); ++i) { - DeviceProfileQuery p = pointsByNearness.get(i); - if (i < kNearestNeighbors) { - float w = weight(xy, p.dimens, pow); - sum += w * p.value / weights; - } + InvariantDeviceProfile out = new InvariantDeviceProfile(); + for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) { + p = new InvariantDeviceProfile(points.get(i)); + float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER); + weights += w; + out.add(p.multiply(w)); } + return out.multiply(1.0f/weights); + } - return sum; + private void add(InvariantDeviceProfile p) { + iconSize += p.iconSize; + iconTextSize += p.iconTextSize; + hotseatIconSize += p.hotseatIconSize; + } + + private InvariantDeviceProfile multiply(float w) { + iconSize *= w; + iconTextSize *= w; + hotseatIconSize *= w; + return this; + } + + private float weight(float x0, float y0, float x1, float y1, float pow) { + float d = dist(x0, y0, x1, y1); + if (Float.compare(d, 0f) == 0) { + return Float.POSITIVE_INFINITY; + } + return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow)); } -} +}
\ No newline at end of file diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index da6430a73..51f091613 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -112,11 +112,8 @@ import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetsContainerView; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -311,8 +308,6 @@ public class Launcher extends Activity private boolean mHasFocus = false; private boolean mAttached = false; - @Thunk static LocaleConfiguration sLocaleConfiguration = null; - private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>(); private View.OnTouchListener mHapticFeedbackTouchListener; @@ -488,7 +483,6 @@ public class Launcher extends Activity Environment.getExternalStorageDirectory() + "/launcher"); } - checkForLocaleChange(); setContentView(R.layout.launcher); setupViews(); @@ -667,108 +661,6 @@ public class Launcher extends Activity } } - @Thunk void checkForLocaleChange() { - if (sLocaleConfiguration == null) { - new AsyncTask<Void, Void, LocaleConfiguration>() { - @Override - protected LocaleConfiguration doInBackground(Void... unused) { - LocaleConfiguration localeConfiguration = new LocaleConfiguration(); - readConfiguration(Launcher.this, localeConfiguration); - return localeConfiguration; - } - - @Override - protected void onPostExecute(LocaleConfiguration result) { - sLocaleConfiguration = result; - checkForLocaleChange(); // recursive, but now with a locale configuration - } - }.execute(); - return; - } - - final Configuration configuration = getResources().getConfiguration(); - - final String previousLocale = sLocaleConfiguration.locale; - final String locale = configuration.locale.toString(); - - final int previousMcc = sLocaleConfiguration.mcc; - final int mcc = configuration.mcc; - - final int previousMnc = sLocaleConfiguration.mnc; - final int mnc = configuration.mnc; - - boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc; - - if (localeChanged) { - sLocaleConfiguration.locale = locale; - sLocaleConfiguration.mcc = mcc; - sLocaleConfiguration.mnc = mnc; - - mIconCache.flush(); - - final LocaleConfiguration localeConfiguration = sLocaleConfiguration; - new AsyncTask<Void, Void, Void>() { - public Void doInBackground(Void ... args) { - writeConfiguration(Launcher.this, localeConfiguration); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); - } - } - - @Thunk static class LocaleConfiguration { - public String locale; - public int mcc = -1; - public int mnc = -1; - } - - @Thunk static void readConfiguration(Context context, LocaleConfiguration configuration) { - DataInputStream in = null; - try { - in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFERENCES)); - configuration.locale = in.readUTF(); - configuration.mcc = in.readInt(); - configuration.mnc = in.readInt(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - // Ignore - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - - @Thunk static void writeConfiguration(Context context, LocaleConfiguration configuration) { - DataOutputStream out = null; - try { - out = new DataOutputStream(context.openFileOutput( - LauncherFiles.LAUNCHER_PREFERENCES, MODE_PRIVATE)); - out.writeUTF(configuration.locale); - out.writeInt(configuration.mcc); - out.writeInt(configuration.mnc); - out.flush(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - //noinspection ResultOfMethodCallIgnored - context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFERENCES).delete(); - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - // Ignore - } - } - } - } - public Stats getStats() { return mStats; } diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java index 4aeaef0ad..ec4e4f942 100644 --- a/src/com/android/launcher3/LauncherFiles.java +++ b/src/com/android/launcher3/LauncherFiles.java @@ -17,7 +17,6 @@ public class LauncherFiles { public static final String DEFAULT_WALLPAPER_THUMBNAIL = "default_thumb2.jpg"; public static final String DEFAULT_WALLPAPER_THUMBNAIL_OLD = "default_thumb.jpg"; public static final String LAUNCHER_DB = "launcher.db"; - public static final String LAUNCHER_PREFERENCES = "launcher.preferences"; public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs"; public static final String WALLPAPER_CROP_PREFERENCES_KEY = "com.android.launcher3.WallpaperCropActivity"; @@ -33,7 +32,6 @@ public class LauncherFiles { DEFAULT_WALLPAPER_THUMBNAIL, DEFAULT_WALLPAPER_THUMBNAIL_OLD, LAUNCHER_DB, - LAUNCHER_PREFERENCES, SHARED_PREFERENCES_KEY + XML, WALLPAPER_CROP_PREFERENCES_KEY + XML, WALLPAPER_IMAGES_DB, @@ -46,5 +44,6 @@ public class LauncherFiles { public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList( "launches.log", "stats.log", + "launcher.preferences", "com.android.launcher3.compat.PackageInstallerCompatV16.queue")); } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index f0990444d..9271e8b15 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -623,6 +623,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mCurrentPage != getNextPage()) { AccessibilityEvent ev = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + ev.setScrollable(true); + ev.setScrollX(getScrollX()); + ev.setScrollY(getScrollY()); + ev.setMaxScrollX(mMaxScrollX); + ev.setMaxScrollY(0); sendAccessibilityEventUnchecked(ev); } @@ -843,7 +848,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc int offsetY = getViewportOffsetY(); // Update the viewport offsets - mViewport.offset(offsetX, offsetY); + mViewport.offset(offsetX, offsetY); final int startIndex = mIsRtl ? childCount - 1 : 0; final int endIndex = mIsRtl ? -1 : childCount; @@ -2327,6 +2332,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (getCurrentPage() > 0) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } + info.setClassName(getClass().getName()); + + // Accessibility-wise, PagedView doesn't support long click, so disabling it. + // Besides disabling the accessibility long-click, this also prevents this view from getting + // accessibility focus. + info.setLongClickable(false); + if (Utilities.isLmpOrAbove()) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + } } @Override @@ -2340,7 +2354,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); - event.setScrollable(true); + event.setScrollable(getPageCount() > 1); } @Override diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index c0a1cfc1e..6d5affb59 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -64,8 +64,8 @@ import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.UninstallDropTarget.UninstallSource; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; -import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; +import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; @@ -89,6 +89,8 @@ public class Workspace extends PagedView Insettable, UninstallSource, AccessibilityDragSource { private static final String TAG = "Launcher.Workspace"; + private static boolean ENFORCE_DRAG_EVENT_ORDER = false; + protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; protected static final int FADE_EMPTY_SCREEN_DURATION = 150; @@ -215,7 +217,6 @@ public class Workspace extends PagedView private FolderIcon mDragOverFolderIcon = null; private boolean mCreateUserFolderOnDrop = false; private boolean mAddToExistingFolderOnDrop = false; - private DropTarget.DragEnforcer mDragEnforcer; private float mMaxDistanceForFolderCreation; private final Canvas mCanvas = new Canvas(); @@ -301,9 +302,6 @@ public class Workspace extends PagedView mOutlineHelper = HolographicOutlineHelper.obtain(context); - mDragEnforcer = new DropTarget.DragEnforcer(context); - // With workspace, data is available straight from the get-go - mLauncher = (Launcher) context; mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); final Resources res = getResources(); @@ -327,7 +325,6 @@ public class Workspace extends PagedView // Disable multitouch across the workspace/all apps/customize tray setMotionEventSplittingEnabled(true); - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } @Override @@ -372,22 +369,23 @@ public class Workspace extends PagedView return r; } + @Override public void onDragStart(final DragSource source, Object info, int dragAction) { + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragStart", 0, 0); + } + mIsDragOccuring = true; updateChildrenLayersEnabled(false); mLauncher.lockScreenOrientation(); mLauncher.onInteractionBegin(); // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging InstallShortcutReceiver.enableInstallQueue(); - post(new Runnable() { - @Override - public void run() { - if (mIsDragOccuring && mAddNewPageOnDrag) { - mDeferRemoveExtraEmptyScreen = false; - addExtraEmptyScreenOnDrag(); - } - } - }); + + if (mAddNewPageOnDrag) { + mDeferRemoveExtraEmptyScreen = false; + addExtraEmptyScreenOnDrag(); + } } public void setAddNewPageOnDrag(boolean addPage) { @@ -398,7 +396,12 @@ public class Workspace extends PagedView mDeferRemoveExtraEmptyScreen = true; } + @Override public void onDragEnd() { + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragEnd", 0, 0); + } + if (!mDeferRemoveExtraEmptyScreen) { removeExtraEmptyScreen(true, mDragSourceInternal != null); } @@ -731,6 +734,7 @@ public class Workspace extends PagedView fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION, onComplete, stripEmptyScreens); } else { + snapToPage(getNextPage(), 0); fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION, onComplete, stripEmptyScreens); } @@ -2014,14 +2018,9 @@ public class Workspace extends PagedView for (int i = numCustomPages(); i < total; i++) { updateAccessibilityFlags((CellLayout) getPageAt(i), i); } - if (mState == State.NORMAL) { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - } else { - setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - } } else { int accessible = mState == State.NORMAL ? - IMPORTANT_FOR_ACCESSIBILITY_NO : + IMPORTANT_FOR_ACCESSIBILITY_AUTO : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; setImportantForAccessibility(accessible); } @@ -2040,7 +2039,7 @@ public class Workspace extends PagedView page.setAccessibilityDelegate(mPagesAccessibilityDelegate); } else { int accessible = mState == State.NORMAL ? - IMPORTANT_FOR_ACCESSIBILITY_NO : + IMPORTANT_FOR_ACCESSIBILITY_AUTO : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); page.getShortcutsAndWidgets().setImportantForAccessibility(accessible); @@ -2822,8 +2821,12 @@ public class Workspace extends PagedView location[1] = vY - y; } + @Override public void onDragEnter(DragObject d) { - mDragEnforcer.onDragEnter(); + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragEnter", 1, 1); + } + mCreateUserFolderOnDrop = false; mAddToExistingFolderOnDrop = false; @@ -2876,8 +2879,11 @@ public class Workspace extends PagedView return null; } + @Override public void onDragExit(DragObject d) { - mDragEnforcer.onDragExit(); + if (ENFORCE_DRAG_EVENT_ORDER) { + enfoceDragParity("onDragExit", -1, 0); + } // Here we store the final page that will be dropped to, if the workspace in fact // receives the drop @@ -2909,6 +2915,24 @@ public class Workspace extends PagedView mLauncher.getDragLayer().hidePageHints(); } + private void enfoceDragParity(String event, int update, int expectedValue) { + enfoceDragParity(this, event, update, expectedValue); + for (int i = 0; i < getChildCount(); i++) { + enfoceDragParity(getChildAt(i), event, update, expectedValue); + } + } + + private void enfoceDragParity(View v, String event, int update, int expectedValue) { + Object tag = v.getTag(R.id.drag_event_parity); + int value = tag == null ? 0 : (Integer) tag; + value += update; + v.setTag(R.id.drag_event_parity, value); + + if (value != expectedValue) { + Log.e(TAG, event + ": Drag contract violated: " + value); + } + } + void setCurrentDropLayout(CellLayout layout) { if (mDragTargetLayout != null) { mDragTargetLayout.revertTempState(); diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index eeec8c580..93cf8d050 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -219,9 +219,13 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { } private ArrayList<Integer> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) { - AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo(); ArrayList<Integer> actions = new ArrayList<>(); + AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo(); + if (providerInfo == null) { + return actions; + } + CellLayout layout = (CellLayout) host.getParent().getParent(); if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) { if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) || diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 60f9ab347..d81f97f24 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -447,7 +447,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override protected void onFixedBoundsUpdated() { // Update the number of items in the grid - LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = mLauncher.getDeviceProfile(); if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) { mNumAppsPerRow = grid.allAppsNumCols; diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index e95fa325a..cc5add3b2 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -15,75 +15,34 @@ */ package com.android.launcher3.allapps; -import android.animation.ObjectAnimator; import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; + import com.android.launcher3.BaseRecyclerView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.R; import com.android.launcher3.Utilities; import java.util.List; /** - * A RecyclerView with custom fastscroll support. This is the main container for the all apps - * icons. + * A RecyclerView with custom fast scroll support for the all apps view. */ public class AllAppsRecyclerView extends BaseRecyclerView { - /** - * The current scroll state of the recycler view. We use this in updateVerticalScrollbarBounds() - * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so - * that we can calculate what the scroll bar looks like, and where to jump to from the fast - * scroller. - */ - private static class ScrollPositionState { - // The index of the first visible row - int rowIndex; - // The offset of the first visible row - int rowTopOffset; - // The height of a given row (they are currently all the same height) - int rowHeight; - } - - private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f; - private AlphabeticalAppsList mApps; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; - private Drawable mScrollbar; - private Drawable mFastScrollerBg; - private Rect mTmpFastScrollerInvalidateRect = new Rect(); - private Rect mFastScrollerBounds = new Rect(); - private Rect mVerticalScrollbarBounds = new Rect(); - private boolean mDraggingFastScroller; - private String mFastScrollSectionName; - private Paint mFastScrollTextPaint; - private Rect mFastScrollTextBounds = new Rect(); - private float mFastScrollAlpha; private int mPredictionBarHeight; - private int mDownX; - private int mDownY; - private int mLastX; - private int mLastY; - private int mScrollbarWidth; private int mScrollbarMinHeight; - private int mScrollbarInset; + private Rect mBackgroundPadding = new Rect(); - private ScrollPositionState mScrollPosState = new ScrollPositionState(); private Launcher mLauncher; @@ -102,25 +61,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView { public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); - mLauncher = (Launcher) context; - Resources res = context.getResources(); - int fastScrollerSize = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_popup_size); - mScrollbar = res.getDrawable(R.drawable.all_apps_scrollbar_thumb); - mFastScrollerBg = res.getDrawable(R.drawable.all_apps_fastscroll_bg); - mFastScrollerBg.setBounds(0, 0, fastScrollerSize, fastScrollerSize); - mFastScrollTextPaint = new Paint(); - mFastScrollTextPaint.setColor(Color.WHITE); - mFastScrollTextPaint.setAntiAlias(true); - mFastScrollTextPaint.setTextSize(res.getDimensionPixelSize( - R.dimen.all_apps_fast_scroll_text_size)); - mScrollbarWidth = res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_width); - mScrollbarMinHeight = - res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_bar_min_height); - mScrollbarInset = - res.getDimensionPixelSize(R.dimen.all_apps_fast_scroll_scrubber_touch_inset); - setFastScrollerAlpha(getFastScrollerAlpha()); - setOverScrollMode(View.OVER_SCROLL_NEVER); } /** @@ -158,28 +99,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView { } /** - * Sets the fast scroller alpha. - */ - public void setFastScrollerAlpha(float alpha) { - mFastScrollAlpha = alpha; - invalidateFastScroller(mFastScrollerBounds); - } - - /** - * Gets the fast scroller alpha. - */ - public float getFastScrollerAlpha() { - return mFastScrollAlpha; - } - - /** - * Returns the scroll bar width. - */ - public int getScrollbarWidth() { - return mScrollbarWidth; - } - - /** * Scrolls this recycler view to the top. */ public void scrollToTop() { @@ -191,11 +110,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView { */ public int getScrollPosition() { List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); - getCurScrollState(mScrollPosState, items); - if (mScrollPosState.rowIndex != -1) { + getCurScrollState(scrollPosState, items); + if (scrollPosState.rowIndex != -1) { int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; - return getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + - predictionBarHeight - mScrollPosState.rowTopOffset; + return getPaddingTop() + (scrollPosState.rowIndex * scrollPosState.rowHeight) + + predictionBarHeight - scrollPosState.rowTopOffset; } return 0; } @@ -206,150 +125,11 @@ public class AllAppsRecyclerView extends BaseRecyclerView { addOnItemTouchListener(this); } - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - drawVerticalScrubber(canvas); - drawFastScrollerPopup(canvas); - } - - /** - * We intercept the touch handling only to support fast scrolling when initiated from the - * scroll bar. Otherwise, we fall back to the default RecyclerView touch handling. - */ - @Override - public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) { - return handleTouchEvent(ev); - } - - @Override - public void onTouchEvent(RecyclerView rv, MotionEvent ev) { - handleTouchEvent(ev); - } - - /** - * Handles the touch event and determines whether to show the fast scroller (or updates it if - * it is already showing). - */ - private boolean handleTouchEvent(MotionEvent ev) { - ViewConfiguration config = ViewConfiguration.get(getContext()); - - int action = ev.getAction(); - int x = (int) ev.getX(); - int y = (int) ev.getY(); - switch (action) { - case MotionEvent.ACTION_DOWN: - // Keep track of the down positions - mDownX = mLastX = x; - mDownY = mLastY = y; - if (shouldStopScroll(ev)) { - stopScroll(); - } - break; - case MotionEvent.ACTION_MOVE: - // Check if we are scrolling - if (!mDraggingFastScroller && isPointNearScrollbar(mDownX, mDownY) && - Math.abs(y - mDownY) > config.getScaledTouchSlop()) { - getParent().requestDisallowInterceptTouchEvent(true); - mDraggingFastScroller = true; - animateFastScrollerVisibility(true); - } - if (mDraggingFastScroller) { - mLastX = x; - mLastY = y; - - // Scroll to the right position, and update the section name - int top = getPaddingTop() + (mFastScrollerBg.getBounds().height() / 2); - int bottom = getHeight() - getPaddingBottom() - - (mFastScrollerBg.getBounds().height() / 2); - float boundedY = (float) Math.max(top, Math.min(bottom, y)); - mFastScrollSectionName = scrollToPositionAtProgress((boundedY - top) / - (bottom - top)); - - // Combine the old and new fast scroller bounds to create the full invalidate - // rect - mTmpFastScrollerInvalidateRect.set(mFastScrollerBounds); - updateFastScrollerBounds(); - mTmpFastScrollerInvalidateRect.union(mFastScrollerBounds); - invalidateFastScroller(mTmpFastScrollerInvalidateRect); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mDraggingFastScroller = false; - animateFastScrollerVisibility(false); - break; - } - return mDraggingFastScroller; - } - - /** - * Animates the visibility of the fast scroller popup. - */ - private void animateFastScrollerVisibility(boolean visible) { - ObjectAnimator anim = ObjectAnimator.ofFloat(this, "fastScrollerAlpha", visible ? 1f : 0f); - anim.setDuration(visible ? 200 : 150); - anim.start(); - } - - /** - * Returns whether a given point is near the scrollbar. - */ - private boolean isPointNearScrollbar(int x, int y) { - // Check if we are scrolling - updateVerticalScrollbarBounds(); - mVerticalScrollbarBounds.inset(mScrollbarInset, mScrollbarInset); - return mVerticalScrollbarBounds.contains(x, y); - } - - /** - * Draws the fast scroller popup. - */ - private void drawFastScrollerPopup(Canvas canvas) { - if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) { - // Draw the fast scroller popup - int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(mFastScrollerBounds.left, mFastScrollerBounds.top); - mFastScrollerBg.setAlpha((int) (mFastScrollAlpha * 255)); - mFastScrollerBg.draw(canvas); - mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255)); - mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0, - mFastScrollSectionName.length(), mFastScrollTextBounds); - float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName); - canvas.drawText(mFastScrollSectionName, - (mFastScrollerBounds.width() - textWidth) / 2, - mFastScrollerBounds.height() - - (mFastScrollerBounds.height() - mFastScrollTextBounds.height()) / 2, - mFastScrollTextPaint); - canvas.restoreToCount(restoreCount); - } - } - - /** - * Draws the vertical scrollbar. - */ - private void drawVerticalScrubber(Canvas canvas) { - updateVerticalScrollbarBounds(); - - // Draw the scroll bar - int restoreCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.translate(mVerticalScrollbarBounds.left, mVerticalScrollbarBounds.top); - mScrollbar.setBounds(0, 0, mScrollbarWidth, mVerticalScrollbarBounds.height()); - mScrollbar.draw(canvas); - canvas.restoreToCount(restoreCount); - } - - /** - * Invalidates the fast scroller popup. - */ - private void invalidateFastScroller(Rect bounds) { - invalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); - } - /** * Maps the touch (from 0..1) to the adapter position that should be visible. */ - private String scrollToPositionAtProgress(float touchFraction) { + @Override + public String scrollToPositionAtProgress(float touchFraction) { // Ensure that we have any sections List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections = mApps.getFastScrollerSections(); @@ -393,27 +173,60 @@ public class AllAppsRecyclerView extends BaseRecyclerView { return lastScrollSection.sectionName; } + + /** + * Returns the row index for a app index in the list. + */ + private int findRowForAppIndex(int index) { + List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); + int appIndex = 0; + int rowCount = 0; + for (AlphabeticalAppsList.SectionInfo info : sections) { + int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); + if (appIndex + info.numApps > index) { + return rowCount + ((index - appIndex) / mNumAppsPerRow); + } + appIndex += info.numApps; + rowCount += numRowsInSection; + } + return appIndex; + } + + /** + * Returns the total number of rows in the list. + */ + private int getNumRows() { + List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); + int rowCount = 0; + for (AlphabeticalAppsList.SectionInfo info : sections) { + int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); + rowCount += numRowsInSection; + } + return rowCount; + } + + /** * Updates the bounds for the scrollbar. */ - private void updateVerticalScrollbarBounds() { + @Override + public void updateVerticalScrollbarBounds() { List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems(); - // Skip early if there are no items + // Skip early if there are no items. if (items.isEmpty()) { - mVerticalScrollbarBounds.setEmpty(); + verticalScrollbarBounds.setEmpty(); return; } // Find the index and height of the first visible row (all rows have the same height) - int x; - int y; + int x, y; int predictionBarHeight = mApps.getPredictedApps().isEmpty() ? 0 : mPredictionBarHeight; int rowCount = getNumRows(); - getCurScrollState(mScrollPosState, items); - if (mScrollPosState.rowIndex != -1) { + getCurScrollState(scrollPosState, items); + if (scrollPosState.rowIndex != -1) { int height = getHeight() - getPaddingTop() - getPaddingBottom(); - int totalScrollHeight = rowCount * mScrollPosState.rowHeight + predictionBarHeight; + int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight; if (totalScrollHeight > height) { int scrollbarHeight = Math.max(mScrollbarMinHeight, (int) (height / ((float) totalScrollHeight / height))); @@ -422,78 +235,23 @@ public class AllAppsRecyclerView extends BaseRecyclerView { if (Utilities.isRtl(getResources())) { x = mBackgroundPadding.left; } else { - x = getWidth() - mBackgroundPadding.right - mScrollbarWidth; + x = getWidth() - mBackgroundPadding.right - getScrollbarWidth(); } // To calculate the offset, we compute the percentage of the total scrollable height // that the user has already scrolled and then map that to the scroll bar bounds int availableY = totalScrollHeight - height; int availableScrollY = height - scrollbarHeight; - y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + predictionBarHeight - - mScrollPosState.rowTopOffset; + y = (scrollPosState.rowIndex * scrollPosState.rowHeight) + predictionBarHeight + - scrollPosState.rowTopOffset; y = getPaddingTop() + (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY); - mVerticalScrollbarBounds.set(x, y, x + mScrollbarWidth, y + scrollbarHeight); + verticalScrollbarBounds.set(x, y, x + getScrollbarWidth(), y + scrollbarHeight); return; } } - mVerticalScrollbarBounds.setEmpty(); - } - - /** - * Updates the bounds for the fast scroller. - */ - private void updateFastScrollerBounds() { - if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) { - int x; - int y; - - // Calculate the position for the fast scroller popup - Rect bgBounds = mFastScrollerBg.getBounds(); - if (Utilities.isRtl(getResources())) { - x = mBackgroundPadding.left + getScrollBarSize(); - } else { - x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width(); - } - y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height()); - y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() - - bgBounds.height())); - mFastScrollerBounds.set(x, y, x + bgBounds.width(), y + bgBounds.height()); - } else { - mFastScrollerBounds.setEmpty(); - } - } - - /** - * Returns the row index for a app index in the list. - */ - private int findRowForAppIndex(int index) { - List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); - int appIndex = 0; - int rowCount = 0; - for (AlphabeticalAppsList.SectionInfo info : sections) { - int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); - if (appIndex + info.numApps > index) { - return rowCount + ((index - appIndex) / mNumAppsPerRow); - } - appIndex += info.numApps; - rowCount += numRowsInSection; - } - return appIndex; - } - - /** - * Returns the total number of rows in the list. - */ - private int getNumRows() { - List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections(); - int rowCount = 0; - for (AlphabeticalAppsList.SectionInfo info : sections) { - int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow); - rowCount += numRowsInSection; - } - return rowCount; + verticalScrollbarBounds.setEmpty(); } /** diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 3d1503d46..7a9dfa16e 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -10,6 +10,8 @@ import com.android.launcher3.Launcher; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.model.AppNameComparator; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -130,46 +132,63 @@ public class AlphabeticalAppsList { * Common interface for different merging strategies. */ private interface MergeAlgorithm { - boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount); + boolean continueMerging(SectionInfo section, SectionInfo withSection, + int sectionAppCount, int numAppsPerRow, int mergeCount); } /** - * The logic we use to merge sections on tablets. + * The logic we use to merge sections on tablets. Currently, we don't show section names on + * tablet layouts, so just merge all the sections indiscriminately. */ private static class TabletMergeAlgorithm implements MergeAlgorithm { @Override - public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) { + public boolean continueMerging(SectionInfo section, SectionInfo withSection, + int sectionAppCount, int numAppsPerRow, int mergeCount) { // Merge EVERYTHING return true; } } /** - * The logic we use to merge sections on phones. + * The logic we use to merge sections on phones. We only merge sections when their final row + * contains less than a certain number of icons, and stop at a specified max number of merges. + * In addition, we will try and not merge sections that identify apps from different scripts. */ private static class PhoneMergeAlgorithm implements MergeAlgorithm { private int mMinAppsPerRow; private int mMinRowsInMergedSection; private int mMaxAllowableMerges; + private CharsetEncoder mAsciiEncoder; public PhoneMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) { mMinAppsPerRow = minAppsPerRow; mMinRowsInMergedSection = minRowsInMergedSection; mMaxAllowableMerges = maxNumMerges; + mAsciiEncoder = StandardCharsets.US_ASCII.newEncoder(); } @Override - public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) { + public boolean continueMerging(SectionInfo section, SectionInfo withSection, + int sectionAppCount, int numAppsPerRow, int mergeCount) { // Continue merging if the number of hanging apps on the final row is less than some // fixed number (ragged), the merged rows has yet to exceed some minimum row count, // and while the number of merged sections is less than some fixed number of merges int rows = sectionAppCount / numAppsPerRow; int cols = sectionAppCount % numAppsPerRow; + + // Ensure that we do not merge across scripts, currently we only allow for english and + // native scripts so we can test if both can just be ascii encoded + boolean isCrossScript = false; + if (section.firstAppItem != null && withSection.firstAppItem != null) { + isCrossScript = mAsciiEncoder.canEncode(section.firstAppItem.sectionName) != + mAsciiEncoder.canEncode(withSection.firstAppItem.sectionName); + } return (0 < cols && cols < mMinAppsPerRow) && rows < mMinRowsInMergedSection && - mergeCount < mMaxAllowableMerges; + mergeCount < mMaxAllowableMerges && + !isCrossScript; } } @@ -521,15 +540,16 @@ public class AlphabeticalAppsList { // Go through each section and try and merge some of the sections if (AllAppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) { int sectionAppCount = 0; - for (int i = 0; i < mSections.size(); i++) { + for (int i = 0; i < mSections.size() - 1; i++) { SectionInfo section = mSections.get(i); + SectionInfo nextSection = mSections.get(i + 1); sectionAppCount = section.numApps; int mergeCount = 1; // Merge rows based on the current strategy - while (mMergeAlgorithm.continueMerging(sectionAppCount, mNumAppsPerRow, mergeCount) && - (i + 1) < mSections.size()) { - SectionInfo nextSection = mSections.remove(i + 1); + while (mMergeAlgorithm.continueMerging(section, nextSection, sectionAppCount, + mNumAppsPerRow, mergeCount)) { + nextSection = mSections.remove(i + 1); // Remove the next section break mAdapterItems.remove(nextSection.sectionBreakItem); diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 05e842e71..11c2107f2 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -35,7 +35,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragController; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.Folder; import com.android.launcher3.IconCache; import com.android.launcher3.ItemInfo; @@ -46,6 +45,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.Workspace; +import com.android.launcher3.model.WidgetsModel; /** * The widgets list view container. @@ -56,8 +56,6 @@ public class WidgetsContainerView extends BaseContainerView private static final String TAG = "WidgetsContainerView"; private static final boolean DEBUG = false; - private static final int SPRING_MODE_DELAY_MS = 150; - /* Coefficient multiplied to the screen height for preloading widgets. */ private static final int PRELOAD_SCREEN_HEIGHT_MULTIPLE = 1; @@ -67,7 +65,7 @@ public class WidgetsContainerView extends BaseContainerView private IconCache mIconCache; /* Recycler view related member variables */ - private RecyclerView mView; + private WidgetsRecyclerView mView; private WidgetsListAdapter mAdapter; /* Touch handling related member variables. */ @@ -102,7 +100,7 @@ public class WidgetsContainerView extends BaseContainerView @Override protected void onFinishInflate() { - mView = (RecyclerView) findViewById(R.id.widgets_list_view); + mView = (WidgetsRecyclerView) findViewById(R.id.widgets_list_view); mView.setAdapter(mAdapter); // This extends the layout space so that preloading happen for the {@link RecyclerView} @@ -186,18 +184,11 @@ public class WidgetsContainerView extends BaseContainerView Log.e(TAG, "Unexpected dragging view: " + v); } - // We delay entering spring-loaded mode slightly to make sure the UI - // thread is free of any work. - postDelayed(new Runnable() { - @Override - public void run() { - // We don't enter spring-loaded mode if the drag has been cancelled - if (mLauncher.getDragController().isDragging()) { - // Go into spring loaded mode (must happen before we startDrag()) - mLauncher.enterSpringLoadedDragMode(); - } - } - }, SPRING_MODE_DELAY_MS); + // We don't enter spring-loaded mode if the drag has been cancelled + if (mLauncher.getDragController().isDragging()) { + // Go into spring loaded mode (must happen before we startDrag()) + mLauncher.enterSpringLoadedDragMode(); + } return true; } @@ -360,6 +351,7 @@ public class WidgetsContainerView extends BaseContainerView * Initialize the widget data model. */ public void addWidgets(WidgetsModel model) { + mView.setWidgets(model); mAdapter.setWidgetsModel(model); mAdapter.notifyDataSetChanged(); } diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index 7439a44f8..e82c0a631 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -32,7 +32,6 @@ import android.widget.LinearLayout; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.IconCache; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java index 31ef5d6fc..bef255908 100644 --- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java @@ -17,14 +17,23 @@ package com.android.launcher3.widget; import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; +import android.view.MotionEvent; + import com.android.launcher3.BaseRecyclerView; +import com.android.launcher3.model.WidgetsModel; /** * The widgets recycler view. */ public class WidgetsRecyclerView extends BaseRecyclerView { + private WidgetsModel mWidgets; + private Rect mBackgroundPadding = new Rect(); + public WidgetsRecyclerView(Context context) { this(context, null); } @@ -37,4 +46,67 @@ public class WidgetsRecyclerView extends BaseRecyclerView { super(context, attrs, defStyleAttr); } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + addOnItemTouchListener(this); + } + + public void updateBackgroundPadding(Drawable background) { + background.getPadding(mBackgroundPadding); + } + + /** + * Sets the widget model in this view, used to determine the fast scroll position. + */ + public void setWidgets(WidgetsModel widgets) { + mWidgets = widgets; + } + + /** + * Maps the touch (from 0..1) to the adapter position that should be visible. + */ + @Override + public String scrollToPositionAtProgress(float touchFraction) { + // Ensure that we have any sections + return ""; + } + + /** + * Updates the bounds for the scrollbar. + */ + @Override + public void updateVerticalScrollbarBounds() { + int rowCount = mWidgets.getPackageSize(); + + // Skip early if there are no items. + if (rowCount == 0) { + verticalScrollbarBounds.setEmpty(); + return; + } + + int x, y; + getCurScrollState(scrollPosState); + if (scrollPosState.rowIndex < 0) { + verticalScrollbarBounds.setEmpty(); + } + // TODO + } + + /** + * Returns the current scroll state. + */ + private void getCurScrollState(ScrollPositionState stateOut) { + stateOut.rowIndex = -1; + stateOut.rowTopOffset = -1; + stateOut.rowHeight = -1; + + int rowCount = mWidgets.getPackageSize(); + + // Return early if there are no items + if (rowCount == 0) { + return; + } + // TODO + } }
\ No newline at end of file |