diff options
Diffstat (limited to 'src/com/android')
33 files changed, 1039 insertions, 546 deletions
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index a03e6081a..ca8ae255c 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -60,6 +60,7 @@ public class AppInfo extends ItemInfo { public static final int DOWNLOADED_FLAG = 1; static final int UPDATED_SYSTEM_APP_FLAG = 2; + static final int REMOTE_APP_FLAG = 4; public int flags = 0; @@ -91,6 +92,15 @@ public class AppInfo extends ItemInfo { this.user = user; } + public AppInfo(Intent intent, String title, UserHandleCompat user) { + this.componentName = intent.getComponent(); + this.container = ItemInfo.NO_ID; + + this.intent = intent; + this.title = title; + this.user = user; + } + public static int initFlags(LauncherActivityInfoCompat info) { int appFlags = info.getApplicationInfo().flags; int flags = 0; @@ -114,6 +124,23 @@ public class AppInfo extends ItemInfo { iconBitmap = info.iconBitmap; } + /** + * Check if this app has a specific flag. + * @param flag flag to check. + * @return true if the flag is present, false otherwise. + */ + public boolean hasFlag(int flag) { + return (flags & flag) != 0; + } + + /** + * Set a flag for this app + * @param flag flag to apply. + */ + public void setFlag(int flag) { + flags |= flag; + } + @Override public String toString() { return "ApplicationInfo(title=" + title + " id=" + this.id @@ -152,4 +179,18 @@ public class AppInfo extends ItemInfo { .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) .putExtra(EXTRA_PROFILE, serialNumber); } + + @Override + public boolean equals(Object o) { + if (o != null && o instanceof AppInfo) { + return componentName.equals(((AppInfo) o).componentName); + } else { + return false; + } + } + + @Override + public int hashCode() { + return componentName.hashCode(); + } } diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index 77925b5b3..cdf9aa2aa 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -77,7 +77,7 @@ public abstract class BaseRecyclerView extends RecyclerView mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP; ScrollListener listener = new ScrollListener(); - setOnScrollListener(listener); + addOnScrollListener(listener); } private class ScrollListener extends OnScrollListener { @@ -286,8 +286,7 @@ public abstract class BaseRecyclerView extends RecyclerView // Calculate the current scroll position, the scrollY of the recycler view accounts for the // view padding, while the scrollBarY is drawn right up to the background padding (ignoring // padding) - int scrollY = getPaddingTop() + - (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset; + int scrollY = getCurrentScroll(scrollPosState); int scrollBarY = mBackgroundPadding.top + (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight); @@ -302,6 +301,15 @@ public abstract class BaseRecyclerView extends RecyclerView } /** + * @param scrollPosState current state of view scrolling. + * @return the vertical scroll position + */ + protected int getCurrentScroll(ScrollPositionState scrollPosState) { + return getPaddingTop() + (scrollPosState.rowIndex * scrollPosState.rowHeight) - + scrollPosState.rowTopOffset; + } + + /** * Maps the touch (from 0..1) to the adapter position that should be visible. * <p>Override in each subclass of this base class. */ diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java index e7b79927a..e70be9989 100644 --- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java +++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java @@ -18,6 +18,7 @@ package com.android.launcher3; import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.content.res.Resources; import android.graphics.Canvas; @@ -27,8 +28,11 @@ import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; import android.view.MotionEvent; +import android.view.View; import android.view.ViewConfiguration; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import com.android.launcher3.util.Thunk; /** @@ -36,11 +40,130 @@ import com.android.launcher3.util.Thunk; */ public class BaseRecyclerViewFastScrollBar { - public interface FastScrollFocusableView { + public interface FastScrollFocusable { + int FAST_SCROLL_FOCUS_DIMMABLE = 1; + int FAST_SCROLL_FOCUS_SCALABLE = 2; + void setFastScrollFocused(boolean focused, boolean animated); void setFastScrollDimmed(boolean dimmed, boolean animated); } + /** + * Helper class to apply fast scroll focus functionality to any view. + */ + public static class FastScrollFocusApplicator implements FastScrollFocusable { + private static final int FAST_SCROLL_FOCUS_FADE_IN_DURATION = 175; + private static final int FAST_SCROLL_FOCUS_FADE_OUT_DURATION = 125; + private static final float FAST_SCROLL_FOCUS_MAX_SCALE = 1.15f; + + private final View mView; + private final int mFastScrollMode; + + private ObjectAnimator mFastScrollFocusAnimator; + private ObjectAnimator mFastScrollDimAnimator; + private boolean mFastScrollFocused; + private boolean mFastScrollDimmed; + + public static void createApplicator(final View v, int mode) { + FastScrollFocusApplicator applicator = new FastScrollFocusApplicator(v, mode); + v.setTag(R.id.fast_scroll_focus_applicator_tag, applicator); + } + + public static void setFastScrollFocused(final View v, boolean focused, boolean animated) { + FastScrollFocusable focusable = getFromView(v); + if (focusable == null) return; + + focusable.setFastScrollFocused(focused, animated); + } + + public static void setFastScrollDimmed(final View v, boolean dimmed, boolean animated) { + FastScrollFocusable focusable = getFromView(v); + if (focusable == null) return; + + focusable.setFastScrollDimmed(dimmed, animated); + } + + private static FastScrollFocusable getFromView(final View v) { + Object tag = v.getTag(R.id.fast_scroll_focus_applicator_tag); + if (tag != null) { + return (FastScrollFocusApplicator) tag; + } + return null; + } + + private FastScrollFocusApplicator(final View v, final int mode) { + mView = v; + mFastScrollMode = mode; + } + + public void setFastScrollFocused(boolean focused, boolean animated) { + if ((mFastScrollMode & FAST_SCROLL_FOCUS_SCALABLE) == 0) { + return; + } + + if (mFastScrollFocused != focused) { + mFastScrollFocused = focused; + + if (animated) { + // Clean up the previous focus animator + if (mFastScrollFocusAnimator != null) { + mFastScrollFocusAnimator.cancel(); + } + + // Setup animator for bi-directional scaling. + float value = focused ? FAST_SCROLL_FOCUS_MAX_SCALE : 1f; + PropertyValuesHolder pvhScaleX = + PropertyValuesHolder.ofFloat(View.SCALE_X, value); + PropertyValuesHolder pvhScaleY = + PropertyValuesHolder.ofFloat(View.SCALE_Y, value); + mFastScrollFocusAnimator = ObjectAnimator.ofPropertyValuesHolder(mView, + pvhScaleX, pvhScaleY); + + if (focused) { + mFastScrollFocusAnimator.setInterpolator(new DecelerateInterpolator()); + } else { + mFastScrollFocusAnimator.setInterpolator(new AccelerateInterpolator()); + } + mFastScrollFocusAnimator.setDuration(focused ? + FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION); + mFastScrollFocusAnimator.start(); + } + } + + // Let the view do any additional operations if it wants. + if (mView instanceof FastScrollFocusable) { + ((FastScrollFocusable) mView).setFastScrollFocused(focused, animated); + } + } + + public void setFastScrollDimmed(boolean dimmed, boolean animated) { + if ((mFastScrollMode & FAST_SCROLL_FOCUS_DIMMABLE) == 0) { + return; + } + + if (!animated) { + mFastScrollDimmed = dimmed; + mView.setAlpha(dimmed ? 0.4f : 1f); + } else if (mFastScrollDimmed != dimmed) { + mFastScrollDimmed = dimmed; + + // Clean up the previous dim animator + if (mFastScrollDimAnimator != null) { + mFastScrollDimAnimator.cancel(); + } + mFastScrollDimAnimator = ObjectAnimator.ofFloat(mView, View.ALPHA, dimmed ? 0.4f : 1f); + mFastScrollDimAnimator.setDuration(dimmed ? + FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION); + mFastScrollDimAnimator.start(); + } + + // Let the view do any additional operations if it wants. + if (mView instanceof FastScrollFocusable) { + ((FastScrollFocusable) mView).setFastScrollDimmed(dimmed, animated); + } + } + } + private final static int MAX_TRACK_ALPHA = 30; private final static int SCROLL_BAR_VIS_DURATION = 150; diff --git a/src/com/android/launcher3/BaseRecyclerViewScrubber.java b/src/com/android/launcher3/BaseRecyclerViewScrubber.java index 4299b4457..e3e08aa7c 100644 --- a/src/com/android/launcher3/BaseRecyclerViewScrubber.java +++ b/src/com/android/launcher3/BaseRecyclerViewScrubber.java @@ -400,6 +400,9 @@ public class BaseRecyclerViewScrubber extends LinearLayout { } private void progressChanged(SeekBar seekBar, int index, boolean fromUser) { + if (!fromUser) { + return; + } sendAnimatePickMessage(index, seekBar.getWidth(), mLastIndex); diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index adc153f2e..facafd354 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -16,7 +16,6 @@ package com.android.launcher3; -import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; @@ -37,13 +36,10 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; import android.widget.TextView; import com.android.launcher3.IconCache.IconLoadRequest; import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.settings.SettingsProvider; /** * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan @@ -51,7 +47,7 @@ import com.android.launcher3.settings.SettingsProvider; * too aggressive. */ public class BubbleTextView extends TextView - implements BaseRecyclerViewFastScrollBar.FastScrollFocusableView { + implements BaseRecyclerViewFastScrollBar.FastScrollFocusable { private static SparseArray<Theme> sPreloaderThemes = new SparseArray<Theme>(2); @@ -64,12 +60,9 @@ public class BubbleTextView extends TextView private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; - private static final float FAST_SCROLL_FOCUS_MAX_SCALE = 1.15f; private static final int FAST_SCROLL_FOCUS_MODE_NONE = 0; private static final int FAST_SCROLL_FOCUS_MODE_SCALE_ICON = 1; private static final int FAST_SCROLL_FOCUS_MODE_DRAW_CIRCLE_BG = 2; - private static final int FAST_SCROLL_FOCUS_FADE_IN_DURATION = 175; - private static final int FAST_SCROLL_FOCUS_FADE_OUT_DURATION = 125; private final Launcher mLauncher; private Drawable mIcon; @@ -94,12 +87,8 @@ public class BubbleTextView extends TextView private boolean mIgnorePressedStateChange; private boolean mDisableRelayout = false; - private ObjectAnimator mFastScrollFocusAnimator; - private ObjectAnimator mFastScrollDimAnimator; private Paint mFastScrollFocusBgPaint; private float mFastScrollFocusFraction; - private boolean mFastScrollFocused; - private boolean mFastScrollDimmed; private final int mFastScrollMode = FAST_SCROLL_FOCUS_MODE_SCALE_ICON; private IconLoadRequest mIconLoadRequest; @@ -170,16 +159,21 @@ public class BubbleTextView extends TextView public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache, boolean promiseStateChanged) { - Bitmap b = info.getIcon(iconCache); + Drawable iconDrawable; + if (info.customDrawable != null) { + iconDrawable = info.customDrawable; + } else { + Bitmap b = info.getIcon(iconCache); - if (b.getWidth() > mIconSize || b.getHeight() > mIconSize) { - b = Bitmap.createScaledBitmap(b, mIconSize, mIconSize, false); - info.setIcon(b); - info.updateIcon(iconCache); - } + if (b.getWidth() > mIconSize || b.getHeight() > mIconSize) { + b = Bitmap.createScaledBitmap(b, mIconSize, mIconSize, false); + info.setIcon(b); + info.updateIcon(iconCache); + } - FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b); - iconDrawable.setGhostModeEnabled(info.isDisabled != 0); + iconDrawable = mLauncher.createIconDrawable(b); + ((FastBitmapDrawable) iconDrawable).setGhostModeEnabled(info.isDisabled != 0); + } setIcon(iconDrawable, mIconSize); if (info.contentDescription != null) { @@ -194,7 +188,13 @@ public class BubbleTextView extends TextView } public void applyFromApplicationInfo(AppInfo info) { - setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize); + Drawable iconDrawable; + if (info.customDrawable != null) { + iconDrawable = info.customDrawable; + } else { + iconDrawable = mLauncher.createIconDrawable(info.iconBitmap); + } + setIcon(iconDrawable, mIconSize); setText(info.title); if (info.contentDescription != null) { setContentDescription(info.contentDescription); @@ -567,6 +567,11 @@ public class BubbleTextView extends TextView * Verifies that the current icon is high-res otherwise posts a request to load the icon. */ public void verifyHighRes() { + // Custom drawables cannot be verified. + if (getTag() instanceof ItemInfo && ((ItemInfo) getTag()).customDrawable != null) { + return; + } + if (mIconLoadRequest != null) { mIconLoadRequest.cancel(); mIconLoadRequest = null; @@ -592,73 +597,20 @@ public class BubbleTextView extends TextView } } - // Setters & getters for the animation - public void setFastScrollFocus(float fraction) { - mFastScrollFocusFraction = fraction; - if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_SCALE_ICON) { - setScaleX(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f)); - setScaleY(1f + fraction * (FAST_SCROLL_FOCUS_MAX_SCALE - 1f)); - } else { - invalidate(); - } - } - - public float getFastScrollFocus() { - return mFastScrollFocusFraction; - } - @Override public void setFastScrollFocused(final boolean focused, boolean animated) { if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) { return; } - if (mFastScrollFocused != focused) { - mFastScrollFocused = focused; - - if (animated) { - // Clean up the previous focus animator - if (mFastScrollFocusAnimator != null) { - mFastScrollFocusAnimator.cancel(); - } - mFastScrollFocusAnimator = ObjectAnimator.ofFloat(this, "fastScrollFocus", - focused ? 1f : 0f); - if (focused) { - mFastScrollFocusAnimator.setInterpolator(new DecelerateInterpolator()); - } else { - mFastScrollFocusAnimator.setInterpolator(new AccelerateInterpolator()); - } - mFastScrollFocusAnimator.setDuration(focused ? - FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION); - mFastScrollFocusAnimator.start(); - } else { - mFastScrollFocusFraction = focused ? 1f : 0f; - } + if (!animated) { + mFastScrollFocusFraction = focused ? 1f : 0f; } } @Override public void setFastScrollDimmed(boolean dimmed, boolean animated) { - if (mFastScrollMode == FAST_SCROLL_FOCUS_MODE_NONE) { - return; - } - - if (!animated) { - mFastScrollDimmed = dimmed; - setAlpha(dimmed ? 0.4f : 1f); - } else if (mFastScrollDimmed != dimmed) { - mFastScrollDimmed = dimmed; - - // Clean up the previous dim animator - if (mFastScrollDimAnimator != null) { - mFastScrollDimAnimator.cancel(); - } - mFastScrollDimAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, - dimmed ? 0.4f : 1f); - mFastScrollDimAnimator.setDuration(dimmed ? - FAST_SCROLL_FOCUS_FADE_IN_DURATION : FAST_SCROLL_FOCUS_FADE_OUT_DURATION); - mFastScrollDimAnimator.start(); - } + // No special functionality here. } /** diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index daf26411d..10599fa5e 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -2224,7 +2224,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mLauncher.getWorkspace().updateItemLocationsInDatabase(this); } - private void setUseTempCoords(boolean useTempCoords) { + public void setUseTempCoords(boolean useTempCoords) { int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams(); diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java index 483c62249..bd1d84128 100644 --- a/src/com/android/launcher3/CheckLongPressHelper.java +++ b/src/com/android/launcher3/CheckLongPressHelper.java @@ -30,6 +30,8 @@ public class CheckLongPressHelper { class CheckForLongPress implements Runnable { public void run() { + if (!mView.isLongClickable()) return; + if ((mView.getParent() != null) && mView.hasWindowFocus() && !mHasPerformedLongPress) { boolean handled; diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 9c8659c29..f51a43a91 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -74,8 +74,14 @@ public class DeleteDropTarget extends ButtonDropTarget { LauncherModel.deleteItemFromDatabase(launcher, item); } else if (item instanceof FolderInfo) { FolderInfo folder = (FolderInfo) item; - launcher.removeFolder(folder); - LauncherModel.deleteFolderContentsFromDatabase(launcher, folder); + + // Remote folder should not really be deleted. Let the manager handle it. + if (folder.isRemote()) { + launcher.getRemoteFolderManager().onFolderDeleted(); + } else { + launcher.removeFolder(folder); + LauncherModel.deleteFolderContentsFromDatabase(launcher, folder); + } } else if (item instanceof LauncherAppWidgetInfo) { final LauncherAppWidgetInfo widget = (LauncherAppWidgetInfo) item; diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java index 1c18747c1..03c5a1491 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/DragLayer.java @@ -823,6 +823,7 @@ public class DragLayer extends InsettableFrameLayout { @Override public void onChildViewRemoved(View parent, View child) { + super.onChildViewRemoved(parent, child); updateChildIndices(); } diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index ccb12b62a..bcc274d9e 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -227,10 +227,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mContent = (FolderPagedView) findViewById(R.id.folder_content); mContent.setFolder(this); - // 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; - mFolderName = (ExtendedEditText) findViewById(R.id.folder_name); mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() { @Override @@ -256,14 +252,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mFolderName.setVisibility(View.GONE); } mFolderLock = (ImageView) findViewById(R.id.folder_lock); - mFolderLock.measure(measureSpec, measureSpec); mFolderLock.setOnClickListener(this); - mFolderLockHeight = mFolderLock.getMeasuredHeight(); mFooter = findViewById(R.id.folder_footer); - - mFooter.measure(measureSpec, measureSpec); - mFooterHeight = mFooter.getMeasuredHeight(); } private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { @@ -458,7 +449,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return mInfo; } - void bind(FolderInfo info) { + void bind(final FolderInfo info) { mInfo = info; ArrayList<ShortcutInfo> children = info.contents; Collections.sort(children, ITEM_POS_COMPARATOR); @@ -473,14 +464,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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); @@ -509,12 +492,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList * Creates a new UserFolder, inflated from R.layout.user_folder. * * @param context The application's context. + * @param root The {@link View} parent of this folder. * * @return A new UserFolder. */ @SuppressLint("InflateParams") - static Folder fromXml(Launcher launcher) { - return (Folder) launcher.getLayoutInflater().inflate(R.layout.user_folder, null); + static Folder fromXml(Launcher launcher, ViewGroup root) { + return (Folder) launcher.getLayoutInflater().inflate(R.layout.user_folder, root, false); } /** @@ -542,6 +526,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } private void prepareFakeFolderIcon() { + mFolderIcon.destroyDrawingCache(); mFolderIcon.buildDrawingCache(true); Bitmap fakeFolderIcon = Bitmap.createBitmap(mFolderIcon.getDrawingCache()); @@ -568,7 +553,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList fakeFolderIconView.setVisibility(View.INVISIBLE); } - public void animateOpen(Workspace workspace, int[] folderTouch) { + public void animateOpen() { if (!(getParent() instanceof DragLayer)) return; mContent.completePendingPageChanges(); @@ -581,7 +566,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList final Runnable onCompleteRunnable; if (!Utilities.ATLEAST_LOLLIPOP) { positionAndSizeAsIcon(); - centerAboutIcon(); + calculatePivot(); PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); @@ -599,11 +584,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } }; } else { - centerAboutIcon(); + calculatePivot(); AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); - int height = getFolderHeight(); + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); float transX = - 0.075f * (width / 2 - getPivotX()); float transY = - 0.075f * (height / 2 - getPivotY()); @@ -1156,7 +1141,16 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // Do nothing } + /** + * @return true if contents should persist their status to the database. + */ + protected boolean shouldUpdateContentsInDatabase() { + return true; + } + private void updateItemLocationsInDatabaseBatch() { + if (!shouldUpdateContentsInDatabase()) return; + ArrayList<View> list = getItemsInReadingOrder(); ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); for (int i = 0; i < list.size(); i++) { @@ -1170,6 +1164,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } public void addItemLocationsInDatabase() { + if (!shouldUpdateContentsInDatabase()) return; + ArrayList<View> list = getItemsInReadingOrder(); for (int i = 0; i < list.size(); i++) { View v = list.get(i); @@ -1193,12 +1189,22 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return mContent.isFull(); } - private void centerAboutIcon() { - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + protected void calculatePivot() { + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + + // If we haven't measured ourselves yet, force one now to determine our dimensions. + if (width <= 0 && height <= 0) { + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + width = getMeasuredWidth(); + height = getMeasuredHeight(); + } + + calculatePivot(width, height); + } + private void calculatePivot(int width, int height) { DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); - int height = getFolderHeight(); float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect); @@ -1234,11 +1240,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList (1.0f * folderPivotX / width)); mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * (1.0f * folderPivotY / height)); - - lp.width = width; - lp.height = height; - lp.x = left; - lp.y = top; } float getPivotXForIconAnimation() { @@ -1248,53 +1249,18 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return mFolderIconPivotY; } - private int getContentAreaHeight() { - DeviceProfile grid = mLauncher.getDeviceProfile(); - Rect workspacePadding = grid.getWorkspacePadding(mContent.mIsRtl); - int maxContentAreaHeight = grid.availableHeightPx - - workspacePadding.top - workspacePadding.bottom - - mFooterHeight; - int height = Math.min(maxContentAreaHeight, - mContent.getDesiredHeight()); - return Math.max(height, MIN_CONTENT_DIMEN); + protected int getContentAreaHeight() { + return Math.max(mContent.getDesiredHeight(), MIN_CONTENT_DIMEN); } - private int getContentAreaWidth() { + protected int getContentAreaWidth() { return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN); } - private int getFolderHeight() { - return getFolderHeight(getContentAreaHeight()); - } - - private int getFolderHeight(int contentAreaHeight) { - return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight; - } - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int contentWidth = getContentAreaWidth(); - int contentHeight = getContentAreaHeight(); - - int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY); - int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY); + mContent.setFixedSize(getContentAreaWidth(), getContentAreaHeight()); - mContent.setFixedSize(contentWidth, contentHeight); - mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec); - - if (mContent.getChildCount() > 0) { - int cellIconGap = (mContent.getPageAt(0).getCellWidth() - - mLauncher.getDeviceProfile().iconSizePx) / 2; - mFooter.setPadding(mContent.getPaddingLeft() + cellIconGap, - mFooter.getPaddingTop(), - mContent.getPaddingRight() + cellIconGap, - mFooter.getPaddingBottom()); - } - mFooter.measure(contentAreaWidthSpec, - MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY)); - - int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth; - int folderHeight = getFolderHeight(contentHeight); - setMeasuredDimension(folderWidth, folderHeight); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** @@ -1445,8 +1411,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList 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( - mLauncher, si, mInfo.id, 0, si.cellX, si.cellY); + if (shouldUpdateContentsInDatabase()) { + LauncherModel.addOrMoveItemInDatabase( + mLauncher, si, mInfo.id, 0, si.cellX, si.cellY); + } // We only need to update the locations if it doesn't get handled in #onDropCompleted. if (d.dragSource != this) { @@ -1509,8 +1477,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (mSuppressOnAdd) return; mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem(item)); mItemsInvalidated = true; - LauncherModel.addOrMoveItemInDatabase( - mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); + + if (shouldUpdateContentsInDatabase()) { + LauncherModel.addOrMoveItemInDatabase( + mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); + } } public void onRemove(ShortcutInfo item) { @@ -1518,8 +1489,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // If this item is being dragged from this open folder, we have already handled // 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.removeItem(v); + mContent.removeItem(getViewForInfo(item)); if (mState == STATE_ANIMATING) { mRearrangeOnClose = true; } else { @@ -1530,14 +1500,64 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } } - private View getViewForInfo(final ShortcutInfo item) { - return mContent.iterateOverItems(new ItemOperator() { + @Override + public void onRemoveAll() { + // Clear the UX after folder contents are removed from the DB + removeViewsForItems(null); + mLauncher.closeFolder(this); + replaceFolderWithFinalItem(); + } - @Override - public boolean evaluate(ItemInfo info, View view, View parent) { - return info == item; + @Override + public void onRemoveAll(ArrayList<ShortcutInfo> items) { + removeViewsForItems(items); + if (mState == STATE_ANIMATING) { + mRearrangeOnClose = true; + } else { + rearrangeChildren(); + } + if (mInfo.contents.isEmpty()) { + mLauncher.closeFolder(this); + } + replaceFolderWithFinalItem(); + } + + /** + * Remove all the supplied item views from this folder. + * @param items info of views to remove, or null if all views should be removed. + */ + protected void removeViewsForItems(ArrayList<ShortcutInfo> items) { + mItemsInvalidated = true; + if (items == null) { + mContent.removeAllItems(); + } else { + for (ShortcutInfo item : items) { + mContent.removeItem(getViewForInfo(item)); } - }); + } + } + + /** + * Update the view tied to this shortcut. + * @param info updated info to be applied to view. + */ + @SuppressWarnings("unused") + public void updateViewForInfo(final ShortcutInfo info) { + View v = getViewForInfo(info); + if (v != null & v instanceof BubbleTextView) { + ((BubbleTextView) v).reapplyItemInfo(info); + + mItemsInvalidated = true; + } + } + + public View getViewForInfo(ShortcutInfo item) { + View v = mContent.getChildAtRank(item.rank); + if (v != null && v.getTag() == item) { + return v; + } + + return null; } public void onItemsChanged() { diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index 9b6e3026b..356c2754b 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -174,7 +174,16 @@ public class FolderIcon extends FrameLayout implements FolderListener { icon.mLauncher = launcher; icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format), folderInfo.title)); - Folder folder = Folder.fromXml(launcher); + Folder folder; + if (folderInfo.isRemote()) { + folder = launcher.getRemoteFolderManager().createRemoteFolder(icon, launcher.getDragLayer()); + if (folder == null) { + LauncherModel.deleteItemFromDatabase(launcher, folderInfo); + return null; + } + } else { + folder = Folder.fromXml(launcher, launcher.getDragLayer()); + } folder.setDragController(launcher.getDragController()); folder.setFolderIcon(icon); folder.bind(folderInfo); @@ -236,6 +245,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { } } + // Create an overlay badge if this FolderIcon is for a RemoteFolder + if (folderInfo.isRemote()) { + icon = RemoteFolderManager.addBadgeToFolderIcon(icon); + } + return icon; } @@ -377,10 +391,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { } private boolean willAcceptItem(ItemInfo item) { + if (mInfo.isRemote()) return false; + final int itemType = item.itemType; boolean hidden = false; if (item instanceof FolderInfo){ + if (((FolderInfo) item).isRemote()) return false; + hidden = ((FolderInfo) item).hidden; } return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || @@ -686,7 +704,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { super.dispatchDraw(canvas); if (mFolder == null) return; - if (mFolder.getItemCount() == 0 && !mAnimating) return; + if (mFolder.getItemCount() == 0 && !mAnimating && !mInfo.isRemote()) return; ArrayList<View> items = mFolder.getItemsInReadingOrder(); Drawable d; @@ -695,13 +713,13 @@ public class FolderIcon extends FrameLayout implements FolderListener { // Update our drawing parameters if necessary if (mAnimating) { computePreviewDrawingParams(mAnimParams.drawable); - } else { + } else if (!items.isEmpty()) { v = (TextView) items.get(0); d = getTopDrawable(v); - computePreviewDrawingParams(d); + if (d != null) computePreviewDrawingParams(d); } - int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); + int ntemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); // Hidden folder - don't display Preview View folderLock = findViewById(R.id.folder_lock_image); @@ -720,17 +738,22 @@ public class FolderIcon extends FrameLayout implements FolderListener { } if (!mAnimating) { - for (int i = NUM_ITEMS_IN_PREVIEW; i >= 0; i--) { + for (int i = 0; i < NUM_ITEMS_IN_PREVIEW; i++) { d = null; - if (i < items.size()) { + if (mInfo.isRemote()) { + d = mLauncher.getRemoteFolderManager().getFolderIconDrawable(items, i); + } else if (i < items.size()) { v = (TextView) items.get(i); if (!mHiddenItems.contains(v.getTag())) { d = getTopDrawable(v); - mParams = computePreviewItemDrawingParams(i, mParams); - mParams.drawable = d; } } + if (d != null) { + mParams = computePreviewItemDrawingParams(i, mParams); + mParams.drawable = d; + } + ImageView appIcon = null; switch(i) { case 0: @@ -829,6 +852,18 @@ public class FolderIcon extends FrameLayout implements FolderListener { requestLayout(); } + @Override + public void onRemoveAll() { + invalidate(); + requestLayout(); + } + + @Override + public void onRemoveAll(ArrayList<ShortcutInfo> items) { + invalidate(); + requestLayout(); + } + public void onTitleChanged(CharSequence title) { mFolderName.setText(title); setContentDescription(String.format(getContext().getString(R.string.folder_name_format), diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index a1147408f..7969d627b 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -28,6 +28,7 @@ import java.util.Arrays; * Represents a folder containing shortcuts or apps. */ public class FolderInfo extends ItemInfo { + public static final int REMOTE_SUBTYPE = 1; public static final int NO_FLAGS = 0x00000000; @@ -50,6 +51,7 @@ public class FolderInfo extends ItemInfo { * Whether this folder has been opened */ boolean opened; + int subType; public int options; @@ -92,6 +94,54 @@ public class FolderInfo extends ItemInfo { itemsChanged(); } + /** + * Remove all apps and shortcuts. Does not change the DB unless + * LauncherModel.deleteFolderContentsFromDatabase(Context, FolderInfo) is called first. + */ + public void removeAll() { + if (contents.isEmpty()) return; + + contents.clear(); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onRemoveAll(); + } + itemsChanged(); + } + + /** + * Remove all supplied shortcuts. Does not change the DB unless + * LauncherModel.deleteFolderContentsFromDatabase(Context, FolderInfo) is called first. + * @param items the shortcuts to remove. + */ + public void removeAll(ArrayList<ShortcutInfo> items) { + if (items.isEmpty()) return; + + contents.removeAll(items); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onRemoveAll(items); + } + itemsChanged(); + } + + /** + * @return true if this info represents a remote folder, false otherwise + */ + public boolean isRemote() { + return (subType & REMOTE_SUBTYPE) != 0; + } + + /** + * Set flag indicating whether this folder is remote + * @param remote true if folder is remote, false otherwise + */ + public void setRemote(final boolean remote) { + if (remote) { + subType |= REMOTE_SUBTYPE; + } else { + subType &= ~REMOTE_SUBTYPE; + } + } + public void setTitle(CharSequence title) { this.title = title; for (int i = 0; i < listeners.size(); i++) { @@ -105,6 +155,7 @@ public class FolderInfo extends ItemInfo { values.put(LauncherSettings.Favorites.TITLE, title.toString()); values.put(LauncherSettings.Favorites.OPTIONS, options); values.put(LauncherSettings.Favorites.HIDDEN, hidden ? 1 : 0); + values.put(LauncherSettings.BaseLauncherColumns.SUBTYPE, subType); } void addListener(FolderListener listener) { @@ -130,15 +181,17 @@ public class FolderInfo extends ItemInfo { } interface FolderListener { - public void onAdd(ShortcutInfo item); - public void onRemove(ShortcutInfo item); - public void onTitleChanged(CharSequence title); - public void onItemsChanged(); + void onAdd(ShortcutInfo item); + void onRemove(ShortcutInfo item); + void onRemoveAll(); + void onRemoveAll(ArrayList<ShortcutInfo> items); + void onTitleChanged(CharSequence title); + void onItemsChanged(); } @Override public String toString() { - return "FolderInfo(id=" + this.id + " type=" + this.itemType + return "FolderInfo(id=" + this.id + " type=" + this.itemType + " subtype=" + this.subType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java index a7940d552..5d5ac3d5a 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/FolderPagedView.java @@ -114,7 +114,7 @@ public class FolderPagedView extends PagedView { * 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) { + public void setupContentDimensions(int count) { mAllocatedContentSize = count; boolean done; if (count >= mMaxItemsPerPage) { @@ -274,6 +274,51 @@ public class FolderPagedView extends PagedView { } } + public void removeAllItems() { + for (int i = 0; i < getChildCount(); i++) { + getPageAt(i).removeAllViews(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (getChildCount() == 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } else { + // We should only be as large as our pages, so measure all of them first. + View page = null; + for (int i = 0; i < getChildCount(); i++) { + page = getChildAt(i); + page.measure(widthMeasureSpec, heightMeasureSpec); + } + + // And then set ourselves to their size. + int width = getPaddingLeft() + page.getMeasuredWidth() + getPaddingRight(); + int height = getPaddingTop() + page.getMeasuredHeight() + getPaddingBottom(); + mViewport.set(0, 0, width, height); + setMeasuredDimension(width, height); + } + } + + /** + * Find the child view for the given rank. + * @param rank sorted index of child. + * @return view of child at given rank. + */ + public View getChildAtRank(int rank) { + int pagePos = rank % mMaxItemsPerPage; + int pageNo = rank / mMaxItemsPerPage; + int cellX = pagePos % mGridCountX; + int cellY = pagePos / mGridCountX; + + CellLayout page = getPageAt(pageNo); + if (page != null) { + return page.getChildAt(cellX, cellY); + } else { + return null; + } + } + /** * 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 diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java index 6400a0f89..770d1de1a 100644 --- a/src/com/android/launcher3/InsettableFrameLayout.java +++ b/src/com/android/launcher3/InsettableFrameLayout.java @@ -101,6 +101,7 @@ public class InsettableFrameLayout extends FrameLayout implements @Override public void onChildViewRemoved(View parent, View child) { + setFrameLayoutChildInsets(child, new Rect(), mInsets); } } diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index c7729ffe6..b30c954f5 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -20,6 +20,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; @@ -127,6 +128,12 @@ public class ItemInfo { public UserHandleCompat user; + /** + * A custom drawable to use for the icon. Not persisted to the database because + * it is not guaranteed to be a bitmap (could be a vector). + */ + Drawable customDrawable; + public ItemInfo() { user = UserHandleCompat.myUserHandle(); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 3454280b8..3b31b3ac5 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -112,7 +112,6 @@ import com.android.launcher3.list.SettingsPinnedHeaderAdapter; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.stats.LauncherStats; -import com.android.launcher3.stats.internal.service.AggregationIntentService; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; @@ -266,6 +265,9 @@ public class Launcher extends Activity protected HiddenFolderFragment mHiddenFolderFragment; private DynamicGridSizeFragment mDynamicGridSizeFragment; + private static RemoteFolderManager sRemoteFolderManager; + private boolean mRemoteDrawerEnabled; + private AppWidgetManagerCompat mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; @@ -376,7 +378,7 @@ public class Launcher extends Activity // This is set to the view that launched the activity that navigated the user away from // launcher. Since there is no callback for when the activity has finished launching, enable // the press state and keep this reference to reset the press state when we return to launcher. - private BubbleTextView mWaitingForResume; + BubbleTextView mWaitingForResume; private long mDefaultScreenId; @@ -519,6 +521,14 @@ public class Launcher extends Activity mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); mAppWidgetHost.startListening(); + if (sRemoteFolderManager == null) { + sRemoteFolderManager = new RemoteFolderManager(this); + } else { + sRemoteFolderManager.onRecreateLauncher(this); + } + mRemoteDrawerEnabled = SettingsProvider.getBoolean(this, null, + R.bool.preferences_interface_homescreen_remote_folder_default); + // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here, // this also ensures that any synchronous binding below doesn't re-trigger another // LauncherModel load. @@ -1825,7 +1835,8 @@ public class Launcher extends Activity SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, R.bool.preferences_interface_homescreen_hide_icon_labels_default); mDefaultScreenId = SettingsProvider.getLongCustomDefault(this, - SettingsProvider.SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID, 1); + SettingsProvider.SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID, + R.integer.preferences_interface_homescreen_id_default); mModel = app.setLauncher(this); mIconCache = app.getIconCache(); @@ -1884,6 +1895,7 @@ public class Launcher extends Activity mAppsView.addApps(addedApps); tryAndUpdatePredictedApps(); mAppsView.reset(); + sRemoteFolderManager.onReloadAppDrawer(); } public void reloadWidgetView() { @@ -2148,6 +2160,10 @@ public class Launcher extends Activity return mWorkspace; } + public RemoteFolderManager getRemoteFolderManager() { + return sRemoteFolderManager; + } + public Hotseat getHotseat() { return mHotseat; } @@ -2719,10 +2735,14 @@ public class Launcher extends Activity } FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX, - int cellY) { - final FolderInfo folderInfo = new FolderInfo(); + int cellY) { + FolderInfo folderInfo = new FolderInfo(); folderInfo.title = getText(R.string.folder_name); + return addFolder(layout, container, screenId, cellX, cellY, folderInfo); + } + FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX, + int cellY, FolderInfo folderInfo) { // Update the model LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY); @@ -2869,10 +2889,11 @@ public class Launcher extends Activity } else if (v == mAllAppsButton) { onClickAllAppsButton(v); } else if (tag instanceof AppInfo) { + AppInfo info = (AppInfo) tag; startAppShortcutOrInfoActivity(v); LauncherApplication.getLauncherStats().sendAppLaunchEvent( - LauncherStats.ORIGIN_APPDRAWER, ((AppInfo)tag).componentName.getPackageName()); - String packageName = ((AppInfo)tag).getIntent().getComponent().getPackageName(); + LauncherStats.ORIGIN_APPDRAWER, info.componentName.getPackageName()); + String packageName = info.getIntent().getComponent().getPackageName(); if (LauncherStats.SETTINGS_PACKAGE_NAME.equals(packageName)) { LauncherApplication.getLauncherStats() .sendSettingsOpenedEvent(LauncherStats.ORIGIN_APPDRAWER); @@ -3539,7 +3560,7 @@ public class Launcher extends Activity folder.getParent() + ")."); return; } - folder.animateOpen(getWorkspace(), folderTouch); + folder.animateOpen(); /*growAndFadeOutFolderIcon(folderIcon);*/ // Notify the accessibility manager that this folder "window" has appeared and occluded @@ -3783,6 +3804,8 @@ public class Launcher extends Activity tryAndUpdatePredictedApps(); } showAppsOrWidgets(State.APPS, animated, focusSearchBar); + + sRemoteFolderManager.onAppDrawerOpened(); } /** @@ -3902,10 +3925,12 @@ public class Launcher extends Activity * resumed. */ private void tryAndUpdatePredictedApps() { - if (mLauncherCallbacks != null) { - List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps(); - if (apps != null) { - mAppsView.setPredictedApps(apps); + if (!mRemoteDrawerEnabled) { + if (mLauncherCallbacks != null) { + List<ComponentKey> apps = mLauncherCallbacks.getPredictedApps(); + if (apps != null) { + mAppsView.setPredictedAppComponents(apps); + } } } } @@ -4196,6 +4221,7 @@ public class Launcher extends Activity if (addedApps != null && mAppsView != null) { mAppsView.addApps(addedApps); + sRemoteFolderManager.onBindAddApps(addedApps); } } @@ -4219,7 +4245,7 @@ public class Launcher extends Activity final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); final Collection<Animator> bounceAnims = new ArrayList<Animator>(); final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation(); - Workspace workspace = mWorkspace; + final Workspace workspace = mWorkspace; long newShortcutsScreenId = -1; for (int i = start; i < end; i++) { final ItemInfo item = shortcuts.get(i); @@ -4259,6 +4285,9 @@ public class Launcher extends Activity view = FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), (FolderInfo) item, mIconCache); + if (view == null) { + continue; + } ((FolderIcon) view).setTextVisible(!mHideIconLabels); break; default: @@ -4518,6 +4547,8 @@ public class Launcher extends Activity } mWorkspace.stripEmptyScreens(); + + sRemoteFolderManager.bindFinished(); } private void sendLoadingCompleteBroadcastIfNecessary() { @@ -4717,7 +4748,7 @@ public class Launcher extends Activity for (AppInfo info : appInfos) { removedComponents.add(info.componentName); } - if (!packageNames.isEmpty()) { + if (packageNames != null && !packageNames.isEmpty()) { mWorkspace.removeItemsByPackageName(packageNames, user); } if (!removedComponents.isEmpty()) { diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java index 0bcc3bc6c..4bbcec073 100644 --- a/src/com/android/launcher3/LauncherApplication.java +++ b/src/com/android/launcher3/LauncherApplication.java @@ -37,7 +37,7 @@ public class LauncherApplication extends Application { @Override public void onCreate() { super.onCreate(); - sLauncherStats = LauncherStats.createInstance(this); + sLauncherStats = LauncherStats.getInstance(this); AggregationIntentService.scheduleService(this); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index e140e2fab..2934b600d 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -441,6 +441,33 @@ public class LauncherModel extends BroadcastReceiver ArrayList<Long> workspaceScreens, ArrayList<Long> addedWorkspaceScreensFinal, int spanX, int spanY) { + + // Preferred screen is the next one after the default. + long preferredScreenId = SettingsProvider.getLongCustomDefault(context, + SettingsProvider.SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID, + R.integer.preferences_interface_homescreen_id_default); + int preferredScreenIndex = 0; + for (int i = 0; i < workspaceScreens.size(); i++) { + if (workspaceScreens.get(i) == preferredScreenId) { + preferredScreenIndex = i + 1; + break; + } + } + + return findSpaceForItem(context, workspaceScreens, addedWorkspaceScreensFinal, + spanX, spanY, preferredScreenIndex); + } + + /** + * Find a position on the screen for the given size or adds a new screen. Checks + * preferredScreen first, and if no space is found then starts searching from the left. + * @return screenId and the coordinates for the item. + */ + @Thunk Pair<Long, int[]> findSpaceForItem( + Context context, + ArrayList<Long> workspaceScreens, + ArrayList<Long> addedWorkspaceScreensFinal, + int spanX, int spanY, int preferredScreenIndex) { LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>(); // Use sBgItemsIdMap as all the items are already loaded. @@ -458,6 +485,12 @@ public class LauncherModel extends BroadcastReceiver } } + // If we have a zero-id screen then we skip over it. + boolean hasZero = false; + if (!workspaceScreens.isEmpty() && workspaceScreens.get(0) == 0) { + hasZero = true; + } + // Find appropriate space for the item. long screenId = 0; int[] cordinates = new int[2]; @@ -465,7 +498,6 @@ public class LauncherModel extends BroadcastReceiver int screenCount = workspaceScreens.size(); // First check the preferred screen. - int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1; if (preferredScreenIndex < screenCount) { screenId = workspaceScreens.get(preferredScreenIndex); found = findNextAvailableIconSpaceInScreen( @@ -474,7 +506,8 @@ public class LauncherModel extends BroadcastReceiver if (!found) { // Search on any of the screens starting from the first screen. - for (int screen = 1; screen < screenCount; screen++) { + int firstScreen = hasZero ? 1 : 0; + for (int screen = firstScreen; screen < screenCount; screen++) { screenId = workspaceScreens.get(screen); if (findNextAvailableIconSpaceInScreen( screenItems.get(screenId), cordinates, spanX, spanY)) { @@ -988,6 +1021,7 @@ public class LauncherModel extends BroadcastReceiver final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS); final int hiddenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.HIDDEN); + final int subType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SUBTYPE); FolderInfo folderInfo = null; switch (c.getInt(itemTypeIndex)) { @@ -1005,6 +1039,7 @@ public class LauncherModel extends BroadcastReceiver folderInfo.cellY = c.getInt(cellYIndex); folderInfo.options = c.getInt(optionsIndex); folderInfo.hidden = c.getInt(hiddenIndex) > 0; + folderInfo.subType = subType; return folderInfo; } @@ -1937,6 +1972,7 @@ public class LauncherModel extends BroadcastReceiver LauncherSettings.Favorites.OPTIONS); final int hiddenIndex = c.getColumnIndexOrThrow( LauncherSettings.Favorites.HIDDEN); + final int subTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SUBTYPE); final CursorIconInfo cursorIconInfo = new CursorIconInfo(c); final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>(); @@ -2216,6 +2252,7 @@ public class LauncherModel extends BroadcastReceiver folderInfo.spanY = 1; folderInfo.options = c.getInt(optionsIndex); folderInfo.hidden = c.getInt(hiddenIndex) > 0; + folderInfo.subType = c.getInt(subTypeIndex); // check & update map of what's occupied if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens, @@ -2704,7 +2741,9 @@ public class LauncherModel extends BroadcastReceiver } workspaceItems.remove(i); folders.remove(Long.valueOf(item.id)); - } else if (folder.contents.size() == 0) { + + // Remote folders are always empty on bind. + } else if (folder.contents.size() == 0 && !folder.isRemote()) { LauncherModel.deleteFolderContentsFromDatabase(mContext, folder); workspaceItems.remove(i); folders.remove(Long.valueOf(item.id)); diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 4c401866e..022ccaccd 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -69,7 +69,7 @@ public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; private static final boolean LOGD = false; - private static final int DATABASE_VERSION = 27; + private static final int DATABASE_VERSION = 28; public static final String AUTHORITY = ProviderConfig.AUTHORITY; @@ -540,7 +540,8 @@ public class LauncherProvider extends ContentProvider { "profileId INTEGER DEFAULT " + userSerialNumber + "," + "hidden INTEGER DEFAULT 0" + "," + "rank INTEGER NOT NULL DEFAULT 0," + - "options INTEGER NOT NULL DEFAULT 0" + + "options INTEGER NOT NULL DEFAULT 0," + + "subType INTEGER DEFAULT 0" + ");"); addWorkspacesTable(db); @@ -767,7 +768,6 @@ public class LauncherProvider extends ContentProvider { break; } case 27: { - // DB Upgraded successfully migrateLauncherFavorite(db, "com.android.dialer", "com.cyngn.dialer", "com.android.dialer.DialtactsActivity", "com.android.dialer.DialtactsActivity"); @@ -780,7 +780,20 @@ public class LauncherProvider extends ContentProvider { migrateLauncherFavorite(db, "org.cyanogenmod.snap", "com.android.camera2", "com.android.camera.CameraLauncher", "com.android.camera.CameraLauncher"); - return; + } + case 28: { + db.beginTransaction(); + try { + db.execSQL("ALTER TABLE favorites " + + "ADD COLUMN subType INTEGER DEFAULT 0;"); + db.setTransactionSuccessful(); + db.endTransaction(); + return; + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(TAG, ex.getMessage(), ex); + db.endTransaction(); + } } } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index e9f7b0d09..20428670b 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -47,6 +47,10 @@ public class LauncherSettings { public static final String HIDDEN = "hidden"; /** + * Folder subtype, used for Remote Folders + */ + static final String SUBTYPE = "subType"; + /** * The Intent URL of the gesture, describing what it points to. This * value is given to {@link android.content.Intent#parseUri(String, int)} to create * an Intent that can be launched. diff --git a/src/com/android/launcher3/OverviewSettingsPanel.java b/src/com/android/launcher3/OverviewSettingsPanel.java index 229b01991..1b75602dc 100644 --- a/src/com/android/launcher3/OverviewSettingsPanel.java +++ b/src/com/android/launcher3/OverviewSettingsPanel.java @@ -6,6 +6,10 @@ import android.database.MatrixCursor; import android.widget.ListView; import com.android.launcher3.list.PinnedHeaderListView; import com.android.launcher3.list.SettingsPinnedHeaderAdapter; +import com.android.launcher3.settings.SettingsProvider; + +import java.util.ArrayList; +import java.util.Arrays; public class OverviewSettingsPanel { public static final String ANDROID_SETTINGS = "com.android.settings"; @@ -35,12 +39,6 @@ public class OverviewSettingsPanel { res.getString(R.string.drawer_settings), res.getString(R.string.app_settings)}; - String[] values = new String[]{ - res.getString(R.string.home_screen_search_text), - res.getString(R.string.icon_labels), - res.getString(R.string.scrolling_wallpaper), - res.getString(R.string.grid_size_text)}; - String[] valuesDrawer = new String[] { res.getString(R.string.icon_labels), res.getString(R.string.app_drawer_style), @@ -61,7 +59,8 @@ public class OverviewSettingsPanel { mSettingsAdapter.addPartition(false, true); mSettingsAdapter.mPinnedHeaderCount = headers.length; - mSettingsAdapter.changeCursor(HOME_SETTINGS_POSITION, createCursor(headers[0], values)); + mSettingsAdapter.changeCursor(HOME_SETTINGS_POSITION, + createCursor(headers[0], getValues())); mSettingsAdapter.changeCursor(DRAWER_SETTINGS_POSITION, createCursor(headers[1], valuesDrawer)); mSettingsAdapter.changeCursor(APP_SETTINGS_POSITION, createCursor(headers[2], valuesApp)); @@ -77,6 +76,29 @@ public class OverviewSettingsPanel { return cursor; } + private String[] getValues() { + Resources res = mLauncher.getResources(); + ArrayList<String> values = new ArrayList<String>(Arrays.asList(new String[]{ + res.getString(R.string.home_screen_search_text), + res.getString(R.string.icon_labels), + res.getString(R.string.scrolling_wallpaper), + res.getString(R.string.grid_size_text)})); + + // Optionally add additional value based on setting + boolean remoteAppsEnabled = SettingsProvider.getBoolean(mLauncher, null, + R.bool.preferences_interface_homescreen_remote_folder_default); + if (remoteAppsEnabled) { + String remoteAppsName = RemoteFolderManager.getFeatureTitle(res); + if (remoteAppsName != null) { + values.add(remoteAppsName); + } + } + + String[] valuesArr = new String[values.size()]; + values.toArray(valuesArr); + return valuesArr; + } + public void notifyDataSetInvalidated() { mSettingsAdapter.notifyDataSetInvalidated(); } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 786822c49..fe940c182 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -169,7 +169,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Thunk PageIndicator mPageIndicator; // The viewport whether the pages are to be contained (the actual view may be larger than the // viewport) - private Rect mViewport = new Rect(); + protected Rect mViewport = new Rect(); // Reordering // We use the min scale to determine how much to expand the actually PagedView measured diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 5766cf2f2..b1a787bfc 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -100,7 +100,7 @@ public class ShortcutInfo extends ItemInfo { /** * The application icon. */ - private Bitmap mIcon; + Bitmap mIcon; /** * Indicates that the icon is disabled due to safe mode restrictions. @@ -265,6 +265,14 @@ public class ShortcutInfo extends ItemInfo { return (status & flag) != 0; } + /** + * Check if this shortcut has a specific flag. + * @param flag flag to check. + * @return true if the flag is present, false otherwise. + */ + public boolean hasFlag(int flag) { + return (flags & flag) != 0; + } public final boolean isPromise() { return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINTALL_ICON); diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index b51ef168a..d2ce2eeac 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -43,6 +43,8 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.Build; import android.os.Bundle; import android.os.Process; @@ -726,4 +728,15 @@ public final class Utilities { return false; } } + + public static boolean isNetworkConnected(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); + return activeNetwork != null && activeNetwork.isConnected(); + } + + public static boolean isConnectedToWiFi(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + return connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected(); + } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 54d153299..c9fe24748 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -78,6 +78,8 @@ import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** @@ -359,6 +361,13 @@ public class Workspace extends PagedView } } + /** + * @return A {@link List} of {@link Long}s representing ids of the workspace screens + */ + public List<Long> getWorkspaceScreenIds() { + return mScreenOrder; + } + // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each // dimension if unsuccessful public int[] estimateItemSize(ItemInfo itemInfo, boolean springLoaded) { @@ -3807,6 +3816,11 @@ public class Workspace extends PagedView return; } + // Drop is finished, so we should use our actual cell coordinates now. + if (mDragInfo != null) { + ((CellLayout.LayoutParams) mDragInfo.cell.getLayoutParams()).useTmpCoords = false; + } + boolean beingCalledAfterUninstall = mDeferredAction != null; if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) { diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 0e3cba83e..f9690f509 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -57,75 +57,6 @@ import java.nio.charset.CharsetEncoder; import java.util.ArrayList; import java.util.List; - - -/** - * A merge algorithm that merges every section indiscriminately. - */ -final class FullMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm { - - @Override - public boolean continueMerging(AlphabeticalAppsList.SectionInfo section, - AlphabeticalAppsList.SectionInfo withSection, - int sectionAppCount, int numAppsPerRow, int mergeCount) { - // Don't merge the predicted apps - if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) { - return false; - } - // Otherwise, merge every other section - return true; - } -} - -/** - * The logic we use to merge multiple sections. 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. - */ -final class SimpleSectionMergeAlgorithm implements AlphabeticalAppsList.MergeAlgorithm { - - private int mMinAppsPerRow; - private int mMinRowsInMergedSection; - private int mMaxAllowableMerges; - private CharsetEncoder mAsciiEncoder; - - public SimpleSectionMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, - int maxNumMerges) { - mMinAppsPerRow = minAppsPerRow; - mMinRowsInMergedSection = minRowsInMergedSection; - mMaxAllowableMerges = maxNumMerges; - mAsciiEncoder = Charset.forName("US-ASCII").newEncoder(); - } - - @Override - public boolean continueMerging(AlphabeticalAppsList.SectionInfo section, - AlphabeticalAppsList.SectionInfo withSection, - int sectionAppCount, int numAppsPerRow, int mergeCount) { - // Don't merge the predicted apps - if (section.firstAppItem.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) { - return false; - } - - // 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 && - !isCrossScript; - } -} - /** * The all apps view container. */ @@ -139,9 +70,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc public static final int GRID_THEME_LIGHT = 1; public static final int GRID_THEME_DARK = 2; - private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3; - private static final int MAX_NUM_MERGES_PHONE = 1; - @Thunk Launcher mLauncher; @Thunk AlphabeticalAppsList mApps; private AllAppsGridAdapter mAdapter; @@ -204,15 +132,36 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc Selection.setSelection(mSearchQueryBuilder, 0); } + public int getNumPredictedAppsPerRow() { + return mNumPredictedAppsPerRow; + } + + /** + * Sets the current set of predicted apps by component. + * Only usable when custom predicted apps are disabled. + */ + public void setPredictedAppComponents(List<ComponentKey> apps) { + mApps.setPredictedAppComponents(apps); + updateScrubber(); + } + /** - * Sets the current set of predicted apps. + * Sets the current set of predicted apps by info. + * Only usable when custom predicated apps are enabled. */ - public void setPredictedApps(List<ComponentKey> apps) { + public void setPredictedApps(List<AppInfo> apps) { mApps.setPredictedApps(apps); updateScrubber(); } /** + * Set whether the predicted apps row will have a customized selection of apps. + */ + public void setCustomPredictedAppsEnabled(boolean enabled) { + mApps.mCustomPredictedAppsEnabled = enabled; + } + + /** * Sets the current set of apps. */ public void setApps(List<AppInfo> apps) { @@ -262,6 +211,10 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc return mApps.getApps(); } + public int getSectionStrategy() { + return mSectionStrategy; + } + private void updateSectionStrategy() { Context context = getContext(); Resources res = context.getResources(); @@ -415,17 +368,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mNumAppsPerRow = grid.allAppsNumCols; mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols; - // If there is a start margin to draw section names, determine how we are going to merge - // app sections - boolean mergeSectionsFully = mSectionStrategy == SECTION_STRATEGY_GRID; - AlphabeticalAppsList.MergeAlgorithm mergeAlgorithm = mergeSectionsFully ? - new FullMergeAlgorithm() : - new SimpleSectionMergeAlgorithm((int) Math.ceil(mNumAppsPerRow / 2f), - MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE); - mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); - mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm); + + boolean mergeSections = mSectionStrategy == SECTION_STRATEGY_GRID; + mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeSections); + + mLauncher.getRemoteFolderManager().onMeasureDrawer(mNumPredictedAppsPerRow); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index a736f7770..3065fb428 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -37,9 +37,12 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; import com.android.launcher3.AppInfo; +import com.android.launcher3.BaseRecyclerViewFastScrollBar.FastScrollFocusApplicator; +import com.android.launcher3.BaseRecyclerViewFastScrollBar.FastScrollFocusable; import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.RemoteFolderManager; import com.android.launcher3.Utilities; import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.Thunk; @@ -68,6 +71,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. public static final int SEARCH_MARKET_DIVIDER_VIEW_TYPE = 4; // The message to continue to a market search when there are no filtered results public static final int SEARCH_MARKET_VIEW_TYPE = 5; + // Section header for customized predicated apps. + public static final int CUSTOM_PREDICTED_APPS_HEADER_VIEW_TYPE = 6; + // Additional spacing between predicted apps and regular apps. + public static final int CUSTOM_PREDICTED_APPS_FOOTER_VIEW_TYPE = 7; private boolean mIconsDimmed = false; @@ -185,36 +192,34 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. mPredictedAppsDividerPaint); hasDrawnPredictedAppsDivider = true; - } else if (showSectionNames && shouldDrawItemSection(holder, i, items)) { - // At this point, we only draw sections for each section break; + // Only customized predicted apps will draw a section name. + if (!mApps.mCustomPredictedAppsEnabled) continue; + } + + if (showSectionNames && shouldDrawItemSection(holder, items)) { + // Draw the section name for the first visible item int viewTopOffset = (2 * child.getPaddingTop()); int pos = holder.getPosition(); AlphabeticalAppsList.AdapterItem item = items.get(pos); AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo; - - // Draw all the sections for this index String lastSectionName = item.sectionName; - for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) { - AlphabeticalAppsList.AdapterItem nextItem = items.get(pos); - String sectionName = nextItem.sectionName; - if (nextItem.sectionInfo != sectionInfo) { - break; - } - if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) { - continue; - } + // Find the section name bounds + PointF sectionBounds = getAndCacheSectionBounds(lastSectionName); - // Find the section name bounds - PointF sectionBounds = getAndCacheSectionBounds(sectionName); + // Calculate where to draw the section + int sectionBaseline = (int) (viewTopOffset + sectionBounds.y); + int x = mIsRtl ? + parent.getWidth() - mBackgroundPadding.left - mSectionNamesMargin : + mBackgroundPadding.left; + x += (int) ((mSectionNamesMargin - sectionBounds.x) / 2f); - // Calculate where to draw the section - int sectionBaseline = (int) (viewTopOffset + sectionBounds.y); - int x = mIsRtl ? - parent.getWidth() - mBackgroundPadding.left - mSectionNamesMargin : - mBackgroundPadding.left; - x += (int) ((mSectionNamesMargin - sectionBounds.x) / 2f); - int y = child.getTop() + sectionBaseline; + int y; + boolean fixedToRow = false; + if (item.viewType == PREDICTION_ICON_VIEW_TYPE) { + y = child.getTop() - (int) mSectionTextPaint.getTextSize() / 2; + } else { + y = child.getTop() + sectionBaseline; // Determine whether this is the last row with apps in that section, if // so, then fix the section to the row allowing it to scroll past the @@ -223,7 +228,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. int nextRowPos = Math.min(items.size() - 1, pos + mAppsPerRow - (appIndexInSection % mAppsPerRow)); AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos); - boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName); + fixedToRow = !lastSectionName.equals(nextRowItem.sectionName); if (!fixedToRow) { y = Math.max(sectionBaseline, y); } @@ -233,22 +238,21 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) { y += lastSectionTop - y + lastSectionHeight; } + } - // Draw the section header - if (FADE_OUT_SECTIONS) { - int alpha = 255; - if (fixedToRow) { - alpha = Math.min(255, - (int) (255 * (Math.max(0, y) / (float) sectionBaseline))); - } - mSectionTextPaint.setAlpha(alpha); + // Draw the section header + if (FADE_OUT_SECTIONS) { + int alpha = 255; + if (fixedToRow) { + alpha = Math.min(255, + (int) (255 * (Math.max(0, y) / (float) sectionBaseline))); } - c.drawText(sectionName, x, y, mSectionTextPaint); - - lastSectionTop = y; - lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset); - lastSectionName = sectionName; + mSectionTextPaint.setAlpha(alpha); } + c.drawText(lastSectionName, x, y, mSectionTextPaint); + + lastSectionTop = y; + lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset); i += (sectionInfo.numApps - item.sectionAppIndex); } } @@ -308,21 +312,22 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. /** * Returns whether to draw the section for the given child. */ - private boolean shouldDrawItemSection(ViewHolder holder, int childIndex, + private boolean shouldDrawItemSection(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items) { int pos = holder.getPosition(); AlphabeticalAppsList.AdapterItem item = items.get(pos); // Ensure it's an icon - if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) { + if (item.viewType != ICON_VIEW_TYPE && item.viewType != PREDICTION_ICON_VIEW_TYPE) { return false; } - // Draw the section header for the first item in each section - return (childIndex == 0) || - (items.get(pos - 1).viewType == AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE); + + return true; } } + private final RemoteFolderManager mRemoteFolderManager; + private Launcher mLauncher; private LayoutInflater mLayoutInflater; @Thunk AlphabeticalAppsList mApps; @@ -359,6 +364,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. private int mAllAppsTextColor; + private int mCustomPredictedAppsHeaderHeight; + private int mCustomPredictedAppsFooterHeight; + public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnTouchListener touchListener, View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) { @@ -399,8 +407,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. mPredictedAppsDividerPaint.setColor(0x1E000000); mPredictedAppsDividerPaint.setAntiAlias(true); mPredictionBarDividerOffset = - (-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) + - res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2; + res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_divider_offset); // Resolve the market app handling additional searches PackageManager pm = launcher.getPackageManager(); @@ -409,6 +416,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. if (marketInfo != null) { mMarketAppName = marketInfo.loadLabel(pm).toString(); } + + mRemoteFolderManager = launcher.getRemoteFolderManager(); } /** @@ -494,6 +503,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext()) .getLongPressTimeout()); icon.setFocusable(true); + FastScrollFocusApplicator.createApplicator(icon, + FastScrollFocusable.FAST_SCROLL_FOCUS_DIMMABLE | + FastScrollFocusable.FAST_SCROLL_FOCUS_SCALABLE); + return new ViewHolder(icon); } case PREDICTION_ICON_VIEW_TYPE: { @@ -508,7 +521,14 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext()) .getLongPressTimeout()); icon.setFocusable(true); - return new ViewHolder(icon); + FastScrollFocusApplicator.createApplicator(icon, + FastScrollFocusable.FAST_SCROLL_FOCUS_DIMMABLE | + FastScrollFocusable.FAST_SCROLL_FOCUS_SCALABLE); + + ViewHolder holder = new ViewHolder(icon); + mRemoteFolderManager.onCreateViewHolder(holder, viewType); + + return holder; } case EMPTY_SEARCH_VIEW_TYPE: return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, @@ -526,6 +546,22 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } }); return new ViewHolder(searchMarketView); + case CUSTOM_PREDICTED_APPS_HEADER_VIEW_TYPE: { + View v = mLayoutInflater.inflate( + R.layout.custom_predicted_apps_header, parent, false); + FastScrollFocusApplicator.createApplicator(v, + FastScrollFocusable.FAST_SCROLL_FOCUS_DIMMABLE); + ViewHolder holder = new ViewHolder(v); + mRemoteFolderManager.onCreateViewHolder(holder, viewType); + return holder; + } + case CUSTOM_PREDICTED_APPS_FOOTER_VIEW_TYPE: { + View v = mLayoutInflater.inflate(R.layout.custom_predicted_apps_footer, + parent, false); + ViewHolder holder = new ViewHolder(v); + mRemoteFolderManager.onCreateViewHolder(holder, viewType); + return holder; + } default: throw new RuntimeException("Unexpected view type"); } @@ -545,7 +581,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. icon.setTextVisibility(!hideIconLabels); } icon.applyFromApplicationInfo(info); - icon.setFastScrollDimmed(mIconsDimmed, !mIconsDimmed); + FastScrollFocusApplicator.setFastScrollDimmed(icon, mIconsDimmed, !mIconsDimmed); + FastScrollFocusApplicator.setFastScrollFocused(icon, false, !mIconsDimmed); break; } case PREDICTION_ICON_VIEW_TYPE: { @@ -556,6 +593,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. icon.setTextVisibility(!hideIconLabels); } icon.applyFromApplicationInfo(info); + FastScrollFocusApplicator.setFastScrollDimmed(icon, mIconsDimmed, !mIconsDimmed); + FastScrollFocusApplicator.setFastScrollFocused(icon, false, !mIconsDimmed); + + mRemoteFolderManager.onBindViewHolder(holder, info); break; } case EMPTY_SEARCH_VIEW_TYPE: @@ -576,9 +617,32 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. searchView.setVisibility(View.GONE); } break; + case CUSTOM_PREDICTED_APPS_HEADER_VIEW_TYPE: { + TextView title = (TextView) holder.mContent.findViewById(R.id.title); + title.setTextColor(mAllAppsTextColor); + FastScrollFocusApplicator.setFastScrollDimmed(holder.mContent, mIconsDimmed, !mIconsDimmed); + FastScrollFocusApplicator.setFastScrollFocused(holder.mContent, false, !mIconsDimmed); + + ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) holder.mContent.getLayoutParams(); + mCustomPredictedAppsHeaderHeight = holder.mContent.getHeight() + + lp.topMargin + lp.bottomMargin; + break; + } + case CUSTOM_PREDICTED_APPS_FOOTER_VIEW_TYPE: + ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) holder.mContent.getLayoutParams(); + mCustomPredictedAppsFooterHeight = holder.mContent.getHeight() + + lp.topMargin + lp.bottomMargin; } } + public int getCustomPredictedAppsOffset(int rowIndex) { + int offset = mCustomPredictedAppsHeaderHeight; + if (rowIndex > 0) offset += mCustomPredictedAppsFooterHeight; + return offset; + } + @Override public int getItemCount() { return mApps.getAdapterItems().size(); @@ -602,11 +666,15 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. int sectionTextColorId = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ? R.color.all_apps_grid_section_text_color_dark : R.color.all_apps_grid_section_text_color; - mSectionTextPaint.setColor(mLauncher.getResources().getColor(sectionTextColorId)); - Resources res = mLauncher.getResources(); + mSectionTextPaint.setColor(mLauncher.getColor(sectionTextColorId)); + mAllAppsTextColor = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ? - res.getColor(R.color.quantum_panel_text_color_dark) : - res.getColor(R.color.quantum_panel_text_color); + mLauncher.getColor(R.color.quantum_panel_text_color_dark) : + mLauncher.getColor(R.color.quantum_panel_text_color); + + int mPredictedAppsDividerColorId = mGridTheme == AllAppsContainerView.GRID_THEME_DARK ? + R.color.drawer_divider_dark : R.color.drawer_divider_light; + mPredictedAppsDividerPaint.setColor(mLauncher.getColor(mPredictedAppsDividerColorId)); } /** diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index 6c853a037..aaecf7a60 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -26,8 +26,8 @@ import android.util.AttributeSet; import android.view.View; import com.android.launcher3.BaseRecyclerView; -import com.android.launcher3.BaseRecyclerViewFastScrollBar; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.BaseRecyclerViewFastScrollBar.FastScrollFocusApplicator; import com.android.launcher3.R; import com.android.launcher3.Stats; import com.android.launcher3.Utilities; @@ -52,11 +52,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView private int mNumAppsPerRow; private int mSectionStrategy = AllAppsContainerView.SECTION_STRATEGY_RAGGED; - private boolean mFocusSection = false; - - @Thunk BaseRecyclerViewFastScrollBar.FastScrollFocusableView mLastFastScrollFocusedView; - @Thunk ArrayList<BaseRecyclerViewFastScrollBar.FastScrollFocusableView> - mLastFastScrollFocusedViews = new ArrayList(); + @Thunk ArrayList<View> mLastFastScrollFocusedViews = new ArrayList(); @Thunk int mPrevFastScrollFocusedPosition; @Thunk AlphabeticalAppsList.SectionInfo mPrevFastScrollFocusedSection; @Thunk int mFastScrollFrameIndex; @@ -66,6 +62,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView private final int mScrollBarMode = FAST_SCROLL_BAR_MODE_DISTRIBUTE_BY_ROW; private ScrollPositionState mScrollPosState = new ScrollPositionState(); + private boolean mFastScrollDragging; private AllAppsBackgroundDrawable mEmptySearchBackground; private int mEmptySearchBackgroundTopOffset; @@ -92,6 +89,25 @@ public class AllAppsRecyclerView extends BaseRecyclerView } mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize( R.dimen.all_apps_empty_search_bg_top_offset); + + addOnScrollListener(new FocusScrollListener()); + } + + private class FocusScrollListener extends OnScrollListener { + public FocusScrollListener() { } + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + switch (newState) { + case SCROLL_STATE_IDLE: + // Don't change anything if we've stopped touching the scroll bar. + if (mFastScrollDragging) { + // Animation completed, set the fast scroll state on the target views + setSectionFastScrollFocused(mPrevFastScrollFocusedPosition); + setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, false, true); + } + } + } } /** @@ -115,11 +131,12 @@ public class AllAppsRecyclerView extends BaseRecyclerView pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow); pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow); pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows); + pool.setMaxRecycledViews(AllAppsGridAdapter.CUSTOM_PREDICTED_APPS_HEADER_VIEW_TYPE, 1); + pool.setMaxRecycledViews(AllAppsGridAdapter.CUSTOM_PREDICTED_APPS_FOOTER_VIEW_TYPE, 1); } public void setSectionStrategy(int sectionStrategy) { mSectionStrategy = sectionStrategy; - mFocusSection = mSectionStrategy == AllAppsContainerView.SECTION_STRATEGY_RAGGED; } /** @@ -241,49 +258,30 @@ public class AllAppsRecyclerView extends BaseRecyclerView throw new RuntimeException("Unexpected scroll bar mode"); } - // Map the touch position back to the scroll of the recycler view - getCurScrollState(mScrollPosState); - int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight); - LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); - if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) { - layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction)); + // Reset the last focused section + if (mPrevFastScrollFocusedSection != lastInfo.sectionInfo) { + setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, true, true); + clearSectionFocusedItems(); } - if (mPrevFastScrollFocusedPosition != lastInfo.fastScrollToItem.position) { - if (mFocusSection) { - setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, true, true); - } else if (mLastFastScrollFocusedView != null){ - mLastFastScrollFocusedView.setFastScrollDimmed(true, true); - } - mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position; - mPrevFastScrollFocusedSection = - getSectionInfoForPosition(lastInfo.fastScrollToItem.position); - // Reset the last focused section - if (mFocusSection) { - clearSectionFocusedItems(); - } else if (mLastFastScrollFocusedView != null) { - mLastFastScrollFocusedView.setFastScrollFocused(false, true); - mLastFastScrollFocusedView = null; - } + mPrevFastScrollFocusedPosition = lastInfo.fastScrollToItem.position; + mPrevFastScrollFocusedSection = lastInfo.sectionInfo; - if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) { - smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState); - } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) { - if (mFocusSection) { - setSectionFastScrollFocused(mPrevFastScrollFocusedPosition); - } else { - final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition); - if (vh != null && - vh.itemView instanceof - BaseRecyclerViewFastScrollBar.FastScrollFocusableView) { - mLastFastScrollFocusedView = - (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; - mLastFastScrollFocusedView.setFastScrollFocused(true, true); - } - } - } else { - throw new RuntimeException("Unexpected fast scroll mode"); - } + getCurScrollState(mScrollPosState); + if (mFastScrollMode == FAST_SCROLL_MODE_JUMP_TO_FIRST_ICON) { + smoothSnapToPosition(mPrevFastScrollFocusedPosition, mScrollPosState); + + setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, false, true); + setSectionFastScrollFocused(mPrevFastScrollFocusedPosition); + } else if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) { + // Map the touch position back to the scroll of the recycler view + int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight); + LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager(); + layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction)); + + setSectionFastScrollFocused(mPrevFastScrollFocusedPosition); + } else { + throw new RuntimeException("Unexpected fast scroll mode"); } return lastInfo.sectionName; } @@ -291,13 +289,9 @@ public class AllAppsRecyclerView extends BaseRecyclerView @Override public void onFastScrollCompleted() { super.onFastScrollCompleted(); - // Reset and clean up the last focused view - if (mFocusSection) { - clearSectionFocusedItems(); - } else if (mLastFastScrollFocusedView != null) { - mLastFastScrollFocusedView.setFastScrollFocused(false, true); - mLastFastScrollFocusedView = null; - } + + // Reset and clean up the last focused views + clearSectionFocusedItems(); mPrevFastScrollFocusedPosition = -1; mPrevFastScrollFocusedSection = null; } @@ -338,8 +332,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView // Calculate the current scroll position, the scrollY of the recycler view accounts for the // view padding, while the scrollBarY is drawn right up to the background padding (ignoring // padding) - int scrollY = getPaddingTop() + - (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset; + int scrollY = getCurrentScroll(mScrollPosState); int scrollBarY = mBackgroundPadding.top + (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight); @@ -421,29 +414,16 @@ public class AllAppsRecyclerView extends BaseRecyclerView return new String[0]; } - private AlphabeticalAppsList.SectionInfo getSectionInfoForPosition(int position) { - List<AlphabeticalAppsList.SectionInfo> sections = - mApps.getSections(); - for (AlphabeticalAppsList.SectionInfo section : sections) { - if (section.firstAppItem.position == position) { - return section; - } - } - return null; - } - private void setSectionFastScrollFocused(int position) { if (mPrevFastScrollFocusedSection != null) { - for (int i = 0; i < mPrevFastScrollFocusedSection.numApps; i++) { + int size = mPrevFastScrollFocusedSection.numApps + + mPrevFastScrollFocusedSection.numOtherViews; + for (int i = 0; i < size; i++) { int sectionPosition = position+i; final ViewHolder vh = findViewHolderForAdapterPosition(sectionPosition); - if (vh != null && - vh.itemView instanceof - BaseRecyclerViewFastScrollBar.FastScrollFocusableView) { - final BaseRecyclerViewFastScrollBar.FastScrollFocusableView view = - (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; - view.setFastScrollFocused(true, true); - mLastFastScrollFocusedViews.add(view); + if (vh != null) { + FastScrollFocusApplicator.setFastScrollFocused(vh.itemView, true, true); + mLastFastScrollFocusedViews.add(vh.itemView); } } } @@ -451,15 +431,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView private void setSectionFastScrollDimmed(int position, boolean dimmed, boolean animate) { if (mPrevFastScrollFocusedSection != null) { - for (int i = 0; i < mPrevFastScrollFocusedSection.numApps; i++) { + int size = mPrevFastScrollFocusedSection.numApps + + mPrevFastScrollFocusedSection.numOtherViews; + for (int i = 0; i < size; i++) { int sectionPosition = position+i; final ViewHolder vh = findViewHolderForAdapterPosition(sectionPosition); - if (vh != null && - vh.itemView instanceof - BaseRecyclerViewFastScrollBar.FastScrollFocusableView) { - final BaseRecyclerViewFastScrollBar.FastScrollFocusableView view = - (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; - view.setFastScrollDimmed(dimmed, animate); + if (vh != null) { + FastScrollFocusApplicator.setFastScrollDimmed(vh.itemView, dimmed, animate); } } } @@ -468,9 +446,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView private void clearSectionFocusedItems() { final int N = mLastFastScrollFocusedViews.size(); for (int i = 0; i < N; i++) { - BaseRecyclerViewFastScrollBar.FastScrollFocusableView view = - mLastFastScrollFocusedViews.get(i); - view.setFastScrollFocused(false, true); + View view = mLastFastScrollFocusedViews.get(i); + FastScrollFocusApplicator.setFastScrollFocused(view, false, true); } mLastFastScrollFocusedViews.clear(); } @@ -478,6 +455,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView @Override public void setFastScrollDragging(boolean dragging) { ((AllAppsGridAdapter) getAdapter()).setIconsDimmed(dragging); + mFastScrollDragging = dragging; } /** @@ -488,28 +466,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView @Override public void run() { if (mFastScrollFrameIndex < mFastScrollFrames.length) { - if (mFocusSection) { - setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, false, true); - } + setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, false, true); scrollBy(0, mFastScrollFrames[mFastScrollFrameIndex]); mFastScrollFrameIndex++; postOnAnimation(mSmoothSnapNextFrameRunnable); } else { - if (mFocusSection) { - setSectionFastScrollFocused(mPrevFastScrollFocusedPosition); - } else { - // Animation completed, set the fast scroll state on the target view - final ViewHolder vh = findViewHolderForPosition(mPrevFastScrollFocusedPosition); - if (vh != null && - vh.itemView instanceof - BaseRecyclerViewFastScrollBar.FastScrollFocusableView && - mLastFastScrollFocusedView != vh.itemView) { - mLastFastScrollFocusedView = - (BaseRecyclerViewFastScrollBar.FastScrollFocusableView) vh.itemView; - mLastFastScrollFocusedView.setFastScrollFocused(true, true); - mLastFastScrollFocusedView.setFastScrollDimmed(false, true); - } - } + setSectionFastScrollDimmed(mPrevFastScrollFocusedPosition, false, false); + setSectionFastScrollFocused(mPrevFastScrollFocusedPosition); } } }; @@ -523,8 +486,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView // Calculate the full animation from the current scroll position to the final scroll // position, and then run the animation for the duration. - int curScrollY = getPaddingTop() + - (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset; + int curScrollY = getCurrentScroll(scrollPosState); int newScrollY = getScrollAtPosition(position, scrollPosState.rowHeight); int numFrames = mFastScrollFrames.length; for (int i = 0; i < numFrames; i++) { @@ -558,7 +520,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE || item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) { stateOut.rowIndex = item.rowIndex; - stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child); + stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child) - + getAdditionalScrollHeight(stateOut.rowIndex); stateOut.rowHeight = child.getHeight(); break; } @@ -566,6 +529,16 @@ public class AllAppsRecyclerView extends BaseRecyclerView } } + @Override + protected int getAvailableScrollHeight(int rowCount, int rowHeight) { + return super.getAvailableScrollHeight(rowCount, rowHeight) + + getAdditionalScrollHeight(mApps.getAdapterItems().size()); + } + + private int getAdditionalScrollHeight(int rowIndex) { + return ((AllAppsGridAdapter) getAdapter()).getCustomPredictedAppsOffset(rowIndex); + } + /** * Returns the scrollY for the given position in the adapter. */ @@ -574,6 +547,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE || item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) { int offset = item.rowIndex > 0 ? getPaddingTop() : 0; + offset += ((AllAppsGridAdapter) getAdapter()). + getCustomPredictedAppsOffset(item.rowIndex); return offset + item.rowIndex * rowHeight; } else { return 0; diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 98d747936..9d30f81ee 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.allapps; -import android.content.ComponentName; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -32,6 +31,7 @@ import cyanogenmod.providers.CMSettings; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -49,6 +49,9 @@ public class AlphabeticalAppsList { private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0; private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1; + private static final String CUSTOM_PREDICTIONS_SCRUBBER = "★"; + private static final String CUSTOM_PREDICTIONS_HEADER = "☆"; + private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS; /** @@ -57,6 +60,8 @@ public class AlphabeticalAppsList { public static class SectionInfo { // The number of applications in this section public int numApps; + // The number of drawn (non-app) adapter items in this section. + public int numOtherViews; // The section break AdapterItem for this section public AdapterItem sectionBreakItem; // The first app AdapterItem for this section @@ -64,19 +69,21 @@ public class AlphabeticalAppsList { } /** - * Info about a fast scroller section, depending if sections are merged, the fast scroller - * sections will not be the same set as the section headers. + * Info about a fast scroller section. */ public static class FastScrollSectionInfo { // The section name public String sectionName; + // Info for this section + public SectionInfo sectionInfo; // The AdapterItem to scroll to for this section public AdapterItem fastScrollToItem; // The touch fraction that should map to this fast scroll section info public float touchFraction; - public FastScrollSectionInfo(String sectionName) { + public FastScrollSectionInfo(String sectionName, SectionInfo sectionInfo) { this.sectionName = sectionName; + this.sectionInfo = sectionInfo; } } @@ -158,14 +165,20 @@ public class AlphabeticalAppsList { item.position = pos; return item; } - } - /** - * Common interface for different merging strategies. - */ - public interface MergeAlgorithm { - boolean continueMerging(SectionInfo section, SectionInfo withSection, - int sectionAppCount, int numAppsPerRow, int mergeCount); + public static AdapterItem asCustomPredictedAppsHeader(int pos) { + AdapterItem item = new AdapterItem(); + item.viewType = AllAppsGridAdapter.CUSTOM_PREDICTED_APPS_HEADER_VIEW_TYPE; + item.position = pos; + return item; + } + + public static AdapterItem asPredictedAppsSpacer(int pos) { + AdapterItem item = new AdapterItem(); + item.viewType = AllAppsGridAdapter.CUSTOM_PREDICTED_APPS_FOOTER_VIEW_TYPE; + item.position = pos; + return item; + } } private Launcher mLauncher; @@ -180,7 +193,7 @@ public class AlphabeticalAppsList { private List<AdapterItem> mAdapterItems = new ArrayList<>(); // The set of sections for the apps with the current filter private List<SectionInfo> mSections = new ArrayList<>(); - // The set of sections that we allow fast-scrolling to (includes non-merged sections) + // The set of sections that we allow fast-scrolling to private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>(); // The set of predicted app component names private List<ComponentKey> mPredictedAppComponents = new ArrayList<>(); @@ -192,11 +205,13 @@ public class AlphabeticalAppsList { private RecyclerView.Adapter mAdapter; private AlphabeticIndexCompat mIndexer; private AppNameComparator mAppNameComparator; - private MergeAlgorithm mMergeAlgorithm; + private boolean mMergeSections; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; private int mNumAppRowsInAdapter; + boolean mCustomPredictedAppsEnabled; + public AlphabeticalAppsList(Context context) { mLauncher = (Launcher) context; mIndexer = new AlphabeticIndexCompat(context); @@ -207,10 +222,10 @@ public class AlphabeticalAppsList { * Sets the number of apps per row. */ public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow, - MergeAlgorithm mergeAlgorithm) { + boolean mergeSections) { mNumAppsPerRow = numAppsPerRow; mNumPredictedAppsPerRow = numPredictedAppsPerRow; - mMergeAlgorithm = mergeAlgorithm; + mMergeSections = mergeSections; updateAdapterItems(); } @@ -292,13 +307,33 @@ public class AlphabeticalAppsList { * Sets the current set of predicted apps. Since this can be called before we get the full set * of applications, we should merge the results only in onAppsUpdated() which is idempotent. */ - public void setPredictedApps(List<ComponentKey> apps) { + public void setPredictedAppComponents(List<ComponentKey> apps) { + if (!mCustomPredictedAppsEnabled) { + throw new IllegalStateException("Unable to set predicted app components when adapter " + + "is set to accept a custom predicted apps list."); + } + mPredictedAppComponents.clear(); mPredictedAppComponents.addAll(apps); onAppsUpdated(); } /** + * Sets the current set of predicted apps. This uses the info directly, so we do not + * merge data in {@link #onAppsUpdated()}, but go directly to {@link #updateAdapterItems()}. + */ + public void setPredictedApps(List<AppInfo> apps) { + if (!mCustomPredictedAppsEnabled) { + throw new IllegalStateException("Unable to set predicted apps directly when adapter " + + "is not set to accept a custom predicted apps list."); + } + + mPredictedApps.clear(); + mPredictedApps.addAll(apps); + updateAdapterItems(); + } + + /** * Sets the current set of apps. */ public void setApps(List<AppInfo> apps) { @@ -415,27 +450,48 @@ public class AlphabeticalAppsList { } // Process the predicted app components - mPredictedApps.clear(); - if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) { - for (ComponentKey ck : mPredictedAppComponents) { - AppInfo info = mComponentToAppMap.get(ck); - if (info != null) { - mPredictedApps.add(info); - } else { - if (LauncherAppState.isDogfoodBuild()) { - Log.e(TAG, "Predicted app not found: " + ck.flattenToString(mLauncher)); + boolean hasPredictedApps; + + // We haven't measured yet. Skip this for now. We will set properly after measure. + if (mNumPredictedAppsPerRow == 0) { + hasPredictedApps = false; + } else if (mCustomPredictedAppsEnabled) { + hasPredictedApps = !mPredictedApps.isEmpty(); + } else { + mPredictedApps.clear(); + hasPredictedApps = mPredictedAppComponents != null && + !mPredictedAppComponents.isEmpty(); + } + + if (hasPredictedApps && !hasFilter()) { + if (!mCustomPredictedAppsEnabled) { + for (ComponentKey ck : mPredictedAppComponents) { + AppInfo info = mComponentToAppMap.get(ck); + if (info != null) { + mPredictedApps.add(info); + } else { + if (LauncherAppState.isDogfoodBuild()) { + Log.e(TAG, "Predicted app not found: " + ck.flattenToString(mLauncher)); + } + } + // Stop at the number of predicted apps + if (mPredictedApps.size() == mNumPredictedAppsPerRow) { + break; } } - // Stop at the number of predicted apps - if (mPredictedApps.size() == mNumPredictedAppsPerRow) { - break; + } else { + // Shrink to column count. + if (mPredictedApps.size() > mNumPredictedAppsPerRow) { + mPredictedApps.subList(mNumAppsPerRow, mPredictedApps.size()).clear(); } } if (!mPredictedApps.isEmpty()) { // Add a section for the predictions lastSectionInfo = new SectionInfo(); - lastFastScrollerSectionInfo = new FastScrollSectionInfo(""); + String text = mCustomPredictedAppsEnabled ? CUSTOM_PREDICTIONS_SCRUBBER : " "; + lastFastScrollerSectionInfo = + new FastScrollSectionInfo(text, lastSectionInfo); AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo); mSections.add(lastSectionInfo); mFastScrollerSections.add(lastFastScrollerSectionInfo); @@ -443,8 +499,9 @@ public class AlphabeticalAppsList { // Add the predicted app items for (AppInfo info : mPredictedApps) { + text = mCustomPredictedAppsEnabled ? CUSTOM_PREDICTIONS_HEADER : " "; AdapterItem appItem = AdapterItem.asPredictedApp(position++, lastSectionInfo, - "", lastSectionInfo.numApps++, info, appIndex++); + text, lastSectionInfo.numApps++, info, appIndex++); if (lastSectionInfo.firstAppItem == null) { lastSectionInfo.firstAppItem = appItem; lastFastScrollerSectionInfo.fastScrollToItem = appItem; @@ -452,6 +509,11 @@ public class AlphabeticalAppsList { mAdapterItems.add(appItem); mFilteredApps.add(info); } + + if (mCustomPredictedAppsEnabled) { + position = mLauncher.getRemoteFolderManager().onUpdateAdapterItems( + mAdapterItems, lastFastScrollerSectionInfo, lastSectionInfo, position); + } } } @@ -470,7 +532,7 @@ public class AlphabeticalAppsList { if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) { lastSectionName = sectionName; lastSectionInfo = new SectionInfo(); - lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName); + lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName, lastSectionInfo); mSections.add(lastSectionInfo); mFastScrollerSections.add(lastFastScrollerSectionInfo); @@ -502,7 +564,7 @@ public class AlphabeticalAppsList { mAdapterItems.add(AdapterItem.asMarketSearch(position++)); } - // Merge multiple sections together as requested by the merge strategy for this device + // Merge multiple sections together as needed. mergeSections(); if (mNumAppsPerRow != 0) { @@ -536,7 +598,8 @@ public class AlphabeticalAppsList { for (FastScrollSectionInfo info : mFastScrollerSections) { AdapterItem item = info.fastScrollToItem; if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE && - item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) { + item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE && + item.viewType != AllAppsGridAdapter.CUSTOM_PREDICTED_APPS_HEADER_VIEW_TYPE) { info.touchFraction = 0f; continue; } @@ -551,7 +614,8 @@ public class AlphabeticalAppsList { for (FastScrollSectionInfo info : mFastScrollerSections) { AdapterItem item = info.fastScrollToItem; if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE && - item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) { + item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE && + item.viewType != AllAppsGridAdapter.CUSTOM_PREDICTED_APPS_HEADER_VIEW_TYPE) { info.touchFraction = 0f; continue; } @@ -588,52 +652,18 @@ public class AlphabeticalAppsList { */ private void mergeSections() { // Ignore merging until we have an algorithm and a valid row size - if (mMergeAlgorithm == null || mNumAppsPerRow == 0) { + if (!mMergeSections || mNumAppsPerRow == 0 || hasFilter()) { return; } - // Go through each section and try and merge some of the sections - if (!hasFilter()) { - int sectionAppCount = 0; - for (int i = 0; i < mSections.size() - 1; i++) { - SectionInfo section = mSections.get(i); - sectionAppCount = section.numApps; - int mergeCount = 1; - - // Merge rows based on the current strategy - while (i < (mSections.size() - 1) && - mMergeAlgorithm.continueMerging(section, mSections.get(i + 1), - sectionAppCount, mNumAppsPerRow, mergeCount)) { - SectionInfo nextSection = mSections.remove(i + 1); - - // Remove the next section break - mAdapterItems.remove(nextSection.sectionBreakItem); - int pos = mAdapterItems.indexOf(section.firstAppItem); - - // Point the section for these new apps to the merged section - int nextPos = pos + section.numApps; - for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) { - AdapterItem item = mAdapterItems.get(j); - item.sectionInfo = section; - item.sectionAppIndex += section.numApps; - } - - // Update the following adapter items of the removed section item - pos = mAdapterItems.indexOf(nextSection.firstAppItem); - for (int j = pos; j < mAdapterItems.size(); j++) { - AdapterItem item = mAdapterItems.get(j); - item.position--; - } - section.numApps += nextSection.numApps; - sectionAppCount += nextSection.numApps; - - if (DEBUG) { - Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName + - " to " + section.firstAppItem.sectionName + - " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow)); - } - mergeCount++; - } + Iterator<AdapterItem> iter = mAdapterItems.iterator(); + int positionShift = 0; + while (iter.hasNext()) { + AdapterItem item = iter.next(); + item.position -= positionShift; + if (item.viewType == AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE) { + iter.remove(); + positionShift++; } } } diff --git a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java index 31214e3d0..765243de1 100644 --- a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java +++ b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java @@ -123,6 +123,12 @@ public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter { case 3: updateDynamicGridSizeSettingsItem(v); break; + case 4: + current = SettingsProvider.getBoolean(mContext, + SettingsProvider.SETTINGS_UI_HOMESCREEN_REMOTE_FOLDER, + R.bool.preferences_interface_homescreen_remote_folder_default); + setSettingSwitch(stateView, settingSwitch, current); + break; default: hideStates(stateView, settingSwitch); } @@ -270,6 +276,12 @@ public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter { case 3: mLauncher.onClickDynamicGridSizeButton(); break; + case 4: + boolean newValue = onSettingsBooleanChanged(v, + SettingsProvider.SETTINGS_UI_HOMESCREEN_REMOTE_FOLDER, + R.bool.preferences_interface_homescreen_remote_folder_default); + mLauncher.getRemoteFolderManager().onSettingChanged(newValue); + break; } break; case OverviewSettingsPanel.DRAWER_SETTINGS_POSITION: @@ -362,7 +374,7 @@ public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter { mContext.sendBroadcast(intent); } - private void onSettingsBooleanChanged(View v, String key, int res) { + private boolean onSettingsBooleanChanged(View v, String key, int res) { boolean current = SettingsProvider.getBoolean( mContext, key, res); @@ -379,6 +391,8 @@ public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter { LauncherSettings.Settings.CONTENT_URI, LauncherSettings.Settings.METHOD_SET_BOOLEAN, key, extras); + + return !current; } private void onIconLabelsBooleanChanged(View v, String key, int res) { diff --git a/src/com/android/launcher3/settings/SettingsProvider.java b/src/com/android/launcher3/settings/SettingsProvider.java index 4357a451c..c96b8f53a 100644 --- a/src/com/android/launcher3/settings/SettingsProvider.java +++ b/src/com/android/launcher3/settings/SettingsProvider.java @@ -27,6 +27,7 @@ public final class SettingsProvider { public static final String SETTINGS_UI_HOMESCREEN_SEARCH = "ui_homescreen_search"; public static final String SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS = "ui_homescreen_general_hide_icon_labels"; public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL = "ui_homescreen_scrolling_wallpaper_scroll"; + public static final String SETTINGS_UI_HOMESCREEN_REMOTE_FOLDER = "ui_homescreen_remote_folder"; public static final String SETTINGS_UI_DYNAMIC_GRID_SIZE = "ui_dynamic_grid_size"; public static final String SETTINGS_UI_HOMESCREEN_ROWS = "ui_homescreen_rows"; public static final String SETTINGS_UI_HOMESCREEN_COLUMNS = "ui_homescreen_columns"; diff --git a/src/com/android/launcher3/stats/LauncherStats.java b/src/com/android/launcher3/stats/LauncherStats.java index e1dd1bbc6..5e8cb83d5 100644 --- a/src/com/android/launcher3/stats/LauncherStats.java +++ b/src/com/android/launcher3/stats/LauncherStats.java @@ -16,12 +16,12 @@ package com.android.launcher3.stats; +import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.text.TextUtils; import android.util.Log; -import com.android.launcher3.LauncherApplication; import com.android.launcher3.stats.internal.db.DatabaseHelper; import com.android.launcher3.stats.internal.model.TrackingEvent; @@ -41,6 +41,9 @@ public class LauncherStats { public static final String ORIGIN_APPDRAWER = "appdrawer"; public static final String ORIGIN_TREB_LONGPRESS = "trebuchet_longpress"; public static final String ORIGIN_CHOOSER = "theme_chooser"; + public static final String ORIGIN_SETTINGS = "settings"; + public static final String ORIGIN_DRAG_DROP = "drag_drop"; + public static final String ORIGIN_FOLDER = "folder"; private static void log(String msg) throws IllegalArgumentException { if (TextUtils.isEmpty(msg)) { @@ -101,17 +104,16 @@ public class LauncherStats { private static LauncherStats sInstance = null; // Members - private static WriteHandlerThread sHandlerThread = new WriteHandlerThread(); + private static WriteHandlerThread sHandlerThread; private static WriteHandler sWriteHandler; private static DatabaseHelper sDatabaseHelper; - private LauncherApplication mApplication; /** * Send a message to the handler to store event data * * @param trackingEvent {@link TrackingEvent} */ - private void sendStoreEventMessage(TrackingEvent trackingEvent) { + protected void sendStoreEventMessage(TrackingEvent trackingEvent) { log("Sending tracking event to handler: " + trackingEvent); Message msg = new Message(); msg.what = MSG_STORE_EVENT; @@ -134,32 +136,37 @@ public class LauncherStats { } /** + * Used only for overlay extensions + */ + protected LauncherStats() { } + + /** * Constructor * - * @param application {@link LauncherApplication} not null! + * @param context {@link Context} not null! * @throws IllegalArgumentException {@link IllegalArgumentException} */ - private LauncherStats(LauncherApplication application) throws IllegalArgumentException { - if (application == null) { - throw new IllegalArgumentException("'application' cannot be null!"); + private LauncherStats(Context context) throws IllegalArgumentException { + if (context == null) { + throw new IllegalArgumentException("'context' cannot be null!"); } - mApplication = application; - sDatabaseHelper = new DatabaseHelper(application); + sDatabaseHelper = new DatabaseHelper(context); + sHandlerThread = new WriteHandlerThread(); sHandlerThread.start(); sWriteHandler = new WriteHandler(); } /** - * Creates a singleton instance of the stats utility + * Gets a singleton instance of the stats utility * - * @param application {@link LauncherApplication} not null! + * @param context {@link Context} not null! * @return {@link LauncherStats} * @throws IllegalArgumentException {@link IllegalArgumentException} */ - public static LauncherStats createInstance(LauncherApplication application) + public static LauncherStats getInstance(Context context) throws IllegalArgumentException { if (sInstance == null) { - sInstance = new LauncherStats(application); + sInstance = new LauncherStats(context); } return sInstance; } diff --git a/src/com/android/launcher3/stats/internal/model/TrackingEvent.java b/src/com/android/launcher3/stats/internal/model/TrackingEvent.java index 91a9017be..a44d3f148 100644 --- a/src/com/android/launcher3/stats/internal/model/TrackingEvent.java +++ b/src/com/android/launcher3/stats/internal/model/TrackingEvent.java @@ -51,6 +51,14 @@ public class TrackingEvent { WALLPAPER_CHANGE, HOMESCREEN_PAGE, WIDGET, + + // Remote folder specific + REMOTE_FOLDER_DISABLED, + REMOTE_FOLDER_OPENED, + REMOTE_FOLDER_INFO_OPENED, + REMOTE_APP_OPENED, + REMOTE_APP_INSTALLED, + REMOTE_SYNC_TIME } public static final String KEY_ORIGIN = TrackingBundle.KEY_METADATA_ORIGIN; |