diff options
Diffstat (limited to 'src/com/android/launcher3')
97 files changed, 4310 insertions, 2703 deletions
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 3b25dca34..f76c18512 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -118,6 +118,25 @@ class AllAppsList { } } + /** + * Suspend the apps for the given apk identified by packageName. + */ + public void suspendPackage(String packageName, UserHandleCompat user, boolean suspend) { + final List<AppInfo> data = this.data; + for (int i = data.size() - 1; i >= 0; i--) { + AppInfo info = data.get(i); + final ComponentName component = info.intent.getComponent(); + if (info.user.equals(user) && packageName.equals(component.getPackageName())) { + if (suspend) { + info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } else { + info.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } + modified.add(info); + } + } + } + public void updateIconsAndLabels(HashSet<String> packages, UserHandleCompat user, ArrayList<AppInfo> outUpdates) { for (AppInfo info : data) { diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java new file mode 100644 index 000000000..0e188743f --- /dev/null +++ b/src/com/android/launcher3/AnotherWindowDropTarget.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.graphics.PointF; +import android.graphics.Rect; + +/** + * Drop target used when another window (i.e. another process) has accepted a global system drag. + * If the accepted item was a shortcut, we delete it from Launcher. + */ +public class AnotherWindowDropTarget implements DropTarget { + final Launcher mLauncher; + + public AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; } + + @Override + public boolean isDropEnabled() { return true; } + + @Override + public void onDrop(DragObject dragObject) { + dragObject.deferDragViewCleanupPostAnimation = false; + LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo); + } + + @Override + public void onDragEnter(DragObject dragObject) {} + + @Override + public void onDragOver(DragObject dragObject) {} + + @Override + public void onDragExit(DragObject dragObject) {} + + @Override + public void onFlingToDelete(DragObject dragObject, PointF vec) {} + + @Override + public boolean acceptDrop(DragObject dragObject) { + return dragObject.dragInfo instanceof ShortcutInfo; + } + + @Override + public void prepareAccessibilityDrop() {} + + // These methods are implemented in Views + @Override + public void getHitRectRelativeToDragLayer(Rect outRect) {} +} diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index ede6c71c1..b5b6897cc 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -28,13 +28,11 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.ComponentKey; import java.util.ArrayList; -import java.util.Arrays; /** * Represents an app in AllAppsView. */ public class AppInfo extends ItemInfo { - private static final String TAG = "Launcher3.AppInfo"; /** * The intent used to start the application. @@ -58,6 +56,11 @@ public class AppInfo extends ItemInfo { int flags = 0; + /** + * {@see ShortcutInfo#isDisabled} + */ + int isDisabled = ShortcutInfo.DEFAULT; + AppInfo() { itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } @@ -75,10 +78,22 @@ public class AppInfo extends ItemInfo { */ public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user, IconCache iconCache) { + this(context, info, user, iconCache, + UserManagerCompat.getInstance(context).isQuietModeEnabled(user)); + } + + public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user, + IconCache iconCache, boolean quietModeEnabled) { this.componentName = info.getComponentName(); this.container = ItemInfo.NO_ID; - flags = initFlags(info); + if ((info.getApplicationInfo().flags & LauncherActivityInfoCompat.FLAG_SUSPENDED) != 0) { + isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } + if (quietModeEnabled) { + isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER; + } + iconCache.getTitleAndIcon(this, info, true /* useLowResIcon */); intent = makeLaunchIntent(context, info, user); this.user = user; @@ -103,6 +118,7 @@ public class AppInfo extends ItemInfo { title = Utilities.trim(info.title); intent = new Intent(info.intent); flags = info.flags; + isDisabled = info.isDisabled; iconBitmap = info.iconBitmap; } @@ -111,8 +127,7 @@ public class AppInfo extends ItemInfo { return "ApplicationInfo(title=" + title + " id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY - + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) - + " user=" + user + ")"; + + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")"; } /** @@ -121,7 +136,7 @@ public class AppInfo extends ItemInfo { public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) { Log.d(tag, label + " size=" + list.size()); for (AppInfo info: list) { - Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap + Log.d(tag, " title=\"" + info.title + "\" iconBitmap=" + info.iconBitmap + " componentName=" + info.componentName.getPackageName()); } } @@ -143,4 +158,9 @@ 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 isDisabled() { + return isDisabled != 0; + } } diff --git a/src/com/android/launcher3/AppInfoDropTargetBar.java b/src/com/android/launcher3/AppInfoDropTargetBar.java new file mode 100644 index 000000000..e06f94100 --- /dev/null +++ b/src/com/android/launcher3/AppInfoDropTargetBar.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.util.AttributeSet; + +import com.android.launcher3.dragndrop.DragController; + +public class AppInfoDropTargetBar extends BaseDropTargetBar { + private ButtonDropTarget mAppInfoDropTarget; + + public AppInfoDropTargetBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AppInfoDropTargetBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // Get the individual components + mAppInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text); + + mAppInfoDropTarget.setDropTargetBar(this); + } + + @Override + public void setup(Launcher launcher, DragController dragController) { + dragController.addDragListener(this); + + dragController.addDragListener(mAppInfoDropTarget); + dragController.addDropTarget(mAppInfoDropTarget); + + mAppInfoDropTarget.setLauncher(launcher); + } + + @Override + public void showDropTargets() { + animateDropTargetBarToAlpha(1f, DEFAULT_DRAG_FADE_DURATION); + } + + @Override + public void hideDropTargets() { + animateDropTargetBarToAlpha(0f, DEFAULT_DRAG_FADE_DURATION); + } + + private void animateDropTargetBarToAlpha(float alpha, int duration) { + resetAnimation(duration); + if (duration > 0) { + animateAlpha(mDropTargetBar, alpha, DEFAULT_INTERPOLATOR); + mCurrentAnimation.start(); + } else { + mDropTargetBar.setAlpha(alpha); + AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled); + } + } + + @Override + public void enableAccessibleDrag(boolean enable) { + mAppInfoDropTarget.enableAccessibleDrag(enable); + } +} diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index 6b7ff886c..0ba94995b 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -1,5 +1,7 @@ package com.android.launcher3; +import com.android.launcher3.dragndrop.DragLayer; + import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -9,6 +11,7 @@ import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; import android.view.Gravity; import android.view.KeyEvent; @@ -24,7 +27,10 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe private static final float DIMMED_HANDLE_ALPHA = 0f; private static final float RESIZE_THRESHOLD = 0.66f; - private static Rect sTmpRect = new Rect(); + private static final Rect sTmpRect = new Rect(); + + // Represents the cell size on the grid in the two orientations. + private static Point[] sCellSize; private final Launcher mLauncher; private final LauncherAppWidgetHostView mWidgetView; @@ -358,28 +364,27 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe } public static Rect getWidgetSizeRanges(Launcher launcher, int spanX, int spanY, Rect rect) { + if (sCellSize == null) { + InvariantDeviceProfile inv = LauncherAppState.getInstance().getInvariantDeviceProfile(); + + // Initiate cell sizes. + sCellSize = new Point[2]; + sCellSize[0] = inv.landscapeProfile.getCellSize(); + sCellSize[1] = inv.portraitProfile.getCellSize(); + } + if (rect == null) { rect = new Rect(); } - Rect landMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.LANDSCAPE); - Rect portMetrics = Workspace.getCellLayoutMetrics(launcher, CellLayout.PORTRAIT); final float density = launcher.getResources().getDisplayMetrics().density; // Compute landscape size - int cellWidth = landMetrics.left; - int cellHeight = landMetrics.top; - int widthGap = landMetrics.right; - int heightGap = landMetrics.bottom; - int landWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); - int landHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); + int landWidth = (int) ((spanX * sCellSize[0].x) / density); + int landHeight = (int) ((spanY * sCellSize[0].y) / density); // Compute portrait size - cellWidth = portMetrics.left; - cellHeight = portMetrics.top; - widthGap = portMetrics.right; - heightGap = portMetrics.bottom; - int portWidth = (int) ((spanX * cellWidth + (spanX - 1) * widthGap) / density); - int portHeight = (int) ((spanY * cellHeight + (spanY - 1) * heightGap) / density); + int portWidth = (int) ((spanX * sCellSize[1].x) / density); + int portHeight = (int) ((spanY * sCellSize[1].y) / density); rect.set(portWidth, landHeight, landWidth, portHeight); return rect; } @@ -458,10 +463,10 @@ public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListe PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", lp.y, newY); ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(lp, this, width, height, x, y); - ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, "alpha", 1.0f); - ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, "alpha", 1.0f); - ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, "alpha", 1.0f); - ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, "alpha", 1.0f); + ObjectAnimator leftOa = LauncherAnimUtils.ofFloat(mLeftHandle, ALPHA, 1.0f); + ObjectAnimator rightOa = LauncherAnimUtils.ofFloat(mRightHandle, ALPHA, 1.0f); + ObjectAnimator topOa = LauncherAnimUtils.ofFloat(mTopHandle, ALPHA, 1.0f); + ObjectAnimator bottomOa = LauncherAnimUtils.ofFloat(mBottomHandle, ALPHA, 1.0f); oa.addUpdateListener(new AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { requestLayout(); diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java index 7de2774dc..bd4199264 100644 --- a/src/com/android/launcher3/BaseContainerView.java +++ b/src/com/android/launcher3/BaseContainerView.java @@ -26,6 +26,8 @@ import android.util.Log; import android.view.View; import android.widget.FrameLayout; +import com.android.launcher3.config.ProviderConfig; + /** * A base container view, which supports resizing. */ @@ -84,7 +86,7 @@ public abstract class BaseContainerView extends FrameLayout implements Insettabl * Sets the search bar bounds for this container view to match. */ final public void setSearchBarBounds(Rect bounds) { - if (LauncherAppState.isDogfoodBuild() && !isValidSearchBarBounds(bounds)) { + if (ProviderConfig.IS_DOGFOOD_BUILD && !isValidSearchBarBounds(bounds)) { Log.e(TAG, "Invalid search bar bounds: " + bounds); } diff --git a/src/com/android/launcher3/BaseDropTargetBar.java b/src/com/android/launcher3/BaseDropTargetBar.java new file mode 100644 index 000000000..9b38623c2 --- /dev/null +++ b/src/com/android/launcher3/BaseDropTargetBar.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.AccelerateInterpolator; +import android.widget.FrameLayout; + +import com.android.launcher3.dragndrop.DragController; + +/** + * Base class for drop target bars (where you can drop apps to do actions such as uninstall). + */ +public abstract class BaseDropTargetBar extends FrameLayout implements DragController.DragListener { + protected static final int DEFAULT_DRAG_FADE_DURATION = 175; + protected static final TimeInterpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(); + + protected View mDropTargetBar; + protected boolean mAccessibilityEnabled = false; + + protected AnimatorSet mCurrentAnimation; + protected boolean mDeferOnDragEnd; + + public BaseDropTargetBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BaseDropTargetBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mDropTargetBar = findViewById(R.id.drag_target_bar); + + // Create the various fade animations + mDropTargetBar.setAlpha(0f); + } + + /** + * Convenience method to animate the alpha of a view. + */ + protected void animateAlpha(View v, float alpha, TimeInterpolator interpolator) { + if (Float.compare(v.getAlpha(), alpha) != 0) { + ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, alpha); + anim.setInterpolator(interpolator); + anim.addListener(new ViewVisiblilyUpdateHandler(v)); + mCurrentAnimation.play(anim); + } + } + + protected void resetAnimation(int newAnimationDuration) { + // Update the accessibility state + AccessibilityManager am = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + mAccessibilityEnabled = am.isEnabled(); + + // Cancel any existing animation + if (mCurrentAnimation != null) { + mCurrentAnimation.cancel(); + mCurrentAnimation = null; + } + + if (newAnimationDuration > 0) { + mCurrentAnimation = new AnimatorSet(); + mCurrentAnimation.setDuration(newAnimationDuration); + } + } + + /* + * DragController.DragListener implementation + */ + @Override + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { + showDropTargets(); + } + + /** + * This is called to defer hiding the delete drop target until the drop animation has completed, + * instead of hiding immediately when the drag has ended. + */ + protected void deferOnDragEnd() { + mDeferOnDragEnd = true; + } + + @Override + public void onDragEnd() { + if (!mDeferOnDragEnd) { + hideDropTargets(); + } else { + mDeferOnDragEnd = false; + } + } + + public abstract void showDropTargets(); + + public abstract void hideDropTargets(); + + public abstract void enableAccessibleDrag(boolean enable); + + public abstract void setup(Launcher launcher, DragController dragController); + + private class ViewVisiblilyUpdateHandler extends AnimatorListenerAdapter { + private final View mView; + + ViewVisiblilyUpdateHandler(View v) { + mView = v; + } + + @Override + public void onAnimationStart(Animator animation) { + // Ensure that the view is visible for the animation + mView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation){ + AlphaUpdateListener.updateVisibility(mView, mAccessibilityEnabled); + } + + } +} diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index f6c9e3c77..dddd826c4 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -25,7 +25,6 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Region; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; @@ -36,10 +35,12 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewDebug; import android.view.ViewParent; import android.widget.TextView; import com.android.launcher3.IconCache.IconLoadRequest; +import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.PackageItemInfo; import java.text.NumberFormat; @@ -80,10 +81,14 @@ public class BubbleTextView extends TextView private final boolean mCustomShadowsEnabled; private final boolean mLayoutHorizontal; private final int mIconSize; + @ViewDebug.ExportedProperty(category = "launcher") private int mTextColor; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mStayPressed; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mIgnorePressedStateChange; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mDisableRelayout = false; private IconLoadRequest mIconLoadRequest; @@ -131,7 +136,7 @@ public class BubbleTextView extends TextView } mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mOutlineHelper = HolographicOutlineHelper.obtain(getContext()); if (mCustomShadowsEnabled) { @@ -150,7 +155,7 @@ public class BubbleTextView extends TextView Bitmap b = info.getIcon(iconCache); FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(b); - if (info.isDisabled != 0) { + if (info.isDisabled()) { iconDrawable.setState(FastBitmapDrawable.State.DISABLED); } setIcon(iconDrawable, mIconSize); @@ -166,7 +171,11 @@ public class BubbleTextView extends TextView } public void applyFromApplicationInfo(AppInfo info) { - setIcon(mLauncher.createIconDrawable(info.iconBitmap), mIconSize); + FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(info.iconBitmap); + if (info.isDisabled()) { + iconDrawable.setState(FastBitmapDrawable.State.DISABLED); + } + setIcon(iconDrawable, mIconSize); setText(info.title); if (info.contentDescription != null) { setContentDescription(info.contentDescription); @@ -250,11 +259,11 @@ public class BubbleTextView extends TextView private void updateIconState() { if (mIcon instanceof FastBitmapDrawable) { FastBitmapDrawable d = (FastBitmapDrawable) mIcon; - if (isPressed() || mStayPressed) { - d.animateState(FastBitmapDrawable.State.PRESSED); - } else if (getTag() instanceof ShortcutInfo - && ((ShortcutInfo) getTag()).isDisabled != 0) { + if (getTag() instanceof ItemInfo + && ((ItemInfo) getTag()).isDisabled()) { d.animateState(FastBitmapDrawable.State.DISABLED); + } else if (isPressed() || mStayPressed) { + d.animateState(FastBitmapDrawable.State.PRESSED); } else { d.animateState(FastBitmapDrawable.State.NORMAL); } @@ -268,7 +277,7 @@ public class BubbleTextView extends TextView boolean result = super.onTouchEvent(event); // Check for a stylus button press, if it occurs cancel any long press checks. - if (mStylusEventHelper.checkAndPerformStylusEvent(event)) { + if (mStylusEventHelper.onMotionEvent(event)) { mLongPressHelper.cancelLongPress(); result = true; } diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 40a4678de..d8826831a 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -39,6 +39,9 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.TextView; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.util.Thunk; /** @@ -47,11 +50,11 @@ import com.android.launcher3.util.Thunk; public abstract class ButtonDropTarget extends TextView implements DropTarget, DragController.DragListener, OnClickListener { - private static int DRAG_VIEW_DROP_DURATION = 285; + private static final int DRAG_VIEW_DROP_DURATION = 285; protected Launcher mLauncher; private int mBottomDragPadding; - protected SearchDropTargetBar mSearchDropTargetBar; + protected BaseDropTargetBar mDropTargetBar; /** Whether this drop target is active for the current drag */ protected boolean mActive; @@ -62,6 +65,8 @@ public abstract class ButtonDropTarget extends TextView protected ColorStateList mOriginalTextColor; protected Drawable mDrawable; + protected DeviceProfile mDeviceProfile; + private AnimatorSet mCurrentColorAnim; @Thunk ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter; @@ -81,8 +86,8 @@ public abstract class ButtonDropTarget extends TextView mOriginalTextColor = getTextColors(); // Remove the text in the Phone UI in landscape - DeviceProfile grid = ((Launcher) getContext()).getDeviceProfile(); - if (grid.isVerticalBarLayout()) { + mDeviceProfile = ((Launcher) getContext()).getDeviceProfile(); + if (mDeviceProfile.isVerticalBarLayout()) { setText(""); } } @@ -104,8 +109,8 @@ public abstract class ButtonDropTarget extends TextView mLauncher = launcher; } - public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) { - mSearchDropTargetBar = searchDropTargetBar; + public void setDropTargetBar(BaseDropTargetBar dropTargetBar) { + mDropTargetBar = dropTargetBar; } @Override @@ -189,8 +194,8 @@ public abstract class ButtonDropTarget extends TextView } } - @Override - public final void onDragStart(DragSource source, Object info, int dragAction) { + @Override + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { mActive = supportsDrop(source, info); mDrawable.setColorFilter(null); if (mCurrentColorAnim != null) { @@ -206,7 +211,7 @@ public abstract class ButtonDropTarget extends TextView return supportsDrop(dragObject.dragSource, dragObject.dragInfo); } - protected abstract boolean supportsDrop(DragSource source, Object info); + protected abstract boolean supportsDrop(DragSource source, ItemInfo info); @Override public boolean isDropEnabled() { @@ -232,13 +237,13 @@ public abstract class ButtonDropTarget extends TextView final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(), width, height); final float scale = (float) to.width() / from.width(); - mSearchDropTargetBar.deferOnDragEnd(); + mDropTargetBar.deferOnDragEnd(); Runnable onAnimationEndRunnable = new Runnable() { @Override public void run() { completeDrop(d); - mSearchDropTargetBar.onDragEnd(); + mDropTargetBar.onDragEnd(); mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null); } }; @@ -297,11 +302,6 @@ public abstract class ButtonDropTarget extends TextView return to; } - @Override - public void getLocationInDragLayer(int[] loc) { - mLauncher.getDragLayer().getLocationInDragLayer(this, loc); - } - public void enableAccessibleDrag(boolean enable) { setOnClickListener(enable ? this : null); } diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 94e3e4141..5832b9f0d 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -18,7 +18,6 @@ package com.android.launcher3; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -33,6 +32,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.TransitionDrawable; import android.os.Build; import android.os.Parcelable; @@ -48,10 +48,14 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.DecelerateInterpolator; import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; -import com.android.launcher3.FolderIcon.FolderRingAnimator; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate; import com.android.launcher3.accessibility.FolderAccessibilityHelper; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.FolderIcon.FolderRingAnimator; import com.android.launcher3.util.ParcelableSparseArray; import com.android.launcher3.util.Thunk; @@ -70,17 +74,23 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private static final boolean LOGD = false; private Launcher mLauncher; + @ViewDebug.ExportedProperty(category = "launcher") @Thunk int mCellWidth; + @ViewDebug.ExportedProperty(category = "launcher") @Thunk int mCellHeight; private int mFixedCellWidth; private int mFixedCellHeight; + @ViewDebug.ExportedProperty(category = "launcher") @Thunk int mCountX; + @ViewDebug.ExportedProperty(category = "launcher") @Thunk int mCountY; private int mOriginalWidthGap; private int mOriginalHeightGap; + @ViewDebug.ExportedProperty(category = "launcher") @Thunk int mWidthGap; + @ViewDebug.ExportedProperty(category = "launcher") @Thunk int mHeightGap; private int mMaxGap; private boolean mDropPending = false; @@ -150,9 +160,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private static final boolean DESTRUCTIVE_REORDER = false; private static final boolean DEBUG_VISUALIZE_OCCUPIED = false; - static final int LANDSCAPE = 0; - static final int PORTRAIT = 1; - private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f; private static final int REORDER_ANIMATION_DURATION = 150; @Thunk float mReorderPreviewAnimationMagnitude; @@ -206,7 +213,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { final Resources res = getResources(); mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx; - mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel); + mBackground = (TransitionDrawable) res.getDrawable( + FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? R.drawable.bg_screenpanel + : R.drawable.bg_celllayout); mBackground.setCallback(this); mBackground.setAlpha((int) (mBackgroundAlpha * 255)); @@ -272,7 +281,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap, mCountX, mCountY); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mTouchFeedbackView = new ClickShadowView(context); addView(mTouchFeedbackView); @@ -334,7 +343,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // enabled to allow rearranging the different home screens. So check what mode // the workspace is in, and only perform stylus button presses while in overview mode. if (mLauncher.mWorkspace.isInOverviewMode() - && mStylusEventHelper.checkAndPerformStylusEvent(ev)) { + && mStylusEventHelper.onMotionEvent(ev)) { return true; } return handled; @@ -400,7 +409,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mIsDragTarget = false; } - boolean isDragTarget() { + public boolean isDragTarget() { return mIsDragTarget; } @@ -420,10 +429,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } } - public boolean getIsDragOverlapping() { - return mIsDragOverlapping; - } - public void disableJailContent() { mJailContent = false; } @@ -450,6 +455,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { (ParcelableSparseArray) parcelable : new ParcelableSparseArray(); } + public boolean getIsDragOverlapping() { + return mIsDragOverlapping; + } + @Override protected void onDraw(Canvas canvas) { if (!mIsDragTarget) { @@ -469,12 +478,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { for (int i = 0; i < mDragOutlines.length; i++) { final float alpha = mDragOutlineAlphas[i]; if (alpha > 0) { - final Rect r = mDragOutlines[i]; - mTempRect.set(r); - Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale()); final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag(); paint.setAlpha((int)(alpha + .5f)); - canvas.drawBitmap(b, null, mTempRect, paint); + canvas.drawBitmap(b, null, mDragOutlines[i], paint); } } @@ -589,7 +595,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { try { dispatchRestoreInstanceState(states); } catch (IllegalArgumentException ex) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw ex; } // Mismatched viewId / viewType preventing restore. Skip restore on production builds. @@ -810,7 +816,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]); } - int getCellWidth() { + public int getCellWidth() { return mCellWidth; } @@ -1070,53 +1076,57 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mDragCell[0] = cellX; mDragCell[1] = cellY; - // Find the top left corner of the rect the object will occupy - final int[] topLeft = mTmpPoint; - cellToPoint(cellX, cellY, topLeft); - - int left = topLeft[0]; - int top = topLeft[1]; - - if (v != null && dragOffset == null) { - // When drawing the drag outline, it did not account for margin offsets - // added by the view's parent. - MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); - left += lp.leftMargin; - top += lp.topMargin; - - // Offsets due to the size difference between the View and the dragOutline. - // There is a size difference to account for the outer blur, which may lie - // outside the bounds of the view. - top += (v.getHeight() - dragOutline.getHeight()) / 2; - // We center about the x axis - left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragOutline.getWidth()) / 2; - } else { - if (dragOffset != null && dragRegion != null) { - // Center the drag region *horizontally* in the cell and apply a drag - // outline offset - left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragRegion.width()) / 2; - int cHeight = getShortcutsAndWidgets().getCellContentHeight(); - int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); - top += dragOffset.y + cellPaddingY; - } else { - // Center the drag outline in the cell - left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) - - dragOutline.getWidth()) / 2; - top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) - - dragOutline.getHeight()) / 2; - } - } + final int oldIndex = mDragOutlineCurrent; mDragOutlineAnims[oldIndex].animateOut(); mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; Rect r = mDragOutlines[mDragOutlineCurrent]; - r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); + if (resize) { cellToRect(cellX, cellY, spanX, spanY, r); + } else { + // Find the top left corner of the rect the object will occupy + final int[] topLeft = mTmpPoint; + cellToPoint(cellX, cellY, topLeft); + + int left = topLeft[0]; + int top = topLeft[1]; + + if (v != null && dragOffset == null) { + // When drawing the drag outline, it did not account for margin offsets + // added by the view's parent. + MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); + left += lp.leftMargin; + top += lp.topMargin; + + // Offsets due to the size difference between the View and the dragOutline. + // There is a size difference to account for the outer blur, which may lie + // outside the bounds of the view. + top += (v.getHeight() - dragOutline.getHeight()) / 2; + // We center about the x axis + left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragOutline.getWidth()) / 2; + } else { + if (dragOffset != null && dragRegion != null) { + // Center the drag region *horizontally* in the cell and apply a drag + // outline offset + left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragRegion.width()) / 2; + int cHeight = getShortcutsAndWidgets().getCellContentHeight(); + int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); + top += dragOffset.y + cellPaddingY; + } else { + // Center the drag outline in the cell + left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) + - dragOutline.getWidth()) / 2; + top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) + - dragOutline.getHeight()) / 2; + } + } + r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); } + Utilities.scaleRectAboutCenter(r, getChildrenScale()); mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); mDragOutlineAnims[mDragOutlineCurrent].animateIn(); @@ -1146,23 +1156,6 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * * @param pixelX The X location at which you want to search for a vacant area. * @param pixelY The Y location at which you want to search for a vacant area. - * @param spanX Horizontal span of the object. - * @param spanY Vertical span of the object. - * @param result Array in which to place the result, or null (in which case a new array will - * be allocated) - * @return The X, Y cell of a vacant area that can contain this object, - * nearest the requested location. - */ - int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) { - return findNearestVacantArea(pixelX, pixelY, spanX, spanY, spanX, spanY, result, null); - } - - /** - * Find a vacant area that will fit the given bounds nearest the requested - * cell location. Uses Euclidean distance to score multiple vacant areas. - * - * @param pixelX The X location at which you want to search for a vacant area. - * @param pixelY The Y location at which you want to search for a vacant area. * @param minSpanX The minimum horizontal span required * @param minSpanY The minimum vertical span required * @param spanX Horizontal span of the object. @@ -1338,9 +1331,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * @param spanX Horizontal span of the object. * @param spanY Vertical span of the object. * @param direction The favored direction in which the views should move from x, y - * @param exactDirectionOnly If this parameter is true, then only solutions where the direction - * matches exactly. Otherwise we find the best matching direction. - * @param occoupied The array which represents which cells in the CellLayout are occupied + * @param occupied The array which represents which cells in the CellLayout are occupied * @param blockOccupied The array which represents which cells in the specified block (cellX, * cellY, spanX, spanY) are occupied. This is used when try to move a group of views. * @param result Array in which to place the result, or null (in which case a new array will @@ -2234,17 +2225,14 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { a.cancel(); } - AnimatorSet s = LauncherAnimUtils.createAnimatorSet(); - a = s; - s.playTogether( - LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()), - LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()), - LauncherAnimUtils.ofFloat(child, "translationX", 0f), - LauncherAnimUtils.ofFloat(child, "translationY", 0f) - ); - s.setDuration(REORDER_ANIMATION_DURATION); - s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f)); - s.start(); + a = new LauncherViewPropertyAnimator(child) + .scaleX(getChildrenScale()) + .scaleY(getChildrenScale()) + .translationX(0) + .translationY(0) + .setDuration(REORDER_ANIMATION_DURATION); + a.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f)); + a.start(); } } @@ -2261,6 +2249,15 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mOccupied[i][j] = mTmpOccupied[i][j]; } } + + long screenId = mLauncher.getWorkspace().getIdForScreen(this); + int container = Favorites.CONTAINER_DESKTOP; + + if (mLauncher.isHotseatLayout(this)) { + screenId = -1; + container = Favorites.CONTAINER_HOTSEAT; + } + int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { View child = mShortcutsAndWidgets.getChildAt(i); @@ -2269,17 +2266,21 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // We do a null check here because the item info can be null in the case of the // AllApps button in the hotseat. if (info != null) { - if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY || - info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) { - info.requiresDbUpdate = true; - } + final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX + || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan + || info.spanY != lp.cellVSpan); + info.cellX = lp.cellX = lp.tmpCellX; info.cellY = lp.cellY = lp.tmpCellY; info.spanX = lp.cellHSpan; info.spanY = lp.cellVSpan; + + if (requiresDbUpdate) { + LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, + info.cellX, info.cellY, info.spanX, info.spanY); + } } } - mLauncher.getWorkspace().updateItemLocationsInDatabase(this); } private void setUseTempCoords(boolean useTempCoords) { @@ -2623,7 +2624,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { * @return The X, Y cell of a vacant area that can contain this object, * nearest the requested location. */ - int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) { + public int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, int[] result) { return findNearestArea(pixelX, pixelY, spanX, spanY, spanX, spanY, false, result, null); } @@ -2867,10 +2868,10 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // X coordinate of the view in the layout. @ViewDebug.ExportedProperty - int x; + public int x; // Y coordinate of the view in the layout. @ViewDebug.ExportedProperty - int y; + public int y; boolean dropped; @@ -2967,7 +2968,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { // cellX and cellY coordinates and which page was clicked. We then set this as a tag on // the CellLayout that was long clicked public static final class CellInfo { - View cell; + public View cell; int cellX = -1; int cellY = -1; int spanX; @@ -2996,6 +2997,26 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied); } + /** + * Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing + * if necessary). + */ + public boolean hasReorderSolution(ItemInfo itemInfo) { + int[] cellPoint = new int[2]; + // Check for a solution starting at every cell. + for (int cellX = 0; cellX < getCountX(); cellX++) { + for (int cellY = 0; cellY < getCountY(); cellY++) { + cellToPoint(cellX, cellY, cellPoint); + if (findReorderSolution(cellPoint[0], cellPoint[1], itemInfo.minSpanX, + itemInfo.minSpanY, itemInfo.spanX, itemInfo.spanY, mDirectionVector, null, + true, new ItemConfiguration()).isSolution) { + return true; + } + } + } + return false; + } + public boolean isRegionVacant(int x, int y, int spanX, int spanY) { int x2 = x + spanX - 1; int y2 = y + spanY - 1; diff --git a/src/com/android/launcher3/ClickShadowView.java b/src/com/android/launcher3/ClickShadowView.java index e31d7f7f6..e2bc6bac5 100644 --- a/src/com/android/launcher3/ClickShadowView.java +++ b/src/com/android/launcher3/ClickShadowView.java @@ -22,6 +22,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; +import android.view.ViewDebug; import android.view.ViewGroup; public class ClickShadowView extends View { @@ -32,7 +33,9 @@ public class ClickShadowView extends View { private final Paint mPaint; + @ViewDebug.ExportedProperty(category = "launcher") private final float mShadowOffset; + @ViewDebug.ExportedProperty(category = "launcher") private final float mShadowPadding; private Bitmap mBitmap; diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index edaf525d6..997ded2d3 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -23,6 +23,8 @@ import android.util.AttributeSet; import android.view.View; import android.view.animation.AnimationUtils; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.folder.Folder; import com.android.launcher3.util.FlingAnimation; import com.android.launcher3.util.Thunk; @@ -45,20 +47,37 @@ public class DeleteDropTarget extends ButtonDropTarget { setDrawable(R.drawable.ic_remove_launcher); } - public static boolean supportsDrop(Object info) { + @Override + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { + super.onDragStart(source, info, dragAction); + setTextBasedOnDragSource(source); + } + + /** @return true for items that should have a "Remove" action in accessibility. */ + public static boolean supportsAccessibleDrop(ItemInfo info) { return (info instanceof ShortcutInfo) || (info instanceof LauncherAppWidgetInfo) || (info instanceof FolderInfo); } @Override - protected boolean supportsDrop(DragSource source, Object info) { - return source.supportsDeleteDropTarget() && supportsDrop(info); + protected boolean supportsDrop(DragSource source, ItemInfo info) { + return true; + } + + /** + * Set the drop target's text to either "Remove" or "Cancel" depending on the drag source. + */ + public void setTextBasedOnDragSource(DragSource dragSource) { + if (!mDeviceProfile.isVerticalBarLayout()) { + setText(dragSource.supportsDeleteDropTarget() ? R.string.remove_drop_target_label + : android.R.string.cancel); + } } @Override @Thunk void completeDrop(DragObject d) { - ItemInfo item = (ItemInfo) d.dragInfo; + ItemInfo item = d.dragInfo; if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) { removeWorkspaceOrFolderItem(mLauncher, item, null); } @@ -80,7 +99,6 @@ public class DeleteDropTarget extends ButtonDropTarget { public void onFlingToDelete(final DragObject d, PointF vel) { // Don't highlight the icon as it's animating d.dragView.setColor(0); - d.dragView.updateInitialScaleToCurrentScale(); final DragLayer dragLayer = mLauncher.getDragLayer(); FlingAnimation fling = new FlingAnimation(d, vel, diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 380c6b148..b67e07b33 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -33,6 +33,8 @@ import android.view.ViewGroup.MarginLayoutParams; import android.widget.FrameLayout; import android.widget.LinearLayout; +import com.android.launcher3.config.FeatureFlags; + public class DeviceProfile { public final InvariantDeviceProfile inv; @@ -242,7 +244,8 @@ public class DeviceProfile { FontMetrics fm = textPaint.getFontMetrics(); cellWidthPx = iconSizePx; cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top); - final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale); + final float scaleDps = !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 0f + : res.getDimensionPixelSize(R.dimen.dragViewScale); dragViewScale = (iconSizePx + scaleDps) / iconSizePx; // Hotseat @@ -326,8 +329,20 @@ public class DeviceProfile { return bounds; } + public Point getCellSize() { + Point result = new Point(); + // Since we are only concerned with the overall padding, layout direction does + // not matter. + Rect padding = getWorkspacePadding(false /* isLayoutRtl */ ); + result.x = calculateCellWidth(availableWidthPx - padding.left - padding.right, + inv.numColumns); + result.y = calculateCellHeight(availableHeightPx - padding.top - padding.bottom, + inv.numRows); + return result; + } + /** Returns the workspace padding in the specified orientation */ - Rect getWorkspacePadding(boolean isLayoutRtl) { + public Rect getWorkspacePadding(boolean isLayoutRtl) { Rect searchBarBounds = getSearchBarBounds(isLayoutRtl); Rect padding = new Rect(); if (isVerticalBarLayout()) { @@ -388,13 +403,13 @@ public class DeviceProfile { } // The rect returned will be extended to below the system ui that covers the workspace - Rect getHotseatRect() { + public boolean isInHotseatRect(int x, int y) { if (isVerticalBarLayout()) { - return new Rect(availableWidthPx - normalHotseatBarHeightPx, 0, - Integer.MAX_VALUE, availableHeightPx); + return (x >= (availableWidthPx - hotseatBarHeightPx)) + && (y >= 0) && (y <= availableHeightPx); } else { - return new Rect(0, availableHeightPx - hotseatBarHeightPx, - availableWidthPx, Integer.MAX_VALUE); + return (x >= 0) && (x <= availableWidthPx) + && (y >= (availableHeightPx - hotseatBarHeightPx)); } } @@ -410,7 +425,7 @@ public class DeviceProfile { * When {@code false}, either device is in portrait mode or the device is in landscape mode and * the hotseat is on the bottom row. */ - boolean isVerticalBarLayout() { + public boolean isVerticalBarLayout() { return isLandscape && transposeLayoutWithOrientation; } @@ -452,27 +467,18 @@ public class DeviceProfile { // Layout the search bar space Rect searchBarBounds = getSearchBarBounds(isLayoutRtl); View searchBar = launcher.getSearchDropTargetBar(); - lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams(); + lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, searchBar, Gravity.TOP); lp.width = searchBarBounds.width(); lp.height = searchBarBounds.height(); lp.topMargin = searchBarTopExtraPaddingPx; - if (hasVerticalBarLayout) { - // Vertical search bar space -- The search bar is fixed in the layout to be on the left - // of the screen regardless of RTL - lp.gravity = Gravity.LEFT; - - LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar); - targets.setOrientation(LinearLayout.VERTICAL); - FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams(); - targetsLp.gravity = Gravity.TOP; - targetsLp.height = LayoutParams.WRAP_CONTENT; - - } else { - // Horizontal search bar space - lp.gravity = Gravity.TOP|Gravity.CENTER_HORIZONTAL; - } searchBar.setLayoutParams(lp); + // Layout the app info bar space + View appInfoBar = launcher.getAppInfoDropTargetBar(); + lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, appInfoBar, Gravity.BOTTOM); + lp.bottomMargin = hotseatBarHeightPx; + appInfoBar.setLayoutParams(lp); + // Layout the workspace PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace); lp = (FrameLayout.LayoutParams) workspace.getLayoutParams(); @@ -539,7 +545,6 @@ public class DeviceProfile { // Layout the Overview Mode ViewGroup overviewMode = launcher.getOverviewPanel(); if (overviewMode != null) { - int overviewButtonBarHeight = getOverviewModeButtonBarHeight(); lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; @@ -548,7 +553,7 @@ public class DeviceProfile { int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx; lp.width = Math.min(availableWidthPx, maxWidth); - lp.height = overviewButtonBarHeight; + lp.height = getOverviewModeButtonBarHeight(); overviewMode.setLayoutParams(lp); if (lp.width > totalItemWidth && visibleChildCount > 1) { @@ -577,6 +582,28 @@ public class DeviceProfile { } } + private FrameLayout.LayoutParams getDropTargetBarLayoutParams(boolean hasVerticalBarLayout, + View dropTargetBar, int verticalGravity) { + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) dropTargetBar.getLayoutParams(); + if (hasVerticalBarLayout) { + // Vertical drop target bar space -- The drop target bar is fixed in the layout to be on + // the left of the screen regardless of RTL + lp.gravity = Gravity.LEFT; + lp.width = normalSearchBarSpaceHeightPx; + + LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar); + targets.setOrientation(LinearLayout.VERTICAL); + FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams(); + targetsLp.gravity = verticalGravity; + targetsLp.height = LayoutParams.WRAP_CONTENT; + } else { + // Horizontal drop target bar space + lp.gravity = verticalGravity | Gravity.CENTER_HORIZONTAL; + lp.height = searchBarSpaceHeightPx; + } + return lp; + } + private int getCurrentWidth() { return isLandscape ? Math.max(widthPx, heightPx) diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java index 2a1346ef5..da32d82a6 100644 --- a/src/com/android/launcher3/DragSource.java +++ b/src/com/android/launcher3/DragSource.java @@ -37,7 +37,7 @@ public interface DragSource { /** * @return whether items dragged from this source supports 'Delete' drop target (e.g. to remove - * a shortcut. + * a shortcut.) If this returns false, the drop target will say "Cancel" instead of "Remove." */ boolean supportsDeleteDropTarget(); diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index 434059168..90b8f1c75 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import com.android.launcher3.dragndrop.DragView; + import android.graphics.PointF; import android.graphics.Rect; @@ -27,9 +29,7 @@ import com.android.launcher3.accessibility.DragViewStateAnnouncer; */ public interface DropTarget { - public static final String TAG = "DropTarget"; - - public static class DragObject { + class DragObject { public int x = -1; public int y = -1; @@ -49,7 +49,7 @@ public interface DropTarget { public DragView dragView = null; /** The data associated with the object being dragged */ - public Object dragInfo = null; + public ItemInfo dragInfo = null; /** Where the drag originated */ public DragSource dragSource = null; @@ -152,7 +152,4 @@ public interface DropTarget { // These methods are implemented in Views void getHitRectRelativeToDragLayer(Rect outRect); - void getLocationInDragLayer(int[] loc); - int getLeft(); - int getTop(); } diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index c7b64ec7d..bf4551b26 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -17,6 +17,7 @@ package com.android.launcher3; import android.content.Context; import android.util.AttributeSet; +import android.view.DragEvent; import android.view.KeyEvent; import android.widget.EditText; @@ -62,4 +63,10 @@ public class ExtendedEditText extends EditText { } return super.onKeyPreIme(keyCode, event); } + + @Override + public boolean onDragEvent(DragEvent event) { + // We don't want this view to interfere with Launcher own drag and drop. + return false; + } } diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index d7f1d8677..38700805f 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -93,7 +93,7 @@ public class FastBitmapDrawable extends Drawable { private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix(); private static final ColorMatrix sTempFilterMatrix = new ColorMatrix(); - private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); private final Bitmap mBitmap; private State mState = State.NORMAL; diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index d59c64431..f99c08a59 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -22,6 +22,9 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewGroup; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderPagedView; import com.android.launcher3.util.FocusLogic; import com.android.launcher3.util.Thunk; @@ -90,7 +93,7 @@ public class FocusHelper { } if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new IllegalStateException("Parent of the focused item is not supported."); } else { return false; diff --git a/src/com/android/launcher3/FocusIndicatorView.java b/src/com/android/launcher3/FocusIndicatorView.java index 58b38ebcd..a835d9969 100644 --- a/src/com/android/launcher3/FocusIndicatorView.java +++ b/src/com/android/launcher3/FocusIndicatorView.java @@ -16,7 +16,7 @@ package com.android.launcher3; -import android.animation.ObjectAnimator; +import android.animation.Animator; import android.animation.PropertyValuesHolder; import android.content.Context; import android.graphics.Canvas; @@ -37,7 +37,7 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen private final int[] mIndicatorPos = new int[2]; private final int[] mTargetViewPos = new int[2]; - private ObjectAnimator mCurrentAnimation; + private Animator mCurrentAnimation; private ViewAnimState mTargetState; private View mLastFocusedView; @@ -117,12 +117,12 @@ public class FocusIndicatorView extends View implements View.OnFocusChangeListen if (getAlpha() > MIN_VISIBLE_ALPHA) { mTargetState = nextState; - mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, - PropertyValuesHolder.ofFloat(View.ALPHA, 1), - PropertyValuesHolder.ofFloat(View.TRANSLATION_X, mTargetState.x), - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, mTargetState.y), - PropertyValuesHolder.ofFloat(View.SCALE_X, mTargetState.scaleX), - PropertyValuesHolder.ofFloat(View.SCALE_Y, mTargetState.scaleY)); + mCurrentAnimation = new LauncherViewPropertyAnimator(this) + .alpha(1) + .translationX(mTargetState.x) + .translationY(mTargetState.y) + .scaleX(mTargetState.scaleX) + .scaleY(mTargetState.scaleY); } else { applyState(nextState); mCurrentAnimation = LauncherAnimUtils.ofPropertyValuesHolder(this, diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index aea21c95b..861a9354a 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -22,7 +22,6 @@ import android.content.Context; import com.android.launcher3.compat.UserHandleCompat; import java.util.ArrayList; -import java.util.Arrays; /** * Represents a folder containing shortcuts or apps. @@ -49,7 +48,7 @@ public class FolderInfo extends ItemInfo { /** * Whether this folder has been opened */ - boolean opened; + public boolean opened; public int options; @@ -106,7 +105,7 @@ public class FolderInfo extends ItemInfo { } - void addListener(FolderListener listener) { + public void addListener(FolderListener listener) { listeners.add(listener); } @@ -128,7 +127,7 @@ public class FolderInfo extends ItemInfo { listeners.clear(); } - interface FolderListener { + public interface FolderListener { public void onAdd(ShortcutInfo item); public void onRemove(ShortcutInfo item); public void onTitleChanged(CharSequence title); @@ -140,7 +139,7 @@ public class FolderInfo extends ItemInfo { return "FolderInfo(id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX - + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")"; + + " spanY=" + spanY + ")"; } public boolean hasOption(int optionFlag) { diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index 3e838760e..052f7ac90 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -24,6 +24,7 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; import android.widget.FrameLayout; import android.widget.TextView; @@ -34,8 +35,10 @@ public class Hotseat extends FrameLayout private Launcher mLauncher; + @ViewDebug.ExportedProperty(category = "launcher") private int mAllAppsButtonRank; + @ViewDebug.ExportedProperty(category = "launcher") private final boolean mHasVerticalHotseat; public Hotseat(Context context) { diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 05ad5388a..d39ae661e 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -108,7 +108,6 @@ public class IconCache { private final BitmapFactory.Options mLowResOptions; private String mSystemState; - private Bitmap mLowResBitmap; private Canvas mLowResCanvas; private Paint mLowResPaint; @@ -119,6 +118,8 @@ public class IconCache { mLauncherApps = LauncherAppsCompat.getInstance(mContext); mIconDpi = inv.fillResIconDpi; mIconDb = new IconDB(context); + mLowResCanvas = new Canvas(); + mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); @@ -387,7 +388,8 @@ public class IconCache { entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser()); mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry); - return newContentValues(entry.icon, entry.title.toString(), mActivityBgColor); + Bitmap lowResIcon = generateLowResIcon(entry.icon, mActivityBgColor); + return newContentValues(entry.icon, lowResIcon, entry.title.toString()); } /** @@ -625,16 +627,21 @@ public class IconCache { if (appInfo == null) { throw new NameNotFoundException("ApplicationInfo is null"); } - entry.icon = Utilities.createBadgedIconBitmap( + + // Load the full res icon for the application, but if useLowResIcon is set, then + // only keep the low resolution icon instead of the larger full-sized icon + Bitmap icon = Utilities.createBadgedIconBitmap( appInfo.loadIcon(mPackageManager), user, mContext); + Bitmap lowResIcon = generateLowResIcon(icon, mPackageBgColor); entry.title = appInfo.loadLabel(mPackageManager); entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); - entry.isLowResIcon = false; + entry.icon = useLowResIcon ? lowResIcon : icon; + entry.isLowResIcon = useLowResIcon; // Add the icon in the DB here, since these do not get written during // package updates. ContentValues values = - newContentValues(entry.icon, entry.title.toString(), mPackageBgColor); + newContentValues(icon, lowResIcon, entry.title.toString()); addIconToDB(values, cacheKey.componentName, info, mUserManager.getSerialNumberForUser(user)); @@ -674,9 +681,9 @@ public class IconCache { // pass } - ContentValues values = newContentValues( - Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true), - label, Color.TRANSPARENT); + icon = Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true); + Bitmap lowResIcon = generateLowResIcon(icon, Color.TRANSPARENT); + ContentValues values = newContentValues(icon, lowResIcon, label); values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString()); values.put(IconDB.COLUMN_USER, userSerial); mIconDb.insertOrReplace(values); @@ -829,34 +836,37 @@ public class IconCache { } } - private ContentValues newContentValues(Bitmap icon, String label, int lowResBackgroundColor) { + private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, String label) { ContentValues values = new ContentValues(); values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); + values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon)); values.put(IconDB.COLUMN_LABEL, label); values.put(IconDB.COLUMN_SYSTEM_STATE, mSystemState); + return values; + } + + /** + * Generates a new low-res icon given a high-res icon. + */ + private Bitmap generateLowResIcon(Bitmap icon, int lowResBackgroundColor) { if (lowResBackgroundColor == Color.TRANSPARENT) { - values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap( - Bitmap.createScaledBitmap(icon, - icon.getWidth() / LOW_RES_SCALE_FACTOR, - icon.getHeight() / LOW_RES_SCALE_FACTOR, true))); + return Bitmap.createScaledBitmap(icon, + icon.getWidth() / LOW_RES_SCALE_FACTOR, + icon.getHeight() / LOW_RES_SCALE_FACTOR, true); } else { + Bitmap lowResIcon = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR, + icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565); synchronized (this) { - if (mLowResBitmap == null) { - mLowResBitmap = Bitmap.createBitmap(icon.getWidth() / LOW_RES_SCALE_FACTOR, - icon.getHeight() / LOW_RES_SCALE_FACTOR, Bitmap.Config.RGB_565); - mLowResCanvas = new Canvas(mLowResBitmap); - mLowResPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); - } mLowResCanvas.drawColor(lowResBackgroundColor); mLowResCanvas.drawBitmap(icon, new Rect(0, 0, icon.getWidth(), icon.getHeight()), - new Rect(0, 0, mLowResBitmap.getWidth(), mLowResBitmap.getHeight()), + new Rect(0, 0, lowResIcon.getWidth(), lowResIcon.getHeight()), mLowResPaint); - values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(mLowResBitmap)); + mLowResCanvas.setBitmap(null); } + return lowResIcon; } - return values; } private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) { diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java index d93cdcc1b..d444640e6 100644 --- a/src/com/android/launcher3/InfoDropTarget.java +++ b/src/com/android/launcher3/InfoDropTarget.java @@ -20,9 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.util.AttributeSet; -import com.android.launcher3.compat.UserHandleCompat; - -public class InfoDropTarget extends ButtonDropTarget { +public class InfoDropTarget extends UninstallDropTarget { public InfoDropTarget(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -41,7 +39,10 @@ public class InfoDropTarget extends ButtonDropTarget { setDrawable(R.drawable.ic_info_launcher); } - public static void startDetailsActivityForInfo(Object info, Launcher launcher) { + /** + * @return Whether the activity was started. + */ + public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher) { ComponentName componentName = null; if (info instanceof AppInfo) { componentName = ((AppInfo) info).componentName; @@ -49,30 +50,28 @@ public class InfoDropTarget extends ButtonDropTarget { componentName = ((ShortcutInfo) info).intent.getComponent(); } else if (info instanceof PendingAddItemInfo) { componentName = ((PendingAddItemInfo) info).componentName; + } else if (info instanceof LauncherAppWidgetInfo) { + componentName = ((LauncherAppWidgetInfo) info).providerName; } - final UserHandleCompat user; - if (info instanceof ItemInfo) { - user = ((ItemInfo) info).user; - } else { - user = UserHandleCompat.myUserHandle(); - } - if (componentName != null) { - launcher.startApplicationDetailsActivity(componentName, user); + launcher.startApplicationDetailsActivity(componentName, info.user); + return true; } + return false; } @Override - protected boolean supportsDrop(DragSource source, Object info) { - return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info); + protected boolean startActivityWithUninstallAffordance(DragObject d) { + return startDetailsActivityForInfo(d.dragInfo, mLauncher); } - public static boolean supportsDrop(Context context, Object info) { - return info instanceof AppInfo || info instanceof PendingAddItemInfo; + @Override + protected boolean supportsDrop(DragSource source, ItemInfo info) { + return source.supportsAppInfoDropTarget() && supportsDrop(getContext(), info); } - @Override - void completeDrop(DragObject d) { - startDetailsActivityForInfo(d.dragInfo, mLauncher); + public static boolean supportsDrop(Context context, ItemInfo info) { + return info instanceof AppInfo || info instanceof ShortcutInfo + || info instanceof PendingAddItemInfo || info instanceof LauncherAppWidgetInfo; } } diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java index 7343bf686..f4bfa4549 100644 --- a/src/com/android/launcher3/InsettableFrameLayout.java +++ b/src/com/android/launcher3/InsettableFrameLayout.java @@ -5,12 +5,14 @@ import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; +import android.view.ViewDebug; import android.view.ViewGroup; import android.widget.FrameLayout; public class InsettableFrameLayout extends FrameLayout implements ViewGroup.OnHierarchyChangeListener, Insettable { + @ViewDebug.ExportedProperty(category = "launcher") protected Rect mInsets = new Rect(); public InsettableFrameLayout(Context context, AttributeSet attrs) { diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index b3a8bbc32..d60132270 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -73,17 +73,17 @@ public class InvariantDeviceProfile { /** * Number of icons inside the hotseat area. */ - int numHotseatIcons; + public int numHotseatIcons; float hotseatIconSize; int defaultLayoutId; // Derived invariant properties - int hotseatAllAppsRank; + public int hotseatAllAppsRank; DeviceProfile landscapeProfile; DeviceProfile portraitProfile; - InvariantDeviceProfile() { + public InvariantDeviceProfile() { } public InvariantDeviceProfile(InvariantDeviceProfile p) { diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index f7e0ea488..1ba09e1eb 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -24,8 +24,6 @@ import android.graphics.Bitmap; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; -import java.util.Arrays; - /** * Represents an item in the launcher. */ @@ -35,14 +33,14 @@ public class ItemInfo { * Intent extra to store the profile. Format: UserHandle */ static final String EXTRA_PROFILE = "profile"; - + public static final int NO_ID = -1; - + /** * The id in the settings database for this item */ public long id = NO_ID; - + /** * One of {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, @@ -50,20 +48,20 @@ public class ItemInfo { * {@link LauncherSettings.Favorites#ITEM_TYPE_APPWIDGET}. */ public int itemType; - + /** - * The id of the container that holds this item. For the desktop, this will be + * The id of the container that holds this item. For the desktop, this will be * {@link LauncherSettings.Favorites#CONTAINER_DESKTOP}. For the all applications folder it * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders * it will be the id of the folder. */ public long container = NO_ID; - + /** * Iindicates the screen in which the shortcut appears. */ public long screenId = -1; - + /** * Indicates the X position of the associated cell. */ @@ -100,11 +98,6 @@ public class ItemInfo { public int rank = 0; /** - * Indicates that this item needs to be updated in the db - */ - public boolean requiresDbUpdate = false; - - /** * Title of the item */ public CharSequence title; @@ -114,11 +107,6 @@ public class ItemInfo { */ public CharSequence contentDescription; - /** - * The position of the item in a drag-and-drop operation. - */ - public int[] dropPos = null; - public UserHandleCompat user; public ItemInfo() { @@ -146,18 +134,11 @@ public class ItemInfo { } public Intent getIntent() { - throw new RuntimeException("Unexpected Intent"); + return null; } - /** - * Write the fields of this item to the DB - * - * @param context A context object to use for getting UserManagerCompat - * @param values - */ - - void onAddToDatabase(Context context, ContentValues values) { - values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType); + public void writeToValues(ContentValues values) { + values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); values.put(LauncherSettings.Favorites.CONTAINER, container); values.put(LauncherSettings.Favorites.SCREEN, screenId); values.put(LauncherSettings.Favorites.CELLX, cellX); @@ -165,6 +146,27 @@ public class ItemInfo { values.put(LauncherSettings.Favorites.SPANX, spanX); values.put(LauncherSettings.Favorites.SPANY, spanY); values.put(LauncherSettings.Favorites.RANK, rank); + } + + public void readFromValues(ContentValues values) { + itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE); + container = values.getAsLong(LauncherSettings.Favorites.CONTAINER); + screenId = values.getAsLong(LauncherSettings.Favorites.SCREEN); + cellX = values.getAsInteger(LauncherSettings.Favorites.CELLX); + cellY = values.getAsInteger(LauncherSettings.Favorites.CELLY); + spanX = values.getAsInteger(LauncherSettings.Favorites.SPANX); + spanY = values.getAsInteger(LauncherSettings.Favorites.SPANY); + rank = values.getAsInteger(LauncherSettings.Favorites.RANK); + } + + /** + * Write the fields of this item to the DB + * + * @param context A context object to use for getting UserManagerCompat + * @param values + */ + void onAddToDatabase(Context context, ContentValues values) { + writeToValues(values); long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user); values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber); @@ -194,7 +196,13 @@ public class ItemInfo { public String toString() { return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX - + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) - + " user=" + user + ")"; + + " spanY=" + spanY + " user=" + user + ")"; + } + + /** + * Whether this item is disabled. + */ + public boolean isDisabled() { + return false; } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 166acd33b..406a6f563 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -21,7 +21,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -37,6 +36,7 @@ import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -75,7 +75,6 @@ import android.util.Log; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MotionEvent; import android.view.Surface; @@ -103,19 +102,26 @@ import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.model.WidgetsModel; +import com.android.launcher3.userevent.Logger; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.TestingUtils; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.ViewOnDrawExecutor; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; import com.android.launcher3.widget.WidgetsContainerView; +import com.android.wallpaperpicker.WallpaperUtils; -import java.io.File; import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.PrintWriter; import java.text.DateFormat; import java.util.ArrayList; @@ -132,14 +138,13 @@ import java.util.List; public class Launcher extends Activity implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener { - static final String TAG = "Launcher"; + public static final String TAG = "Launcher"; static final boolean LOGD = false; static final boolean PROFILE_STARTUP = false; static final boolean DEBUG_WIDGETS = false; static final boolean DEBUG_STRICT_MODE = false; static final boolean DEBUG_RESUME_TIME = false; - static final boolean DEBUG_DUMP_LOG = false; static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run @@ -165,8 +170,6 @@ public class Launcher extends Activity */ protected static final int REQUEST_LAST = 100; - static final int SCREEN_COUNT = 5; - // To turn on these properties, type // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS] static final String DUMP_STATE_PROPERTY = "launcher_dump_state"; @@ -179,18 +182,10 @@ public class Launcher extends Activity private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen"; // Type: int private static final String RUNTIME_STATE = "launcher.state"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x"; - // Type: int - private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y"; + // Type: long + private static final String RUNTIME_STATE_OPEN_FOLDER_ID = "launcher.open_folder_id"; + // Type: Content Values / parcelable + private static final String RUNTIME_STATE_PENDING_ADD_ITEM = "launcher.add_item"; // Type: parcelable private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info"; // Type: parcelable @@ -209,7 +204,8 @@ public class Launcher extends Activity public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data"; /** The different states that Launcher can be in. */ - enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED } + enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED, + WIDGETS, WIDGETS_SPRING_LOADED } @Thunk State mState = State.WORKSPACE; @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation; @@ -233,8 +229,6 @@ public class Launcher extends Activity private final BroadcastReceiver mCloseSystemDialogsReceiver = new CloseSystemDialogsIntentReceiver(); - private LayoutInflater mInflater; - @Thunk Workspace mWorkspace; private View mLauncherView; private View mPageIndicators; @@ -246,7 +240,7 @@ public class Launcher extends Activity private AppWidgetManagerCompat mAppWidgetManager; private LauncherAppWidgetHost mAppWidgetHost; - @Thunk ItemInfo mPendingAddInfo = new ItemInfo(); + @Thunk final ItemInfo mPendingAddInfo = new ItemInfo(); private LauncherAppWidgetProviderInfo mPendingAddWidgetInfo; private int mPendingAddWidgetId = -1; @@ -259,6 +253,7 @@ public class Launcher extends Activity private View mWidgetsButton; private SearchDropTargetBar mSearchDropTargetBar; + private AppInfoDropTargetBar mAppInfoDropTargetBar; // Main container view for the all apps screen. @Thunk AllAppsContainerView mAppsView; @@ -267,7 +262,6 @@ public class Launcher extends Activity @Thunk WidgetsContainerView mWidgetsView; @Thunk WidgetsModel mWidgetsModel; - private boolean mAutoAdvanceRunning = false; private AppWidgetHostView mQsb; private Bundle mSavedState; @@ -287,8 +281,7 @@ public class Launcher extends Activity private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>(); private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>(); - - private Bundle mSavedInstanceState; + private ViewOnDrawExecutor mPendingExecutor; private LauncherModel mModel; private IconCache mIconCache; @@ -305,28 +298,27 @@ public class Launcher extends Activity // Related to the auto-advancing of widgets private final int ADVANCE_MSG = 1; - private final int mAdvanceInterval = 20000; - private final int mAdvanceStagger = 250; + private static final int ADVANCE_INTERVAL = 20000; + private static final int ADVANCE_STAGGER = 250; + + private boolean mAutoAdvanceRunning = false; private long mAutoAdvanceSentTime; private long mAutoAdvanceTimeLeft = -1; - @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = - new HashMap<View, AppWidgetProviderInfo>(); + @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>(); // Determines how long to wait after a rotation before restoring the screen orientation to // match the sensor state. - private final int mRestoreScreenOrientationDelay = 500; + private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500; @Thunk Drawable mWorkspaceBackgroundDrawable; private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>(); private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false; - static final ArrayList<String> sDumpLogs = new ArrayList<String>(); - static Date sDateStamp = new Date(); - static DateFormat sDateFormat = + private static final ArrayList<String> sDumpLogs = new ArrayList<String>(); + private static final Date sDateStamp = new Date(); + private static final DateFormat sDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); - static long sRunStart = System.currentTimeMillis(); - static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent"; // We only want to get the SharedPreferences once since it does an FS stat each time we get // it from the context. @@ -378,7 +370,9 @@ public class Launcher extends Activity } private Stats mStats; - FocusIndicatorView mFocusHandler; + private Logger mUserEventLogger; + + public FocusIndicatorView mFocusHandler; private boolean mRotationEnabled = false; @Thunk void setOrientation() { @@ -433,9 +427,9 @@ public class Launcher extends Activity mIconCache = app.getIconCache(); mDragController = new DragController(this); - mInflater = getLayoutInflater(); mStateTransitionAnimation = new LauncherStateTransitionAnimation(this); + mUserEventLogger = new Logger(this); mStats = new Stats(this); mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); @@ -492,7 +486,7 @@ public class Launcher extends Activity // In case we are on a device with locked rotation, we should look at preferences to check // if the user has specifically allowed rotation. if (!mRotationEnabled) { - mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false); + mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext()); } // On large interfaces, or on devices that a user has specifically enabled screen rotation, @@ -552,7 +546,7 @@ public class Launcher extends Activity return; } // The underlying workspace and hotseat are temporarily suppressed by the search - // overlay. So they sholudn't be accessible. + // overlay. So they shouldn't be accessible. if (mWorkspace != null) { mWorkspaceImportanceForAccessibility = mWorkspace.getImportantForAccessibility(); @@ -639,10 +633,7 @@ public class Launcher extends Activity public Stats getStats() { return mStats; } - - public LayoutInflater getInflater() { - return mInflater; - } + public Logger getLogger() {return mUserEventLogger; } public boolean isDraggingEnabled() { // We prevent dragging when we are loading the workspace as it is possible to pick up a view @@ -724,7 +715,7 @@ public class Launcher extends Activity } else if (requestCode == REQUEST_PICK_WALLPAPER) { if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) { // User could have free-scrolled between pages before picking a wallpaper; make sure - // we move to the closest one now to avoid visual jump. + // we move to the closest one now. mWorkspace.setCurrentPage(mWorkspace.getPageNearestToCenterOfScreen()); showWorkspace(false); } @@ -810,24 +801,22 @@ public class Launcher extends Activity return; } - // The pattern used here is that a user PICKs a specific application, - // which, depending on the target, might need to CREATE the actual target. - - // For example, the user would PICK_SHORTCUT for "Music playlist", and we - // launch over to the Music app to actually CREATE_SHORTCUT. - if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { - final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, - mPendingAddInfo); - if (isWorkspaceLocked()) { - sPendingAddItem = args; - } else { - completeAdd(args); + if (requestCode == REQUEST_CREATE_SHORTCUT) { + // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT. + if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) { + final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1, + mPendingAddInfo); + if (isWorkspaceLocked()) { + sPendingAddItem = args; + } else { + completeAdd(args); + mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, + ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); + } + } else if (resultCode == RESULT_CANCELED) { mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } - } else if (resultCode == RESULT_CANCELED) { - mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded, - ON_ACTIVITY_RESULT_ANIMATION_DELAY, false); } mDragLayer.clearAnimatedView(); @@ -968,6 +957,7 @@ public class Launcher extends Activity } super.onResume(); + mUserEventLogger.resetElapsedSessionMillis(); // Restore the previous launcher state if (mOnResumeState == State.WORKSPACE) { @@ -991,12 +981,6 @@ public class Launcher extends Activity mPaused = false; if (mRestoring || mOnResumeNeedsLoad) { setWorkspaceLoading(true); - - // If we're starting binding all over again, clear any bind calls we'd postponed in - // the past (see waitUntilResume) -- we don't need them since we're starting binding - // from scratch again - mBindOnResumeCallbacks.clear(); - mModel.startLoader(PagedView.INVALID_RESTORE_PAGE); mRestoring = false; mOnResumeNeedsLoad = false; @@ -1344,16 +1328,9 @@ public class Launcher extends Activity mWorkspace.setRestorePage(currentScreen); } - final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1); - final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1); - - if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) { - mPendingAddInfo.container = pendingAddContainer; - mPendingAddInfo.screenId = pendingAddScreen; - mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X); - mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y); - mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X); - mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y); + ContentValues itemValues = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_ITEM); + if (itemValues != null) { + mPendingAddInfo.readFromValues(itemValues); AppWidgetProviderInfo info = savedState.getParcelable( RUNTIME_STATE_PENDING_ADD_WIDGET_INFO); mPendingAddWidgetInfo = info == null ? @@ -1400,9 +1377,12 @@ public class Launcher extends Activity mWorkspace.setup(dragController); dragController.addDragListener(mWorkspace); - // Get the search/delete bar + // Get the search/delete/uninstall bar mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.search_drop_target_bar); + // Get the app info bar + mAppInfoDropTargetBar = (AppInfoDropTargetBar) + mDragLayer.findViewById(R.id.app_info_drop_target_bar); // Setup Apps and Widgets mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view); @@ -1422,6 +1402,9 @@ public class Launcher extends Activity mSearchDropTargetBar.setup(this, dragController); mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar()); } + if (mAppInfoDropTargetBar != null) { + mAppInfoDropTargetBar.setup(this, dragController); + } if (TestingUtils.MEMORY_DUMP_ENABLED) { TestingUtils.addWeightWatcher(this); @@ -1519,7 +1502,7 @@ public class Launcher extends Activity * @return A View inflated from layoutResId. */ public View createShortcut(ViewGroup parent, ShortcutInfo info) { - BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon, + BubbleTextView favorite = (BubbleTextView) getLayoutInflater().inflate(R.layout.app_icon, parent, false); favorite.applyFromShortcutInfo(info, mIconCache); favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx); @@ -1536,7 +1519,6 @@ public class Launcher extends Activity private void completeAddShortcut(Intent data, long container, long screenId, int cellX, int cellY) { int[] cellXY = mTmpAddItemCellCoordinates; - int[] touchXY = mPendingAddInfo.dropPos; CellLayout layout = getCellLayout(container, screenId); ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data); @@ -1563,10 +1545,6 @@ public class Launcher extends Activity true)) { return; } - } else if (touchXY != null) { - // when dragging and dropping, just find the closest free spot - int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY); - foundCellSpan = (result != null); } else { foundCellSpan = layout.findCellForSpan(cellXY, 1, 1); } @@ -1762,11 +1740,11 @@ public class Launcher extends Activity if (autoAdvanceRunning != mAutoAdvanceRunning) { mAutoAdvanceRunning = autoAdvanceRunning; if (autoAdvanceRunning) { - long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft; + long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft; sendAdvanceMessage(delay); } else { if (!mWidgetsToAdvance.isEmpty()) { - mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval - + mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL - (System.currentTimeMillis() - mAutoAdvanceSentTime)); } mHandler.removeMessages(ADVANCE_MSG); @@ -1783,7 +1761,7 @@ public class Launcher extends Activity int i = 0; for (View key: mWidgetsToAdvance.keySet()) { final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId); - final int delay = mAdvanceStagger * i; + final int delay = ADVANCE_STAGGER * i; if (v instanceof Advanceable) { mHandler.postDelayed(new Runnable() { public void run() { @@ -1793,7 +1771,7 @@ public class Launcher extends Activity } i++; } - sendAdvanceMessage(mAdvanceInterval); + sendAdvanceMessage(ADVANCE_INTERVAL); } return true; } @@ -1849,6 +1827,10 @@ public class Launcher extends Activity return mSearchDropTargetBar; } + public AppInfoDropTargetBar getAppInfoDropTargetBar() { + return mAppInfoDropTargetBar; + } + public LauncherAppWidgetHost getAppWidgetHost() { return mAppWidgetHost; } @@ -1979,18 +1961,16 @@ public class Launcher extends Activity super.onSaveInstanceState(outState); outState.putInt(RUNTIME_STATE, mState.ordinal()); - // We close any open folder since it will not be re-opened, and we need to make sure - // this state is reflected. - closeFolder(false); + Folder openFolder = mWorkspace.getOpenFolder(); + if (openFolder != null) { + outState.putLong(RUNTIME_STATE_OPEN_FOLDER_ID, openFolder.mInfo.id); + } if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 && mWaitingForResult) { - outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container); - outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX); - outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY); + ContentValues itemValues = new ContentValues(); + mPendingAddInfo.writeToValues(itemValues); + outState.putParcelable(RUNTIME_STATE_PENDING_ADD_ITEM, itemValues); outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo); outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId); } @@ -2222,7 +2202,6 @@ public class Launcher extends Activity mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1; mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1; mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1; - mPendingAddInfo.dropPos = null; } void addAppWidgetFromDropImpl(final int appWidgetId, final ItemInfo info, final @@ -2297,7 +2276,6 @@ public class Launcher extends Activity resetAddInfo(); mPendingAddInfo.container = container; mPendingAddInfo.screenId = screenId; - mPendingAddInfo.dropPos = null; if (cell != null) { mPendingAddInfo.cellX = cell[0]; @@ -2321,7 +2299,6 @@ public class Launcher extends Activity resetAddInfo(); mPendingAddInfo.container = info.container = container; mPendingAddInfo.screenId = info.screenId = screenId; - mPendingAddInfo.dropPos = null; mPendingAddInfo.minSpanX = info.minSpanX; mPendingAddInfo.minSpanY = info.minSpanY; @@ -2552,8 +2529,10 @@ public class Launcher extends Activity if (v instanceof CellLayout) { if (mWorkspace.isInOverviewMode()) { - showWorkspace(mWorkspace.indexOfChild(v), true); + mWorkspace.snapToPageFromOverView(mWorkspace.indexOfChild(v)); + showWorkspace(true); } + return; } Object tag = v.getTag(); @@ -2676,12 +2655,17 @@ public class Launcher extends Activity final ShortcutInfo shortcut = (ShortcutInfo) tag; if (shortcut.isDisabled != 0) { - int error = R.string.activity_not_available; - if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) { - error = R.string.safemode_shortcut_error; + if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SUSPENDED) != 0 + || (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_QUIET_USER) != 0) { + // Launch activity anyway, framework will tell the user why the app is suspended. + } else { + int error = R.string.activity_not_available; + if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) { + error = R.string.safemode_shortcut_error; + } + Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); + return; } - Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); - return; } // Check for abandoned promise @@ -2745,38 +2729,10 @@ public class Launcher extends Activity throw new IllegalArgumentException("Input must be a FolderIcon"); } - // TODO(sunnygoyal): Re-evaluate this code. FolderIcon folderIcon = (FolderIcon) v; - final FolderInfo info = folderIcon.getFolderInfo(); - Folder openFolder = mWorkspace.getFolderForTag(info); - - // If the folder info reports that the associated folder is open, then verify that - // it is actually opened. There have been a few instances where this gets out of sync. - if (info.opened && openFolder == null) { - Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: " - + info.screenId + " (" + info.cellX + ", " + info.cellY + ")"); - info.opened = false; - } - - if (!info.opened && !folderIcon.getFolder().isDestroyed()) { - // Close any open folder - closeFolder(); + if (!folderIcon.getFolderInfo().opened && !folderIcon.getFolder().isDestroyed()) { // Open the requested folder - openFolder(folderIcon); - } else { - // Find the open folder... - int folderScreen; - if (openFolder != null) { - folderScreen = mWorkspace.getPageForView(openFolder); - // .. and close it - closeFolder(openFolder, true); - if (folderScreen != mWorkspace.getCurrentPage()) { - // Close any folder open on the current screen - closeFolder(); - // Pull the folder onto this screen - openFolder(folderIcon); - } - } + openFolder(folderIcon, true); } if (mLauncherCallbacks != null) { @@ -2809,7 +2765,7 @@ public class Launcher extends Activity int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen()); float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); startActivityForResult(new Intent(Intent.ACTION_SET_WALLPAPER).setPackage(getPackageName()) - .putExtra(WallpaperPickerActivity.EXTRA_WALLPAPER_OFFSET, offset), + .putExtra(WallpaperUtils.EXTRA_WALLPAPER_OFFSET, offset), REQUEST_PICK_WALLPAPER); if (mLauncherCallbacks != null) { @@ -3083,12 +3039,8 @@ public class Launcher extends Activity } } - private void growAndFadeOutFolderIcon(FolderIcon fi) { + private void growAndFadeOutFolderIcon(FolderIcon fi, boolean animate) { if (fi == null) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f); - FolderInfo info = (FolderInfo) fi.getTag(); if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { CellLayout cl = (CellLayout) fi.getParent().getParent(); @@ -3100,32 +3052,26 @@ public class Launcher extends Activity copyFolderIconToImage(fi); fi.setVisibility(View.INVISIBLE); - ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, - scaleX, scaleY); + ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale( + mFolderIconImageView, 0, 1.5f, 1.5f); if (Utilities.ATLEAST_LOLLIPOP) { oa.setInterpolator(new LogDecelerateInterpolator(100, 0)); } oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); oa.start(); - if (Utilities.isPowerSaverOn(this)) { - // Animations are disabled in battery saver mode, so just skip to the end state. + if (!animate) { oa.end(); } } private void shrinkAndFadeInFolderIcon(final FolderIcon fi, boolean animate) { if (fi == null) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); - final CellLayout cl = (CellLayout) fi.getParent().getParent(); // We remove and re-draw the FolderIcon in-case it has changed mDragLayer.removeView(mFolderIconImageView); copyFolderIconToImage(fi); - ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha, - scaleX, scaleY); + ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1); oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); oa.addListener(new AnimatorListenerAdapter() { @Override @@ -3149,9 +3095,11 @@ public class Launcher extends Activity * is animated relative to the specified View. If the View is null, no animation * is played. * - * @param folderInfo The FolderInfo describing the folder to open. + * @param folderIcon The FolderIcon describing the folder to open. */ - public void openFolder(FolderIcon folderIcon) { + public void openFolder(FolderIcon folderIcon, boolean animate) { + animate &= !Utilities.isPowerSaverOn(this); + Folder folder = folderIcon.getFolder(); Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null; if (openFolder != null && openFolder != folder) { @@ -3170,13 +3118,17 @@ public class Launcher extends Activity // There was a one-off crash where the folder had a parent already. if (folder.getParent() == null) { mDragLayer.addView(folder); - mDragController.addDropTarget((DropTarget) folder); + mDragController.addDropTarget(folder); } else { Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" + folder.getParent() + ")."); } - folder.animateOpen(); - growAndFadeOutFolderIcon(folderIcon); + if (animate) { + folder.animateOpen(); + } else { + folder.open(); + } + growAndFadeOutFolderIcon(folderIcon, animate); // Notify the accessibility manager that this folder "window" has appeared and occluded // the workspace items @@ -3199,6 +3151,8 @@ public class Launcher extends Activity } public void closeFolder(Folder folder, boolean animate) { + animate &= !Utilities.isPowerSaverOn(this); + folder.getInfo().opened = false; ViewGroup parent = (ViewGroup) folder.getParent().getParent(); @@ -3355,38 +3309,17 @@ public class Launcher extends Activity } } - /** - * @return whether or not the Launcher state changed. - */ public boolean showWorkspace(boolean animated) { - return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null); + return showWorkspace(animated, null); } - /** - * @return whether or not the Launcher state changed. - */ public boolean showWorkspace(boolean animated, Runnable onCompleteRunnable) { - return showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, - onCompleteRunnable); - } - - /** - * @return whether or not the Launcher state changed. - */ - protected boolean showWorkspace(int snapToPage, boolean animated) { - return showWorkspace(snapToPage, animated, null); - } - - /** - * @return whether or not the Launcher state changed. - */ - boolean showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) { boolean changed = mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL; if (changed) { mWorkspace.setVisibility(View.VISIBLE); mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.NORMAL, snapToPage, animated, onCompleteRunnable); + Workspace.State.NORMAL, animated, onCompleteRunnable); // Set focus to the AppsCustomize button if (mAllAppsButton != null) { @@ -3406,6 +3339,7 @@ public class Launcher extends Activity getWindow().getDecorView() .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + mUserEventLogger.resetElapsedContainerMillis(); return changed; } @@ -3435,9 +3369,7 @@ public class Launcher extends Activity } mWorkspace.setVisibility(View.VISIBLE); mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.OVERVIEW, - WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, - postAnimRunnable); + Workspace.State.OVERVIEW, animated, postAnimRunnable); mState = State.WORKSPACE; } @@ -3514,31 +3446,50 @@ public class Launcher extends Activity * Updates the workspace and interaction state on state change, and return the animation to this * new state. */ - public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage, + public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, boolean animated, HashMap<View, Integer> layerViews) { Workspace.State fromState = mWorkspace.getState(); - Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews); + Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews); updateInteraction(fromState, toState); return anim; } + public void onLauncherClingShown() { + // When a launcher cling appears, it should cover the underlying layers, so their focus + // should be blocked. + if (mDragLayer.getDescendantFocusability() != ViewGroup.FOCUS_BLOCK_DESCENDANTS) { + mDragLayer.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + } + } + + public void onLauncherClingDismissed() { + mDragLayer.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + } + public void enterSpringLoadedDragMode() { if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name())); - if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED || - mState == State.WIDGETS_SPRING_LOADED) { + if (isStateSpringLoaded()) { return; } mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(), - Workspace.State.SPRING_LOADED, - WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */, + Workspace.State.SPRING_LOADED, true /* animated */, null /* onCompleteRunnable */); - mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED; + + if (isAppsViewVisible()) { + mState = State.APPS_SPRING_LOADED; + } else if (isWidgetsViewVisible()) { + mState = State.WIDGETS_SPRING_LOADED; + } else if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { + mState = State.WORKSPACE_SPRING_LOADED; + } else { + mState = State.WORKSPACE; + } } public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay, final Runnable onCompleteRunnable) { - if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return; + if (!isStateSpringLoaded()) return; mHandler.postDelayed(new Runnable() { @Override @@ -3558,12 +3509,19 @@ public class Launcher extends Activity }, delay); } + boolean isStateSpringLoaded() { + return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED + || mState == State.WIDGETS_SPRING_LOADED; + } + void exitSpringLoadedDragMode() { if (mState == State.APPS_SPRING_LOADED) { showAppsView(true /* animated */, false /* resetListToTop */, false /* updatePredictedApps */, false /* focusSearchBar */); } else if (mState == State.WIDGETS_SPRING_LOADED) { showWidgetsView(true, false); + } else if (mState == State.WORKSPACE_SPRING_LOADED) { + showWorkspace(true); } } @@ -3784,7 +3742,20 @@ public class Launcher extends Activity if (mWorkspace != null) { return mWorkspace.getCurrentPage(); } else { - return SCREEN_COUNT / 2; + return 0; + } + } + + /** + * Clear any pending bind callbacks. This is called when is loader is planning to + * perform a full rebind from scratch. + */ + @Override + public void clearPendingBinds() { + mBindOnResumeCallbacks.clear(); + if (mPendingExecutor != null) { + mPendingExecutor.markCompleted(); + mPendingExecutor = null; } } @@ -3796,11 +3767,6 @@ public class Launcher extends Activity public void startBinding() { setWorkspaceLoading(true); - // If we're starting binding all over again, clear any bind calls we'd postponed in - // the past (see waitUntilResume) -- we don't need them since we're starting binding - // from scratch again - mBindOnResumeCallbacks.clear(); - // Clear the workspace because it's going to be rebound mWorkspace.clearDropTargets(); mWorkspace.removeAllWorkspaceScreens(); @@ -3922,7 +3888,7 @@ public class Launcher extends Activity Object tag = v.getTag(); String desc = "Collision while binding workspace item: " + item + ". Collides with " + tag; - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw (new RuntimeException(desc)); } else { Log.d(TAG, desc); @@ -4143,6 +4109,21 @@ public class Launcher extends Activity mSynchronouslyBoundPages.add(page); } + @Override + public void executeOnNextDraw(ViewOnDrawExecutor executor) { + if (mPendingExecutor != null) { + mPendingExecutor.markCompleted(); + } + mPendingExecutor = executor; + executor.attachTo(this); + } + + public void clearPendingExecutor(ViewOnDrawExecutor executor) { + if (mPendingExecutor == executor) { + mPendingExecutor = null; + } + } + /** * Callback saying that there aren't any more items to bind. * @@ -4161,6 +4142,26 @@ public class Launcher extends Activity if (!mWorkspace.hasFocus()) { mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); } + + long folderId = mSavedState.getLong(RUNTIME_STATE_OPEN_FOLDER_ID); + if (folderId != 0) { + View view = mWorkspace.getHomescreenIconByItemId(folderId); + if (view instanceof FolderIcon) { + FolderIcon icon = (FolderIcon) view; + FolderInfo info = icon.getFolderInfo(); + long currentScreenId = mWorkspace.getScreenIdForPageIndex( + mWorkspace.getNextPage()); + if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + || info.screenId == currentScreenId) { + // We can show the folder + openFolder(icon, false); + } else { + Launcher.addDumpLog(TAG, "Saved state contains folder " + info + + " but current screen is " + currentScreenId); + } + } + } + mSavedState = null; } @@ -4219,10 +4220,7 @@ public class Launcher extends Activity } private ValueAnimator createNewAppBounceAnimation(View v, int i) { - ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v, - PropertyValuesHolder.ofFloat("alpha", 1f), - PropertyValuesHolder.ofFloat("scaleX", 1f), - PropertyValuesHolder.ofFloat("scaleY", 1f)); + ValueAnimator bounceAnim = LauncherAnimUtils.ofViewAlphaAndScale(v, 1, 1, 1); bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION)); @@ -4489,7 +4487,7 @@ public class Launcher extends Activity public void run() { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } - }, mRestoreScreenOrientationDelay); + }, RESTORE_SCREEN_ORIENTATION_DELAY); } } } @@ -4757,7 +4755,6 @@ public class Launcher extends Activity Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading); Log.d(TAG, "mRestoring=" + mRestoring); Log.d(TAG, "mWaitingForResult=" + mWaitingForResult); - Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState); Log.d(TAG, "sFolders.size=" + sFolders.size()); mModel.dumpState(); // TODO(hyunyoungs): add mWidgetsView.dumpState(); or mWidgetsModel.dumpState(); @@ -4768,51 +4765,47 @@ public class Launcher extends Activity @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { super.dump(prefix, fd, writer, args); - synchronized (sDumpLogs) { - writer.println(" "); - writer.println("Debug logs: "); - for (int i = 0; i < sDumpLogs.size(); i++) { - writer.println(" " + sDumpLogs.get(i)); + // Dump workspace + writer.println(prefix + "Workspace Items"); + for (int i = mWorkspace.numCustomPages(); i < mWorkspace.getPageCount(); i++) { + writer.println(prefix + " Homescreen " + i); + + ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets(); + for (int j = 0; j < layout.getChildCount(); j++) { + Object tag = layout.getChildAt(j).getTag(); + if (tag != null) { + writer.println(prefix + " " + tag.toString()); + } } } - if (mLauncherCallbacks != null) { - mLauncherCallbacks.dump(prefix, fd, writer, args); + + writer.println(prefix + " Hotseat"); + ViewGroup layout = mHotseat.getLayout().getShortcutsAndWidgets(); + for (int j = 0; j < layout.getChildCount(); j++) { + Object tag = layout.getChildAt(j).getTag(); + if (tag != null) { + writer.println(prefix + " " + tag.toString()); + } } - } - public static void dumpDebugLogsToConsole() { - if (DEBUG_DUMP_LOG) { - synchronized (sDumpLogs) { - Log.d(TAG, ""); - Log.d(TAG, "*********************"); - Log.d(TAG, "Launcher debug logs: "); - for (int i = 0; i < sDumpLogs.size(); i++) { - Log.d(TAG, " " + sDumpLogs.get(i)); - } - Log.d(TAG, "*********************"); - Log.d(TAG, ""); + synchronized (sDumpLogs) { + writer.println(); + writer.println(prefix + "Debug logs"); + for (String log : sDumpLogs) { + writer.println(prefix + " " + log); } } - } - public static void addDumpLog(String tag, String log, boolean debugLog) { - addDumpLog(tag, log, null, debugLog); + if (mLauncherCallbacks != null) { + mLauncherCallbacks.dump(prefix, fd, writer, args); + } } - public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) { - if (debugLog) { - if (e != null) { - Log.d(tag, log, e); - } else { - Log.d(tag, log); - } - } - if (DEBUG_DUMP_LOG) { + public static void addDumpLog(String tag, String log) { + Log.d(tag, log); + synchronized(sDumpLogs) { sDateStamp.setTime(System.currentTimeMillis()); - synchronized (sDumpLogs) { - sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log - + (e == null ? "" : (", Exception: " + e))); - } + sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log); } } @@ -4824,53 +4817,6 @@ public class Launcher extends Activity return sCustomAppWidgets; } - public void dumpLogsToLocalData() { - if (DEBUG_DUMP_LOG) { - new AsyncTask<Void, Void, Void>() { - public Void doInBackground(Void ... args) { - boolean success = false; - sDateStamp.setTime(sRunStart); - String FILENAME = sDateStamp.getMonth() + "-" - + sDateStamp.getDay() + "_" - + sDateStamp.getHours() + "-" - + sDateStamp.getMinutes() + "_" - + sDateStamp.getSeconds() + ".txt"; - - FileOutputStream fos = null; - File outFile = null; - try { - outFile = new File(getFilesDir(), FILENAME); - outFile.createNewFile(); - fos = new FileOutputStream(outFile); - } catch (Exception e) { - e.printStackTrace(); - } - if (fos != null) { - PrintWriter writer = new PrintWriter(fos); - - writer.println(" "); - writer.println("Debug logs: "); - synchronized (sDumpLogs) { - for (int i = 0; i < sDumpLogs.size(); i++) { - writer.println(" " + sDumpLogs.get(i)); - } - } - writer.close(); - } - try { - if (fos != null) { - fos.close(); - success = true; - } - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); - } - } - public static List<View> getFolderContents(View icon) { if (icon instanceof FolderIcon) { return ((FolderIcon) icon).getFolder().getItemsInReadingOrder(); diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java index fa20f0360..01e73d4a1 100644 --- a/src/com/android/launcher3/LauncherAnimUtils.java +++ b/src/com/android/launcher3/LauncherAnimUtils.java @@ -21,13 +21,10 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; -import android.annotation.TargetApi; -import android.os.Build; +import android.util.Property; import android.view.View; import android.view.ViewTreeObserver; -import com.android.launcher3.util.UiThreadCircularReveal; - import java.util.HashSet; import java.util.WeakHashMap; @@ -102,42 +99,32 @@ public class LauncherAnimUtils { return anim; } - public static ObjectAnimator ofFloat(View target, String propertyName, float... values) { - ObjectAnimator anim = new ObjectAnimator(); - anim.setTarget(target); - anim.setPropertyName(propertyName); - anim.setFloatValues(values); + public static ObjectAnimator ofFloat(View target, Property<View, Float> property, + float... values) { + ObjectAnimator anim = ObjectAnimator.ofFloat(target, property, values); cancelOnDestroyActivity(anim); new FirstFrameAnimatorHelper(anim, target); return anim; } + public static ObjectAnimator ofViewAlphaAndScale(View target, + float alpha, float scaleX, float scaleY) { + return ofPropertyValuesHolder(target, + PropertyValuesHolder.ofFloat(View.ALPHA, alpha), + PropertyValuesHolder.ofFloat(View.SCALE_X, scaleX), + PropertyValuesHolder.ofFloat(View.SCALE_Y, scaleY)); + } + public static ObjectAnimator ofPropertyValuesHolder(View target, PropertyValuesHolder... values) { - ObjectAnimator anim = new ObjectAnimator(); - anim.setTarget(target); - anim.setValues(values); - cancelOnDestroyActivity(anim); - new FirstFrameAnimatorHelper(anim, target); - return anim; + return ofPropertyValuesHolder(target, target, values); } public static ObjectAnimator ofPropertyValuesHolder(Object target, View view, PropertyValuesHolder... values) { - ObjectAnimator anim = new ObjectAnimator(); - anim.setTarget(target); - anim.setValues(values); + ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(target, values); cancelOnDestroyActivity(anim); new FirstFrameAnimatorHelper(anim, view); return anim; } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static ValueAnimator createCircularReveal(View view, int centerX, - int centerY, float startRadius, float endRadius) { - ValueAnimator anim = UiThreadCircularReveal.createCircularReveal(view, centerX, - centerY, startRadius, endRadius); - new FirstFrameAnimatorHelper(anim, view); - return anim; - } } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 4ac5ef3d5..e58652b8e 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -17,6 +17,7 @@ package com.android.launcher3; import android.app.SearchManager; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -40,7 +41,7 @@ public class LauncherAppState { private final IconCache mIconCache; private final WidgetPreviewLoader mWidgetCache; - private boolean mWallpaperChangedSinceLastCheck; + @Thunk boolean mWallpaperChangedSinceLastCheck; private static WeakReference<LauncherProvider> sLauncherProvider; private static Context sContext; @@ -66,11 +67,17 @@ public class LauncherAppState { return sContext; } - public static void setApplicationContext(Context context) { - if (sContext != null) { - Log.w(Launcher.TAG, "setApplicationContext called twice! old=" + sContext + " new=" + context); + static void setLauncherProvider(LauncherProvider provider) { + if (sLauncherProvider != null) { + Log.w(Launcher.TAG, "setLauncherProvider called twice! old=" + + sLauncherProvider.get() + " new=" + provider); } - sContext = context.getApplicationContext(); + sLauncherProvider = new WeakReference<>(provider); + + // The content provider exists for the entire duration of the launcher main process and + // is the first component to get created. Initializing application context here ensures + // that LauncherAppState always exists in the main process. + sContext = provider.getContext().getApplicationContext(); } private LauncherAppState() { @@ -100,9 +107,19 @@ public class LauncherAppState { // For handling managed profiles filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED); filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED); + filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED); sContext.registerReceiver(mModel, filter); UserManagerCompat.getInstance(sContext).enableAndResetCache(); + if (!Utilities.ATLEAST_KITKAT) { + sContext.registerReceiver(new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + mWallpaperChangedSinceLastCheck = true; + } + }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)); + } new ConfigMonitor(sContext).register(); } @@ -126,7 +143,7 @@ public class LauncherAppState { } LauncherModel setLauncher(Launcher launcher) { - getLauncherProvider().setLauncherProviderChangeListener(launcher); + sLauncherProvider.get().setLauncherProviderChangeListener(launcher); mModel.initialize(launcher); mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ? new LauncherAccessibilityDelegate(launcher) : null; @@ -145,22 +162,10 @@ public class LauncherAppState { return mModel; } - static void setLauncherProvider(LauncherProvider provider) { - sLauncherProvider = new WeakReference<LauncherProvider>(provider); - } - - public static LauncherProvider getLauncherProvider() { - return sLauncherProvider.get(); - } - public WidgetPreviewLoader getWidgetCache() { return mWidgetCache; } - public void onWallpaperChanged() { - mWallpaperChangedSinceLastCheck = true; - } - public boolean hasWallpaperChangedSinceLastCheck() { boolean result = mWallpaperChangedSinceLastCheck; mWallpaperChangedSinceLastCheck = false; diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index 418525758..c78908273 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -25,10 +25,12 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewDebug; import android.view.ViewGroup; import android.widget.RemoteViews; -import com.android.launcher3.DragLayer.TouchCompleteListener; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener; import java.util.ArrayList; @@ -42,18 +44,20 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc private CheckLongPressHelper mLongPressHelper; private StylusEventHelper mStylusEventHelper; private Context mContext; + @ViewDebug.ExportedProperty(category = "launcher") private int mPreviousOrientation; private DragLayer mDragLayer; private float mSlop; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mChildrenFocused; public LauncherAppWidgetHostView(Context context) { super(context); mContext = context; mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mDragLayer = ((Launcher) context).getDragLayer(); setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); @@ -101,7 +105,7 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc // Watch for longpress or stylus button press events at this level to // make sure users can always pick up this widget - if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) { + if (mStylusEventHelper.onMotionEvent(ev)) { mLongPressHelper.cancelLongPress(); return true; } diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java index 2177f527e..c2ab20a62 100644 --- a/src/com/android/launcher3/LauncherBackupAgentHelper.java +++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java @@ -25,7 +25,7 @@ import android.database.Cursor; import android.os.ParcelFileDescriptor; import android.util.Log; -import com.android.launcher3.model.MigrateFromRestoreTask; +import com.android.launcher3.model.GridSizeMigrationTask; import java.io.IOException; @@ -93,7 +93,8 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper { } // Clear dB before restore - LauncherAppState.getLauncherProvider().createEmptyDB(); + LauncherSettings.Settings.call(getContentResolver(), + LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); boolean hasData; try { @@ -110,25 +111,27 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper { } if (hasData && mHelper.restoreSuccessful) { - LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated(); + LauncherSettings.Settings.call(getContentResolver(), + LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); LauncherClings.markFirstRunClingDismissed(this); // Rank was added in v4. if (mHelper.restoredBackupVersion <= 3) { - LauncherAppState.getLauncherProvider().updateFolderItemsRank(); + LauncherSettings.Settings.call(getContentResolver(), + LauncherSettings.Settings.METHOD_UPDATE_FOLDER_ITEMS_RANK); } - if (MigrateFromRestoreTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) { - MigrateFromRestoreTask.markForMigration(getApplicationContext(), - (int) mHelper.migrationCompatibleProfileData.desktopCols, - (int) mHelper.migrationCompatibleProfileData.desktopRows, - mHelper.widgetSizes); + if (GridSizeMigrationTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) { + GridSizeMigrationTask.markForMigration(getApplicationContext(), + mHelper.widgetSizes, mHelper.migrationCompatibleProfileData); } - LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities(); + LauncherSettings.Settings.call(getContentResolver(), + LauncherSettings.Settings.METHOD_CONVERT_SHORTCUTS_TO_ACTIVITIES); } else { if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB"); - LauncherAppState.getLauncherProvider().createEmptyDB(); + LauncherSettings.Settings.call(getContentResolver(), + LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); } } } diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java index 47d1ce9c0..05d729e78 100644 --- a/src/com/android/launcher3/LauncherBackupHelper.java +++ b/src/com/android/launcher3/LauncherBackupHelper.java @@ -52,7 +52,7 @@ import com.android.launcher3.backup.nano.BackupProtos.Screen; import com.android.launcher3.backup.nano.BackupProtos.Widget; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.model.MigrateFromRestoreTask; +import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.util.Thunk; import com.google.protobuf.nano.InvalidProtocolBufferNanoException; import com.google.protobuf.nano.MessageNano; @@ -315,14 +315,13 @@ public class LauncherBackupHelper implements BackupHelper { return true; } - if (MigrateFromRestoreTask.ENABLED && - (oldProfile.desktopCols - currentProfile.desktopCols <= 1) && - (oldProfile.desktopRows - currentProfile.desktopRows <= 1)) { - // Allow desktop migration when row and/or column count contracts by 1. - + if (GridSizeMigrationTask.ENABLED) { + // One time migrate the workspace when launcher starts. migrationCompatibleProfileData = initDeviceProfileData(mIdp); migrationCompatibleProfileData.desktopCols = oldProfile.desktopCols; migrationCompatibleProfileData.desktopRows = oldProfile.desktopRows; + migrationCompatibleProfileData.hotseatCount = oldProfile.hotseatCount; + migrationCompatibleProfileData.allappsRank = oldProfile.allappsRank; return true; } return false; diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 772fbf358..1d7575253 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -54,20 +54,33 @@ public interface LauncherCallbacks { */ public void onLauncherProviderChange(); public void finishBindingItems(final boolean upgradePath); - public void onClickAllAppsButton(View v); public void bindAllApplications(ArrayList<AppInfo> apps); + public void onInteractionBegin(); + public void onInteractionEnd(); + + /** + * Extension points for Gel Logging. + */ + @Deprecated + public void onClickAllAppsButton(View v); + @Deprecated public void onClickFolderIcon(View v); + @Deprecated public void onClickAppShortcut(View v); @Deprecated public void onClickPagedViewIcon(View v); + @Deprecated public void onClickWallpaperPicker(View v); + @Deprecated public void onClickSettingsButton(View v); + @Deprecated public void onClickAddWidgetButton(View v); + @Deprecated public void onPageSwitch(View newPage, int newPageIndex); + @Deprecated public void onWorkspaceLockedChanged(); + @Deprecated public void onDragStarted(View view); - public void onInteractionBegin(); - public void onInteractionEnd(); /* * Extension points for replacing the search experience diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java index 5c1e83036..9b8e89429 100644 --- a/src/com/android/launcher3/LauncherClings.java +++ b/src/com/android/launcher3/LauncherClings.java @@ -27,9 +27,11 @@ import android.os.Build; import android.os.Bundle; import android.os.UserManager; import android.provider.Settings; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnGlobalLayoutListener; @@ -37,7 +39,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.launcher3.util.Thunk; -class LauncherClings implements OnClickListener { +class LauncherClings implements OnClickListener, OnKeyListener { private static final String MIGRATION_CLING_DISMISSED_KEY = "cling_gel.migration.dismissed"; private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed"; @@ -83,6 +85,20 @@ class LauncherClings implements OnClickListener { } } + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.isPrintingKey()) { + // Should ignore all printing keys, otherwise they come to the search box. + return true; + } + if (keyCode == KeyEvent.KEYCODE_MENU) { + // Menu key goes to the overview mode similar to longpress, therefore it needs to + // dismiss the clings. + dismissLongPressCling(); + } + return false; + } + /** * Shows the migration cling. * @@ -90,6 +106,7 @@ class LauncherClings implements OnClickListener { * package was not preinstalled and there exists a db to migrate from. */ public void showMigrationCling() { + mLauncher.onLauncherClingShown(); mIsVisible = true; mLauncher.hideWorkspaceSearchAndHotseat(); @@ -134,7 +151,9 @@ class LauncherClings implements OnClickListener { final ViewGroup content = (ViewGroup) cling.findViewById(R.id.cling_content); mInflater.inflate(showWelcome ? R.layout.longpress_cling_welcome_content : R.layout.longpress_cling_content, content); - content.findViewById(R.id.cling_dismiss_longpress_info).setOnClickListener(this); + final View button = content.findViewById(R.id.cling_dismiss_longpress_info); + button.setOnClickListener(this); + button.setOnKeyListener(this); if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) { Drawable bg = new BorderCropDrawable(mLauncher.getResources().getDrawable(R.drawable.cling_bg), @@ -142,6 +161,7 @@ class LauncherClings implements OnClickListener { content.setBackground(bg); } + mLauncher.onLauncherClingShown(); root.addView(cling); if (showWelcome) { @@ -159,12 +179,12 @@ class LauncherClings implements OnClickListener { ObjectAnimator anim; if (TAG_CROP_TOP_AND_SIDES.equals(content.getTag())) { content.setTranslationY(-content.getMeasuredHeight()); - anim = LauncherAnimUtils.ofFloat(content, "translationY", 0); + anim = LauncherAnimUtils.ofFloat(content, View.TRANSLATION_Y, 0); } else { content.setScaleX(0); content.setScaleY(0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1); + PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat(View.SCALE_X, 1); + PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1); anim = LauncherAnimUtils.ofPropertyValuesHolder(content, scaleX, scaleY); } @@ -178,7 +198,12 @@ class LauncherClings implements OnClickListener { @Thunk void dismissLongPressCling() { Runnable dismissCb = new Runnable() { public void run() { - dismissCling(mLauncher.findViewById(R.id.longpress_cling), null, + Runnable cb = new Runnable() { + public void run() { + mLauncher.onLauncherClingDismissed(); + } + }; + dismissCling(mLauncher.findViewById(R.id.longpress_cling), cb, WORKSPACE_CLING_DISMISSED_KEY, DISMISS_CLING_DURATION); } }; diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 774cca459..f707ec501 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -56,13 +56,17 @@ import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.model.MigrateFromRestoreTask; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.CursorIconInfo; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.ViewOnDrawExecutor; import java.lang.ref.WeakReference; import java.net.URISyntaxException; @@ -78,6 +82,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.Executor; /** * Maintains in-memory state of the Launcher. It is expected that there should be only one @@ -123,12 +128,6 @@ public class LauncherModel extends BroadcastReceiver @Thunk boolean mWorkspaceLoaded; @Thunk boolean mAllAppsLoaded; - // When we are loading pages synchronously, we can't just post the binding of items on the side - // pages as this delays the rotation process. Instead, we wait for a callback from the first - // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start - // a normal load, we also clear this set of Runnables. - static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>(); - /** * Set of runnables to be called on the background thread after the workspace binding * is complete. @@ -189,6 +188,7 @@ public class LauncherModel extends BroadcastReceiver public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); + public void clearPendingBinds(); public void startBinding(); public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, boolean forceAnimateIcons); @@ -213,7 +213,7 @@ public class LauncherModel extends BroadcastReceiver public void bindSearchProviderChanged(); public boolean isAllAppsButtonRank(int rank); public void onPageBoundSynchronously(int page); - public void dumpLogsToLocalData(); + public void executeOnNextDraw(ViewOnDrawExecutor executor); } public interface ItemInfoFilter { @@ -486,7 +486,9 @@ public class LauncherModel extends BroadcastReceiver if (!found) { // Still no position found. Add a new screen to the end. - screenId = LauncherAppState.getLauncherProvider().generateNewScreenId(); + screenId = LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) + .getLong(LauncherSettings.Settings.EXTRA_VALUE); // Save the screen id for binding in the workspace workspaceScreens.add(screenId); @@ -531,8 +533,7 @@ public class LauncherModel extends BroadcastReceiver // Find appropriate space for the item. Pair<Long, int[]> coords = findSpaceForItem(context, - workspaceScreens, addedWorkspaceScreensFinal, - 1, 1); + workspaceScreens, addedWorkspaceScreensFinal, 1, 1); long screenId = coords.first; int[] cordinates = coords.second; @@ -592,11 +593,6 @@ public class LauncherModel extends BroadcastReceiver "main thread"); } - // Clear any deferred bind runnables - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.clear(); - } - // Remove any queued UI runnables mHandler.cancelAll(); // Unbind all the workspace items @@ -627,7 +623,7 @@ public class LauncherModel extends BroadcastReceiver * Adds an item to the DB if it was not created previously, or move it to a new * <container, screen, cellX, cellY> */ - static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, + public static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, long screenId, int cellX, int cellY) { if (item.container == ItemInfo.NO_ID) { // From all apps @@ -655,12 +651,7 @@ public class LauncherModel extends BroadcastReceiver modelShortcut.cellX == shortcut.cellX && modelShortcut.cellY == shortcut.cellY && modelShortcut.spanX == shortcut.spanX && - modelShortcut.spanY == shortcut.spanY && - ((modelShortcut.dropPos == null && shortcut.dropPos == null) || - (modelShortcut.dropPos != null && - shortcut.dropPos != null && - modelShortcut.dropPos[0] == shortcut.dropPos[0] && - modelShortcut.dropPos[1] == shortcut.dropPos[1]))) { + modelShortcut.spanY == shortcut.spanY) { // For all intents and purposes, this is the same object return; } @@ -813,7 +804,7 @@ public class LauncherModel extends BroadcastReceiver * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the * cellX, cellY have already been updated on the ItemInfos. */ - static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items, + public static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items, final long container, final int screen) { ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>(); @@ -887,7 +878,7 @@ public class LauncherModel extends BroadcastReceiver } private void assertWorkspaceLoaded() { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { synchronized (mLock) { if (!mHasLoaderCompletedOnce || (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) { @@ -939,51 +930,6 @@ public class LauncherModel extends BroadcastReceiver } /** - * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList. - */ - FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) { - final ContentResolver cr = context.getContentResolver(); - Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, - "_id=? and (itemType=? or itemType=?)", - new String[] { String.valueOf(id), - String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null); - - try { - if (c.moveToFirst()) { - final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); - final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); - final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); - final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); - final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); - final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS); - - FolderInfo folderInfo = null; - switch (c.getInt(itemTypeIndex)) { - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - folderInfo = findOrMakeFolder(folderList, id); - break; - } - - // Do not trim the folder label, as is was set by the user. - folderInfo.title = c.getString(titleIndex); - folderInfo.id = id; - folderInfo.container = c.getInt(containerIndex); - folderInfo.screenId = c.getInt(screenIndex); - folderInfo.cellX = c.getInt(cellXIndex); - folderInfo.cellY = c.getInt(cellYIndex); - folderInfo.options = c.getInt(optionsIndex); - - return folderInfo; - } - } finally { - c.close(); - } - - return null; - } - - /** * Add an item to the database in a specified container. Sets the container, screen, cellX and * cellY fields of the item. Also assigns an ID to the item. */ @@ -1005,7 +951,9 @@ public class LauncherModel extends BroadcastReceiver final ContentResolver cr = context.getContentResolver(); item.onAddToDatabase(context, values); - item.id = LauncherAppState.getLauncherProvider().generateNewItemId(); + item.id = LauncherSettings.Settings.call(cr, LauncherSettings.Settings.METHOD_NEW_ITEM_ID) + .getLong(LauncherSettings.Settings.EXTRA_VALUE); + values.put(LauncherSettings.Favorites._ID, item.id); final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); @@ -1045,15 +993,6 @@ public class LauncherModel extends BroadcastReceiver runOnWorkerThread(r); } - /** - * Creates a new unique child id, for a given cell span across all layouts. - */ - static int getCellLayoutChildId( - long container, long screen, int localCellX, int localCellY, int spanX, int spanY) { - return (((int) container & 0xFF) << 24) - | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF); - } - private static ArrayList<ItemInfo> getItemsByPackageName( final String pn, final UserHandleCompat user) { ItemInfoFilter filter = new ItemInfoFilter() { @@ -1264,6 +1203,20 @@ public class LauncherModel extends BroadcastReceiver } } + @Override + public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { + enqueuePackageUpdated(new PackageUpdatedTask( + PackageUpdatedTask.OP_SUSPEND, packageNames, + user)); + } + + @Override + public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { + enqueuePackageUpdated(new PackageUpdatedTask( + PackageUpdatedTask.OP_UNSUSPEND, packageNames, + user)); + } + /** * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and * ACTION_PACKAGE_CHANGED. @@ -1282,7 +1235,8 @@ public class LauncherModel extends BroadcastReceiver callbacks.bindSearchProviderChanged(); } } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) - || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { + || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action) + || LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED.equals(action)) { UserManagerCompat.getInstance(context).enableAndResetCache(); forceReload(); } @@ -1349,14 +1303,16 @@ public class LauncherModel extends BroadcastReceiver // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems InstallShortcutReceiver.enableInstallQueue(); synchronized (mLock) { - // Clear any deferred bind-runnables from the synchronized load process - // We must do this before any loading/binding is scheduled below. - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.clear(); - } - // Don't bother to start the thread if we know it's not going to do anything if (mCallbacks != null && mCallbacks.get() != null) { + final Callbacks oldCallbacks = mCallbacks.get(); + // Clear any pending bind-runnables from the synchronized load process. + runOnMainThread(new Runnable() { + public void run() { + oldCallbacks.clearPendingBinds(); + } + }); + // If there is already one running, tell it to stop. stopLoaderLocked(); mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags); @@ -1371,21 +1327,6 @@ public class LauncherModel extends BroadcastReceiver } } - void bindRemainingSynchronousPages() { - // Post the remaining side pages to be loaded - if (!mDeferredBindRunnables.isEmpty()) { - Runnable[] deferredBindRunnables = null; - synchronized (mDeferredBindRunnables) { - deferredBindRunnables = mDeferredBindRunnables.toArray( - new Runnable[mDeferredBindRunnables.size()]); - mDeferredBindRunnables.clear(); - } - for (final Runnable r : deferredBindRunnables) { - mHandler.post(r); - } - } - } - public void stopLoader() { synchronized (mLock) { if (mLoaderTask != null) { @@ -1411,8 +1352,7 @@ public class LauncherModel extends BroadcastReceiver try { screenIds.add(sc.getLong(idIndex)); } catch (Exception e) { - Launcher.addDumpLog(TAG, "Desktop items loading interrupted" - + " - invalid screens: " + e, true); + addDumpLog("Invalid screen id: " + e); } } } finally { @@ -1421,10 +1361,6 @@ public class LauncherModel extends BroadcastReceiver return screenIds; } - public boolean isAllAppsLoaded() { - return mAllAppsLoaded; - } - /** * Runnable for the thread that loads the contents of the launcher: * - workspace icons @@ -1728,46 +1664,35 @@ public class LauncherModel extends BroadcastReceiver final PackageManager manager = context.getPackageManager(); final boolean isSafeMode = manager.isSafeMode(); final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - final boolean isSdCardReady = context.registerReceiver(null, - new IntentFilter(StartupReceiver.SYSTEM_READY)) != null; + final boolean isSdCardReady = Utilities.isBootCompleted(); LauncherAppState app = LauncherAppState.getInstance(); InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); int countX = profile.numColumns; int countY = profile.numRows; - if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) { - long migrationStartTime = System.currentTimeMillis(); - Log.v(TAG, "Starting workspace migration after restore"); - try { - MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext); - // Clear the flags before starting the task, so that we do not run the task - // again, in case there was an uncaught error. - MigrateFromRestoreTask.clearFlags(mContext); - task.execute(); - } catch (Exception e) { - Log.e(TAG, "Error during grid migration", e); - - // Clear workspace. - mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE; - } - Log.v(TAG, "Workspace migration completed in " - + (System.currentTimeMillis() - migrationStartTime)); + if (GridSizeMigrationTask.ENABLED && + !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) { + // Migration failed. Clear workspace. + mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE; } if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) { - Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true); - LauncherAppState.getLauncherProvider().deleteDatabase(); + Log.d(TAG, "loadWorkspace: resetting launcher database"); + LauncherSettings.Settings.call(contentResolver, + LauncherSettings.Settings.METHOD_DELETE_DB); } if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) { // append the user's Launcher2 shortcuts - Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true); - LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts(); + Log.d(TAG, "loadWorkspace: migrating from launcher2"); + LauncherSettings.Settings.call(contentResolver, + LauncherSettings.Settings.METHOD_MIGRATE_LAUNCHER2_SHORTCUTS); } else { // Make sure the default workspace is loaded - Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false); - LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(); + Log.d(TAG, "loadWorkspace: loading default favorites"); + LauncherSettings.Settings.call(contentResolver, + LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); } synchronized (sBgLock) { @@ -1822,8 +1747,11 @@ public class LauncherModel extends BroadcastReceiver final CursorIconInfo cursorIconInfo = new CursorIconInfo(c); final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>(); + final LongSparseArray<Boolean> quietMode = new LongSparseArray<>(); for (UserHandleCompat user : mUserManager.getUserProfiles()) { - allUsers.put(mUserManager.getSerialNumberForUser(user), user); + long serialNo = mUserManager.getSerialNumberForUser(user); + allUsers.put(serialNo, user); + quietMode.put(serialNo, mUserManager.isQuietModeEnabled(user)); } ShortcutInfo info; @@ -1872,6 +1800,14 @@ public class LauncherModel extends BroadcastReceiver restoredRows.add(id); restored = false; } + boolean isSuspended = launcherApps.isPackageSuspendedForProfile( + cn.getPackageName(), user); + if (isSuspended) { + disabledState = ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } + if (quietMode.get(serialNumber)) { + disabledState |= ShortcutInfo.FLAG_DISABLED_QUIET_USER; + } } else if (validPkg) { intent = null; if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) { @@ -1890,8 +1826,7 @@ public class LauncherModel extends BroadcastReceiver if (intent == null) { // The app is installed but the component is no // longer available. - Launcher.addDumpLog(TAG, - "Invalid component removed: " + cn, true); + addDumpLog("Invalid component removed: " + cn); itemsToRemove.add(id); continue; } else { @@ -1902,8 +1837,7 @@ public class LauncherModel extends BroadcastReceiver } else if (restored) { // Package is not yet available but might be // installed later. - Launcher.addDumpLog(TAG, - "package not yet restored: " + cn, true); + addDumpLog("package not yet restored: " + cn); if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) { // Restore has started once. @@ -1929,14 +1863,12 @@ public class LauncherModel extends BroadcastReceiver itemReplaced = true; } else if (REMOVE_UNRESTORED_ICONS) { - Launcher.addDumpLog(TAG, - "Unrestored package removed: " + cn, true); + addDumpLog("Unrestored package removed: " + cn); itemsToRemove.add(id); continue; } } else if (REMOVE_UNRESTORED_ICONS) { - Launcher.addDumpLog(TAG, - "Unrestored package removed: " + cn, true); + addDumpLog("Unrestored package removed: " + cn); itemsToRemove.add(id); continue; } @@ -1949,8 +1881,7 @@ public class LauncherModel extends BroadcastReceiver } else if (!isSdCardReady) { // SdCard is not ready yet. Package might get available, // once it is ready. - Launcher.addDumpLog(TAG, "Invalid package: " + cn - + " (check again later)", true); + Log.d(TAG, "Invalid package: " + cn + " (check again later)"); HashSet<String> pkgs = sPendingPackages.get(user); if (pkgs == null) { pkgs = new HashSet<String>(); @@ -1963,8 +1894,7 @@ public class LauncherModel extends BroadcastReceiver } else { // Do not wait for external media load anymore. // Log the invalid package, and remove it - Launcher.addDumpLog(TAG, - "Invalid package removed: " + cn, true); + addDumpLog("Invalid package removed: " + cn); itemsToRemove.add(id); continue; } @@ -1974,8 +1904,7 @@ public class LauncherModel extends BroadcastReceiver restored = false; } } catch (URISyntaxException e) { - Launcher.addDumpLog(TAG, - "Invalid uri: " + intentDescription, true); + addDumpLog("Invalid uri: " + intentDescription); itemsToRemove.add(id); continue; } @@ -1995,9 +1924,6 @@ public class LauncherModel extends BroadcastReceiver } } else if (restored) { if (user.equals(UserHandleCompat.myUserHandle())) { - Launcher.addDumpLog(TAG, - "constructing info for partially restored package", - true); info = getRestoredItemInfo(c, titleIndex, intent, promiseType, itemType, cursorIconInfo, context); intent = getRestoredItemIntent(c, context, intent); @@ -2152,11 +2078,8 @@ public class LauncherModel extends BroadcastReceiver final boolean isProviderReady = isValidProvider(provider); if (!isSafeMode && !customWidget && wasProviderReady && !isProviderReady) { - String log = "Deleting widget that isn't installed anymore: " - + "id=" + id + " appWidgetId=" + appWidgetId; - - Log.e(TAG, log); - Launcher.addDumpLog(TAG, log, false); + addDumpLog("Deleting widget that isn't installed anymore: " + + provider); itemsToRemove.add(id); } else { if (isProviderReady) { @@ -2197,8 +2120,7 @@ public class LauncherModel extends BroadcastReceiver appWidgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) { - Launcher.addDumpLog(TAG, - "Unrestored widget removed: " + component, true); + addDumpLog("Unrestored widget removed: " + component); itemsToRemove.add(id); continue; } @@ -2250,7 +2172,7 @@ public class LauncherModel extends BroadcastReceiver break; } } catch (Exception e) { - Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true); + Log.e(TAG, "Desktop items loading interrupted", e); } } } finally { @@ -2276,8 +2198,11 @@ public class LauncherModel extends BroadcastReceiver } // Remove any empty folder - for (long folderId : LauncherAppState.getLauncherProvider() - .deleteEmptyFolders()) { + ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings + .call(contentResolver, + LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS) + .getSerializable(LauncherSettings.Settings.EXTRA_VALUE); + for (long folderId : deletedFolderIds) { sBgWorkspaceItems.remove(sBgFolders.get(folderId)); sBgFolders.remove(folderId); sBgItemsIdMap.remove(folderId); @@ -2310,7 +2235,7 @@ public class LauncherModel extends BroadcastReceiver if (!isSdCardReady && !sPendingPackages.isEmpty()) { context.registerReceiver(new AppsAvailabilityCheck(), - new IntentFilter(StartupReceiver.SYSTEM_READY), + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, sWorker); } @@ -2460,19 +2385,36 @@ public class LauncherModel extends BroadcastReceiver private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) { final LauncherAppState app = LauncherAppState.getInstance(); final InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); - // XXX: review this + final int screenCols = profile.numColumns; + final int screenCellCount = profile.numColumns * profile.numRows; Collections.sort(workspaceItems, new Comparator<ItemInfo>() { @Override public int compare(ItemInfo lhs, ItemInfo rhs) { - int cellCountX = (int) profile.numColumns; - int cellCountY = (int) profile.numRows; - int screenOffset = cellCountX * cellCountY; - int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat - long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset + - lhs.cellY * cellCountX + lhs.cellX); - long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset + - rhs.cellY * cellCountX + rhs.cellX); - return Utilities.longCompare(lr, rr); + if (lhs.container == rhs.container) { + // Within containers, order by their spatial position in that container + switch ((int) lhs.container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: { + long lr = (lhs.screenId * screenCellCount + + lhs.cellY * screenCols + lhs.cellX); + long rr = (rhs.screenId * screenCellCount + + rhs.cellY * screenCols + rhs.cellX); + return Utilities.longCompare(lr, rr); + } + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { + // We currently use the screen id as the rank + return Utilities.longCompare(lhs.screenId, rhs.screenId); + } + default: + if (ProviderConfig.IS_DOGFOOD_BUILD) { + throw new RuntimeException("Unexpected container type when " + + "sorting workspace items."); + } + return 0; + } + } else { + // Between containers, order by hotseat, desktop + return Utilities.longCompare(lhs.container, rhs.container); + } } }); } @@ -2495,9 +2437,7 @@ public class LauncherModel extends BroadcastReceiver final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final LongArrayMap<FolderInfo> folders, - ArrayList<Runnable> deferredBindRunnables) { - - final boolean postOnMainThread = (deferredBindRunnables != null); + final Executor executor) { // Bind the workspace items int N = workspaceItems.size(); @@ -2514,13 +2454,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - if (postOnMainThread) { - synchronized (deferredBindRunnables) { - deferredBindRunnables.add(r); - } - } else { - runOnMainThread(r); - } + executor.execute(r); } // Bind the folders @@ -2533,13 +2467,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - if (postOnMainThread) { - synchronized (deferredBindRunnables) { - deferredBindRunnables.add(r); - } - } else { - runOnMainThread(r); - } + executor.execute(r); } // Bind the widgets, one at a time @@ -2554,11 +2482,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - if (postOnMainThread) { - deferredBindRunnables.add(r); - } else { - runOnMainThread(r); - } + executor.execute(r); } } @@ -2636,6 +2560,7 @@ public class LauncherModel extends BroadcastReceiver public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { + callbacks.clearPendingBinds(); callbacks.startBinding(); } } @@ -2644,28 +2569,20 @@ public class LauncherModel extends BroadcastReceiver bindWorkspaceScreens(oldCallbacks, orderedScreenIds); - // Load items on the current page + Executor mainExecutor = new DeferredMainThreadExecutor(); + // Load items on the current page. bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, - currentFolders, null); - if (isLoadingSynchronously) { - r = new Runnable() { - public void run() { - Callbacks callbacks = tryGetCallbacks(oldCallbacks); - if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) { - callbacks.onPageBoundSynchronously(currentScreen); - } - } - }; - runOnMainThread(r); - } + currentFolders, mainExecutor); - // Load all the remaining pages (if we are loading synchronously, we want to defer this - // work until after the first render) - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.clear(); - } - bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, - (isLoadingSynchronously ? mDeferredBindRunnables : null)); + // In case of isLoadingSynchronously, only bind the first screen, and defer binding the + // remaining screens after first onDraw is called. This ensures that the first screen + // is immediately visible (eg. during rotation) + // In case of !isLoadingSynchronously, bind all pages one after other. + final Executor deferredExecutor = isLoadingSynchronously ? + new ViewOnDrawExecutor(mHandler) : mainExecutor; + + bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, + otherFolders, deferredExecutor); // Tell the workspace that we're done binding items r = new Runnable() { @@ -2695,11 +2612,23 @@ public class LauncherModel extends BroadcastReceiver } }; + deferredExecutor.execute(r); + if (isLoadingSynchronously) { - synchronized (mDeferredBindRunnables) { - mDeferredBindRunnables.add(r); - } - } else { + r = new Runnable() { + public void run() { + Callbacks callbacks = tryGetCallbacks(oldCallbacks); + if (callbacks != null) { + // We are loading synchronously, which means, some of the pages will be + // bound after first draw. Inform the callbacks that page binding is + // not complete, and schedule the remaining pages. + if (currentScreen != PagedView.INVALID_RESTORE_PAGE) { + callbacks.onPageBoundSynchronously(currentScreen); + } + callbacks.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor); + } + } + }; runOnMainThread(r); } } @@ -2810,12 +2739,12 @@ public class LauncherModel extends BroadcastReceiver if (apps == null || apps.isEmpty()) { return; } - + boolean quietMode = mUserManager.isQuietModeEnabled(user); // Create the ApplicationInfos for (int i = 0; i < apps.size(); i++) { LauncherActivityInfoCompat app = apps.get(i); // This builds the icon bitmaps. - mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache)); + mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode)); } final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user); @@ -2963,10 +2892,8 @@ public class LauncherModel extends BroadcastReceiver boolean packageOnSdcard = launcherApps.isAppEnabled( manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES); if (packageOnSdcard) { - Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true); packagesUnavailable.add(pkg); } else { - Launcher.addDumpLog(TAG, "Package not found: " + pkg, true); packagesRemoved.add(pkg); } } @@ -2995,6 +2922,8 @@ public class LauncherModel extends BroadcastReceiver public static final int OP_UPDATE = 2; public static final int OP_REMOVE = 3; // uninstlled public static final int OP_UNAVAILABLE = 4; // external media unmounted + public static final int OP_SUSPEND = 5; // package suspended + public static final int OP_UNSUSPEND = 6; // package unsuspended public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) { @@ -3052,6 +2981,15 @@ public class LauncherModel extends BroadcastReceiver mApp.getWidgetCache().removePackage(packages[i], mUser); } break; + case OP_SUSPEND: + case OP_UNSUSPEND: + boolean suspend = mOp == OP_SUSPEND; + for (int i=0; i<N; i++) { + if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.suspendPackage " + + suspend + " " + packages[i]); + mBgAllAppsList.suspendPackage(packages[i], mUser, suspend); + } + break; } ArrayList<AppInfo> added = null; @@ -3104,7 +3042,7 @@ public class LauncherModel extends BroadcastReceiver } // Update shortcut infos - if (mOp == OP_ADD || mOp == OP_UPDATE) { + if (mOp == OP_ADD || mOp == OP_UPDATE || mOp == OP_UNSUSPEND) { final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>(); final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>(); final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>(); @@ -3117,6 +3055,11 @@ public class LauncherModel extends BroadcastReceiver boolean infoUpdated = false; boolean shortcutUpdated = false; + if (mOp == OP_UNSUSPEND) { + si.isDisabled &= ~ ShortcutInfo.FLAG_DISABLED_SUSPENDED; + infoUpdated = true; + } + // Update shortcuts which use iconResource. if ((si.iconResource != null) && packageSet.contains(si.iconResource.packageName)) { @@ -3242,7 +3185,7 @@ public class LauncherModel extends BroadcastReceiver final ArrayList<String> removedPackageNames = new ArrayList<String>(); - if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) { + if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE || mOp == OP_SUSPEND) { // Mark all packages in the broadcast to be removed removedPackageNames.addAll(Arrays.asList(packages)); } else if (mOp == OP_UPDATE) { @@ -3258,6 +3201,8 @@ public class LauncherModel extends BroadcastReceiver final int removeReason; if (mOp == OP_UNAVAILABLE) { removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE; + } else if (mOp == OP_SUSPEND) { + removeReason = ShortcutInfo.FLAG_DISABLED_SUSPENDED; } else { // Remove all the components associated with this package for (String pn : removedPackageNames) { @@ -3313,7 +3258,7 @@ public class LauncherModel extends BroadcastReceiver .setPackage(pkg), 0); needToRefresh |= widgets != null && !widgets.isEmpty(); } catch (RuntimeException e) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw e; } // Ignore the crash. We can live with a state widget list. @@ -3324,16 +3269,6 @@ public class LauncherModel extends BroadcastReceiver loadAndBindWidgetsAndShortcuts(callbacks, needToRefresh); } - - // Write all the logs to disk - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.dumpLogsToLocalData(); - } - } - }); } } @@ -3370,7 +3305,7 @@ public class LauncherModel extends BroadcastReceiver return results; } } catch (Exception e) { - if (!LauncherAppState.isDogfoodBuild() && + if (!ProviderConfig.IS_DOGFOOD_BUILD && (e.getCause() instanceof TransactionTooLargeException || e.getCause() instanceof DeadObjectException)) { // the returned value may be incomplete and will not be refreshed until the next @@ -3439,7 +3374,7 @@ public class LauncherModel extends BroadcastReceiver List<ResolveInfo> providers = packageManager.queryIntentActivities(shortcutsIntent, 0); sBgShortcutProviders = providers; } catch (RuntimeException e) { - if (!LauncherAppState.isDogfoodBuild() && + if (!ProviderConfig.IS_DOGFOOD_BUILD && (e.getCause() instanceof TransactionTooLargeException || e.getCause() instanceof DeadObjectException)) { /** @@ -3756,10 +3691,22 @@ public class LauncherModel extends BroadcastReceiver } } + @Thunk class DeferredMainThreadExecutor implements Executor { + + @Override + public void execute(Runnable command) { + runOnMainThread(command); + } + } + /** * @return the looper for the worker thread which can be used to start background tasks. */ public static Looper getWorkerLooper() { return sWorkerThread.getLooper(); } + + @Thunk static final void addDumpLog(String log) { + Launcher.addDumpLog(TAG, log); + } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 476093032..a57f68037 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -43,7 +43,6 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Process; -import android.os.StrictMode; import android.os.UserManager; import android.text.TextUtils; import android.util.Log; @@ -61,7 +60,6 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.List; public class LauncherProvider extends ContentProvider { private static final String TAG = "LauncherProvider"; @@ -77,30 +75,23 @@ public class LauncherProvider extends ContentProvider { private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name"; + private static final Object LISTENER_LOCK = new Object(); @Thunk LauncherProviderChangeListener mListener; - @Thunk DatabaseHelper mOpenHelper; + protected DatabaseHelper mOpenHelper; @Override public boolean onCreate() { - final Context context = getContext(); - // The content provider exists for the entire duration of the launcher main process and - // is the first component to get created. Initializing application context here ensures - // that LauncherAppState always exists in the main process. - LauncherAppState.setApplicationContext(context.getApplicationContext()); - StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); - mOpenHelper = new DatabaseHelper(context); - StrictMode.setThreadPolicy(oldPolicy); LauncherAppState.setLauncherProvider(this); return true; } - public boolean wasNewDbCreated() { - return mOpenHelper.wasNewDbCreated(); - } - + /** + * Sets a provider listener. + */ public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) { - mListener = listener; - mOpenHelper.mListener = mListener; + synchronized (LISTENER_LOCK) { + mListener = listener; + } } @Override @@ -113,9 +104,19 @@ public class LauncherProvider extends ContentProvider { } } + /** + * Overridden in tests + */ + protected synchronized void createDbIfNotExists() { + if (mOpenHelper == null) { + mOpenHelper = new DatabaseHelper(getContext(), this); + } + } + @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + createDbIfNotExists(); SqlArguments args = new SqlArguments(uri, selection, selectionArgs); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); @@ -151,6 +152,7 @@ public class LauncherProvider extends ContentProvider { @Override public Uri insert(Uri uri, ContentValues initialValues) { + createDbIfNotExists(); SqlArguments args = new SqlArguments(uri); // In very limited cases, we support system|signature permission apps to modify the db. @@ -185,9 +187,9 @@ public class LauncherProvider extends ContentProvider { return uri; } - @Override public int bulkInsert(Uri uri, ContentValues[] values) { + createDbIfNotExists(); SqlArguments args = new SqlArguments(uri); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); @@ -213,6 +215,7 @@ public class LauncherProvider extends ContentProvider { @Override public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { + createDbIfNotExists(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.beginTransaction(); try { @@ -227,6 +230,7 @@ public class LauncherProvider extends ContentProvider { @Override public int delete(Uri uri, String selection, String[] selectionArgs) { + createDbIfNotExists(); SqlArguments args = new SqlArguments(uri, selection, selectionArgs); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); @@ -239,6 +243,7 @@ public class LauncherProvider extends ContentProvider { @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + createDbIfNotExists(); SqlArguments args = new SqlArguments(uri, selection, selectionArgs); addModifiedTime(values); @@ -255,6 +260,7 @@ public class LauncherProvider extends ContentProvider { if (Binder.getCallingUid() != Process.myUid()) { return null; } + createDbIfNotExists(); switch (method) { case LauncherSettings.Settings.METHOD_GET_BOOLEAN: { @@ -267,8 +273,10 @@ public class LauncherProvider extends ContentProvider { case LauncherSettings.Settings.METHOD_SET_BOOLEAN: { boolean value = extras.getBoolean(LauncherSettings.Settings.EXTRA_VALUE); Utilities.getPrefs(getContext()).edit().putBoolean(arg, value).apply(); - if (mListener != null) { - mListener.onSettingsChanged(arg, value); + synchronized (LISTENER_LOCK) { + if (mListener != null) { + mListener.onSettingsChanged(arg, value); + } } if (extras.getBoolean(LauncherSettings.Settings.NOTIFY_BACKUP)) { LauncherBackupAgentHelper.dataChanged(getContext()); @@ -277,6 +285,51 @@ public class LauncherProvider extends ContentProvider { result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, value); return result; } + case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: { + clearFlagEmptyDbCreated(); + return null; + } + case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: { + Bundle result = new Bundle(); + result.putSerializable(LauncherSettings.Settings.EXTRA_VALUE, deleteEmptyFolders()); + return result; + } + case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: { + Bundle result = new Bundle(); + result.putLong(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewItemId()); + return result; + } + case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: { + Bundle result = new Bundle(); + result.putLong(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewScreenId()); + return result; + } + case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: { + createEmptyDB(); + return null; + } + case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: { + loadDefaultFavoritesIfNecessary(); + return null; + } + case LauncherSettings.Settings.METHOD_MIGRATE_LAUNCHER2_SHORTCUTS: { + mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), + Uri.parse(getContext().getString(R.string.old_launcher_provider_uri))); + return null; + } + case LauncherSettings.Settings.METHOD_UPDATE_FOLDER_ITEMS_RANK: { + mOpenHelper.updateFolderItemsRank(mOpenHelper.getWritableDatabase(), false); + return null; + } + case LauncherSettings.Settings.METHOD_CONVERT_SHORTCUTS_TO_ACTIVITIES: { + mOpenHelper.convertShortcutsToLauncherActivities(mOpenHelper.getWritableDatabase()); + return null; + } + case LauncherSettings.Settings.METHOD_DELETE_DB: { + // Are you sure? (y/n) + mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); + return null; + } } return null; } @@ -285,8 +338,8 @@ public class LauncherProvider extends ContentProvider { * Deletes any empty folder from the DB. * @return Ids of deleted folders. */ - public List<Long> deleteEmptyFolders() { - ArrayList<Long> folderIds = new ArrayList<Long>(); + private ArrayList<Long> deleteEmptyFolders() { + ArrayList<Long> folderIds = new ArrayList<>(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.beginTransaction(); try { @@ -303,7 +356,7 @@ public class LauncherProvider extends ContentProvider { folderIds.add(c.getLong(0)); } c.close(); - if (folderIds.size() > 0) { + if (!folderIds.isEmpty()) { db.delete(TABLE_FAVORITES, Utilities.createDbSelectionQuery( LauncherSettings.Favorites._ID, folderIds), null); } @@ -317,11 +370,16 @@ public class LauncherProvider extends ContentProvider { return folderIds; } - private void notifyListeners() { + /** + * Overridden in tests + */ + protected void notifyListeners() { // always notify the backup agent LauncherBackupAgentHelper.dataChanged(getContext()); - if (mListener != null) { - mListener.onLauncherProviderChange(); + synchronized (LISTENER_LOCK) { + if (mListener != null) { + mListener.onLauncherProviderChange(); + } } } @@ -329,22 +387,14 @@ public class LauncherProvider extends ContentProvider { values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis()); } - public long generateNewItemId() { - return mOpenHelper.generateNewItemId(); - } - - public long generateNewScreenId() { - return mOpenHelper.generateNewScreenId(); - } - /** * Clears all the data for a fresh start. */ - synchronized public void createEmptyDB() { + synchronized private void createEmptyDB() { mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); } - public void clearFlagEmptyDbCreated() { + private void clearFlagEmptyDbCreated() { Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit(); } @@ -355,7 +405,7 @@ public class LauncherProvider extends ContentProvider { * 3) From a partner configuration APK, already in the system image * 4) The default configuration for the particular device */ - synchronized public void loadDefaultFavoritesIfNecessary() { + synchronized private void loadDefaultFavoritesIfNecessary() { SharedPreferences sp = Utilities.getPrefs(getContext()); if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { @@ -440,38 +490,41 @@ public class LauncherProvider extends ContentProvider { mOpenHelper, getContext().getResources(), defaultLayout); } - public void migrateLauncher2Shortcuts() { - mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), - Uri.parse(getContext().getString(R.string.old_launcher_provider_uri))); - } - - public void updateFolderItemsRank() { - mOpenHelper.updateFolderItemsRank(mOpenHelper.getWritableDatabase(), false); - } - - public void convertShortcutsToLauncherActivities() { - mOpenHelper.convertShortcutsToLauncherActivities(mOpenHelper.getWritableDatabase()); - } - - - public void deleteDatabase() { - // Are you sure? (y/n) - mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); + /** + * Send notification that we've deleted the {@link AppWidgetHost}, + * probably as part of the initial database creation. The receiver may + * want to re-call {@link AppWidgetHost#startListening()} to ensure + * callbacks are correctly set. + */ + @Thunk void notifyAppHostReset() { + new MainThreadExecutor().execute(new Runnable() { + + @Override + public void run() { + synchronized (LISTENER_LOCK) { + if (mListener != null) { + mListener.onAppWidgetHostReset(); + } + } + } + }); } - private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { + /** + * The class is subclassed in tests to create an in-memory db. + */ + protected static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback { + private final LauncherProvider mProvider; private final Context mContext; @Thunk final AppWidgetHost mAppWidgetHost; private long mMaxItemId = -1; private long mMaxScreenId = -1; - private boolean mNewDbCreated = false; - - @Thunk LauncherProviderChangeListener mListener; - - DatabaseHelper(Context context) { + DatabaseHelper(Context context, LauncherProvider provider) { super(context, LauncherFiles.LAUNCHER_DB, null, DATABASE_VERSION); mContext = context; + mProvider = provider; + mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); // Table creation sometimes fails silently, which leads to a crash loop. @@ -494,6 +547,19 @@ public class LauncherProvider extends ContentProvider { } } + /** + * Constructor used only in tests. + */ + public DatabaseHelper(Context context, LauncherProvider provider, String tableName) { + super(context, tableName, null, DATABASE_VERSION); + mContext = context; + mProvider = provider; + + mAppWidgetHost = null; + mMaxItemId = initializeMaxItemId(getWritableDatabase()); + mMaxScreenId = initializeMaxScreenId(getWritableDatabase()); + } + private boolean tableExists(String tableName) { Cursor c = getReadableDatabase().query( true, "sqlite_master", new String[] {"tbl_name"}, @@ -506,17 +572,12 @@ public class LauncherProvider extends ContentProvider { } } - public boolean wasNewDbCreated() { - return mNewDbCreated; - } - @Override public void onCreate(SQLiteDatabase db) { if (LOGD) Log.d(TAG, "creating new launcher database"); mMaxItemId = 1; mMaxScreenId = 0; - mNewDbCreated = true; addFavoritesTable(db, false); addWorkspacesTable(db, false); @@ -524,38 +585,33 @@ public class LauncherProvider extends ContentProvider { // Database was just created, so wipe any previous widgets if (mAppWidgetHost != null) { mAppWidgetHost.deleteHost(); - - /** - * Send notification that we've deleted the {@link AppWidgetHost}, - * probably as part of the initial database creation. The receiver may - * want to re-call {@link AppWidgetHost#startListening()} to ensure - * callbacks are correctly set. - */ - new MainThreadExecutor().execute(new Runnable() { - - @Override - public void run() { - if (mListener != null) { - mListener.onAppWidgetHostReset(); - } - } - }); + mProvider.notifyAppHostReset(); } // Fresh and clean launcher DB. mMaxItemId = initializeMaxItemId(db); - setFlagEmptyDbCreated(); + onEmptyDbCreated(); + } + + /** + * Overriden in tests. + */ + protected void onEmptyDbCreated() { + // Set the flag for empty DB + Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit(); // When a new DB is created, remove all previously stored managed profile information. - ManagedProfileHeuristic.processAllUsers(Collections.<UserHandleCompat>emptyList(), mContext); + ManagedProfileHeuristic.processAllUsers(Collections.<UserHandleCompat>emptyList(), + mContext); } - private void addFavoritesTable(SQLiteDatabase db, boolean optional) { - UserManagerCompat userManager = UserManagerCompat.getInstance(mContext); - long userSerialNumber = userManager.getSerialNumberForUser( + protected long getDefaultUserSerial() { + return UserManagerCompat.getInstance(mContext).getSerialNumberForUser( UserHandleCompat.myUserHandle()); - String ifNotExists = optional ? " IF NOT EXISTS " : ""; + } + private void addFavoritesTable(SQLiteDatabase db, boolean optional) { + String ifNotExists = optional ? " IF NOT EXISTS " : ""; db.execSQL("CREATE TABLE " + ifNotExists + TABLE_FAVORITES + " (" + "_id INTEGER PRIMARY KEY," + "title TEXT," + @@ -578,7 +634,7 @@ public class LauncherProvider extends ContentProvider { "appWidgetProvider TEXT," + "modified INTEGER NOT NULL DEFAULT 0," + "restored INTEGER NOT NULL DEFAULT 0," + - "profileId INTEGER DEFAULT " + userSerialNumber + "," + + "profileId INTEGER DEFAULT " + getDefaultUserSerial() + "," + "rank INTEGER NOT NULL DEFAULT 0," + "options INTEGER NOT NULL DEFAULT 0" + ");"); @@ -628,10 +684,6 @@ public class LauncherProvider extends ContentProvider { Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, false).commit(); } - private void setFlagEmptyDbCreated() { - Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit(); - } - @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion); @@ -1119,10 +1171,6 @@ public class LauncherProvider extends ContentProvider { = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); - final int uriIndex - = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); - final int displayModeIndex - = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); final int profileIndex = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID); @@ -1170,17 +1218,10 @@ public class LauncherProvider extends ContentProvider { } if (userHandle == null) { - Launcher.addDumpLog(TAG, "skipping deleted user", true); + Log.d(TAG, "skipping deleted user"); continue; } - Launcher.addDumpLog(TAG, "migrating \"" - + c.getString(titleIndex) + "\" (" - + cellX + "," + cellY + "@" - + LauncherSettings.Favorites.containerToString(container) - + "/" + screen - + "): " + intentStr, true); - if (itemType != Favorites.ITEM_TYPE_FOLDER) { final Intent intent; @@ -1189,22 +1230,20 @@ public class LauncherProvider extends ContentProvider { intent = Intent.parseUri(intentStr, 0); } catch (URISyntaxException e) { // bogus intent? - Launcher.addDumpLog(TAG, - "skipping invalid intent uri", true); + Log.d(TAG, "skipping invalid intent uri"); continue; } cn = intent.getComponent(); if (TextUtils.isEmpty(intentStr)) { // no intent? no icon - Launcher.addDumpLog(TAG, "skipping empty intent", true); + Log.d(TAG, "skipping empty intent"); continue; } else if (cn != null && !LauncherModel.isValidPackageActivity(mContext, cn, userHandle)) { // component no longer exists. - Launcher.addDumpLog(TAG, "skipping item whose component " + - "no longer exists.", true); + Log.d(TAG, "skipping item whose component no longer exists."); continue; } else if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { @@ -1220,7 +1259,7 @@ public class LauncherProvider extends ContentProvider { final String key = intent.toUri(0); intent.setFlags(flags); if (seenIntents.contains(key)) { - Launcher.addDumpLog(TAG, "skipping duplicate", true); + Log.d(TAG, "skipping duplicate"); continue; } else { seenIntents.add(key); @@ -1241,9 +1280,6 @@ public class LauncherProvider extends ContentProvider { c.getString(iconResourceIndex)); values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); - values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); - values.put(LauncherSettings.Favorites.DISPLAY_MODE, - c.getInt(displayModeIndex)); values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber); if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { @@ -1363,8 +1399,8 @@ public class LauncherProvider extends ContentProvider { } } - Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into " - + (curScreen+1) + " screens", true); + Log.d(TAG, "migrated " + count + " icons from Launcher2 into " + + (curScreen+1) + " screens"); // ensure that new screens are created to hold these icons setFlagJustLoadedOldDb(); diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java index 55a512fb5..104af5280 100644 --- a/src/com/android/launcher3/LauncherRootView.java +++ b/src/com/android/launcher3/LauncherRootView.java @@ -9,11 +9,14 @@ import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; +import android.view.ViewDebug; public class LauncherRootView extends InsettableFrameLayout { private final Paint mOpaquePaint; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mDrawRightInsetBar; + @ViewDebug.ExportedProperty(category = "launcher") private int mRightInsetBarWidth; private View mAlignedView; diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 01d670d7b..fb5306830 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -16,7 +16,9 @@ package com.android.launcher3; +import android.content.ContentResolver; import android.net.Uri; +import android.os.Bundle; import android.provider.BaseColumns; import com.android.launcher3.config.ProviderConfig; @@ -113,7 +115,7 @@ public class LauncherSettings { /** * The content:// style URL for this table */ - static final Uri CONTENT_URI = Uri.parse("content://" + + public static final Uri CONTENT_URI = Uri.parse("content://" + ProviderConfig.AUTHORITY + "/" + TABLE_NAME); /** @@ -321,11 +323,29 @@ public class LauncherSettings { public static final String METHOD_GET_BOOLEAN = "get_boolean_setting"; public static final String METHOD_SET_BOOLEAN = "set_boolean_setting"; + public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag"; + + public static final String METHOD_DELETE_EMPTY_FOLDERS = "delete_empty_folders"; + public static final String METHOD_UPDATE_FOLDER_ITEMS_RANK = "update_folder_items_rank"; + public static final String METHOD_CONVERT_SHORTCUTS_TO_ACTIVITIES = + "convert_shortcuts_to_launcher_activities"; + + public static final String METHOD_NEW_ITEM_ID = "generate_new_item_id"; + public static final String METHOD_NEW_SCREEN_ID = "generate_new_screen_id"; + + public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db"; + public static final String METHOD_DELETE_DB = "delete_db"; + + public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites"; + public static final String METHOD_MIGRATE_LAUNCHER2_SHORTCUTS = "migrate_l2_shortcuts"; public static final String EXTRA_VALUE = "value"; public static final String EXTRA_DEFAULT_VALUE = "default_value"; - // Extra for set_boolean method to also notify the backup manager of the change. public static final String NOTIFY_BACKUP = "notify_backup"; + + public static Bundle call(ContentResolver cr, String method) { + return cr.call(CONTENT_URI, method, null, null); + } } } diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index 30cae3114..54945be3d 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -24,15 +24,17 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.res.Resources; +import android.os.Build; import android.util.Log; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.util.UiThreadCircularReveal; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.UiThreadCircularReveal; import com.android.launcher3.widget.WidgetsContainerView; import java.util.HashMap; @@ -163,18 +165,17 @@ public class LauncherStateTransitionAnimation { final boolean animated) { final WidgetsContainerView toView = mLauncher.getWidgetsView(); final View buttonView = mLauncher.getWidgetsButton(); - mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState, Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS)); } /** - * Starts and animation to the workspace from the current overlay view. + * Starts an animation to the workspace from the current overlay view. */ public void startAnimationToWorkspace(final Launcher.State fromState, final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, - final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) { + final boolean animated, final Runnable onCompleteRunnable) { if (toWorkspaceState != Workspace.State.NORMAL && toWorkspaceState != Workspace.State.SPRING_LOADED && toWorkspaceState != Workspace.State.OVERVIEW) { @@ -182,10 +183,14 @@ public class LauncherStateTransitionAnimation { } if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) { - startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage, + startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, + animated, onCompleteRunnable); + } else if (fromState == Launcher.State.WIDGETS || + fromState == Launcher.State.WIDGETS_SPRING_LOADED) { + startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, animated, onCompleteRunnable); } else { - startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage, + startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState, animated, onCompleteRunnable); } } @@ -214,16 +219,8 @@ public class LauncherStateTransitionAnimation { // Cancel the current animation cancelAnimation(); - // Create the workspace animation. - // NOTE: this call apparently also sets the state for the workspace if !animated - Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1, - animated, layerViews); - - // Animate the search bar - startWorkspaceSearchBarAnimation( - toWorkspaceState, animated ? revealDuration : 0, animation); - - Animator updateTransitionStepAnim = dispatchOnLauncherTransitionStepAnim(fromView, toView); + playCommonTransitionAnimations(toWorkspaceState, fromView, toView, + animated, initialized, animation, revealDuration, layerViews); final View contentView = toView.getContentView(); @@ -257,11 +254,11 @@ public class LauncherStateTransitionAnimation { // Create the animators PropertyValuesHolder panelAlpha = - PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f); + PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f); PropertyValuesHolder panelDriftY = - PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0); + PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0); PropertyValuesHolder panelDriftX = - PropertyValuesHolder.ofFloat("translationX", revealViewToXDrift, 0); + PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0); ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView, panelAlpha, panelDriftY, panelDriftX); panelAlphaAndDrift.setDuration(revealDuration); @@ -328,13 +325,6 @@ public class LauncherStateTransitionAnimation { }); - // Play the workspace animation - if (workspaceAnim != null) { - animation.play(workspaceAnim); - } - - animation.play(updateTransitionStepAnim); - // Dispatch the prepare transition signal dispatchOnLauncherTransitionPrepare(fromView, animated, false); dispatchOnLauncherTransitionPrepare(toView, animated, false); @@ -354,7 +344,7 @@ public class LauncherStateTransitionAnimation { if (layerViews.get(v) == BUILD_AND_SET_LAYER) { v.setLayerType(View.LAYER_TYPE_HARDWARE, null); } - if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) { + if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { v.buildLayer(); } } @@ -394,6 +384,34 @@ public class LauncherStateTransitionAnimation { } /** + * Plays animations used by various transitions. + */ + private void playCommonTransitionAnimations( + Workspace.State toWorkspaceState, View fromView, View toView, + boolean animated, boolean initialized, AnimatorSet animation, int revealDuration, + HashMap<View, Integer> layerViews) { + // Create the workspace animation. + // NOTE: this call apparently also sets the state for the workspace if !animated + Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, + animated, layerViews); + + // Animate the search bar + final SearchDropTargetBar.State toSearchBarState = + toWorkspaceState.getSearchDropTargetBarState(); + mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, + animated ? revealDuration : 0, animation); + + if (animated && initialized) { + // Play the workspace animation + if (workspaceAnim != null) { + animation.play(workspaceAnim); + } + // Dispatch onLauncherTransitionStep() as the animation interpolates. + animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView)); + } + } + + /** * Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on * {@param fromView} and {@param toView} as the animation interpolates. * @@ -412,11 +430,11 @@ public class LauncherStateTransitionAnimation { } /** - * Starts and animation to the workspace from the apps view. + * Starts an animation to the workspace from the apps view. */ private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final int toWorkspacePage, - final boolean animated, final Runnable onCompleteRunnable) { + final Workspace.State toWorkspaceState, final boolean animated, + final Runnable onCompleteRunnable) { AllAppsContainerView appsView = mLauncher.getAppsView(); // No alpha anim from all apps PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) { @@ -447,16 +465,16 @@ public class LauncherStateTransitionAnimation { }; // Only animate the search bar if animating to spring loaded mode from all apps mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState, - toWorkspacePage, mLauncher.getAllAppsButton(), appsView, + mLauncher.getAllAppsButton(), appsView, animated, onCompleteRunnable, cb); } /** - * Starts and animation to the workspace from the widgets view. + * Starts an animation to the workspace from the widgets view. */ private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState, - final Workspace.State toWorkspaceState, final int toWorkspacePage, - final boolean animated, final Runnable onCompleteRunnable) { + final Workspace.State toWorkspaceState, final boolean animated, + final Runnable onCompleteRunnable) { final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) { @@ -473,16 +491,96 @@ public class LauncherStateTransitionAnimation { }; mCurrentAnimation = startAnimationToWorkspaceFromOverlay( fromWorkspaceState, toWorkspaceState, - toWorkspacePage, mLauncher.getWidgetsButton(), widgetsView, + mLauncher.getWidgetsButton(), widgetsView, animated, onCompleteRunnable, cb); } /** + * Starts an animation to the workspace from another workspace state, e.g. normal to overview. + */ + private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState, + final Workspace.State toWorkspaceState, final boolean animated, + final Runnable onCompleteRunnable) { + final View fromWorkspace = mLauncher.getWorkspace(); + final HashMap<View, Integer> layerViews = new HashMap<>(); + final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); + final int revealDuration = mLauncher.getResources() + .getInteger(R.integer.config_overlayRevealTime); + + // Cancel the current animation + cancelAnimation(); + + playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null, + animated, animated, animation, revealDuration, layerViews); + + if (animated) { + dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true); + + final AnimatorSet stateAnimation = animation; + final Runnable startAnimRunnable = new Runnable() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public void run() { + // Check that mCurrentAnimation hasn't changed while + // we waited for a layout/draw pass + if (mCurrentAnimation != stateAnimation) + return; + + dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); + + // Enable all necessary layers + for (View v : layerViews.keySet()) { + if (layerViews.get(v) == BUILD_AND_SET_LAYER) { + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { + v.buildLayer(); + } + } + stateAnimation.start(); + } + }; + animation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); + + // Run any queued runnables + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + + // Disable all necessary layers + for (View v : layerViews.keySet()) { + if (layerViews.get(v) == BUILD_AND_SET_LAYER) { + v.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + + // This can hold unnecessary references to views. + cleanupAnimation(); + } + }); + fromWorkspace.post(startAnimRunnable); + mCurrentAnimation = animation; + } else /* if (!animated) */ { + dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true); + dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); + dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true); + + // Run any queued runnables + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + + mCurrentAnimation = null; + } + } + + /** * Creates and starts a new animation to the workspace. */ private AnimatorSet startAnimationToWorkspaceFromOverlay( final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, - final int toWorkspacePage, final View buttonView, final BaseContainerView fromView, final boolean animated, final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb) { @@ -503,24 +601,10 @@ public class LauncherStateTransitionAnimation { // Cancel the current animation cancelAnimation(); - // Create the workspace animation. - // NOTE: this call apparently also sets the state for the workspace if !animated - Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, - toWorkspacePage, animated, layerViews); - - // Animate the search bar - startWorkspaceSearchBarAnimation( - toWorkspaceState, animated ? revealDuration : 0, animation); - - Animator updateTransitionStepAnim = dispatchOnLauncherTransitionStepAnim(fromView, toView); + playCommonTransitionAnimations(toWorkspaceState, fromView, toView, + animated, initialized, animation, revealDuration, layerViews); if (animated && initialized) { - // Play the workspace animation - if (workspaceAnim != null) { - animation.play(workspaceAnim); - } - - animation.play(updateTransitionStepAnim); final View revealView = fromView.getRevealView(); final View contentView = fromView.getContentView(); @@ -653,6 +737,7 @@ public class LauncherStateTransitionAnimation { final AnimatorSet stateAnimation = animation; final Runnable startAnimRunnable = new Runnable() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void run() { // Check that mCurrentAnimation hasn't changed while // we waited for a layout/draw pass @@ -667,7 +752,7 @@ public class LauncherStateTransitionAnimation { if (layerViews.get(v) == BUILD_AND_SET_LAYER) { v.setLayerType(View.LAYER_TYPE_HARDWARE, null); } - if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) { + if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { v.buildLayer(); } } @@ -677,7 +762,7 @@ public class LauncherStateTransitionAnimation { fromView.post(startAnimRunnable); return animation; - } else { + } else /* if (!(animated && initialized)) */ { fromView.setVisibility(View.GONE); dispatchOnLauncherTransitionPrepare(fromView, animated, true); dispatchOnLauncherTransitionStart(fromView, animated, true); @@ -697,16 +782,6 @@ public class LauncherStateTransitionAnimation { } /** - * Coordinates the workspace search bar animation along with the launcher state animation. - */ - private void startWorkspaceSearchBarAnimation( - final Workspace.State toWorkspaceState, int duration, AnimatorSet animation) { - final SearchDropTargetBar.State toSearchBarState = - toWorkspaceState.getSearchDropTargetBarState(); - mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration, animation); - } - - /** * Dispatches the prepare-transition event to suitable views. */ void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) { diff --git a/src/com/android/launcher3/PageIndicator.java b/src/com/android/launcher3/PageIndicator.java index 62ea03bcc..8adbf8d01 100644 --- a/src/com/android/launcher3/PageIndicator.java +++ b/src/com/android/launcher3/PageIndicator.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.LayoutInflater; +import android.view.ViewDebug; import android.widget.LinearLayout; import java.util.ArrayList; @@ -37,6 +38,7 @@ public class PageIndicator extends LinearLayout { private ArrayList<PageIndicatorMarker> mMarkers = new ArrayList<PageIndicatorMarker>(); + @ViewDebug.ExportedProperty(category = "launcher") private int mActiveMarkerIndex; public static class PageMarkerResources { diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 2dcff35b5..c826c5fcd 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -18,10 +18,10 @@ package com.android.launcher3; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; @@ -42,16 +42,15 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; - import com.android.launcher3.util.LauncherEdgeEffect; import com.android.launcher3.util.Thunk; - import java.util.ArrayList; /** @@ -66,9 +65,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // the min drag distance for a fling to register, to prevent random page shifts private static final int MIN_LENGTH_FOR_FLING = 25; - protected static final int PAGE_SNAP_ANIMATION_DURATION = 750; + public static final int PAGE_SNAP_ANIMATION_DURATION = 750; protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; - protected static final float NANOTIME_DIV = 1000000000.0f; private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. @@ -92,19 +90,17 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected int mMinFlingVelocity; protected int mMinSnapVelocity; - protected float mDensity; - protected float mSmoothingTime; - protected float mTouchX; - protected boolean mFirstLayout = true; private int mNormalChildHeight; + @ViewDebug.ExportedProperty(category = "launcher") protected int mCurrentPage; protected int mRestorePage = INVALID_RESTORE_PAGE; - protected int mChildCountOnLastLayout; + private int mChildCountOnLastLayout; + @ViewDebug.ExportedProperty(category = "launcher") protected int mNextPage = INVALID_PAGE; - protected int mMaxScrollX; + private int mMaxScrollX; protected LauncherScroller mScroller; private Interpolator mDefaultInterpolator; private VelocityTracker mVelocityTracker; @@ -116,10 +112,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private float mDownMotionY; private float mDownScrollX; private float mDragViewBaselineLeft; - protected float mLastMotionX; - protected float mLastMotionXRemainder; - protected float mLastMotionY; - protected float mTotalMotionX; + private float mLastMotionX; + private float mLastMotionXRemainder; + private float mLastMotionY; + private float mTotalMotionX; private int mLastScreenCenter = -1; private boolean mCancelTap; @@ -133,15 +129,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected final static int TOUCH_STATE_REORDERING = 4; protected int mTouchState = TOUCH_STATE_REST; - protected boolean mForceScreenScrolled = false; + private boolean mForceScreenScrolled = false; protected OnLongClickListener mLongClickListener; protected int mTouchSlop; private int mMaximumVelocity; - protected int mPageLayoutWidthGap; - protected int mPageLayoutHeightGap; - protected boolean mCenterPagesVertically; protected boolean mAllowOverScroll = true; protected int[] mTempVisiblePagesRange = new int[2]; @@ -163,6 +156,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) + @ViewDebug.ExportedProperty(category = "launcher") private Rect mViewport = new Rect(); // Reordering @@ -174,7 +168,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private float mMinScale = 1f; private boolean mUseMinScale = false; - protected View mDragView; + @Thunk View mDragView; private Runnable mSidePageHoverRunnable; @Thunk int mSidePageHoverIndex = -1; // This variable's scope is only for the duration of startReordering() and endReordering() @@ -183,7 +177,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // animation after endReordering() private boolean mIsReordering; // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition - private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2; + private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2; private int mPostReorderingPreZoomInRemainingAnimationCount; private Runnable mPostReorderingPreZoomInRunnable; @@ -218,11 +212,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PagedView, defStyle, 0); - - mPageLayoutWidthGap = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutWidthGap, 0); - mPageLayoutHeightGap = a.getDimensionPixelSize( - R.styleable.PagedView_pageLayoutHeightGap, 0); mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1); a.recycle(); @@ -238,16 +227,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mScroller = new LauncherScroller(getContext()); setDefaultInterpolator(new ScrollInterpolator()); mCurrentPage = 0; - mCenterPagesVertically = true; final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledPagingTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - mDensity = getResources().getDisplayMetrics().density; - mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); - mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); - mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); + float density = getResources().getDisplayMetrics().density; + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density); + mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density); + mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density); setOnHierarchyChangeListener(this); setWillNotDraw(false); } @@ -355,7 +343,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc int getViewportWidth() { return mViewport.width(); } - int getViewportHeight() { + public int getViewportHeight() { return mViewport.height(); } @@ -388,7 +376,10 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } /** - * Returns the index of the currently displayed page. + * Returns the index of the currently displayed page. When in free scroll mode, this is the page + * that the user was on before entering free scroll mode (e.g. the home screen page they + * long-pressed on to enter the overview). Try using {@link #getPageNearestToCenterOfScreen()} + * to get the page the user is currently scrolling over. */ public int getCurrentPage() { return mCurrentPage; @@ -397,11 +388,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc /** * Returns the index of page to be shown immediately afterwards. */ - int getNextPage() { + public int getNextPage() { return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; } - int getPageCount() { + public int getPageCount() { return getChildCount(); } @@ -454,7 +445,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc Math.min(newPage, mTempVisiblePagesRange[1])); } // Ensure that it is clamped by the actual set of children in all cases - validatedPage = Math.max(0, Math.min(validatedPage, getPageCount() - 1)); + validatedPage = Utilities.boundInRange(validatedPage, 0, getPageCount() - 1); return validatedPage; } @@ -601,9 +592,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc super.scrollTo(x, y); } - mTouchX = x; - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; - // Update the last motion events when scrolling if (isReordering(true)) { float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); @@ -831,6 +819,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc setMeasuredDimension(scaledWidthSize, scaledHeightSize); } + @SuppressLint("DrawAllocation") @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (getChildCount() == 0) { @@ -869,9 +858,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc childTop = offsetY; } else { childTop = offsetY + getPaddingTop() + mInsets.top; - if (mCenterPagesVertically) { - childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2; - } + childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2; } final int childWidth = child.getMeasuredWidth(); @@ -1056,13 +1043,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc range[1] = -1; if (pageCount > 0) { + int lastVisiblePageIndex = 0; final int visibleLeft = -getLeft(); final int visibleRight = visibleLeft + getViewportWidth(); - int curScreen = 0; - int count = getChildCount(); - for (int i = 0; i < count; i++) { - View currPage = getPageAt(i); + for (int currPageIndex = 0; currPageIndex < pageCount; currPageIndex++) { + View currPage = getPageAt(currPageIndex); // Verify if the page bounds are within the visible range. sTmpRectF.left = 0; @@ -1078,14 +1064,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc break; } } - - curScreen = i; if (range[0] < 0) { - range[0] = curScreen; + range[0] = currPageIndex; } + lastVisiblePageIndex = currPageIndex; } - range[1] = curScreen; + range[1] = lastVisiblePageIndex; } else { range[0] = -1; range[1] = -1; @@ -1449,8 +1434,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTotalMotionX += Math.abs(mLastMotionX - x); mLastMotionX = x; mLastMotionXRemainder = 0; - mTouchX = getViewportOffsetX() + getScrollX(); - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; onScrollInteractionBegin(); pageBeginMoving(); } @@ -1649,8 +1632,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // keep the remainder because we are actually testing if we've moved from the last // scrolled position (which is discrete). if (Math.abs(deltaX) >= 1.0f) { - mTouchX += deltaX; - mSmoothingTime = System.nanoTime() / NANOTIME_DIV; scrollBy((int) deltaX, 0); mLastMotionX = x; mLastMotionXRemainder = deltaX - (int) deltaX; @@ -1710,16 +1691,14 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Animate the view translation from its old position to its new // position - AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY); + ObjectAnimator anim = (ObjectAnimator) v.getTag(); if (anim != null) { anim.cancel(); } v.setTranslationX(oldX - newX); - anim = new AnimatorSet(); + anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0); anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION); - anim.playTogether( - ObjectAnimator.ofFloat(v, "translationX", 0f)); anim.start(); v.setTag(anim); } @@ -2041,7 +2020,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); } - protected void snapToPageImmediately(int whichPage) { + public void snapToPageImmediately(int whichPage) { snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null); } @@ -2111,20 +2090,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1); } - public int getPageForView(View v) { - int result = -1; - if (v != null) { - ViewParent vp = v.getParent(); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - if (vp == getPageAt(i)) { - return i; - } - } - } - return result; - } - @Override public boolean performLongClick() { mCancelTap = true; @@ -2164,13 +2129,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Animate the drag view back to the original position private void animateDragViewToOriginalPosition() { if (mDragView != null) { - AnimatorSet anim = new AnimatorSet(); - anim.setDuration(REORDERING_DROP_REPOSITION_DURATION); - anim.playTogether( - ObjectAnimator.ofFloat(mDragView, "translationX", 0f), - ObjectAnimator.ofFloat(mDragView, "translationY", 0f), - ObjectAnimator.ofFloat(mDragView, "scaleX", 1f), - ObjectAnimator.ofFloat(mDragView, "scaleY", 1f)); + Animator anim = new LauncherViewPropertyAnimator(mDragView) + .translationX(0) + .translationY(0) + .scaleX(1) + .scaleY(1) + .setDuration(REORDERING_DROP_REPOSITION_DURATION); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -2267,9 +2231,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc animateDragViewToOriginalPosition(); } - private static final int ANIM_TAG_KEY = 100; - /* Accessibility */ + @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { @@ -2329,7 +2292,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } protected String getCurrentPageDescription() { - return String.format(getContext().getString(R.string.default_scroll_format), + return getContext().getString(R.string.default_scroll_format, getNextPage() + 1, getChildCount()); } diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java index a31ae0bb0..a9acf087c 100644 --- a/src/com/android/launcher3/PreloadIconDrawable.java +++ b/src/com/android/launcher3/PreloadIconDrawable.java @@ -12,7 +12,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; -class PreloadIconDrawable extends Drawable { +public class PreloadIconDrawable extends Drawable { private static final float ANIMATION_PROGRESS_STOPPED = -1.0f; private static final float ANIMATION_PROGRESS_STARTED = 0f; @@ -30,7 +30,7 @@ class PreloadIconDrawable extends Drawable { private boolean mIndicatorRectDirty; private final Paint mPaint; - final Drawable mIcon; + public final Drawable mIcon; private Drawable mBgDrawable; private int mRingOutset; diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java index f7288a0a0..171dd8771 100644 --- a/src/com/android/launcher3/SearchDropTargetBar.java +++ b/src/com/android/launcher3/SearchDropTargetBar.java @@ -24,25 +24,23 @@ import android.animation.TimeInterpolator; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.view.View; +import android.view.ViewDebug; import android.view.accessibility.AccessibilityManager; -import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; -import android.widget.FrameLayout; +import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.util.Thunk; /* - * Ths bar will manage the transition between the QSB search bar and the delete drop - * targets so that each of the individual IconDropTargets don't have to. + * This bar will manage the transition between the QSB search bar and the delete/uninstall drop + * targets so that each of the individual ButtonDropTargets don't have to. */ -public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener { +public class SearchDropTargetBar extends BaseDropTargetBar { private static final TimeInterpolator MOVE_DOWN_INTERPOLATOR = new DecelerateInterpolator(0.6f); private static final TimeInterpolator MOVE_UP_INTERPOLATOR = new DecelerateInterpolator(1.5f); - private static final TimeInterpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(); /** The different states that the search bar space can be in. */ public enum State { @@ -63,18 +61,12 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D } - private static int DEFAULT_DRAG_FADE_DURATION = 175; - - private AnimatorSet mCurrentAnimation; + @ViewDebug.ExportedProperty(category = "launcher") private State mState = State.SEARCH_BAR; @Thunk View mQSB; - @Thunk View mDropTargetBar; - private boolean mDeferOnDragEnd = false; - @Thunk boolean mAccessibilityEnabled = false; // Drop targets - private ButtonDropTarget mInfoDropTarget; private ButtonDropTarget mDeleteDropTarget; private ButtonDropTarget mUninstallDropTarget; @@ -86,40 +78,42 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D super(context, attrs, defStyle); } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // Get the individual components + mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text); + mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar + .findViewById(R.id.uninstall_target_text); + + mDeleteDropTarget.setDropTargetBar(this); + mUninstallDropTarget.setDropTargetBar(this); + } + + @Override public void setup(Launcher launcher, DragController dragController) { dragController.addDragListener(this); dragController.setFlingToDeleteDropTarget(mDeleteDropTarget); - dragController.addDragListener(mInfoDropTarget); dragController.addDragListener(mDeleteDropTarget); dragController.addDragListener(mUninstallDropTarget); - dragController.addDropTarget(mInfoDropTarget); dragController.addDropTarget(mDeleteDropTarget); dragController.addDropTarget(mUninstallDropTarget); - mInfoDropTarget.setLauncher(launcher); mDeleteDropTarget.setLauncher(launcher); mUninstallDropTarget.setLauncher(launcher); } @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - // Get the individual components - mDropTargetBar = findViewById(R.id.drag_target_bar); - mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text); - mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text); - mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.uninstall_target_text); - - mInfoDropTarget.setSearchDropTargetBar(this); - mDeleteDropTarget.setSearchDropTargetBar(this); - mUninstallDropTarget.setSearchDropTargetBar(this); + public void showDropTargets() { + animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION); + } - // Create the various fade animations - mDropTargetBar.setAlpha(0f); - AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled); + @Override + public void hideDropTargets() { + animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION); } public void setQsbSearchBar(View qsb) { @@ -138,21 +132,8 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D if (mState != newState) { mState = newState; - // Update the accessibility state - AccessibilityManager am = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - mAccessibilityEnabled = am.isEnabled(); - - if (mCurrentAnimation != null) { - mCurrentAnimation.cancel(); - mCurrentAnimation = null; - } - mCurrentAnimation = null; - + resetAnimation(duration); if (duration > 0) { - mCurrentAnimation = new AnimatorSet(); - mCurrentAnimation.setDuration(duration); - animateAlpha(mDropTargetBar, mState.mDropTargetBarAlpha, DEFAULT_INTERPOLATOR); } else { mDropTargetBar.setAlpha(mState.mDropTargetBarAlpha); @@ -194,40 +175,6 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D } } - private void animateAlpha(View v, float alpha, TimeInterpolator interpolator) { - if (Float.compare(v.getAlpha(), alpha) != 0) { - ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, alpha); - anim.setInterpolator(interpolator); - anim.addListener(new ViewVisiblilyUpdateHandler(v)); - mCurrentAnimation.play(anim); - } - } - - /* - * DragController.DragListener implementation - */ - @Override - public void onDragStart(DragSource source, Object info, int dragAction) { - animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION); - } - - /** - * This is called to defer hiding the delete drop target until the drop animation has completed, - * instead of hiding immediately when the drag has ended. - */ - public void deferOnDragEnd() { - mDeferOnDragEnd = true; - } - - @Override - public void onDragEnd() { - if (!mDeferOnDragEnd) { - animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION); - } else { - mDeferOnDragEnd = false; - } - } - /** * @return the bounds of the QSB search bar. */ @@ -247,32 +194,12 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D } } + @Override public void enableAccessibleDrag(boolean enable) { if (mQSB != null) { mQSB.setVisibility(enable ? View.GONE : View.VISIBLE); } - mInfoDropTarget.enableAccessibleDrag(enable); mDeleteDropTarget.enableAccessibleDrag(enable); mUninstallDropTarget.enableAccessibleDrag(enable); } - - private class ViewVisiblilyUpdateHandler extends AnimatorListenerAdapter { - private final View mView; - - ViewVisiblilyUpdateHandler(View v) { - mView = v; - } - - @Override - public void onAnimationStart(Animator animation) { - // Ensure that the view is visible for the animation - mView.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(Animator animation){ - AlphaUpdateListener.updateVisibility(mView, mAccessibilityEnabled); - } - - } } diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java index 21e72e99a..008dd847e 100644 --- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java +++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java @@ -18,8 +18,6 @@ package com.android.launcher3; import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.Rect; import android.view.View; import android.view.ViewGroup; @@ -42,7 +40,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { private int mHeightGap; private int mCountX; - private int mCountY; private Launcher mLauncher; @@ -61,7 +58,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { mWidthGap = widthGap; mHeightGap = heightGap; mCountX = countX; - mCountY = countY; } public View getChildAt(int x, int y) { @@ -79,24 +75,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { } @Override - protected void dispatchDraw(Canvas canvas) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - // Debug drawing for hit space - Paint p = new Paint(); - p.setColor(0x6600FF00); - for (int i = getChildCount() - 1; i >= 0; i--) { - final View child = getChildAt(i); - final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); - - canvas.drawRect(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height, p); - } - } - super.dispatchDraw(canvas); - } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); @@ -236,7 +214,6 @@ public class ShortcutAndWidgetContainer extends ViewGroup { } } - @Override protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { super.setChildrenDrawnWithCacheEnabled(enabled); } diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 6bdcb4bf1..128d695eb 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -27,9 +27,9 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.folder.FolderIcon; import java.util.ArrayList; -import java.util.Arrays; /** * Represents a launchable icon on the workspaces and in folders. @@ -71,7 +71,7 @@ public class ShortcutInfo extends ItemInfo { /** * The intent used to start the application. */ - Intent intent; + public Intent intent; /** * Indicates whether the icon comes from an application's resource (if false) @@ -113,6 +113,16 @@ public class ShortcutInfo extends ItemInfo { public static final int FLAG_DISABLED_NOT_AVAILABLE = 2; /** + * Indicates that the icon is disabled as the app is suspended + */ + public static final int FLAG_DISABLED_SUSPENDED = 4; + + /** + * Indicates that the icon is disabled as the user is in quiet mode. + */ + public static final int FLAG_DISABLED_QUIET_USER = 8; + + /** * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when * sd-card is not available). */ @@ -178,6 +188,7 @@ public class ShortcutInfo extends ItemInfo { intent = new Intent(info.intent); customIcon = false; flags = info.flags; + isDisabled = info.isDisabled; } public void setIcon(Bitmap b) { @@ -238,16 +249,7 @@ public class ShortcutInfo extends ItemInfo { return "ShortcutInfo(title=" + title + "intent=" + intent + "id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY - + " dropPos=" + Arrays.toString(dropPos) + " user=" + user + ")"; - } - - public static void dumpShortcutInfoList(String tag, String label, - ArrayList<ShortcutInfo> list) { - Log.d(tag, label + " size=" + list.size()); - for (ShortcutInfo info: list) { - Log.d(tag, " title=\"" + info.title + " icon=" + info.mIcon - + " customIcon=" + info.customIcon); - } + + " user=" + user + ")"; } public ComponentName getTargetComponent() { @@ -288,5 +290,10 @@ public class ShortcutInfo extends ItemInfo { shortcut.flags = AppInfo.initFlags(info); return shortcut; } + + @Override + public boolean isDisabled() { + return isDisabled != 0; + } } diff --git a/src/com/android/launcher3/SimpleOnStylusPressListener.java b/src/com/android/launcher3/SimpleOnStylusPressListener.java new file mode 100644 index 000000000..6b97dcee6 --- /dev/null +++ b/src/com/android/launcher3/SimpleOnStylusPressListener.java @@ -0,0 +1,25 @@ +package com.android.launcher3; + +import android.view.MotionEvent; +import android.view.View; + +import com.android.launcher3.StylusEventHelper.StylusButtonListener; + +/** + * Simple listener that performs a long click on the view after a stylus button press. + */ +public class SimpleOnStylusPressListener implements StylusButtonListener { + private View mView; + + public SimpleOnStylusPressListener(View view) { + mView = view; + } + + public boolean onPressed(MotionEvent event) { + return mView.isLongClickable() && mView.performLongClick(); + } + + public boolean onReleased(MotionEvent event) { + return false; + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/StartupReceiver.java b/src/com/android/launcher3/StartupReceiver.java deleted file mode 100644 index 65f913fdf..000000000 --- a/src/com/android/launcher3/StartupReceiver.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.launcher3; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class StartupReceiver extends BroadcastReceiver { - - static final String SYSTEM_READY = "com.android.launcher3.SYSTEM_READY"; - - @Override - public void onReceive(Context context, Intent intent) { - context.sendStickyBroadcast(new Intent(SYSTEM_READY)); - } -} diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java index 83bca855a..287382869 100644 --- a/src/com/android/launcher3/Stats.java +++ b/src/com/android/launcher3/Stats.java @@ -25,6 +25,9 @@ import android.util.Log; import android.view.View; import android.view.ViewParent; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.userevent.Logger; + public class Stats { /** @@ -71,7 +74,7 @@ public class Stats { if (provider != null) { provider.fillInLaunchSourceData(v, sourceData); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new RuntimeException("Expected LaunchSourceProvider"); } } @@ -143,5 +146,6 @@ public class Stats { LaunchSourceUtils.populateSourceDataFromAncestorProvider(v, sourceExtras); broadcastIntent.putExtra(EXTRA_SOURCE, sourceExtras); mLauncher.sendBroadcast(broadcastIntent, mLaunchBroadcastPermission); + mLauncher.getLogger().logAppLaunch(intent.getComponent().getPackageName(), shortcut, sourceExtras); } } diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java index e03273a6e..d5fc0fad4 100644 --- a/src/com/android/launcher3/StylusEventHelper.java +++ b/src/com/android/launcher3/StylusEventHelper.java @@ -6,55 +6,82 @@ import android.view.ViewConfiguration; /** * Helper for identifying when a stylus touches a view while the primary stylus button is pressed. - * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}. On a - * stylus button press this performs the view's {@link View#performLongClick()} method, if the view - * is long clickable. + * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}. */ public class StylusEventHelper { - private boolean mIsButtonPressed; - private View mView; - public StylusEventHelper(View view) { - mView = view; + /** + * Implement this interface to receive callbacks for a stylus button press and release. + */ + public interface StylusButtonListener { + /** + * Called when the stylus button is pressed. + * + * @param event The MotionEvent that the button press occurred for. + * @return Whether the event was handled. + */ + public boolean onPressed(MotionEvent event); + + /** + * Called when the stylus button is released after a button press. This is also called if + * the event is canceled or the stylus is lifted off the screen. + * + * @param event The MotionEvent the button release occurred for. + * @return Whether the event was handled. + */ + public boolean onReleased(MotionEvent event); } + private boolean mIsButtonPressed; + private View mView; + private StylusButtonListener mListener; + private final float mSlop; + /** - * Call this in onTouchEvent method of a view to identify a stylus button press and perform a - * long click (if the view is long clickable). + * Constructs a helper for listening to stylus button presses and releases. Ensure that { + * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on + * the helper to correctly identify stylus events. * - * @param event The event to check for a stylus button press. - * @return Whether a stylus event occurred and was handled. + * @param listener The listener to call for stylus events. + * @param view Optional view associated with the touch events. */ - public boolean checkAndPerformStylusEvent(MotionEvent event) { - final float slop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); - - if (!mView.isLongClickable()) { - // We don't do anything unless the view is long clickable. - return false; + public StylusEventHelper(StylusButtonListener listener, View view) { + mListener = listener; + mView = view; + if (mView != null) { + mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop(); + } else { + mSlop = ViewConfiguration.getTouchSlop(); } + } + public boolean onMotionEvent(MotionEvent event) { final boolean stylusButtonPressed = isStylusButtonPressed(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - mIsButtonPressed = false; - if (stylusButtonPressed && mView.performLongClick()) { - mIsButtonPressed = true; - return true; + mIsButtonPressed = stylusButtonPressed; + if (mIsButtonPressed) { + return mListener.onPressed(event); } break; case MotionEvent.ACTION_MOVE: - if (Utilities.pointInView(mView, event.getX(), event.getY(), slop)) { - if (!mIsButtonPressed && stylusButtonPressed && mView.performLongClick()) { - mIsButtonPressed = true; - return true; - } else if (mIsButtonPressed && !stylusButtonPressed) { - mIsButtonPressed = false; - } + if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) { + return false; + } + if (!mIsButtonPressed && stylusButtonPressed) { + mIsButtonPressed = true; + return mListener.onPressed(event); + } else if (mIsButtonPressed && !stylusButtonPressed) { + mIsButtonPressed = false; + return mListener.onReleased(event); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mIsButtonPressed = false; + if (mIsButtonPressed) { + mIsButtonPressed = false; + return mListener.onReleased(event); + } break; } return false; diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java index 955d4013c..73881610b 100644 --- a/src/com/android/launcher3/UninstallDropTarget.java +++ b/src/com/android/launcher3/UninstallDropTarget.java @@ -8,7 +8,7 @@ import android.os.Bundle; import android.os.UserManager; import android.util.AttributeSet; import android.util.Pair; -import com.android.launcher3.R; + import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.util.Thunk; @@ -32,7 +32,7 @@ public class UninstallDropTarget extends ButtonDropTarget { } @Override - protected boolean supportsDrop(DragSource source, Object info) { + protected boolean supportsDrop(DragSource source, ItemInfo info) { return supportsDrop(getContext(), info); } @@ -81,15 +81,18 @@ public class UninstallDropTarget extends ButtonDropTarget { @Override void completeDrop(final DragObject d) { final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo); - final UserHandleCompat user = ((ItemInfo) d.dragInfo).user; - if (startUninstallActivity(mLauncher, d.dragInfo)) { + final UserHandleCompat user = d.dragInfo.user; + if (startActivityWithUninstallAffordance(d)) { final Runnable checkIfUninstallWasSuccess = new Runnable() { @Override public void run() { - String packageName = componentInfo.first.getPackageName(); - boolean uninstallSuccessful = !AllAppsList.packageHasActivities( - getContext(), packageName, user); + boolean uninstallSuccessful = false; + if (componentInfo != null) { + String packageName = componentInfo.first.getPackageName(); + uninstallSuccessful = !AllAppsList.packageHasActivities( + getContext(), packageName, user); + } sendUninstallResult(d.dragSource, uninstallSuccessful); } }; @@ -99,9 +102,13 @@ public class UninstallDropTarget extends ButtonDropTarget { } } - public static boolean startUninstallActivity(Launcher launcher, Object info) { + protected boolean startActivityWithUninstallAffordance(DragObject d) { + return startUninstallActivity(mLauncher, d.dragInfo); + } + + public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) { final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info); - final UserHandleCompat user = ((ItemInfo) info).user; + final UserHandleCompat user = info.user; return launcher.startApplicationUninstallActivity( componentInfo.first, componentInfo.second, user); } @@ -115,7 +122,7 @@ public class UninstallDropTarget extends ButtonDropTarget { /** * Interface defining an object that can provide uninstallable drag objects. */ - public static interface UninstallSource { + public interface UninstallSource { /** * A pending uninstall operation was complete. diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 87c9262d7..57b583b32 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -62,10 +62,12 @@ import android.widget.Toast; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.util.IconNormalizer; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Locale; import java.util.Set; @@ -140,11 +142,9 @@ public final class Utilities { return Log.isLoggable(propertyName, Log.VERBOSE); } - public static boolean isAllowRotationPrefEnabled(Context context, boolean multiProcess) { - SharedPreferences sharedPrefs = context.getSharedPreferences( - LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE | (multiProcess ? - Context.MODE_MULTI_PROCESS : 0)); - boolean allowRotationPref = sharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false); + public static boolean isAllowRotationPrefEnabled(Context context) { + boolean allowRotationPref = getPrefs(context) + .getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false); return sForceEnableRotation || allowRotationPref; } @@ -152,6 +152,18 @@ public final class Utilities { return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation); } + public static boolean isNycOrAbove() { + // TODO(vadimt): Replace using reflection with looking at the API version once + // Build.VERSION.SDK_INT gets bumped to 24. b/22942492. + try { + View.class.getDeclaredField("DRAG_FLAG_OPAQUE"); + // View.DRAG_FLAG_OPAQUE doesn't exist in M-release, so it's an indication of N+. + return true; + } catch (NoSuchFieldException e) { + return false; + } + } + public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { byte[] data = c.getBlob(iconIndex); try { @@ -399,15 +411,6 @@ public final class Utilities { localY < (v.getHeight() + slop); } - public static void scaleRect(Rect r, float scale) { - if (scale != 1.0f) { - r.left = (int) (r.left * scale + 0.5f); - r.top = (int) (r.top * scale + 0.5f); - r.right = (int) (r.right * scale + 0.5f); - r.bottom = (int) (r.bottom * scale + 0.5f); - } - } - public static int[] getCenterDeltaInScreenSpace(View v0, View v1, int[] delta) { v0.getLocationInWindow(sLoc0); v1.getLocationInWindow(sLoc1); @@ -428,11 +431,18 @@ public final class Utilities { } public static void scaleRectAboutCenter(Rect r, float scale) { - int cx = r.centerX(); - int cy = r.centerY(); - r.offset(-cx, -cy); - Utilities.scaleRect(r, scale); - r.offset(cx, cy); + if (scale != 1.0f) { + int cx = r.centerX(); + int cy = r.centerY(); + r.offset(-cx, -cy); + + r.left = (int) (r.left * scale + 0.5f); + r.top = (int) (r.top * scale + 0.5f); + r.right = (int) (r.right * scale + 0.5f); + r.bottom = (int) (r.bottom * scale + 0.5f); + + r.offset(cx, cy); + } } public static void startActivityForResultSafely( @@ -575,16 +585,6 @@ public final class Utilities { return null; } - @TargetApi(Build.VERSION_CODES.KITKAT) - public static boolean isViewAttachedToWindow(View v) { - if (ATLEAST_KITKAT) { - return v.isAttachedToWindow(); - } else { - // A proxy call which returns null, if the view is not attached to the window. - return v.getKeyDispatcherState() != null; - } - } - /** * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX} * provided by the same package which is set to be global search activity. @@ -720,7 +720,7 @@ public final class Utilities { } public static void assertWorkerThread() { - if (LauncherAppState.isDogfoodBuild() && + if (ProviderConfig.IS_DOGFOOD_BUILD && (LauncherModel.sWorkerThread.getThreadId() != Process.myTid())) { throw new IllegalStateException(); } @@ -770,6 +770,28 @@ public final class Utilities { return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values)); } + public static boolean isBootCompleted() { + try { + Class clazz = Class.forName("android.os.SystemProperties"); + Method getter = clazz.getDeclaredMethod("get", String.class); + String value = (String) getter.invoke(null, "sys.boot_completed"); + return "1".equals(value); + } catch (Exception e) { + Log.d(TAG, "Unable to read system properties"); + // Assume that boot has completed + return true; + } + } + + /** + * Ensures that a value is within given bounds. Specifically: + * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound, + * return upperBound; else return value unchanged. + */ + public static int boundInRange(int value, int lowerBound, int upperBound) { + return Math.max(lowerBound, Math.min(value, upperBound)); + } + /** * Wraps a message with a TTS span, so that a different message is spoken than * what is getting displayed. diff --git a/src/com/android/launcher3/WallpaperChangedReceiver.java b/src/com/android/launcher3/WallpaperChangedReceiver.java deleted file mode 100644 index c24fbffc6..000000000 --- a/src/com/android/launcher3/WallpaperChangedReceiver.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class WallpaperChangedReceiver extends BroadcastReceiver { - public void onReceive(Context context, Intent data) { - LauncherAppState.getInstance().onWallpaperChanged(); - } -} diff --git a/src/com/android/launcher3/WallpaperPickerActivity.java b/src/com/android/launcher3/WallpaperPickerActivity.java new file mode 100644 index 000000000..62b9be43e --- /dev/null +++ b/src/com/android/launcher3/WallpaperPickerActivity.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.util.Pair; + +import com.android.wallpaperpicker.tileinfo.DefaultWallpaperInfo; +import com.android.wallpaperpicker.tileinfo.FileWallpaperInfo; +import com.android.wallpaperpicker.tileinfo.WallpaperTileInfo; + +import java.io.File; +import java.util.ArrayList; + +public class WallpaperPickerActivity extends com.android.wallpaperpicker.WallpaperPickerActivity { + + @Override + public void startActivityForResultSafely(Intent intent, int requestCode) { + Utilities.startActivityForResultSafely(this, intent, requestCode); + } + + @Override + public boolean enableRotation() { + return super.enableRotation() || + getContentResolver().call(LauncherSettings.Settings.CONTENT_URI, + LauncherSettings.Settings.METHOD_GET_BOOLEAN, + Utilities.ALLOW_ROTATION_PREFERENCE_KEY, new Bundle()) + .getBoolean(LauncherSettings.Settings.EXTRA_VALUE); + } + + @Override + public ArrayList<WallpaperTileInfo> findBundledWallpapers() { + final PackageManager pm = getPackageManager(); + final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24); + + Partner partner = Partner.get(pm); + if (partner != null) { + final Resources partnerRes = partner.getResources(); + final int resId = partnerRes.getIdentifier(Partner.RES_WALLPAPERS, "array", + partner.getPackageName()); + if (resId != 0) { + addWallpapers(bundled, partnerRes, partner.getPackageName(), resId); + } + + // Add system wallpapers + File systemDir = partner.getWallpaperDirectory(); + if (systemDir != null && systemDir.isDirectory()) { + for (File file : systemDir.listFiles()) { + if (!file.isFile()) { + continue; + } + String name = file.getName(); + int dotPos = name.lastIndexOf('.'); + String extension = ""; + if (dotPos >= -1) { + extension = name.substring(dotPos); + name = name.substring(0, dotPos); + } + + if (name.endsWith("_small")) { + // it is a thumbnail + continue; + } + + File thumbnail = new File(systemDir, name + "_small" + extension); + Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath()); + if (thumb != null) { + bundled.add(new FileWallpaperInfo( + file, new BitmapDrawable(getResources(), thumb))); + } + } + } + } + + Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId(); + if (r != null) { + try { + Resources wallpaperRes = pm.getResourcesForApplication(r.first); + addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second); + } catch (PackageManager.NameNotFoundException e) { + } + } + + if (partner == null || !partner.hideDefaultWallpaper()) { + // Add an entry for the default wallpaper (stored in system resources) + WallpaperTileInfo defaultWallpaperInfo = DefaultWallpaperInfo.get(this); + if (defaultWallpaperInfo != null) { + bundled.add(0, defaultWallpaperInfo); + } + } + return bundled; + } +} diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 7b873d420..840f9f2fa 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -28,12 +28,9 @@ import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PointF; @@ -49,31 +46,37 @@ import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; -import android.view.Choreographer; -import android.view.Display; import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; import android.widget.TextView; -import com.android.launcher3.FolderIcon.FolderRingAnimator; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.FolderIcon.FolderRingAnimator; import com.android.launcher3.Launcher.CustomContentCallbacks; import com.android.launcher3.Launcher.LauncherOverlay; -import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.UninstallDropTarget.UninstallSource; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragScroller; +import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.SpringLoadedDragController; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; -import com.android.launcher3.util.WallpaperUtils; +import com.android.launcher3.util.WallpaperOffsetInterpolator; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.wallpaperpicker.WallpaperUtils; import java.util.ArrayList; import java.util.HashMap; @@ -93,13 +96,17 @@ public class Workspace extends PagedView private static boolean ENFORCE_DRAG_EVENT_ORDER = false; - protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; - protected static final int FADE_EMPTY_SCREEN_DURATION = 150; + private static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400; + private static final int FADE_EMPTY_SCREEN_DURATION = 150; private static final int ADJACENT_SCREEN_DROP_DURATION = 300; - static final boolean MAP_NO_RECURSE = false; - static final boolean MAP_RECURSE = true; + private static final boolean MAP_NO_RECURSE = false; + private static final boolean MAP_RECURSE = true; + + // The screen id used for the empty screen always present to the right. + public final static long EXTRA_EMPTY_SCREEN_ID = -201; + private final static long CUSTOM_CONTENT_SCREEN_ID = -301; private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200; private long mTouchDownTime = -1; @@ -107,17 +114,9 @@ public class Workspace extends PagedView private LayoutTransition mLayoutTransition; @Thunk final WallpaperManager mWallpaperManager; - @Thunk IBinder mWindowToken; - - private int mOriginalDefaultPage; - private int mDefaultPage; private ShortcutAndWidgetContainer mDragSourceInternal; - // The screen id used for the empty screen always present to the right. - final static long EXTRA_EMPTY_SCREEN_ID = -201; - private final static long CUSTOM_CONTENT_SCREEN_ID = -301; - @Thunk LongArrayMap<CellLayout> mWorkspaceScreens = new LongArrayMap<>(); @Thunk ArrayList<Long> mScreenOrder = new ArrayList<Long>(); @@ -137,9 +136,6 @@ public class Workspace extends PagedView private int mDragOverX = -1; private int mDragOverY = -1; - static Rect mLandscapeCellLayoutMetrics = null; - static Rect mPortraitCellLayoutMetrics = null; - CustomContentCallbacks mCustomContentCallbacks; boolean mCustomContentShowing; private float mLastCustomContentScrollProgress = -1f; @@ -165,12 +161,11 @@ public class Workspace extends PagedView // These are temporary variables to prevent having to allocate a new object just to // return an (x, y) value from helper functions. Do NOT use them to maintain other state. - private int[] mTempCell = new int[2]; - private int[] mTempPt = new int[2]; - private int[] mTempEstimate = new int[2]; + private static final Rect sTempRect = new Rect(); + private final int[] mTempXY = new int[2]; @Thunk float[] mDragViewVisualCenter = new float[2]; private float[] mTempCellLayoutCenterCoordinates = new float[2]; - private Matrix mTempInverseMatrix = new Matrix(); + private int[] mTempVisiblePagesRange = new int[2]; private SpringLoadedDragController mSpringLoadedDragController; private float mSpringLoadedShrinkFactor; @@ -197,11 +192,11 @@ public class Workspace extends PagedView } }; + @ViewDebug.ExportedProperty(category = "launcher") private State mState = State.NORMAL; private boolean mIsSwitchingState = false; boolean mAnimatingViewIntoPlace = false; - boolean mIsDragOccuring = false; boolean mChildrenLayersEnabled = true; private boolean mStripScreensOnPageStopMoving = false; @@ -211,20 +206,13 @@ public class Workspace extends PagedView private HolographicOutlineHelper mOutlineHelper; @Thunk Bitmap mDragOutline = null; - private static final Rect sTempRect = new Rect(); - private final int[] mTempXY = new int[2]; - private int[] mTempVisiblePagesRange = new int[2]; public static final int DRAG_BITMAP_PADDING = 2; private boolean mWorkspaceFadeInAdjacentScreens; - WallpaperOffsetInterpolator mWallpaperOffset; - @Thunk boolean mWallpaperIsLiveWallpaper; - @Thunk int mNumPagesForWallpaperParallax; - @Thunk float mLastSetWallpaperOffsetSteps = 0; + final WallpaperOffsetInterpolator mWallpaperOffset; @Thunk Runnable mDelayedResizeRunnable; private Runnable mDelayedSnapToPageRunnable; - private Point mDisplaySize = new Point(); // Variables relating to the creation of user folders by hovering shortcuts over shortcuts private static final int FOLDER_CREATION_TIMEOUT = 0; @@ -278,19 +266,13 @@ public class Workspace extends PagedView boolean mStartedSendingScrollEvents; boolean mShouldSendPageSettled; int mLastOverlaySroll = 0; + private boolean mForceDrawAdjacentPages = false; // Handles workspace state transitions private WorkspaceStateTransitionAnimation mStateTransitionAnimation; private AccessibilityDelegate mPagesAccessibilityDelegate; - private final Runnable mBindPages = new Runnable() { - @Override - public void run() { - mLauncher.getModel().bindRemainingSynchronousPages(); - } - }; - /** * Used to inflate the Workspace from XML. * @@ -321,14 +303,12 @@ public class Workspace extends PagedView mFadeInAdjacentScreens = false; mWallpaperManager = WallpaperManager.getInstance(context); - TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.Workspace, defStyle, 0); + mWallpaperOffset = new WallpaperOffsetInterpolator(this); + mSpringLoadedShrinkFactor = res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; mOverviewModeShrinkFactor = res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f; - mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1); - a.recycle(); setOnHierarchyChangeListener(this); setHapticFeedbackEnabled(false); @@ -381,12 +361,11 @@ public class Workspace extends PagedView } @Override - public void onDragStart(final DragSource source, Object info, int dragAction) { + public void onDragStart(final DragSource source, ItemInfo info, int dragAction) { if (ENFORCE_DRAG_EVENT_ORDER) { enfoceDragParity("onDragStart", 0, 0); } - mIsDragOccuring = true; updateChildrenLayersEnabled(false); mLauncher.lockScreenOrientation(); mLauncher.onInteractionBegin(); @@ -417,7 +396,6 @@ public class Workspace extends PagedView removeExtraEmptyScreen(true, mDragSourceInternal != null); } - mIsDragOccuring = false; updateChildrenLayersEnabled(false); mLauncher.unlockScreenOrientation(false); @@ -432,7 +410,7 @@ public class Workspace extends PagedView * Initializes various states for this workspace. */ protected void initWorkspace() { - mCurrentPage = mDefaultPage; + mCurrentPage = getDefaultPage(); LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = mLauncher.getDeviceProfile(); mIconCache = app.getIconCache(); @@ -444,10 +422,6 @@ public class Workspace extends PagedView setMinScale(mOverviewModeShrinkFactor); setupLayoutTransition(); - mWallpaperOffset = new WallpaperOffsetInterpolator(); - Display display = mLauncher.getWindowManager().getDefaultDisplay(); - display.getSize(mDisplaySize); - mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx); // Set the wallpaper dimensions when Launcher starts up @@ -456,6 +430,10 @@ public class Workspace extends PagedView setEdgeGlowColor(getResources().getColor(R.color.workspace_edge_effect_color)); } + private int getDefaultPage() { + return numCustomPages(); + } + private void setupLayoutTransition() { // We want to show layout transitions when pages are deleted, to close the gap. mLayoutTransition = new LayoutTransition(); @@ -588,9 +566,6 @@ public class Workspace extends PagedView addFullScreenPage(customScreen); - // Ensure that the current page and default page are maintained. - mDefaultPage = mOriginalDefaultPage + 1; - // Update the custom content hint if (mRestorePage != INVALID_RESTORE_PAGE) { mRestorePage = mRestorePage + 1; @@ -616,9 +591,6 @@ public class Workspace extends PagedView mCustomContentCallbacks = null; - // Ensure that the current page and default page are maintained. - mDefaultPage = mOriginalDefaultPage - 1; - // Update the custom content hint if (mRestorePage != INVALID_RESTORE_PAGE) { mRestorePage = mRestorePage - 1; @@ -697,7 +669,6 @@ public class Workspace extends PagedView private void convertFinalScreenToEmptyScreenIfNecessary() { if (mLauncher.isWorkspaceLoading()) { // Invalid and dangerous operation if workspace is loading - Launcher.addDumpLog(TAG, " - workspace loading, skip", true); return; } @@ -730,7 +701,6 @@ public class Workspace extends PagedView final int delay, final boolean stripEmptyScreens) { if (mLauncher.isWorkspaceLoading()) { // Don't strip empty screens if the workspace is still loading - Launcher.addDumpLog(TAG, " - workspace loading, skip", true); return; } @@ -816,7 +786,6 @@ public class Workspace extends PagedView public long commitExtraEmptyScreen() { if (mLauncher.isWorkspaceLoading()) { // Invalid and dangerous operation if workspace is loading - Launcher.addDumpLog(TAG, " - workspace loading, skip", true); return -1; } @@ -825,7 +794,9 @@ public class Workspace extends PagedView mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID); mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID); - long newId = LauncherAppState.getLauncherProvider().generateNewScreenId(); + long newId = LauncherSettings.Settings.call(getContext().getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) + .getLong(LauncherSettings.Settings.EXTRA_VALUE); mWorkspaceScreens.put(newId, cl); mScreenOrder.add(newId); @@ -841,8 +812,7 @@ public class Workspace extends PagedView } public CellLayout getScreenWithId(long screenId) { - CellLayout layout = mWorkspaceScreens.get(screenId); - return layout; + return mWorkspaceScreens.get(screenId); } public long getIdForScreen(CellLayout layout) { @@ -872,7 +842,6 @@ public class Workspace extends PagedView if (mLauncher.isWorkspaceLoading()) { // Don't strip empty screens if the workspace is still loading. // This is dangerous and can result in data loss. - Launcher.addDumpLog(TAG, " - workspace loading, skip", true); return; } @@ -941,7 +910,7 @@ public class Workspace extends PagedView // At bind time, we use the rank (screenId) to compute x and y for hotseat items. // See implementation for parameter definition. - void addInScreenFromBind(View child, long container, long screenId, int x, int y, + public void addInScreenFromBind(View child, long container, long screenId, int x, int y, int spanX, int spanY) { addInScreen(child, container, screenId, x, y, spanX, spanY, false, true); } @@ -1032,7 +1001,7 @@ public class Workspace extends PagedView // TODO: This branch occurs when the workspace is adding views // outside of the defined grid // maybe we should be deleting these items from the LauncherModel? - Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true); + Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout"); } if (!(child instanceof Folder)) { @@ -1220,7 +1189,7 @@ public class Workspace extends PagedView } } - if (mDelayedResizeRunnable != null) { + if (mDelayedResizeRunnable != null && !mIsSwitchingState) { mDelayedResizeRunnable.run(); mDelayedResizeRunnable = null; } @@ -1323,12 +1292,12 @@ public class Workspace extends PagedView protected void setWallpaperDimension() { new AsyncTask<Void, Void, Void>() { public Void doInBackground(Void ... args) { - String spKey = LauncherFiles.WALLPAPER_CROP_PREFERENCES_KEY; - SharedPreferences sp = - mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); - WallpaperUtils.suggestWallpaperDimension(mLauncher.getResources(), - sp, mLauncher.getWindowManager(), mWallpaperManager, - mLauncher.overrideWallpaperDimensions()); + if (Utilities.ATLEAST_KITKAT) { + WallpaperUtils.suggestWallpaperDimension(mLauncher); + } else { + WallpaperUtils.suggestWallpaperDimensionPreK(mLauncher, + mLauncher.overrideWallpaperDimensions()); + } return null; } }.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); @@ -1354,191 +1323,6 @@ public class Workspace extends PagedView snapToPage(getPageIndexForScreenId(screenId), r); } - class WallpaperOffsetInterpolator implements Choreographer.FrameCallback { - float mFinalOffset = 0.0f; - float mCurrentOffset = 0.5f; // to force an initial update - boolean mWaitingForUpdate; - Choreographer mChoreographer; - Interpolator mInterpolator; - boolean mAnimating; - long mAnimationStartTime; - float mAnimationStartOffset; - private final int ANIMATION_DURATION = 250; - // Don't use all the wallpaper for parallax until you have at least this many pages - private final int MIN_PARALLAX_PAGE_SPAN = 3; - int mNumScreens; - - public WallpaperOffsetInterpolator() { - mChoreographer = Choreographer.getInstance(); - mInterpolator = new DecelerateInterpolator(1.5f); - } - - @Override - public void doFrame(long frameTimeNanos) { - updateOffset(false); - } - - private void updateOffset(boolean force) { - if (mWaitingForUpdate || force) { - mWaitingForUpdate = false; - if (computeScrollOffset() && mWindowToken != null) { - try { - mWallpaperManager.setWallpaperOffsets(mWindowToken, - mWallpaperOffset.getCurrX(), 0.5f); - setWallpaperOffsetSteps(); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Error updating wallpaper offset: " + e); - } - } - } - } - - public boolean computeScrollOffset() { - final float oldOffset = mCurrentOffset; - if (mAnimating) { - long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime; - float t0 = durationSinceAnimation / (float) ANIMATION_DURATION; - float t1 = mInterpolator.getInterpolation(t0); - mCurrentOffset = mAnimationStartOffset + - (mFinalOffset - mAnimationStartOffset) * t1; - mAnimating = durationSinceAnimation < ANIMATION_DURATION; - } else { - mCurrentOffset = mFinalOffset; - } - - if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) { - scheduleUpdate(); - } - if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) { - return true; - } - return false; - } - - public float wallpaperOffsetForScroll(int scroll) { - // TODO: do different behavior if it's a live wallpaper? - // Don't use up all the wallpaper parallax until you have at least - // MIN_PARALLAX_PAGE_SPAN pages - int numScrollingPages = getNumScreensExcludingEmptyAndCustom(); - int parallaxPageSpan; - if (mWallpaperIsLiveWallpaper) { - parallaxPageSpan = numScrollingPages - 1; - } else { - parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1); - } - mNumPagesForWallpaperParallax = parallaxPageSpan; - - if (getChildCount() <= 1) { - if (mIsRtl) { - return 1 - 1.0f/mNumPagesForWallpaperParallax; - } - return 0; - } - - // Exclude the leftmost page - int emptyExtraPages = numEmptyScreensToIgnore(); - int firstIndex = numCustomPages(); - // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages) - int lastIndex = getChildCount() - 1 - emptyExtraPages; - if (mIsRtl) { - int temp = firstIndex; - firstIndex = lastIndex; - lastIndex = temp; - } - - int firstPageScrollX = getScrollForPage(firstIndex); - int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX; - if (scrollRange == 0) { - return 0; - } else { - // Sometimes the left parameter of the pages is animated during a layout transition; - // this parameter offsets it to keep the wallpaper from animating as well - int adjustedScroll = - scroll - firstPageScrollX - getLayoutTransitionOffsetForPage(0); - float offset = Math.min(1, adjustedScroll / (float) scrollRange); - offset = Math.max(0, offset); - - // On RTL devices, push the wallpaper offset to the right if we don't have enough - // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN) - if (!mWallpaperIsLiveWallpaper && numScrollingPages < MIN_PARALLAX_PAGE_SPAN - && mIsRtl) { - return offset * (parallaxPageSpan - numScrollingPages + 1) / parallaxPageSpan; - } - return offset * (numScrollingPages - 1) / parallaxPageSpan; - } - } - - private float wallpaperOffsetForCurrentScroll() { - return wallpaperOffsetForScroll(getScrollX()); - } - - private int numEmptyScreensToIgnore() { - int numScrollingPages = getChildCount() - numCustomPages(); - if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) { - return 1; - } else { - return 0; - } - } - - private int getNumScreensExcludingEmptyAndCustom() { - int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages(); - return numScrollingPages; - } - - public void syncWithScroll() { - float offset = wallpaperOffsetForCurrentScroll(); - mWallpaperOffset.setFinalX(offset); - updateOffset(true); - } - - public float getCurrX() { - return mCurrentOffset; - } - - public float getFinalX() { - return mFinalOffset; - } - - private void animateToFinal() { - mAnimating = true; - mAnimationStartOffset = mCurrentOffset; - mAnimationStartTime = System.currentTimeMillis(); - } - - private void setWallpaperOffsetSteps() { - // Set wallpaper offset steps (1 / (number of screens - 1)) - float xOffset = 1.0f / mNumPagesForWallpaperParallax; - if (xOffset != mLastSetWallpaperOffsetSteps) { - mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f); - mLastSetWallpaperOffsetSteps = xOffset; - } - } - - public void setFinalX(float x) { - scheduleUpdate(); - mFinalOffset = Math.max(0f, Math.min(x, 1.0f)); - if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) { - if (mNumScreens > 0) { - // Don't animate if we're going from 0 screens - animateToFinal(); - } - mNumScreens = getNumScreensExcludingEmptyAndCustom(); - } - } - - private void scheduleUpdate() { - if (!mWaitingForUpdate) { - mChoreographer.postFrameCallback(this); - mWaitingForUpdate = true; - } - } - - public void jumpToFinal() { - mCurrentOffset = mFinalOffset; - } - } - @Override public void computeScroll() { super.computeScroll(); @@ -1566,18 +1350,6 @@ public class Workspace extends PagedView } } - float backgroundAlphaInterpolator(float r) { - float pivotA = 0.1f; - float pivotB = 0.4f; - if (r < pivotA) { - return 0; - } else if (r > pivotB) { - return 1.0f; - } else { - return (r - pivotA)/(pivotB - pivotA); - } - } - private void updatePageAlphaValues(int screenCenter) { if (mWorkspaceFadeInAdjacentScreens && !workspaceInModalState() && @@ -1609,6 +1381,7 @@ public class Workspace extends PagedView setOnClickListener(mLauncher); } mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable); + mLauncher.getAppInfoDropTargetBar().enableAccessibleDrag(enable); mLauncher.getHotseat().getLayout() .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG); } @@ -1680,13 +1453,12 @@ public class Workspace extends PagedView if (!am.isTouchExplorationEnabled()) { return null; } - OnClickListener listener = new OnClickListener() { + return new OnClickListener() { @Override public void onClick(View arg0) { mLauncher.showOverviewMode(true); } }; - return listener; } @Override @@ -1698,14 +1470,15 @@ public class Workspace extends PagedView protected void onAttachedToWindow() { super.onAttachedToWindow(); - mWindowToken = getWindowToken(); + IBinder windowToken = getWindowToken(); + mWallpaperOffset.setWindowToken(windowToken); computeScroll(); - mDragController.setWindowToken(mWindowToken); + mDragController.setWindowToken(windowToken); } protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mWindowToken = null; + mWallpaperOffset.setWindowToken(null); } protected void onResume() { @@ -1723,10 +1496,7 @@ public class Workspace extends PagedView if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) { setWallpaperDimension(); } - mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null; - // Force the wallpaper offset steps to be set again, because another app might have changed - // them - mLastSetWallpaperOffsetSteps = 0f; + mWallpaperOffset.onResume(); } @Override @@ -1739,14 +1509,6 @@ public class Workspace extends PagedView } @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // Call back to LauncherModel to finish binding after the first draw - post(mBindPages); - } - - @Override protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { if (!mLauncher.isAppsViewVisible()) { final Folder openFolder = getOpenFolder(); @@ -1872,8 +1634,18 @@ public class Workspace extends PagedView updateChildrenLayersEnabled(false); } + @Override + protected void getVisiblePages(int[] range) { + super.getVisiblePages(range); + if (mForceDrawAdjacentPages) { + // In overview mode, make sure that the two side pages are visible. + range[0] = Utilities.boundInRange(getCurrentPage() - 1, numCustomPages(), range[1]); + range[1] = Utilities.boundInRange(getCurrentPage() + 1, range[0], getPageCount() - 1); + } + } + protected void onWallpaperTap(MotionEvent ev) { - final int[] position = mTempCell; + final int[] position = mTempXY; getLocationOnScreen(position); int pointerIndex = ev.getActionIndex(); @@ -1937,6 +1709,18 @@ public class Workspace extends PagedView } public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) { + // Find a page that has enough space to place this widget (after rearranging/resizing). + // Start at the current page and search right (on LTR) until finding a page with enough + // space. Since an empty screen is the furthest right, a page must be found. + int currentPageInOverview = getPageNearestToCenterOfScreen(); + for (int pageIndex = currentPageInOverview; pageIndex < getPageCount(); pageIndex++) { + CellLayout page = (CellLayout) getPageAt(pageIndex); + if (page.hasReorderSolution(info)) { + setCurrentPage(pageIndex); + break; + } + } + int[] size = estimateItemSize(info, false); // The outline is used to visualize where the item will land if dropped @@ -1992,6 +1776,10 @@ public class Workspace extends PagedView return mState == State.OVERVIEW; } + public void snapToPageFromOverView(int whichPage) { + mStateTransitionAnimation.snapToPageFromOverView(whichPage); + } + int getOverviewModeTranslationY() { DeviceProfile grid = mLauncher.getDeviceProfile(); Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources())); @@ -2007,19 +1795,35 @@ public class Workspace extends PagedView return -workspaceOffsetTopEdge + overviewOffsetTopEdge; } + int getSpringLoadedTranslationY() { + DeviceProfile grid = mLauncher.getDeviceProfile(); + Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources())); + int scaledHeight = (int) (mSpringLoadedShrinkFactor * getNormalChildHeight()); + int workspaceTop = mInsets.top + workspacePadding.top; + int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom; + int workspaceHeight = workspaceBottom - workspaceTop; + // Center the spring-loaded pages by translating it up by half of the reduced height. + return -(workspaceHeight - scaledHeight) / 2; + } + /** * Sets the current workspace {@link State}, returning an animation transitioning the workspace * to that new state. */ - public Animator setStateWithAnimation(State toState, int toPage, boolean animated, + public Animator setStateWithAnimation(State toState, boolean animated, HashMap<View, Integer> layerViews) { // Create the animation to the new state Animator workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState, - toState, toPage, animated, layerViews); + toState, animated, layerViews); // Update the current state mState = toState; updateAccessibilityFlags(); + if (mState == State.OVERVIEW || mState == State.SPRING_LOADED) { + // Redraw pages, as we might want to draw pages which were not visible. + mForceDrawAdjacentPages = true; + invalidate(); // This will call dispatchDraw(), which calls getVisiblePages(). + } return workspaceAnim; } @@ -2093,6 +1897,7 @@ public class Workspace extends PagedView mIsSwitchingState = false; updateChildrenLayersEnabled(false); showCustomContentIfNecessary(); + mForceDrawAdjacentPages = false; } void updateCustomContentVisibility() { @@ -2139,19 +1944,17 @@ public class Workspace extends PagedView * @param padding the horizontal and vertical padding to use when drawing */ private static void drawDragView(View v, Canvas destCanvas, int padding) { - final Rect clipRect = sTempRect; - v.getDrawingRect(clipRect); - - boolean textVisible = false; - destCanvas.save(); if (v instanceof TextView) { Drawable d = getTextViewIcon((TextView) v); Rect bounds = getDrawableBounds(d); - clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding); destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top); d.draw(destCanvas); } else { + final Rect clipRect = sTempRect; + v.getDrawingRect(clipRect); + + boolean textVisible = false; if (v instanceof FolderIcon) { // For FolderIcons the text can bleed into the icon area, and so we need to // hide the text completely (which can't be achieved by clipping). @@ -2329,7 +2132,8 @@ public class Workspace extends PagedView icon.clearPressedBackground(); } - if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) { + Object dragObject = child.getTag(); + if (!(dragObject instanceof ItemInfo)) { String msg = "Drag started with a view that has no tag set. This " + "will cause a crash (issue 11627249) down the line. " + "View: " + child + " tag: " + child.getTag(); @@ -2340,11 +2144,16 @@ public class Workspace extends PagedView mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent(); } - DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), - DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible); + DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, + (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, + dragRect, scale, accessible); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); b.recycle(); + + if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { + mLauncher.enterSpringLoadedDragMode(); + } } public void beginExternalDragShared(View child, DragSource source) { @@ -2377,7 +2186,8 @@ public class Workspace extends PagedView Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2); Rect dragRect = new Rect(0, 0, iconSize, iconSize); - if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) { + Object dragObject = child.getTag(); + if (!(dragObject instanceof ItemInfo)) { String msg = "Drag started with a view that has no tag set. This " + "will cause a crash (issue 11627249) down the line. " + "View: " + child + " tag: " + child.getTag(); @@ -2385,12 +2195,17 @@ public class Workspace extends PagedView } // Start the drag - DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), - DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, false); + DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, + (ItemInfo) dragObject, DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, + dragRect, scale, false); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); // Recycle temporary bitmaps tmpB.recycle(); + + if (!FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { + mLauncher.enterSpringLoadedDragMode(); + } } public boolean transitionStateShouldAllowDrop() { @@ -2417,7 +2232,7 @@ public class Workspace extends PagedView if (mLauncher.isHotseatLayout(dropTargetLayout)) { mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); } else { - mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); + mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); } int spanX = 1; @@ -2427,9 +2242,8 @@ public class Workspace extends PagedView spanX = dragCellInfo.spanX; spanY = dragCellInfo.spanY; } else { - final ItemInfo dragInfo = (ItemInfo) d.dragInfo; - spanX = dragInfo.spanX; - spanY = dragInfo.spanY; + spanX = d.dragInfo.spanX; + spanY = d.dragInfo.spanY; } int minSpanX = spanX; @@ -2444,12 +2258,12 @@ public class Workspace extends PagedView mTargetCell); float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); - if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo, + if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo, dropTargetLayout, mTargetCell, distance, true)) { return true; } - if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo, + if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(d.dragInfo, dropTargetLayout, mTargetCell, distance)) { return true; } @@ -2518,14 +2332,14 @@ public class Workspace extends PagedView return (aboveShortcut && willBecomeShortcut); } - boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell, + boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell, float distance) { if (distance > mMaxDistanceForFolderCreation) return false; View dropOverView = target.getChildAt(targetCell[0], targetCell[1]); return willAddToExistingUserFolder(dragInfo, dropOverView); } - boolean willAddToExistingUserFolder(Object dragInfo, View dropOverView) { + boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) { if (dropOverView != null) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams(); if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) { @@ -2630,11 +2444,11 @@ public class Workspace extends PagedView if (mLauncher.isHotseatLayout(dropTargetLayout)) { mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); } else { - mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null); + mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter); } } - int snapScreen = WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE; + int snapScreen = -1; boolean resizeOnDrop = false; if (d.dragSource != this) { final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0], @@ -2643,7 +2457,6 @@ public class Workspace extends PagedView } else if (mDragInfo != null) { final View cell = mDragInfo.cell; - Runnable resizeRunnable = null; if (dropTargetLayout != null && !d.cancelled) { // Move internally boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout); @@ -2677,7 +2490,7 @@ public class Workspace extends PagedView // Aside from the special case where we're dropping a shortcut onto a shortcut, // we need to find the nearest cell location that is vacant - ItemInfo item = (ItemInfo) d.dragInfo; + ItemInfo item = d.dragInfo; int minSpanX = item.spanX; int minSpanY = item.spanY; if (item.minSpanX > 0 && item.minSpanY > 0) { @@ -2715,7 +2528,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(cell); if (parentCell != null) { parentCell.removeView(cell); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new NullPointerException("mDragInfo.cell has null parent"); } addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], @@ -2740,21 +2553,14 @@ public class Workspace extends PagedView AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE && !d.accessibleDrag) { - final Runnable addResizeFrame = new Runnable() { - public void run() { - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.addResizeFrame(info, hostView, cellLayout); - } - }; - resizeRunnable = (new Runnable() { + mDelayedResizeRunnable = new Runnable() { public void run() { - if (!isPageMoving()) { - addResizeFrame.run(); - } else { - mDelayedResizeRunnable = addResizeFrame; + if (!isPageMoving() && !mIsSwitchingState) { + DragLayer dragLayer = mLauncher.getDragLayer(); + dragLayer.addResizeFrame(info, hostView, cellLayout); } } - }); + }; } } @@ -2771,7 +2577,6 @@ public class Workspace extends PagedView } final CellLayout parent = (CellLayout) cell.getParent().getParent(); - final Runnable finalResizeRunnable = resizeRunnable; // Prepare it to be animated into its new position // This must be called after the view has been re-parented final Runnable onCompleteRunnable = new Runnable() { @@ -2779,9 +2584,6 @@ public class Workspace extends PagedView public void run() { mAnimatingViewIntoPlace = false; updateChildrenLayersEnabled(false); - if (finalResizeRunnable != null) { - finalResizeRunnable.run(); - } } }; mAnimatingViewIntoPlace = true; @@ -2795,9 +2597,7 @@ public class Workspace extends PagedView animateWidgetDrop(info, parent, d.dragView, onCompleteRunnable, animationType, cell, false); } else { - int duration = snapScreen < 0 ? - WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE : - ADJACENT_SCREEN_DROP_DURATION; + int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION; mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, onCompleteRunnable, this); } @@ -2831,19 +2631,6 @@ public class Workspace extends PagedView (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight())); } - public void getViewLocationRelativeToSelf(View v, int[] location) { - getLocationInWindow(location); - int x = location[0]; - int y = location[1]; - - v.getLocationInWindow(location); - int vX = location[0]; - int vY = location[1]; - - location[0] = vX - x; - location[1] = vY - y; - } - @Override public void onDragEnter(DragObject d) { if (ENFORCE_DRAG_EVENT_ORDER) { @@ -2858,50 +2645,11 @@ public class Workspace extends PagedView setCurrentDropLayout(layout); setCurrentDragOverlappingLayout(layout); - if (!workspaceInModalState()) { + if (!workspaceInModalState() && FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) { mLauncher.getDragLayer().showPageHints(); } } - /** Return a rect that has the cellWidth/cellHeight (left, top), and - * widthGap/heightGap (right, bottom) */ - static Rect getCellLayoutMetrics(Launcher launcher, int orientation) { - LauncherAppState app = LauncherAppState.getInstance(); - InvariantDeviceProfile inv = app.getInvariantDeviceProfile(); - - Display display = launcher.getWindowManager().getDefaultDisplay(); - Point smallestSize = new Point(); - Point largestSize = new Point(); - display.getCurrentSizeRange(smallestSize, largestSize); - int countX = (int) inv.numColumns; - int countY = (int) inv.numRows; - boolean isLayoutRtl = Utilities.isRtl(launcher.getResources()); - if (orientation == CellLayout.LANDSCAPE) { - if (mLandscapeCellLayoutMetrics == null) { - Rect padding = inv.landscapeProfile.getWorkspacePadding(isLayoutRtl); - int width = largestSize.x - padding.left - padding.right; - int height = smallestSize.y - padding.top - padding.bottom; - mLandscapeCellLayoutMetrics = new Rect(); - mLandscapeCellLayoutMetrics.set( - DeviceProfile.calculateCellWidth(width, countX), - DeviceProfile.calculateCellHeight(height, countY), 0, 0); - } - return mLandscapeCellLayoutMetrics; - } else if (orientation == CellLayout.PORTRAIT) { - if (mPortraitCellLayoutMetrics == null) { - Rect padding = inv.portraitProfile.getWorkspacePadding(isLayoutRtl); - int width = smallestSize.x - padding.left - padding.right; - int height = largestSize.y - padding.top - padding.bottom; - mPortraitCellLayoutMetrics = new Rect(); - mPortraitCellLayoutMetrics.set( - DeviceProfile.calculateCellWidth(width, countX), - DeviceProfile.calculateCellHeight(height, countY), 0, 0); - } - return mPortraitCellLayoutMetrics; - } - return null; - } - @Override public void onDragExit(DragObject d) { if (ENFORCE_DRAG_EVENT_ORDER) { @@ -3040,41 +2788,27 @@ public class Workspace extends PagedView * * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's * coordinate space. The argument xy is modified with the return result. - * - * if cachedInverseMatrix is not null, this method will just use that matrix instead of - * computing it itself; we use this to avoid redundant matrix inversions in - * findMatchingPageForDragOver - * */ - void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) { + void mapPointFromSelfToChild(View v, float[] xy) { xy[0] = xy[0] - v.getLeft(); xy[1] = xy[1] - v.getTop(); } - boolean isPointInSelfOverHotseat(int x, int y, Rect r) { - if (r == null) { - r = new Rect(); - } - mTempPt[0] = x; - mTempPt[1] = y; - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); - - DeviceProfile grid = mLauncher.getDeviceProfile(); - r = grid.getHotseatRect(); - if (r.contains(mTempPt[0], mTempPt[1])) { - return true; - } - return false; + boolean isPointInSelfOverHotseat(int x, int y) { + mTempXY[0] = x; + mTempXY[1] = y; + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); + return mLauncher.getDeviceProfile().isInHotseatRect(mTempXY[0], mTempXY[1]); } void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) { - mTempPt[0] = (int) xy[0]; - mTempPt[1] = (int) xy[1]; - mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true); - mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt); + mTempXY[0] = (int) xy[0]; + mTempXY[1] = (int) xy[1]; + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true); + mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempXY); - xy[0] = mTempPt[0]; - xy[1] = mTempPt[1]; + xy[0] = mTempXY[0]; + xy[1] = mTempXY[1]; } /* @@ -3120,10 +2854,7 @@ public class Workspace extends PagedView CellLayout cl = (CellLayout) getChildAt(i); final float[] touchXy = {originX, originY}; - // Transform the touch coordinates to the CellLayout's local coordinates - // If the touch point is within the bounds of the cell layout, we can return immediately - cl.getMatrix().invert(mTempInverseMatrix); - mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix); + mapPointFromSelfToChild(cl, touchXy); if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() && touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) { @@ -3165,11 +2896,10 @@ public class Workspace extends PagedView // Skip drag over events while we are dragging over side pages if (mInScrollArea || !transitionStateShouldAllowDrop()) return; - Rect r = new Rect(); CellLayout layout = null; - ItemInfo item = (ItemInfo) d.dragInfo; + ItemInfo item = d.dragInfo; if (item == null) { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new NullPointerException("DragObject has null info"); } return; @@ -3183,7 +2913,7 @@ public class Workspace extends PagedView // Identify whether we have dragged over a side page if (workspaceInModalState()) { if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) { - if (isPointInSelfOverHotseat(d.x, d.y, r)) { + if (isPointInSelfOverHotseat(d.x, d.y)) { layout = mLauncher.getHotseat().getLayout(); } } @@ -3206,7 +2936,7 @@ public class Workspace extends PagedView } else { // Test to see if we are over the hotseat otherwise just use the current page if (mLauncher.getHotseat() != null && !isDragWidget(d)) { - if (isPointInSelfOverHotseat(d.x, d.y, r)) { + if (isPointInSelfOverHotseat(d.x, d.y)) { layout = mLauncher.getHotseat().getLayout(); } } @@ -3225,7 +2955,7 @@ public class Workspace extends PagedView if (mLauncher.isHotseatLayout(mDragTargetLayout)) { mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter); } else { - mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null); + mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter); } int minSpanX = item.spanX; @@ -3286,7 +3016,7 @@ public class Workspace extends PagedView if (distance > mMaxDistanceForFolderCreation) return; final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]); - ItemInfo info = (ItemInfo) dragObject.dragInfo; + ItemInfo info = dragObject.dragInfo; boolean userFolderPending = willCreateUserFolder(info, dragOverView, false); if (mDragMode == DRAG_MODE_NONE && userFolderPending && !mFolderCreationAlarm.alarmPending()) { @@ -3407,24 +3137,6 @@ public class Workspace extends PagedView } /** - * Add the item specified by dragInfo to the given layout. - * @return true if successful - */ - public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) { - if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) { - onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false); - return true; - } - mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout)); - return false; - } - - private void onDropExternal(int[] touchXY, Object dragInfo, - CellLayout cellLayout, boolean insertAtFirst) { - onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null); - } - - /** * Drop an item that didn't originate on one of the workspace screens. * It may have come from Launcher (e.g. from all apps or customize), or it may have * come from another app altogether. @@ -3432,7 +3144,7 @@ public class Workspace extends PagedView * NOTE: This can also be called when we are outside of a drag event, when we want * to add an item to one of the workspace screens. */ - private void onDropExternal(final int[] touchXY, final Object dragInfo, + private void onDropExternal(final int[] touchXY, final ItemInfo dragInfo, final CellLayout cellLayout, boolean insertAtFirst, DragObject d) { final Runnable exitSpringLoadedRunnable = new Runnable() { @Override @@ -3442,7 +3154,7 @@ public class Workspace extends PagedView } }; - ItemInfo info = (ItemInfo) dragInfo; + ItemInfo info = dragInfo; int spanX = info.spanX; int spanY = info.spanY; if (mDragInfo != null) { @@ -3469,14 +3181,14 @@ public class Workspace extends PagedView cellLayout, mTargetCell); float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell); - if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell, - distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo, - cellLayout, mTargetCell, distance)) { + if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true) + || willAddToExistingUserFolder( + d.dragInfo, cellLayout, mTargetCell, distance)) { findNearestVacantCell = false; } } - final ItemInfo item = (ItemInfo) d.dragInfo; + final ItemInfo item = d.dragInfo; boolean updateWidgetSize = false; if (findNearestVacantCell) { int minSpanX = item.spanX; @@ -3789,7 +3501,7 @@ public class Workspace extends PagedView mDragInfo.container, mDragInfo.screenId); if (cellLayout != null) { cellLayout.onDropChild(mDragInfo.cell); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { throw new RuntimeException("Invalid state: cellLayout == null in " + "Workspace#onDropCompleted. Please file a bug. "); }; @@ -3800,6 +3512,13 @@ public class Workspace extends PagedView } mDragOutline = null; mDragInfo = null; + + if (!isFlingToDelete) { + // Fling to delete already exits spring loaded mode after the animation finishes. + mLauncher.exitSpringLoadedDragModeDelayed(success, + Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, mDelayedResizeRunnable); + mDelayedResizeRunnable = null; + } } /** @@ -3809,7 +3528,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(v); if (parentCell != null) { parentCell.removeView(v); - } else if (LauncherAppState.isDogfoodBuild()) { + } else if (ProviderConfig.IS_DOGFOOD_BUILD) { // When an app is uninstalled using the drop target, we wait until resume to remove // the icon. We also remove all the corresponding items from the workspace at // {@link Launcher#bindComponentsRemoved}. That call can come before or after @@ -3836,70 +3555,6 @@ public class Workspace extends PagedView } } - void updateItemLocationsInDatabase(CellLayout cl) { - int count = cl.getShortcutsAndWidgets().getChildCount(); - - long screenId = getIdForScreen(cl); - int container = Favorites.CONTAINER_DESKTOP; - - if (mLauncher.isHotseatLayout(cl)) { - screenId = -1; - container = Favorites.CONTAINER_HOTSEAT; - } - - for (int i = 0; i < count; i++) { - View v = cl.getShortcutsAndWidgets().getChildAt(i); - ItemInfo info = (ItemInfo) v.getTag(); - // Null check required as the AllApps button doesn't have an item info - if (info != null && info.requiresDbUpdate) { - info.requiresDbUpdate = false; - LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX, - info.cellY, info.spanX, info.spanY); - } - } - } - - void saveWorkspaceToDb() { - saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout()); - int count = getChildCount(); - for (int i = 0; i < count; i++) { - CellLayout cl = (CellLayout) getChildAt(i); - saveWorkspaceScreenToDb(cl); - } - } - - void saveWorkspaceScreenToDb(CellLayout cl) { - int count = cl.getShortcutsAndWidgets().getChildCount(); - - long screenId = getIdForScreen(cl); - int container = Favorites.CONTAINER_DESKTOP; - - Hotseat hotseat = mLauncher.getHotseat(); - if (mLauncher.isHotseatLayout(cl)) { - screenId = -1; - container = Favorites.CONTAINER_HOTSEAT; - } - - for (int i = 0; i < count; i++) { - View v = cl.getShortcutsAndWidgets().getChildAt(i); - ItemInfo info = (ItemInfo) v.getTag(); - // Null check required as the AllApps button doesn't have an item info - if (info != null) { - int cellX = info.cellX; - int cellY = info.cellY; - if (container == Favorites.CONTAINER_HOTSEAT) { - cellX = hotseat.getCellXFromOrder((int) info.screenId); - cellY = hotseat.getCellYFromOrder((int) info.screenId); - } - LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX, cellY); - } - if (v instanceof FolderIcon) { - FolderIcon fi = (FolderIcon) v; - fi.getFolder().addItemLocationsInDatabase(); - } - } - } - @Override public float getIntrinsicIconScaleFactor() { return 1f; @@ -3912,7 +3567,7 @@ public class Workspace extends PagedView @Override public boolean supportsAppInfoDropTarget() { - return false; + return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND; } @Override @@ -4078,8 +3733,7 @@ public class Workspace extends PagedView * the hotseat and workspace pages */ ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() { - ArrayList<ShortcutAndWidgetContainer> childrenLayouts = - new ArrayList<ShortcutAndWidgetContainer>(); + ArrayList<ShortcutAndWidgetContainer> childrenLayouts = new ArrayList<>(); int screenCount = getChildCount(); for (int screen = 0; screen < screenCount; screen++) { childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets()); @@ -4090,17 +3744,6 @@ public class Workspace extends PagedView return childrenLayouts; } - public Folder getFolderForTag(final Object tag) { - return (Folder) getFirstMatch(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View v, View parent) { - return (v instanceof Folder) && (((Folder) v).getInfo() == tag) - && ((Folder) v).getInfo().opened; - } - }); - } - public View getHomescreenIconByItemId(final long id) { return getFirstMatch(new ItemOperator() { @@ -4301,7 +3944,7 @@ public class Workspace extends PagedView stripEmptyScreens(); } - interface ItemOperator { + public interface ItemOperator { /** * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}. * @@ -4437,7 +4080,7 @@ public class Workspace extends PagedView } void moveToDefaultScreen(boolean animate) { - moveToScreen(mDefaultPage, animate); + moveToScreen(getDefaultPage(), animate); } void moveToCustomContentScreen(boolean animate) { @@ -4493,13 +4136,8 @@ public class Workspace extends PagedView } nScreens--; } - return String.format(getContext().getString(R.string.workspace_scroll_format), + return getContext().getString(R.string.workspace_scroll_format, page + 1 - delta, nScreens); - - } - - public void getLocationInDragLayer(int[] loc) { - mLauncher.getDragLayer().getLocationInDragLayer(this, loc); } @Override diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 54f63bbd8..c0eb7eda3 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -30,6 +30,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Thunk; import java.util.HashMap; @@ -174,7 +175,6 @@ public class WorkspaceStateTransitionAnimation { public static final String TAG = "WorkspaceStateTransitionAnimation"; - public static final int SCROLL_TO_CURRENT_PAGE = -1; @Thunk static final int BACKGROUND_FADE_OUT_DURATION = 350; final @Thunk Launcher mLauncher; @@ -198,6 +198,7 @@ public class WorkspaceStateTransitionAnimation { @Thunk int mAllAppsTransitionTime; @Thunk int mOverviewTransitionTime; @Thunk int mOverlayTransitionTime; + @Thunk int mSpringLoadedTransitionTime; @Thunk boolean mWorkspaceFadeInAdjacentScreens; public WorkspaceStateTransitionAnimation(Launcher launcher, Workspace workspace) { @@ -209,6 +210,7 @@ public class WorkspaceStateTransitionAnimation { mAllAppsTransitionTime = res.getInteger(R.integer.config_allAppsTransitionTime); mOverviewTransitionTime = res.getInteger(R.integer.config_overviewTransitionTime); mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime); + mSpringLoadedTransitionTime = mOverlayTransitionTime / 2; mSpringLoadedShrinkFactor = res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f; mOverviewModeShrinkFactor = @@ -217,14 +219,18 @@ public class WorkspaceStateTransitionAnimation { mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens(); } + public void snapToPageFromOverView(int whichPage) { + mWorkspace.snapToPage(whichPage, mOverviewTransitionTime, mZoomInInterpolator); + } + public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState, - int toPage, boolean animated, HashMap<View, Integer> layerViews) { + boolean animated, HashMap<View, Integer> layerViews) { AccessibilityManager am = (AccessibilityManager) mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); final boolean accessibilityEnabled = am.isEnabled(); TransitionStates states = new TransitionStates(fromState, toState); int workspaceDuration = getAnimationDuration(states); - animateWorkspace(states, toPage, animated, workspaceDuration, layerViews, + animateWorkspace(states, animated, workspaceDuration, layerViews, accessibilityEnabled); animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION); return mStateAnimator; @@ -255,6 +261,9 @@ public class WorkspaceStateTransitionAnimation { return mAllAppsTransitionTime; } else if (states.workspaceToOverview || states.overviewToWorkspace) { return mOverviewTransitionTime; + } else if (mLauncher.mState == Launcher.State.WORKSPACE_SPRING_LOADED + || states.oldStateIsNormal && states.stateIsSpringLoaded) { + return mSpringLoadedTransitionTime; } else { return mOverlayTransitionTime; } @@ -263,7 +272,7 @@ public class WorkspaceStateTransitionAnimation { /** * Starts a transition animation for the workspace. */ - private void animateWorkspace(final TransitionStates states, int toPage, final boolean animated, + private void animateWorkspace(final TransitionStates states, final boolean animated, final int duration, final HashMap<View, Integer> layerViews, final boolean accessibilityEnabled) { // Reinitialize animation arrays for the current workspace state @@ -278,11 +287,16 @@ public class WorkspaceStateTransitionAnimation { // Update the workspace state float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ? 1.0f : 0f; - float finalHotseatAndPageIndicatorAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ? - 1f : 0f; + float finalHotseatAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ? 1f : 0f; + float finalPageIndicatorAlpha = states.stateIsNormal ? 1f : 0f; float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f; - float finalWorkspaceTranslationY = states.stateIsOverview || states.stateIsOverviewHidden ? - mWorkspace.getOverviewModeTranslationY() : 0; + + float finalWorkspaceTranslationY = 0; + if (states.stateIsOverview || states.stateIsOverviewHidden) { + finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY(); + } else if (states.stateIsSpringLoaded) { + finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY(); + } final int childCount = mWorkspace.getChildCount(); final int customPageCount = mWorkspace.numCustomPages(); @@ -303,11 +317,7 @@ public class WorkspaceStateTransitionAnimation { } } - if (toPage == SCROLL_TO_CURRENT_PAGE) { - toPage = mWorkspace.getPageNearestToCenterOfScreen(); - } - mWorkspace.snapToPage(toPage, duration, mZoomInInterpolator); - + int toPage = mWorkspace.getPageNearestToCenterOfScreen(); for (int i = 0; i < childCount; i++) { final CellLayout cl = (CellLayout) mWorkspace.getChildAt(i); boolean isCurrentPage = (i == toPage); @@ -379,7 +389,6 @@ public class WorkspaceStateTransitionAnimation { mNewBackgroundAlphas[i] != 0) { ValueAnimator bgAnim = ObjectAnimator.ofFloat(cl, "backgroundAlpha", mOldBackgroundAlphas[i], mNewBackgroundAlphas[i]); - LauncherAnimUtils.ofFloat(cl, 0f, 1f); bgAnim.setInterpolator(mZoomInInterpolator); bgAnim.setDuration(duration); mStateAnimator.play(bgAnim); @@ -389,7 +398,7 @@ public class WorkspaceStateTransitionAnimation { Animator pageIndicatorAlpha; if (pageIndicator != null) { pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator) - .alpha(finalHotseatAndPageIndicatorAlpha).withLayer(); + .alpha(finalPageIndicatorAlpha).withLayer(); pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator, accessibilityEnabled)); } else { @@ -398,7 +407,7 @@ public class WorkspaceStateTransitionAnimation { } LauncherViewPropertyAnimator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat) - .alpha(finalHotseatAndPageIndicatorAlpha); + .alpha(finalHotseatAlpha); hotseatAlpha.addListener(new AlphaUpdateListener(hotseat, accessibilityEnabled)); LauncherViewPropertyAnimator overviewPanelAlpha = @@ -452,10 +461,10 @@ public class WorkspaceStateTransitionAnimation { } else { overviewPanel.setAlpha(finalOverviewPanelAlpha); AlphaUpdateListener.updateVisibility(overviewPanel, accessibilityEnabled); - hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha); + hotseat.setAlpha(finalHotseatAlpha); AlphaUpdateListener.updateVisibility(hotseat, accessibilityEnabled); if (pageIndicator != null) { - pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha); + pageIndicator.setAlpha(finalPageIndicatorAlpha); AlphaUpdateListener.updateVisibility(pageIndicator, accessibilityEnabled); } mWorkspace.updateCustomContentVisibility(); @@ -488,8 +497,7 @@ public class WorkspaceStateTransitionAnimation { if (animated) { // These properties refer to the background protection gradient used for AllApps // and Widget tray. - ValueAnimator bgFadeOutAnimation = - LauncherAnimUtils.ofFloat(mWorkspace, startAlpha, finalAlpha); + ValueAnimator bgFadeOutAnimation = ValueAnimator.ofFloat(startAlpha, finalAlpha); bgFadeOutAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java index ff9989036..d271f1d4e 100644 --- a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java +++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java @@ -17,7 +17,7 @@ package com.android.launcher3.accessibility; import com.android.launcher3.CellLayout; -import com.android.launcher3.FolderPagedView; +import com.android.launcher3.folder.FolderPagedView; import com.android.launcher3.R; /** diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 2306b776e..8560b2167 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -20,9 +20,8 @@ import com.android.launcher3.AppInfo; import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; -import com.android.launcher3.DragController.DragListener; import com.android.launcher3.DragSource; -import com.android.launcher3.Folder; +import com.android.launcher3.folder.Folder; import com.android.launcher3.FolderInfo; import com.android.launcher3.InfoDropTarget; import com.android.launcher3.ItemInfo; @@ -36,6 +35,7 @@ import com.android.launcher3.R; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.UninstallDropTarget; import com.android.launcher3.Workspace; +import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -75,11 +75,11 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme mLauncher = launcher; mActions.put(REMOVE, new AccessibilityAction(REMOVE, - launcher.getText(R.string.delete_target_label))); + launcher.getText(R.string.remove_drop_target_label))); mActions.put(INFO, new AccessibilityAction(INFO, - launcher.getText(R.string.info_target_label))); + launcher.getText(R.string.app_info_drop_target_label))); mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL, - launcher.getText(R.string.delete_target_uninstall_label))); + launcher.getText(R.string.uninstall_drop_target_label))); mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE, launcher.getText(R.string.action_add_to_workspace))); mActions.put(MOVE, new AccessibilityAction(MOVE, @@ -96,7 +96,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme if (!(host.getTag() instanceof ItemInfo)) return; ItemInfo item = (ItemInfo) host.getTag(); - if (DeleteDropTarget.supportsDrop(item)) { + if (DeleteDropTarget.supportsAccessibleDrop(item)) { info.addAction(mActions.get(REMOVE)); } if (UninstallDropTarget.supportsDrop(host.getContext(), item)) { @@ -372,7 +372,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme @Override - public void onDragStart(DragSource source, Object info, int dragAction) { + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { // No-op } diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 57da6c57c..013bd8ccc 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -40,13 +40,14 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.ExtendedEditText; -import com.android.launcher3.Folder; +import com.android.launcher3.folder.Folder; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherTransitionable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; +import com.android.launcher3.userevent.Logger; import com.android.launcher3.util.ComponentKey; import java.nio.charset.Charset; @@ -312,6 +313,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc icon.getMeasuredHeight()); updateBackgroundAndPaddings(); + mLauncher.getLogger().resetElapsedContainerMillis(); } @Override @@ -501,7 +503,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc int currentScreen = mLauncher.getCurrentWorkspaceScreen(); Workspace workspace = (Workspace) target; CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); - ItemInfo itemInfo = (ItemInfo) d.dragInfo; + ItemInfo itemInfo = d.dragInfo; if (layout != null) { showOutOfSpaceMessage = !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index cfaf19515..d9313f8ec 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -324,7 +324,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. } } - private Launcher mLauncher; + @Thunk Launcher mLauncher; private LayoutInflater mLayoutInflater; @Thunk AlphabeticalAppsList mApps; private GridLayoutManager mGridLayoutMgr; @@ -348,9 +348,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. // each time the search query changes. private String mMarketSearchMessage; // The intent to send off to the market app, updated each time the search query changes. - private Intent mMarketSearchIntent; + @Thunk Intent mMarketSearchIntent; // The last query that the user entered into the search field - private String mLastSearchQuery; + @Thunk String mLastSearchQuery; // Section drawing @Thunk int mSectionNamesMargin; @@ -420,11 +420,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. */ public void setLastSearchQuery(String query) { Resources res = mLauncher.getResources(); - String formatStr = res.getString(R.string.all_apps_no_search_results); mLastSearchQuery = query; - mEmptySearchMessage = String.format(formatStr, query); + mEmptySearchMessage = res.getString(R.string.all_apps_no_search_results, query); if (mMarketAppName != null) { - mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message), + mMarketSearchMessage = res.getString(R.string.all_apps_search_market_message, mMarketAppName); mMarketSearchIntent = createMarketSearchIntent(query); } diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java index 14e2a1863..09a7d59bf 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java @@ -17,17 +17,15 @@ package com.android.launcher3.allapps; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Canvas; import android.util.AttributeSet; -import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; + import com.android.launcher3.BubbleTextView; import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler; import com.android.launcher3.ClickShadowView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; -import com.android.launcher3.R; /** * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 26e923152..9d2fe54db 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -16,13 +16,13 @@ package com.android.launcher3.allapps; import android.content.Context; -import android.support.v7.widget.RecyclerView; import android.util.Log; + import com.android.launcher3.AppInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.model.AppNameComparator; import com.android.launcher3.util.ComponentKey; @@ -186,7 +186,7 @@ public class AlphabeticalAppsList { // The of ordered component names as a result of a search query private ArrayList<ComponentKey> mSearchResults; private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>(); - private RecyclerView.Adapter mAdapter; + private AllAppsGridAdapter mAdapter; private AlphabeticIndexCompat mIndexer; private AppNameComparator mAppNameComparator; private MergeAlgorithm mMergeAlgorithm; @@ -215,7 +215,7 @@ public class AlphabeticalAppsList { /** * Sets the adapter to notify when this dataset changes. */ - public void setAdapter(RecyclerView.Adapter adapter) { + public void setAdapter(AllAppsGridAdapter adapter) { mAdapter = adapter; } @@ -422,7 +422,7 @@ public class AlphabeticalAppsList { if (info != null) { mPredictedApps.add(info); } else { - if (LauncherAppState.isDogfoodBuild()) { + if (ProviderConfig.IS_DOGFOOD_BUILD) { Log.e(TAG, "Predicted app not found: " + ck.flattenToString(mLauncher)); } } diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java index ec1fb669f..463278ab4 100644 --- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java +++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java @@ -1,6 +1,7 @@ package com.android.launcher3.compat; import android.content.Context; + import com.android.launcher3.Utilities; import java.lang.reflect.Constructor; @@ -62,6 +63,7 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex { private boolean mHasValidAlphabeticIndex; private String mDefaultMiscLabel; + @SuppressWarnings({"unchecked", "rawtypes"}) public AlphabeticIndexCompat(Context context) { super(); try { diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java index 0bc9588aa..aaf756eda 100644 --- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java +++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java @@ -24,6 +24,8 @@ import android.graphics.drawable.Drawable; public abstract class LauncherActivityInfoCompat { + public static final int FLAG_SUSPENDED = 1<<30; + LauncherActivityInfoCompat() { } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index 95e3ba902..db5b89e81 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -35,6 +35,8 @@ public abstract class LauncherAppsCompat { "android.intent.action.MANAGED_PROFILE_ADDED"; public static final String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED"; + public static final String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED = + "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED"; public interface OnAppsChangedCallbackCompat { void onPackageRemoved(String packageName, UserHandleCompat user); @@ -42,6 +44,8 @@ public abstract class LauncherAppsCompat { void onPackageChanged(String packageName, UserHandleCompat user); void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing); void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing); + void onPackagesSuspended(String[] packageNames, UserHandleCompat user); + void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user); } protected LauncherAppsCompat() { @@ -53,7 +57,9 @@ public abstract class LauncherAppsCompat { public static LauncherAppsCompat getInstance(Context context) { synchronized (sInstanceLock) { if (sInstance == null) { - if (Utilities.ATLEAST_LOLLIPOP) { + if (Utilities.isNycOrAbove()) { + sInstance = new LauncherAppsCompatVN(context.getApplicationContext()); + } else if (Utilities.ATLEAST_LOLLIPOP) { sInstance = new LauncherAppsCompatVL(context.getApplicationContext()); } else { sInstance = new LauncherAppsCompatV16(context.getApplicationContext()); @@ -75,6 +81,7 @@ public abstract class LauncherAppsCompat { public abstract boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user); public abstract boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user); + public abstract boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user); public boolean isAppEnabled(PackageManager pm, String packageName, int flags) { try { @@ -84,4 +91,4 @@ public abstract class LauncherAppsCompat { return false; } } -}
\ No newline at end of file +} diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java index 339c457e1..2d0778d30 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java @@ -126,6 +126,10 @@ public class LauncherAppsCompatV16 extends LauncherAppsCompat { } } + public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) { + return false; + } + private void unregisterForPackageIntents() { mContext.unregisterReceiver(mPackageMonitor); } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java index fbf91b548..7270d023b 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java @@ -36,7 +36,7 @@ import java.util.Map; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class LauncherAppsCompatVL extends LauncherAppsCompat { - private LauncherApps mLauncherApps; + protected LauncherApps mLauncherApps; private Map<OnAppsChangedCallbackCompat, WrappedCallback> mCallbacks = new HashMap<OnAppsChangedCallbackCompat, WrappedCallback>(); @@ -106,6 +106,10 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { return mLauncherApps.isActivityEnabled(component, user.getUser()); } + public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) { + return false; + } + private static class WrappedCallback extends LauncherApps.Callback { private LauncherAppsCompat.OnAppsChangedCallbackCompat mCallback; @@ -134,6 +138,14 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { mCallback.onPackagesUnavailable(packageNames, UserHandleCompat.fromUser(user), replacing); } + + public void onPackagesSuspended(String[] packageNames, UserHandle user) { + mCallback.onPackagesSuspended(packageNames, UserHandleCompat.fromUser(user)); + } + + public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { + mCallback.onPackagesUnsuspended(packageNames, UserHandleCompat.fromUser(user)); + } } } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVN.java b/src/com/android/launcher3/compat/LauncherAppsCompatVN.java new file mode 100644 index 000000000..0d883b6fd --- /dev/null +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVN.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.compat; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherApps; +import android.os.UserHandle; +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +//TODO: Once gogole3 SDK is updated to N, add @TargetApi(Build.VERSION_CODES.N) +public class LauncherAppsCompatVN extends LauncherAppsCompatVL { + + private static final String TAG = "LauncherAppsCompatVN"; + + LauncherAppsCompatVN(Context context) { + super(context); + } + + @Override + public boolean isPackageSuspendedForProfile(String packageName, UserHandleCompat user) { + if (user != null && packageName != null) { + try { + //TODO: Replace with proper API call once google3 SDK is updated. + Method getApplicationInfoMethod = LauncherApps.class.getMethod("getApplicationInfo", + String.class, int.class, UserHandle.class); + + ApplicationInfo info = (ApplicationInfo) getApplicationInfoMethod.invoke( + mLauncherApps, packageName, 0, user.getUser()); + if (info != null) { + return (info.flags & LauncherActivityInfoCompat.FLAG_SUSPENDED) != 0; + } + } catch (NoSuchMethodError | NoSuchMethodException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + Log.e(TAG, "Running on N without getApplicationInfo", e); + } + } + return false; + } +} diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java index 6b7cba840..978f9229d 100644 --- a/src/com/android/launcher3/compat/UserManagerCompat.java +++ b/src/com/android/launcher3/compat/UserManagerCompat.java @@ -32,7 +32,9 @@ public abstract class UserManagerCompat { public static UserManagerCompat getInstance(Context context) { synchronized (sInstanceLock) { if (sInstance == null) { - if (Utilities.ATLEAST_LOLLIPOP) { + if (Utilities.isNycOrAbove()) { + sInstance = new UserManagerCompatVN(context.getApplicationContext()); + } else if (Utilities.ATLEAST_LOLLIPOP) { sInstance = new UserManagerCompatVL(context.getApplicationContext()); } else if (Utilities.ATLEAST_JB_MR1) { sInstance = new UserManagerCompatV17(context.getApplicationContext()); @@ -54,4 +56,5 @@ public abstract class UserManagerCompat { public abstract UserHandleCompat getUserForSerialNumber(long serialNumber); public abstract CharSequence getBadgedLabelForUser(CharSequence label, UserHandleCompat user); public abstract long getUserCreationTime(UserHandleCompat user); + public abstract boolean isQuietModeEnabled(UserHandleCompat user); } diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java index fcd755521..a006efd50 100644 --- a/src/com/android/launcher3/compat/UserManagerCompatV16.java +++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java @@ -50,4 +50,9 @@ public class UserManagerCompatV16 extends UserManagerCompat { @Override public void enableAndResetCache() { } + + @Override + public boolean isQuietModeEnabled(UserHandleCompat user) { + return false; + } } diff --git a/src/com/android/launcher3/compat/UserManagerCompatVN.java b/src/com/android/launcher3/compat/UserManagerCompatVN.java new file mode 100644 index 000000000..ae41e68a3 --- /dev/null +++ b/src/com/android/launcher3/compat/UserManagerCompatVN.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.compat; + +import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +//TODO: Once gogole3 SDK is updated to N, add @TargetApi(Build.VERSION_CODES.N) +public class UserManagerCompatVN extends UserManagerCompatVL { + + private static final String TAG = "UserManagerCompatVN"; + + UserManagerCompatVN(Context context) { + super(context); + } + + @Override + public boolean isQuietModeEnabled(UserHandleCompat user) { + if (user != null) { + try { + //TODO: Replace with proper API call once google3 SDK is updated. + Method isQuietModeEnabledMethod = UserManager.class.getMethod("isQuietModeEnabled", + UserHandle.class); + return (boolean) isQuietModeEnabledMethod.invoke(mUserManager, user.getUser()); + } catch (NoSuchMethodError | NoSuchMethodException | IllegalAccessException + | InvocationTargetException e) { + Log.e(TAG, "Running on N without isQuietModeEnabled", e); + } catch (IllegalArgumentException e) { + // TODO remove this when API is fixed to not throw this + // when called on user that isn't a managed profile. + } + } + return false; + } +} + diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 50127085a..1988c2d81 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -24,7 +24,6 @@ package com.android.launcher3.config; * Use LAUNCHER3_ prefix for prevent namespace conflicts. */ public final class FeatureFlags { - private FeatureFlags() {} public static boolean IS_DEV_BUILD = false; @@ -32,6 +31,9 @@ public final class FeatureFlags { public static boolean IS_RELEASE_BUILD = true; // Custom flags go below this - public static boolean LAUNCHER3_ICON_NORMALIZATION = false; - + // As opposed to the new spring-loaded workspace. + public static boolean LAUNCHER3_LEGACY_WORKSPACE_DND = false; + public static boolean LAUNCHER3_ICON_NORMALIZATION = true; + public static boolean LAUNCHER3_CLIPPED_FOLDER_ICON = false; + public static boolean LAUNCHER3_LEGACY_LOGGING = false; } diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java index e8930d063..825b43422 100644 --- a/src/com/android/launcher3/config/ProviderConfig.java +++ b/src/com/android/launcher3/config/ProviderConfig.java @@ -19,4 +19,6 @@ package com.android.launcher3.config; public class ProviderConfig { public static final String AUTHORITY = "com.android.launcher3.settings".intern(); + + public static boolean IS_DOGFOOD_BUILD = false; } diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index eefb287cb..80f9e4665 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; import android.content.ComponentName; import android.content.Context; @@ -26,15 +26,24 @@ import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.util.Log; +import android.view.DragEvent; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; -import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.PagedView; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.util.Thunk; @@ -44,7 +53,7 @@ import java.util.HashSet; /** * Class for initiating a drag within a view or across multiple views. */ -public class DragController { +public class DragController implements DragDriver.EventListener { private static final String TAG = "Launcher.DragController"; /** Indicates the drag is a move. */ @@ -61,9 +70,9 @@ public class DragController { private static final int SCROLL_OUTSIDE_ZONE = 0; private static final int SCROLL_WAITING_IN_ZONE = 1; - static final int SCROLL_NONE = -1; - static final int SCROLL_LEFT = 0; - static final int SCROLL_RIGHT = 1; + public static final int SCROLL_NONE = -1; + public static final int SCROLL_LEFT = 0; + public static final int SCROLL_RIGHT = 1; private static final float MAX_FLING_DEGREES = 35f; @@ -75,10 +84,13 @@ public class DragController { private final int[] mCoordinatesTemp = new int[2]; private final boolean mIsRtl; - /** Whether or not we're dragging. */ - private boolean mDragging; + /** + * Drag driver for the current drag/drop operation, or null if there is no active DND operation. + * It's null during accessible drag operations. + */ + private DragDriver mDragDriver = null; - /** Whether or not this is an accessible drag operation */ + /** Whether or not an accessible drag operation is in progress. */ private boolean mIsAccessibleDrag; /** X coordinate of the down event. */ @@ -90,7 +102,7 @@ public class DragController { /** the area at the edge of the screen that makes the workspace go left * or right while you're dragging. */ - private int mScrollZone; + private final int mScrollZone; private DropTarget.DragObject mDragObject; @@ -122,7 +134,7 @@ public class DragController { private int mTmpPoint[] = new int[2]; private Rect mDragLayerRect = new Rect(); - protected int mFlingToDeleteThresholdVelocity; + protected final int mFlingToDeleteThresholdVelocity; private VelocityTracker mVelocityTracker; /** @@ -137,16 +149,18 @@ public class DragController { * @param dragAction The drag action: either {@link DragController#DRAG_ACTION_MOVE} * or {@link DragController#DRAG_ACTION_COPY} */ - void onDragStart(DragSource source, Object info, int dragAction); + void onDragStart(DragSource source, ItemInfo info, int dragAction); /** * The drag has ended */ void onDragEnd(); } - + /** * Used to create a new DragLayer from XML. + * + * @param context The application's context. */ public DragController(Launcher launcher) { Resources r = launcher.getResources(); @@ -155,16 +169,11 @@ public class DragController { mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone); mVelocityTracker = VelocityTracker.obtain(); - float density = r.getDisplayMetrics().density; mFlingToDeleteThresholdVelocity = - (int) (r.getInteger(R.integer.config_flingToDeleteMinVelocity) * density); + r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity); mIsRtl = Utilities.isRtl(r); } - public boolean dragging() { - return mDragging; - } - /** * Starts a drag. * @@ -172,13 +181,11 @@ public class DragController { * @param bmp The bitmap that represents the view being dragged * @param source An object representing where the drag originated * @param dragInfo The data associated with the object that is being dragged + * @param viewImageBounds the position of the image inside the view * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or * {@link #DRAG_ACTION_COPY} - * @param viewImageBounds the position of the image inside the view - * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. - * Makes dragging feel more precise, e.g. you can clip out a transparent border */ - public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, + public void startDrag(View v, Bitmap bmp, DragSource source, ItemInfo dragInfo, Rect viewImageBounds, int dragAction, float initialDragViewScale) { int[] loc = mCoordinatesTemp; mLauncher.getDragLayer().getLocationInDragLayer(v, loc); @@ -211,7 +218,7 @@ public class DragController { * @param accessible whether this drag should occur in accessibility mode */ public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY, - DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, + DragSource source, ItemInfo dragInfo, int dragAction, Point dragOffset, Rect dragRegion, float initialDragViewScale, boolean accessible) { if (PROFILE_DRAWING_DURING_DRAG) { android.os.Debug.startMethodTracing("Launcher"); @@ -234,13 +241,14 @@ public class DragController { final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; - mDragging = true; mIsAccessibleDrag = accessible; + mLastDropTarget = null; mDragObject = new DropTarget.DragObject(); final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX, - registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale); + registrationY, 0, 0, b.getWidth(), b.getHeight(), + initialDragViewScale); mDragObject.dragComplete = false; if (mIsAccessibleDrag) { @@ -252,6 +260,8 @@ public class DragController { mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); + + mDragDriver = DragDriver.create(this, dragInfo, dragView); } mDragObject.dragSource = source; @@ -271,44 +281,6 @@ public class DragController { } /** - * Draw the view into a bitmap. - */ - Bitmap getViewBitmap(View v) { - v.clearFocus(); - v.setPressed(false); - - boolean willNotCache = v.willNotCacheDrawing(); - v.setWillNotCacheDrawing(false); - - // Reset the drawing cache background color to fully transparent - // for the duration of this operation - int color = v.getDrawingCacheBackgroundColor(); - v.setDrawingCacheBackgroundColor(0); - float alpha = v.getAlpha(); - v.setAlpha(1.0f); - - if (color != 0) { - v.destroyDrawingCache(); - } - v.buildDrawingCache(); - Bitmap cacheBitmap = v.getDrawingCache(); - if (cacheBitmap == null) { - Log.e(TAG, "failed getViewBitmap(" + v + ")", new RuntimeException()); - return null; - } - - Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); - - // Restore the view - v.destroyDrawingCache(); - v.setAlpha(alpha); - v.setWillNotCacheDrawing(willNotCache); - v.setDrawingCacheBackgroundColor(color); - - return bitmap; - } - - /** * Call this from a drag source view like this: * * <pre> @@ -319,18 +291,18 @@ public class DragController { * </pre> */ public boolean dispatchKeyEvent(KeyEvent event) { - return mDragging; + return mDragDriver != null; } public boolean isDragging() { - return mDragging; + return mDragDriver != null || mIsAccessibleDrag; } /** * Stop dragging without dropping. */ public void cancelDrag() { - if (mDragging) { + if (isDragging()) { if (mLastDropTarget != null) { mLastDropTarget.onDragExit(mDragObject); } @@ -341,6 +313,7 @@ public class DragController { } endDrag(); } + public void onAppsRemoved(final ArrayList<String> packageNames, HashSet<ComponentName> cns) { // Cancel the current drag if we are removing an app that we are dragging if (mDragObject != null) { @@ -363,8 +336,8 @@ public class DragController { } private void endDrag() { - if (mDragging) { - mDragging = false; + if (isDragging()) { + mDragDriver = null; mIsAccessibleDrag = false; clearScrollRunnable(); boolean isDeferred = false; @@ -415,18 +388,63 @@ public class DragController { return mTmpPoint; } - long getLastGestureUpTime() { - if (mDragging) { + public long getLastGestureUpTime() { + if (mDragDriver != null) { return System.currentTimeMillis(); } else { return mLastTouchUpTime; } } - void resetLastGestureUpTime() { + public void resetLastGestureUpTime() { mLastTouchUpTime = -1; } + @Override + public void onDriverDragMove(float x, float y) { + final int[] dragLayerPos = getClampedDragLayerPos(x, y); + + handleMoveEvent(dragLayerPos[0], dragLayerPos[1]); + } + + @Override + public void onDriverDragExitWindow() { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragObject); + mLastDropTarget = null; + } + } + + @Override + public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) { + final int[] dragLayerPos = getClampedDragLayerPos(x, y); + final int dragLayerX = dragLayerPos[0]; + final int dragLayerY = dragLayerPos[1]; + + DropTarget dropTarget; + PointF vec = null; + + if (dropTargetOverride != null) { + dropTarget = dropTargetOverride; + } else { + vec = isFlingingToDelete(mDragObject.dragSource); + if (vec != null) { + dropTarget = mFlingToDeleteDropTarget; + } else { + dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp); + } + } + + drop(dropTarget, x, y, vec); + + endDrag(); + } + + @Override + public void onDriverDragCancel() { + cancelDrag(); + } + /** * Call this from a drag source view. */ @@ -434,8 +452,8 @@ public class DragController { @SuppressWarnings("all") // suppress dead code warning final boolean debug = false; if (debug) { - Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging=" - + mDragging); + Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging=" + + (mDragDriver != null)); } if (mIsAccessibleDrag) { @@ -451,41 +469,39 @@ public class DragController { final int dragLayerY = dragLayerPos[1]; switch (action) { - case MotionEvent.ACTION_MOVE: - break; case MotionEvent.ACTION_DOWN: // Remember location of down touch mMotionDownX = dragLayerX; mMotionDownY = dragLayerY; - mLastDropTarget = null; break; case MotionEvent.ACTION_UP: mLastTouchUpTime = System.currentTimeMillis(); - if (mDragging) { - PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { - vec = null; - } - if (vec != null) { - dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); - } else { - drop(dragLayerX, dragLayerY); - } - } - endDrag(); - break; - case MotionEvent.ACTION_CANCEL: - cancelDrag(); break; } - return mDragging; + return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev); + } + + /** + * Call this from a drag source view. + */ + public boolean onDragEvent(DragEvent event) { + return mDragDriver != null && mDragDriver.onDragEvent(event); + } + + /** + * Call this from a drag view. + */ + public void onDragViewAnimationEnd() { + if (mDragDriver != null) { + mDragDriver.onDragViewAnimationEnd(); + } } /** * Sets the view that should handle move events. */ - void setMoveTarget(View view) { + public void setMoveTarget(View view) { mMoveTarget = view; } @@ -556,7 +572,7 @@ public class DragController { if (mScrollState == SCROLL_OUTSIDE_ZONE) { mScrollState = SCROLL_WAITING_IN_ZONE; if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) { - dragLayer.onEnterScrollArea(forwardDirection); + dragLayer.onEnterScrollArea(); mScrollRunnable.setDirection(forwardDirection); mHandler.postDelayed(mScrollRunnable, delay); } @@ -565,7 +581,7 @@ public class DragController { if (mScrollState == SCROLL_OUTSIDE_ZONE) { mScrollState = SCROLL_WAITING_IN_ZONE; if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) { - dragLayer.onEnterScrollArea(backwardsDirection); + dragLayer.onEnterScrollArea(); mScrollRunnable.setDirection(backwardsDirection); mHandler.postDelayed(mScrollRunnable, delay); } @@ -579,7 +595,7 @@ public class DragController { * Call this from a drag source view. */ public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging || mIsAccessibleDrag) { + if (mDragDriver == null || mIsAccessibleDrag) { return false; } @@ -592,47 +608,25 @@ public class DragController { final int dragLayerY = dragLayerPos[1]; switch (action) { - case MotionEvent.ACTION_DOWN: - // Remember where the motion event started - mMotionDownX = dragLayerX; - mMotionDownY = dragLayerY; + case MotionEvent.ACTION_DOWN: + // Remember where the motion event started + mMotionDownX = dragLayerX; + mMotionDownY = dragLayerY; - if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { - mScrollState = SCROLL_WAITING_IN_ZONE; - mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); - } else { - mScrollState = SCROLL_OUTSIDE_ZONE; - } - handleMoveEvent(dragLayerX, dragLayerY); - break; - case MotionEvent.ACTION_MOVE: - handleMoveEvent(dragLayerX, dragLayerY); - break; - case MotionEvent.ACTION_UP: - // Ensure that we've processed a move event at the current pointer location. - handleMoveEvent(dragLayerX, dragLayerY); - mHandler.removeCallbacks(mScrollRunnable); - - if (mDragging) { - PointF vec = isFlingingToDelete(mDragObject.dragSource); - if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) { - vec = null; - } - if (vec != null) { - dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec); + if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) { + mScrollState = SCROLL_WAITING_IN_ZONE; + mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY); } else { - drop(dragLayerX, dragLayerY); + mScrollState = SCROLL_OUTSIDE_ZONE; } - } - endDrag(); - break; - case MotionEvent.ACTION_CANCEL: - mHandler.removeCallbacks(mScrollRunnable); - cancelDrag(); - break; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mHandler.removeCallbacks(mScrollRunnable); + break; } - return true; + return mDragDriver.onTouchEvent(ev); } /** @@ -642,7 +636,6 @@ public class DragController { public void prepareAccessibleDrag(int x, int y) { mMotionDownX = x; mMotionDownY = y; - mLastDropTarget = null; } /** @@ -660,7 +653,7 @@ public class DragController { dropTarget.prepareAccessibilityDrop(); // Perform the drop - drop(location[0], location[1]); + drop(dropTarget, location[0], location[1], null); endDrag(); } @@ -675,64 +668,64 @@ public class DragController { ViewConfiguration config = ViewConfiguration.get(mLauncher); mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); - + PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + float theta = MAX_FLING_DEGREES + 1; if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { // Do a quick dot product test to ensure that we are flinging upwards - PointF vel = new PointF(mVelocityTracker.getXVelocity(), - mVelocityTracker.getYVelocity()); PointF upVec = new PointF(0f, -1f); - float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) / - (vel.length() * upVec.length())); - if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { - return vel; - } + theta = getAngleBetweenVectors(vel, upVec); + } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() && + mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) { + // Remove icon is on left side instead of top, so check if we are flinging to the left. + PointF leftVec = new PointF(-1f, 0f); + theta = getAngleBetweenVectors(vel, leftVec); + } + if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { + return vel; } return null; } - private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) { + private float getAngleBetweenVectors(PointF vec1, PointF vec2) { + return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) / + (vec1.length() * vec2.length())); + } + + void drop(DropTarget dropTarget, float x, float y, PointF flingVel) { final int[] coordinates = mCoordinatesTemp; mDragObject.x = coordinates[0]; mDragObject.y = coordinates[1]; - // Clean up dragging on the target if it's not the current fling delete target otherwise, - // start dragging to it. - if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) { - mLastDropTarget.onDragExit(mDragObject); + // Move dragging to the final target. + if (dropTarget != mLastDropTarget) { + if (mLastDropTarget != null) { + mLastDropTarget.onDragExit(mDragObject); + } + mLastDropTarget = dropTarget; + if (dropTarget != null) { + dropTarget.onDragEnter(mDragObject); + } } - // Drop onto the fling-to-delete target - boolean accepted = false; - mFlingToDeleteDropTarget.onDragEnter(mDragObject); - // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for - // "drop" mDragObject.dragComplete = true; - mFlingToDeleteDropTarget.onDragExit(mDragObject); - if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) { - mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, vel); - accepted = true; - } - mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true, - accepted); - } - private void drop(float x, float y) { - final int[] coordinates = mCoordinatesTemp; - final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates); - - mDragObject.x = coordinates[0]; - mDragObject.y = coordinates[1]; + // Drop onto the target. boolean accepted = false; if (dropTarget != null) { - mDragObject.dragComplete = true; dropTarget.onDragExit(mDragObject); if (dropTarget.acceptDrop(mDragObject)) { - dropTarget.onDrop(mDragObject); + if (flingVel != null) { + dropTarget.onFlingToDelete(mDragObject, flingVel); + } else { + dropTarget.onDrop(mDragObject); + } accepted = true; } } - mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted); + final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null; + mDragObject.dragSource.onDropCompleted( + dropTargetAsView, mDragObject, flingVel != null, accepted); } private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) { @@ -825,10 +818,6 @@ public class DragController { mScrollView = v; } - DragView getDragView() { - return mDragObject.dragView; - } - private class ScrollRunnable implements Runnable { private int mDirection; diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java new file mode 100644 index 000000000..6e4b430d4 --- /dev/null +++ b/src/com/android/launcher3/dragndrop/DragDriver.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.dragndrop; + +import com.android.launcher3.AnotherWindowDropTarget; +import com.android.launcher3.DropTarget; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Utilities; + +import android.content.ClipData; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Point; +import android.view.DragEvent; +import android.view.MotionEvent; +import android.view.View; + +/** + * Base class for driving a drag/drop operation. + */ +public abstract class DragDriver { + protected final EventListener mEventListener; + + public interface EventListener { + void onDriverDragMove(float x, float y); + void onDriverDragExitWindow(); + void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride); + void onDriverDragCancel(); + } + + public DragDriver(EventListener eventListener) { + mEventListener = eventListener; + } + + /** + * Handles ending of the DragView animation. + */ + public abstract void onDragViewAnimationEnd(); + + public boolean onTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_MOVE: + mEventListener.onDriverDragMove(ev.getX(), ev.getY()); + break; + case MotionEvent.ACTION_UP: + mEventListener.onDriverDragMove(ev.getX(), ev.getY()); + mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null); + break; + case MotionEvent.ACTION_CANCEL: + mEventListener.onDriverDragCancel(); + break; + } + + return true; + } + + public abstract boolean onDragEvent (DragEvent event); + + + public boolean onInterceptTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_UP: + mEventListener.onDriverDragEnd(ev.getX(), ev.getY(), null); + break; + case MotionEvent.ACTION_CANCEL: + mEventListener.onDriverDragCancel(); + break; + } + + return true; + } + + public static DragDriver create( + DragController dragController, ItemInfo dragInfo, DragView dragView) { + if (Utilities.isNycOrAbove()) { + return new SystemDragDriver(dragController, dragInfo.getIntent(), dragView); + } else { + return new InternalDragDriver(dragController); + } + } + +}; + +/** + * Class for driving a system (i.e. framework) drag/drop operation. + */ +class SystemDragDriver extends DragDriver { + /** Intent associated with the drag operation, or null is there no associated intent. */ + private final Intent mDragIntent; + + private final DragView mDragView; + boolean mIsFrameworkDragActive = false; + boolean mReceivedDropEvent = false; + float mLastX = 0; + float mLastY = 0; + + public SystemDragDriver(DragController dragController, Intent dragIntent, DragView dragView) { + super(dragController); + mDragIntent = dragIntent; + mDragView = dragView; + } + + private static class ShadowBuilder extends View.DragShadowBuilder { + final DragView mDragView; + + public ShadowBuilder(DragView dragView) { + mDragView = dragView; + } + + @Override + public void onProvideShadowMetrics (Point size, Point touch) { + mDragView.provideDragShadowMetrics(size, touch); + } + + @Override + public void onDrawShadow(Canvas canvas) { + mDragView.drawDragShadow(canvas); + } + }; + + @Override + public void onDragViewAnimationEnd() { + // Clip data for the drag operation. If there is an intent, create an intent-based ClipData, + // which will be passed to a global DND. + // If there is no intent, craft a fake ClipData and start a local DND operation; this + // ClipData will be ignored. + final ClipData dragData = mDragIntent != null ? + ClipData.newIntent("", mDragIntent) : + ClipData.newPlainText("", ""); + + View.DragShadowBuilder shadowBuilder = new ShadowBuilder(mDragView); + // TODO: DND flags are in flux, once settled, use the appropriate constant. + final int flagGlobal = 1 << 0; + final int flagOpaque = 1 << 9; + final int flags = (mDragIntent != null ? flagGlobal : 0) | flagOpaque; + + mIsFrameworkDragActive = true; + + if (!mDragView.startDrag(dragData, shadowBuilder, null, flags)) { + mIsFrameworkDragActive = false; + mEventListener.onDriverDragCancel(); + return; + } + + // Starting from this point, the driver takes over showing the drag shadow, so hiding the + // drag view. + mDragView.setVisibility(View.INVISIBLE); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return !mIsFrameworkDragActive && super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return !mIsFrameworkDragActive && super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onDragEvent (DragEvent event) { + if (!mIsFrameworkDragActive) { + // We are interested only in drag events started by this driver. + return false; + } + + final int action = event.getAction(); + + switch (action) { + case DragEvent.ACTION_DRAG_STARTED: + mLastX = event.getX(); + mLastY = event.getY(); + return true; + + case DragEvent.ACTION_DRAG_ENTERED: + mLastX = event.getX(); + mLastY = event.getY(); + return true; + + case DragEvent.ACTION_DRAG_LOCATION: + mLastX = event.getX(); + mLastY = event.getY(); + mEventListener.onDriverDragMove(event.getX(), event.getY()); + return true; + + case DragEvent.ACTION_DROP: + mLastX = event.getX(); + mLastY = event.getY(); + mReceivedDropEvent = true; + return true; + + case DragEvent.ACTION_DRAG_EXITED: + mLastX = event.getX(); + mLastY = event.getY(); + mEventListener.onDriverDragExitWindow(); + return true; + + case DragEvent.ACTION_DRAG_ENDED: + final boolean dragAccepted = event.getResult(); + final boolean acceptedByAnotherWindow = dragAccepted && !mReceivedDropEvent; + + // When the system drag ends, its drag shadow disappears. Resume showing the drag + // view for the possible final animation. + mDragView.setVisibility(View.VISIBLE); + + final DropTarget dropTargetOverride = acceptedByAnotherWindow ? + new AnotherWindowDropTarget(mDragView.getContext()) : null; + + mEventListener.onDriverDragEnd(mLastX, mLastY, dropTargetOverride); + mIsFrameworkDragActive = false; + return true; + + default: + return false; + } + } +}; + +/** + * Class for driving an internal (i.e. not using framework) drag/drop operation. + */ +class InternalDragDriver extends DragDriver { + public InternalDragDriver(DragController dragController) { + super(dragController); + } + + @Override + public void onDragViewAnimationEnd() {} + + @Override + public boolean onDragEvent (DragEvent event) { return false; } +}; diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index ad9063c25..3128db21e 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -28,6 +28,7 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.DragEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -39,7 +40,21 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.TextView; +import com.android.launcher3.AppWidgetResizeFrame; +import com.android.launcher3.CellLayout; +import com.android.launcher3.InsettableFrameLayout; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetHostView; +import com.android.launcher3.R; +import com.android.launcher3.SearchDropTargetBar; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -163,6 +178,11 @@ public class DragLayer extends InsettableFrameLayout { if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { return true; } + + getDescendantRectRelativeToSelf(mLauncher.getAppInfoDropTargetBar(), mHitRect); + if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) { + return true; + } return false; } @@ -322,6 +342,7 @@ public class DragLayer extends InsettableFrameLayout { if (isInAccessibleDrag()) { childrenForAccessibility.add(mLauncher.getSearchDropTargetBar()); + childrenForAccessibility.add(mLauncher.getAppInfoDropTargetBar()); } } else { super.addChildrenForAccessibility(childrenForAccessibility); @@ -375,6 +396,11 @@ public class DragLayer extends InsettableFrameLayout { return mDragController.onTouchEvent(ev); } + @Override + public boolean onDragEvent (DragEvent event) { + return mDragController.onDragEvent(event); + } + /** * Determine the rect of the descendant in this DragLayer's coordinates * @@ -760,7 +786,7 @@ public class DragLayer extends InsettableFrameLayout { // Show the drop view if it was previously hidden mDropView = view; mDropView.cancelAnimation(); - mDropView.resetLayoutParams(); + mDropView.requestLayout(); // Set the anchor view if the page is scrolling if (anchorView != null) { @@ -870,7 +896,7 @@ public class DragLayer extends InsettableFrameLayout { } } - void onEnterScrollArea(int direction) { + void onEnterScrollArea() { mInScrollArea = true; invalidate(); } @@ -880,7 +906,7 @@ public class DragLayer extends InsettableFrameLayout { invalidate(); } - void showPageHints() { + public void showPageHints() { mShowPageHints = true; Workspace workspace = mLauncher.getWorkspace(); getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()), @@ -888,7 +914,7 @@ public class DragLayer extends InsettableFrameLayout { invalidate(); } - void hidePageHints() { + public void hidePageHints() { mShowPageHints = false; invalidate(); } diff --git a/src/com/android/launcher3/DragScroller.java b/src/com/android/launcher3/dragndrop/DragScroller.java index e261f15d8..165d0b11c 100644 --- a/src/com/android/launcher3/DragScroller.java +++ b/src/com/android/launcher3/dragndrop/DragScroller.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; /** * Handles scrolling while dragging diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index a584667ca..b1df41b9d 100644 --- a/src/com/android/launcher3/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -14,11 +14,14 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.FloatArrayEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.res.Resources; import android.graphics.Bitmap; @@ -33,8 +36,14 @@ import android.os.Build; import android.view.View; import android.view.animation.DecelerateInterpolator; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.Thunk; +import com.android.launcher3.R; + import java.util.Arrays; public class DragView extends View { @@ -45,19 +54,18 @@ public class DragView extends View { private Bitmap mBitmap; private Bitmap mCrossFadeBitmap; @Thunk Paint mPaint; - private int mRegistrationX; - private int mRegistrationY; + private final int mRegistrationX; + private final int mRegistrationY; private Point mDragVisualizeOffset = null; private Rect mDragRegion = null; - private DragLayer mDragLayer = null; + private final DragLayer mDragLayer; + @Thunk final DragController mDragController; private boolean mHasDrawn = false; @Thunk float mCrossFadeProgress = 0f; + private boolean mAnimationCancelled = false; ValueAnimator mAnim; - @Thunk float mOffsetX = 0.0f; - @Thunk float mOffsetY = 0.0f; - private float mInitialScale = 1f; // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace // size. This is ignored for non-icons. private float mIntrinsicIconScale = 1f; @@ -70,7 +78,6 @@ public class DragView extends View { * <p> * The registration point is the point inside our view that the touch events should * be centered upon. - * * @param launcher The Launcher instance * @param bitmap The view that we're dragging around. We scale it up when we draw it. * @param registrationX The x coordinate of the registration point. @@ -81,10 +88,11 @@ public class DragView extends View { int left, int top, int width, int height, final float initialScale) { super(launcher); mDragLayer = launcher.getDragLayer(); - mInitialScale = initialScale; + mDragController = launcher.getDragController(); final Resources res = getResources(); - final float scaleDps = res.getDimensionPixelSize(R.dimen.dragViewScale); + final float scaleDps = !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND ? 0f + : res.getDimensionPixelSize(R.dimen.dragViewScale); final float scale = (width + scaleDps) / width; // Set the initial scale to avoid any jumps @@ -99,11 +107,6 @@ public class DragView extends View { public void onAnimationUpdate(ValueAnimator animation) { final float value = (Float) animation.getAnimatedValue(); - final int deltaX = (int) (-mOffsetX); - final int deltaY = (int) (-mOffsetY); - - mOffsetX += deltaX; - mOffsetY += deltaY; setScaleX(initialScale + (value * (scale - initialScale))); setScaleY(initialScale + (value * (scale - initialScale))); if (sDragAlpha != 1f) { @@ -112,9 +115,15 @@ public class DragView extends View { if (getParent() == null) { animation.cancel(); - } else { - setTranslationX(getTranslationX() + deltaX); - setTranslationY(getTranslationY() + deltaY); + } + } + }); + + mAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!mAnimationCancelled) { + mDragController.onDragViewAnimationEnd(); } } }); @@ -145,10 +154,6 @@ public class DragView extends View { return mIntrinsicIconScale; } - public float getOffsetY() { - return mOffsetY; - } - public int getDragRegionLeft() { return mDragRegion.left; } @@ -181,30 +186,33 @@ public class DragView extends View { return mDragRegion; } - public float getInitialScale() { - return mInitialScale; + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); } - public void updateInitialScaleToCurrentScale() { - mInitialScale = getScaleX(); + // Draws drag shadow for system DND. + @SuppressLint("WrongCall") + public void drawDragShadow(Canvas canvas) { + final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.scale(getScaleX(), getScaleY()); + onDraw(canvas); + canvas.restoreToCount(saveCount); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight()); + // Provides drag shadow metrics for system DND. + public void provideDragShadowMetrics(Point size, Point touch) { + size.set((int)(mBitmap.getWidth() * getScaleX()), (int)(mBitmap.getHeight() * getScaleY())); + + final float xGrowth = mBitmap.getWidth() * (getScaleX() - 1); + final float yGrowth = mBitmap.getHeight() * (getScaleY() - 1); + touch.set( + mRegistrationX + (int)Math.round(xGrowth / 2), + mRegistrationY + (int)Math.round(yGrowth / 2)); } @Override protected void onDraw(Canvas canvas) { - @SuppressWarnings("all") // suppress dead code warning - final boolean debug = false; - if (debug) { - Paint p = new Paint(); - p.setStyle(Paint.Style.FILL); - p.setColor(0x66ffffff); - canvas.drawRect(0, 0, getWidth(), getHeight(), p); - } - mHasDrawn = true; boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null; if (crossFade) { @@ -214,12 +222,12 @@ public class DragView extends View { canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint); if (crossFade) { mPaint.setAlpha((int) (255 * mCrossFadeProgress)); - canvas.save(); + final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth(); float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight(); canvas.scale(sX, sY); canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint); - canvas.restore(); + canvas.restoreToCount(saveCount); } } @@ -235,6 +243,7 @@ public class DragView extends View { @Override public void onAnimationUpdate(ValueAnimator animation) { mCrossFadeProgress = animation.getAnimatedFraction(); + invalidate(); } }); va.start(); @@ -328,16 +337,12 @@ public class DragView extends View { } public void cancelAnimation() { + mAnimationCancelled = true; if (mAnim != null && mAnim.isRunning()) { mAnim.cancel(); } } - public void resetLayoutParams() { - mOffsetX = mOffsetY = 0; - requestLayout(); - } - /** * Move the window containing this view. * @@ -345,8 +350,8 @@ public class DragView extends View { * @param touchY the y coordinate the user touched in DragLayer coordinates */ void move(int touchX, int touchY) { - setTranslationX(touchX - mRegistrationX + (int) mOffsetX); - setTranslationY(touchY - mRegistrationY + (int) mOffsetY); + setTranslationX(touchX - mRegistrationX); + setTranslationY(touchY - mRegistrationY); } void remove() { diff --git a/src/com/android/launcher3/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java index 45edaef86..d7f41c947 100644 --- a/src/com/android/launcher3/SpringLoadedDragController.java +++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java @@ -14,7 +14,13 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.dragndrop; + +import com.android.launcher3.Alarm; +import com.android.launcher3.CellLayout; +import com.android.launcher3.Launcher; +import com.android.launcher3.OnAlarmListener; +import com.android.launcher3.Workspace; public class SpringLoadedDragController implements OnAlarmListener { // how long the user must hover over a mini-screen before it unshrinks diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java new file mode 100644 index 000000000..44d7ac6e9 --- /dev/null +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -0,0 +1,128 @@ +package com.android.launcher3.folder; + +import android.graphics.Path; +import android.graphics.Point; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.Utilities; + +public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { + + static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; + private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2; + + final float MIN_SCALE = 0.48f; + final float MAX_SCALE = 0.58f; + final float MAX_RADIUS_DILATION = 0.15f; + + private float[] mTmpPoint = new float[2]; + + private float mAvailableSpace; + private float mRadius; + private float mIconSize; + private boolean mIsRtl; + private Path mClipPath = new Path(); + + @Override + public void init(int availableSpace, int intrinsicIconSize, boolean rtl) { + mAvailableSpace = availableSpace; + mRadius = 0.66f * availableSpace; + mIconSize = intrinsicIconSize; + mIsRtl = rtl; + + // We make the clip radius just slightly smaller than the background drawable + // TODO(adamcohen): this is hacky, needs cleanup (likely through programmatic drawing). + int clipRadius = (int) mAvailableSpace / 2 - 1; + + mClipPath.addCircle(mAvailableSpace / 2, mAvailableSpace / 2, clipRadius, Path.Direction.CW); + } + + @Override + public FolderIcon.PreviewItemDrawingParams computePreviewItemDrawingParams(int index, + int curNumItems, FolderIcon.PreviewItemDrawingParams params) { + + getPosition(index, curNumItems, mTmpPoint); + + float transX = mTmpPoint[0]; + float transY = mTmpPoint[1]; + float totalScale = scaleForNumItems(curNumItems); + float overlayAlpha = 0; + + if (params == null) { + params = new FolderIcon.PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); + } else { + params.transX = transX; + params.transY = transY; + params.scale = totalScale; + params.overlayAlpha = overlayAlpha; + } + + return params; + } + + private void getPosition(int index, int curNumItems, float[] result) { + // The case of two items is homomorphic to the case of one. + curNumItems = Math.max(curNumItems, 2); + + + // We model the preview as a circle of items starting in the appropriate piece of the + // upper left quadrant (to achieve horizontal and vertical symmetry). + double theta0 = mIsRtl ? 0 : Math.PI; + + // In RTL we go counterclockwise + int direction = mIsRtl ? 1 : -1; + + double thetaShift = 0; + if (curNumItems == 3) { + thetaShift = Math.PI / 6; + } else if (curNumItems == 4) { + thetaShift = Math.PI / 4; + } + theta0 += direction * thetaShift; + + // We want the items to appear in reading order. For the case of 1, 2 and 3 items, this + // is natural for the circular model. With 4 items, however, we need to swap the 3rd and + // 4th indices to achieve reading order. + if (curNumItems == 4 && index == 3) { + index = 2; + } else if (curNumItems == 4 && index == 2) { + index = 3; + } + + // We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increase + float radius = mRadius * (1 + MAX_RADIUS_DILATION * (curNumItems - + MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW)); + double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction; + + float halfIconSize = (mIconSize * scaleForNumItems(curNumItems)) / 2; + + // Map the location along the circle, and offset the coordinates to represent the center + // of the icon, and to be based from the top / left of the preview area. The y component + // is inverted to match the coordinate system. + result[0] = mAvailableSpace / 2 + (float) (radius * Math.cos(theta) / 2) - halfIconSize; + result[1] = mAvailableSpace / 2 + (float) (- radius * Math.sin(theta) / 2) - halfIconSize; + + } + + private float scaleForNumItems(int numItems) { + if (numItems <= 2) { + return MAX_SCALE; + } else if (numItems == 3) { + return (MAX_SCALE + MIN_SCALE) / 2; + } else { + return MIN_SCALE; + } + } + + @Override + public int numItems() { + return MAX_NUM_ITEMS_IN_PREVIEW; + } + + @Override + public Path getClipPath() { + return mClipPath; + } + +} diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/folder/Folder.java index 471c324fe..a411c481c 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.folder; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -36,12 +36,13 @@ import android.text.Spannable; import android.util.AttributeSet; import android.util.Log; import android.view.ActionMode; +import android.view.FocusFinder; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; +import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; @@ -51,13 +52,34 @@ import android.view.inputmethod.InputMethodManager; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.launcher3.Alarm; +import com.android.launcher3.CellLayout; import com.android.launcher3.CellLayout.CellInfo; -import com.android.launcher3.DragController.DragListener; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; +import com.android.launcher3.ExtendedEditText; +import com.android.launcher3.FolderInfo; import com.android.launcher3.FolderInfo.FolderListener; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LogDecelerateInterpolator; +import com.android.launcher3.OnAlarmListener; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Stats; import com.android.launcher3.UninstallDropTarget.UninstallSource; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragController.DragListener; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.UiThreadCircularReveal; @@ -119,13 +141,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList protected final Launcher mLauncher; protected DragController mDragController; - protected FolderInfo mInfo; + public FolderInfo mInfo; @Thunk FolderIcon mFolderIcon; @Thunk FolderPagedView mContent; @Thunk View mContentWrapper; - ExtendedEditText mFolderName; + public ExtendedEditText mFolderName; private View mFooter; private int mFooterHeight; @@ -133,7 +155,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // Cell ranks used for drag and drop @Thunk int mTargetRank, mPrevTargetRank, mEmptyCellRank; + @ViewDebug.ExportedProperty(category = "launcher", + mapping = { + @ViewDebug.IntToString(from = STATE_NONE, to = "STATE_NONE"), + @ViewDebug.IntToString(from = STATE_SMALL, to = "STATE_SMALL"), + @ViewDebug.IntToString(from = STATE_ANIMATING, to = "STATE_ANIMATING"), + @ViewDebug.IntToString(from = STATE_OPEN, to = "STATE_OPEN"), + }) @Thunk int mState = STATE_NONE; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mRearrangeOnClose = false; boolean mItemsInvalidated = false; private ShortcutInfo mCurrentDragInfo; @@ -148,6 +178,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Thunk float mFolderIconPivotY; private boolean mIsEditingName = false; + @ViewDebug.ExportedProperty(category = "launcher") private boolean mDestroyed; @Thunk Runnable mDeferredAction; @@ -208,8 +239,26 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList }); mFolderName.setOnFocusChangeListener(this); - // We disable action mode for now since it messes up the view on phones - mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback); + if (!Utilities.ATLEAST_MARSHMALLOW) { + // We disable action mode in older OSes where floating selection menu is not yet + // available. + mFolderName.setCustomSelectionActionModeCallback(new ActionMode.Callback() { + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + }); + } mFolderName.setOnEditorActionListener(this); mFolderName.setSelectAllOnFocus(true); mFolderName.setInputType(mFolderName.getInputType() | @@ -224,23 +273,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mFooterHeight = mFooter.getMeasuredHeight(); } - private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return false; - } - - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - return false; - } - - public void onDestroyActionMode(ActionMode mode) { - } - - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - }; - public void onClick(View v) { Object tag = v.getTag(); if (tag instanceof ShortcutInfo) { @@ -289,7 +321,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } mFooter.setImportantForAccessibility(enable ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS : - IMPORTANT_FOR_ACCESSIBILITY_AUTO); + IMPORTANT_FOR_ACCESSIBILITY_AUTO); mLauncher.getWorkspace().setAddNewPageOnDrag(!enable); } @@ -317,7 +349,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (commit) { sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - String.format(getContext().getString(R.string.folder_renamed), newTitle)); + getContext().getString(R.string.folder_renamed, newTitle)); } // This ensures that focus is gained every time the field is clicked, which selects all @@ -358,11 +390,26 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } @Override + protected void onAttachedToWindow() { + // requestFocus() causes the focus onto the folder itself, which doesn't cause visual + // effect but the next arrow key can start the keyboard focus inside of the folder, not + // the folder itself. + requestFocus(); + super.onAttachedToWindow(); + } + + @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { // When the folder gets focus, we don't want to announce the list of items. return true; } + @Override + public View focusSearch(int direction) { + // When the folder is focused, further focus search should be within the folder contents. + return FocusFinder.getInstance().findNextFocus(this, null, direction); + } + /** * @return the FolderInfo object associated with this folder */ @@ -466,11 +513,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList positionAndSizeAsIcon(); centerAboutIcon(); - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); - final ObjectAnimator oa = - LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); + final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1); oa.setDuration(mExpandDuration); openFolderAnim = oa; @@ -493,8 +536,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList float transY = - 0.075f * (height / 2 - getPivotY()); setTranslationX(transX); setTranslationY(transY); - PropertyValuesHolder tx = PropertyValuesHolder.ofFloat("translationX", transX, 0); - PropertyValuesHolder ty = PropertyValuesHolder.ofFloat("translationY", transY, 0); + PropertyValuesHolder tx = PropertyValuesHolder.ofFloat(TRANSLATION_X, transX, 0); + PropertyValuesHolder ty = PropertyValuesHolder.ofFloat(TRANSLATION_Y, transY, 0); Animator drift = ObjectAnimator.ofPropertyValuesHolder(this, tx, ty); drift.setDuration(mMaterialExpandDuration); @@ -570,6 +613,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList final boolean updateAnimationFlag = !mDragInProgress; openFolderAnim.addListener(new AnimatorListenerAdapter() { + @SuppressLint("InlinedApi") @Override public void onAnimationEnd(Animator animation) { mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION) @@ -597,8 +641,30 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mDragController.forceTouchMove(); } - FolderPagedView pages = (FolderPagedView) mContent; - pages.verifyVisibleHighResIcons(pages.getNextPage()); + mContent.verifyVisibleHighResIcons(mContent.getNextPage()); + } + + /** + * Opens the folder without any animation + */ + public void open() { + if (!(getParent() instanceof DragLayer)) return; + + mContent.completePendingPageChanges(); + if (!mDragInProgress) { + // Open on the first page. + mContent.snapToPageImmediately(0); + } + centerAboutIcon(); + mFolderName.setTranslationX(0); + mContent.setMarkerScale(1); + + // Make sure the folder picks up the last drag move even if the finger doesn't move. + if (mDragController.isDragging()) { + mDragController.forceTouchMove(); + } + + mContent.verifyVisibleHighResIcons(mContent.getNextPage()); } public void beginExternalDrag(ShortcutInfo item) { @@ -613,7 +679,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } @Override - public void onDragStart(DragSource source, Object info, int dragAction) { } + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } @Override public void onDragEnd() { @@ -636,12 +702,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void animateClosed() { if (!(getParent() instanceof DragLayer)) return; - PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); - PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); - PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); - final ObjectAnimator oa = - LauncherAnimUtils.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); - + final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f); oa.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -689,7 +750,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } public boolean acceptDrop(DragObject d) { - final ItemInfo item = (ItemInfo) d.dragInfo; + final ItemInfo item = d.dragInfo; final int itemType = item.itemType; return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && @@ -905,6 +966,12 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // Show the animation, next time something is added to the folder. mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, mLauncher); } + + if (!isFlingToDelete) { + // Fling to delete already exits spring loaded mode after the animation finishes. + mLauncher.exitSpringLoadedDragModeDelayed(successfulDrop, + Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); + } } @Override @@ -933,7 +1000,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList @Override public boolean supportsAppInfoDropTarget() { - return false; + return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND; } @Override @@ -964,16 +1031,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0); } - public void addItemLocationsInDatabase() { - ArrayList<View> list = getItemsInReadingOrder(); - for (int i = 0; i < list.size(); i++) { - View v = list.get(i); - ItemInfo info = (ItemInfo) v.getTag(); - LauncherModel.addItemToDatabase(mLauncher, info, mInfo.id, 0, - info.cellX, info.cellY); - } - } - public void notifyDrop() { if (mDragInProgress) { mItemAddedBackToSelfViaIcon = true; @@ -1036,10 +1093,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList lp.y = top; } - float getPivotXForIconAnimation() { + public float getPivotXForIconAnimation() { return mFolderIconPivotX; } - float getPivotYForIconAnimation() { + public float getPivotYForIconAnimation() { return mFolderIconPivotY; } @@ -1110,11 +1167,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mItemsInvalidated = true; } - // TODO remove this once GSA code fix is submitted - public ViewGroup getContent() { - return (ViewGroup) mContent; - } - public int getItemCount() { return mContent.getItemCount(); } @@ -1167,7 +1219,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mDestroyed = true; } - boolean isDestroyed() { + public boolean isDestroyed() { return mDestroyed; } @@ -1363,10 +1415,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return mItemsInReadingOrder; } - public void getLocationInDragLayer(int[] loc) { - mLauncher.getDragLayer().getLocationInDragLayer(this, loc); - } - public void onFocusChange(View v, boolean hasFocus) { if (v == mFolderName) { if (hasFocus) { diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index d7b55b3a2..5c084d949 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.folder; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -24,6 +24,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -41,26 +42,55 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; +import com.android.launcher3.Alarm; +import com.android.launcher3.AppInfo; +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.CellLayout; +import com.android.launcher3.CheckLongPressHelper; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.FastBitmapDrawable; +import com.android.launcher3.FolderInfo; import com.android.launcher3.FolderInfo.FolderListener; +import com.android.launcher3.IconCache; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.OnAlarmListener; +import com.android.launcher3.PreloadIconDrawable; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.SimpleOnStylusPressListener; +import com.android.launcher3.StylusEventHelper; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.util.Thunk; import java.util.ArrayList; /** - * An icon that can appear on in the workspace representing an {@link UserFolder}. + * An icon that can appear on in the workspace representing an {@link Folder}. */ public class FolderIcon extends FrameLayout implements FolderListener { - @Thunk Launcher mLauncher; + @Thunk + Launcher mLauncher; @Thunk Folder mFolder; private FolderInfo mInfo; @Thunk static boolean sStaticValuesDirty = true; + public static final int NUM_ITEMS_IN_PREVIEW = FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON ? + ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW : + StackFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; + private CheckLongPressHelper mLongPressHelper; private StylusEventHelper mStylusEventHelper; // The number of icons to display in the - public static final int NUM_ITEMS_IN_PREVIEW = 3; private static final int CONSUMPTION_ANIMATION_DURATION = 100; private static final int DROP_IN_ANIMATION_DURATION = 400; private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; @@ -72,39 +102,33 @@ public class FolderIcon extends FrameLayout implements FolderListener { // The degree to which the outer ring is scaled in its natural state private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; - // The amount of vertical spread between items in the stack [0...1] - private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f; - // Flag as to whether or not to draw an outer ring. Currently none is designed. public static final boolean HAS_OUTER_RING = true; // Flag whether the folder should open itself when an item is dragged over is enabled. public static final boolean SPRING_LOADING_ENABLED = true; - // The degree to which the item in the back of the stack is scaled [0...1] - // (0 means it's not scaled at all, 1 means it's scaled to nothing) - private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; - // Delay when drag enters until the folder opens, in miliseconds. private static final int ON_OPEN_DELAY = 800; public static Drawable sSharedFolderLeaveBehind = null; @Thunk ImageView mPreviewBackground; - @Thunk BubbleTextView mFolderName; + @Thunk + BubbleTextView mFolderName; FolderRingAnimator mFolderRingAnimator = null; // These variables are all associated with the drawing of the preview; they are stored // as member variables for shared usage and to avoid computation on each frame private int mIntrinsicIconSize; - private float mBaselineIconScale; - private int mBaselineIconSize; private int mAvailableSpaceInPreview; - private int mTotalWidth = -1; private int mPreviewOffsetX; private int mPreviewOffsetY; - private float mMaxPerspectiveShift; + private int mTotalWidth; + + private PreviewLayoutRule mPreviewLayoutRule; + boolean mAnimating = false; private Rect mOldBounds = new Rect(); @@ -115,7 +139,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { @Thunk ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>(); private Alarm mOpenAlarm = new Alarm(); - @Thunk ItemInfo mDragInfo; + @Thunk + ItemInfo mDragInfo; public FolderIcon(Context context, AttributeSet attrs) { super(context, attrs); @@ -129,7 +154,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void init() { mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); + mPreviewLayoutRule = FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON ? + new ClippedFolderIconLayoutRule() : + new StackFolderIconLayoutRule(); + setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); } @@ -140,7 +169,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { return !workspace.workspaceInModalState(); } - static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, + public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, FolderInfo folderInfo, IconCache iconCache) { @SuppressWarnings("all") // suppress dead code warning final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION; @@ -171,8 +200,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { icon.setOnClickListener(launcher); icon.mInfo = folderInfo; icon.mLauncher = launcher; - icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format), - folderInfo.title)); + icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title)); Folder folder = Folder.fromXml(launcher); folder.setDragController(launcher.getDragController()); folder.setFolderIcon(icon); @@ -195,7 +223,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { public static class FolderRingAnimator { public int mCellX; public int mCellY; - @Thunk CellLayout mCellLayout; + @Thunk + CellLayout mCellLayout; public float mOuterRingSize; public float mInnerRingSize; public FolderIcon mFolderIcon = null; @@ -319,7 +348,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { return mFolder; } - FolderInfo getFolderInfo() { + public FolderInfo getFolderInfo() { return mInfo; } @@ -330,8 +359,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { !mFolder.isFull() && item != mInfo && !mInfo.opened); } - public boolean acceptDrop(Object dragInfo) { - final ItemInfo item = (ItemInfo) dragInfo; + public boolean acceptDrop(ItemInfo dragInfo) { + final ItemInfo item = dragInfo; return !mFolder.isDestroyed() && willAcceptItem(item); } @@ -339,8 +368,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { mInfo.add(item); } - public void onDragEnter(Object dragInfo) { - if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return; + public void onDragEnter(ItemInfo dragInfo) { + if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return; CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); CellLayout layout = (CellLayout) getParent().getParent(); mFolderRingAnimator.setCell(lp.cellX, lp.cellY); @@ -356,10 +385,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { // Workspace#onDropExternal. mOpenAlarm.setAlarm(ON_OPEN_DELAY); } - mDragInfo = (ItemInfo) dragInfo; - } - - public void onDragOver(Object dragInfo) { + mDragInfo = dragInfo; } OnAlarmListener mOnOpenListener = new OnAlarmListener() { @@ -375,7 +401,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { item = (ShortcutInfo) mDragInfo; } mFolder.beginExternalDrag(item); - mLauncher.openFolder(FolderIcon.this); + mLauncher.openFolder(FolderIcon.this, true); } }; @@ -448,14 +474,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { } int[] center = new int[2]; - float scale = getLocalCenterForIndex(index, center); + float scale = getLocalCenterForIndex(index, index + 1, center); center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); to.offset(center[0] - animateView.getMeasuredWidth() / 2, center[1] - animateView.getMeasuredHeight() / 2); - float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f; + float finalAlpha = index < mPreviewLayoutRule.numItems() ? 0.5f : 0f; float finalScale = scale * scaleRelativeToDragLayer; dragLayer.animateView(animateView, from, to, finalAlpha, @@ -496,22 +522,16 @@ public class FolderIcon extends FrameLayout implements FolderListener { mIntrinsicIconSize = drawableSize; mTotalWidth = totalSize; - final int previewSize = mPreviewBackground.getLayoutParams().height; + final int previewSize = FolderRingAnimator.sPreviewSize; final int previewPadding = FolderRingAnimator.sPreviewPadding; mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); - // cos(45) = 0.707 + ~= 0.1) = 0.8f - int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); - - int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); - - mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); - - mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale); - mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; - mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset; + mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset + getPaddingTop(); + + mPreviewLayoutRule.init(mAvailableSpaceInPreview, mIntrinsicIconSize, + Utilities.isRtl(getResources())); } } @@ -519,7 +539,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); } - class PreviewItemDrawingParams { + static class PreviewItemDrawingParams { PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) { this.transX = transX; this.transY = transY; @@ -533,8 +553,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { Drawable drawable; } - private float getLocalCenterForIndex(int index, int[] center) { - mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams); + private float getLocalCenterForIndex(int index, int curNumItems, int[] center) { + mParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index), + curNumItems, mParams); mParams.transX += mPreviewOffsetX; mParams.transY += mPreviewOffsetY; @@ -546,37 +567,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { return mParams.scale; } - private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, + private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, PreviewItemDrawingParams params) { - index = NUM_ITEMS_IN_PREVIEW - index - 1; - float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1); - float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); - - float offset = (1 - r) * mMaxPerspectiveShift; - float scaledSize = scale * mBaselineIconSize; - float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; - - // We want to imagine our coordinates from the bottom left, growing up and to the - // right. This is natural for the x-axis, but for the y-axis, we have to invert things. - float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection) + getPaddingTop(); - float transX = (mAvailableSpaceInPreview - scaledSize) / 2; - float totalScale = mBaselineIconScale * scale; - final float overlayAlpha = (80 * (1 - r)) / 255f; - - if (params == null) { - params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); - } else { - params.transX = transX; - params.transY = transY; - params.scale = totalScale; - params.overlayAlpha = overlayAlpha; - } - return params; + return mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params); } private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { canvas.save(); - canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY); + canvas.translate(params.transX, params.transY); canvas.scale(params.scale, params.scale); Drawable d = params.drawable; @@ -620,13 +618,20 @@ public class FolderIcon extends FrameLayout implements FolderListener { computePreviewDrawingParams(d); } - int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW); + canvas.save(); + canvas.translate(mPreviewOffsetX, mPreviewOffsetY); + Path clipPath = mPreviewLayoutRule.getClipPath(); + if (clipPath != null) { + canvas.clipPath(clipPath); + } + + int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems()); if (!mAnimating) { for (int i = nItemsInPreview - 1; i >= 0; i--) { v = (TextView) items.get(i); if (!mHiddenItems.contains(v.getTag())) { d = getTopDrawable(v); - mParams = computePreviewItemDrawingParams(i, mParams); + mParams = computePreviewItemDrawingParams(i, nItemsInPreview, mParams); mParams.drawable = d; drawPreviewItem(canvas, mParams); } @@ -634,6 +639,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } else { drawPreviewItem(canvas, mAnimParams); } + canvas.restore(); } private Drawable getTopDrawable(TextView v) { @@ -643,12 +649,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void animateFirstItem(final Drawable d, int duration, final boolean reverse, final Runnable onCompleteRunnable) { - final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null); + + final PreviewItemDrawingParams finalParams = + computePreviewItemDrawingParams(0, reverse ? 1 : 2, null); float iconSize = mLauncher.getDeviceProfile().iconSizePx; final float scale0 = iconSize / d.getIntrinsicWidth() ; final float transX0 = (mAvailableSpaceInPreview - iconSize) / 2; - final float transY0 = (mAvailableSpaceInPreview - iconSize) / 2 + getPaddingTop(); + final float transY0 = (mAvailableSpaceInPreview - iconSize) / 2; mAnimParams.drawable = d; ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f); @@ -712,8 +720,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void onTitleChanged(CharSequence title) { mFolderName.setText(title); - setContentDescription(String.format(getContext().getString(R.string.folder_name_format), - title)); + setContentDescription(getContext().getString(R.string.folder_name_format, title)); } @Override @@ -723,7 +730,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { boolean result = super.onTouchEvent(event); // Check for a stylus button press, if it occurs cancel any long press checks. - if (mStylusEventHelper.checkAndPerformStylusEvent(event)) { + if (mStylusEventHelper.onMotionEvent(event)) { mLongPressHelper.cancelLongPress(); return true; } @@ -754,7 +761,16 @@ public class FolderIcon extends FrameLayout implements FolderListener { @Override public void cancelLongPress() { super.cancelLongPress(); - mLongPressHelper.cancelLongPress(); } + + public interface PreviewLayoutRule { + public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, + PreviewItemDrawingParams params); + + public void init(int availableSpace, int intrinsicIconSize, boolean rtl); + + public int numItems(); + public Path getClipPath(); + } } diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index d503d2cf5..c25444e06 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.folder; import android.annotation.SuppressLint; import android.content.Context; @@ -23,13 +23,31 @@ import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewDebug; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.CellLayout; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener; +import com.android.launcher3.FocusIndicatorView; +import com.android.launcher3.IconCache; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.PageIndicator; import com.android.launcher3.PageIndicator.PageMarkerResources; +import com.android.launcher3.PagedView; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -68,12 +86,17 @@ public class FolderPagedView extends PagedView { @Thunk final HashMap<View, Runnable> mPendingAnimations = new HashMap<>(); + @ViewDebug.ExportedProperty(category = "launcher") private final int mMaxCountX; + @ViewDebug.ExportedProperty(category = "launcher") private final int mMaxCountY; + @ViewDebug.ExportedProperty(category = "launcher") private final int mMaxItemsPerPage; private int mAllocatedContentSize; + @ViewDebug.ExportedProperty(category = "launcher") private int mGridCountX; + @ViewDebug.ExportedProperty(category = "launcher") private int mGridCountY; private Folder mFolder; @@ -226,12 +249,6 @@ public class FolderPagedView extends PagedView { return (CellLayout) getChildAt(index); } - public void removeCellLayoutView(View view) { - for (int i = getChildCount() - 1; i >= 0; i --) { - getPageAt(i).removeView(view); - } - } - public CellLayout getCurrentCellLayout() { return getPageAt(getNextPage()); } @@ -447,8 +464,7 @@ public class FolderPagedView extends PagedView { } public String getAccessibilityDescription() { - return String.format(getContext().getString(R.string.folder_opened), - mGridCountX, mGridCountY); + return getContext().getString(R.string.folder_opened, mGridCountX, mGridCountY); } /** diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java new file mode 100644 index 000000000..87f5f897b --- /dev/null +++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.folder; + +import android.graphics.Path; + +import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams; + +public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { + + static final int MAX_NUM_ITEMS_IN_PREVIEW = 3; + + // The degree to which the item in the back of the stack is scaled [0...1] + // (0 means it's not scaled at all, 1 means it's scaled to nothing) + private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; + + // The amount of vertical spread between items in the stack [0...1] + private static final float PERSPECTIVE_SHIFT_FACTOR = 0.18f; + + private float mBaselineIconScale; + private int mBaselineIconSize; + private int mAvailableSpaceInPreview; + private float mMaxPerspectiveShift; + + @Override + public void init(int availableSpace, int intrinsicIconSize, boolean rtl) { + mAvailableSpaceInPreview = availableSpace; + + // cos(45) = 0.707 + ~= 0.1) = 0.8f + int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f)); + + int unscaledHeight = (int) (intrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR)); + + mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight); + + mBaselineIconSize = (int) (intrinsicIconSize * mBaselineIconScale); + mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR; + } + + @Override + public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, + PreviewItemDrawingParams params) { + + index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1; + float r = (index * 1.0f) / (MAX_NUM_ITEMS_IN_PREVIEW - 1); + float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r)); + + float offset = (1 - r) * mMaxPerspectiveShift; + float scaledSize = scale * mBaselineIconSize; + float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize; + + // We want to imagine our coordinates from the bottom left, growing up and to the + // right. This is natural for the x-axis, but for the y-axis, we have to invert things. + float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection); + float transX = (mAvailableSpaceInPreview - scaledSize) / 2; + float totalScale = mBaselineIconScale * scale; + final float overlayAlpha = (80 * (1 - r)) / 255f; + + if (params == null) { + params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha); + } else { + params.transX = transX; + params.transY = transY; + params.scale = totalScale; + params.overlayAlpha = overlayAlpha; + } + return params; + } + + @Override + public int numItems() { + return MAX_NUM_ITEMS_IN_PREVIEW; + } + + @Override + public Path getClipPath() { + return null; + } +} diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index 786ab6009..19ec3ed64 100644 --- a/src/com/android/launcher3/model/MigrateFromRestoreTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -9,6 +9,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.database.Cursor; import android.graphics.Point; +import android.net.Uri; import android.text.TextUtils; import android.util.Log; @@ -21,32 +22,37 @@ import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Utilities; +import com.android.launcher3.backup.nano.BackupProtos; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.util.LongArrayMap; -import com.android.launcher3.util.Thunk; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; /** * This class takes care of shrinking the workspace (by maximum of one row and one column), as a - * result of restoring from a larger device. + * result of restoring from a larger device or device density change. */ -public class MigrateFromRestoreTask { +public class GridSizeMigrationTask { - public static boolean ENABLED = false; + public static boolean ENABLED = Utilities.isNycOrAbove(); - private static final String TAG = "MigrateFromRestoreTask"; + private static final String TAG = "GridSizeMigrationTask"; private static final boolean DEBUG = true; - private static final String KEY_MIGRATION_SOURCE_SIZE = "migration_restore_src_size"; + private static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size"; + private static final String KEY_MIGRATION_SRC_HOTSEAT_SIZE = "migration_src_hotseat_size"; + + // Set of entries indicating minimum size a widget can be resized to. This is used during + // restore in case the widget has not been installed yet. private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size"; // These are carefully selected weights for various item types (Math.random?), to allow for - // the lease absurd migration experience. + // the least absurd migration experience. private static final float WT_SHORTCUT = 1; private static final float WT_APPLICATION = 0.8f; private static final float WT_WIDGET_MIN = 2; @@ -54,59 +60,141 @@ public class MigrateFromRestoreTask { private static final float WT_FOLDER_FACTOR = 0.5f; private final Context mContext; - private final ContentValues mTempValues = new ContentValues(); - private final HashMap<String, Point> mWidgetMinSize; private final InvariantDeviceProfile mIdp; - private HashSet<String> mValidPackages; - public ArrayList<Long> mEntryToRemove; - private ArrayList<ContentProviderOperation> mUpdateOperations; - - private ArrayList<DbEntry> mCarryOver; + private final HashMap<String, Point> mWidgetMinSize = new HashMap<>(); + private final ContentValues mTempValues = new ContentValues(); + private final ArrayList<Long> mEntryToRemove = new ArrayList<>(); + private final ArrayList<ContentProviderOperation> mUpdateOperations = new ArrayList<>(); + private final ArrayList<DbEntry> mCarryOver = new ArrayList<>(); + private final HashSet<String> mValidPackages; private final int mSrcX, mSrcY; - @Thunk final int mTrgX, mTrgY; + private final int mTrgX, mTrgY; private final boolean mShouldRemoveX, mShouldRemoveY; - public MigrateFromRestoreTask(Context context) { + private final int mSrcHotseatSize; + private final int mSrcAllAppsRank; + private final int mDestHotseatSize; + private final int mDestAllAppsRank; + + protected GridSizeMigrationTask(Context context, InvariantDeviceProfile idp, + HashSet<String> validPackages, HashMap<String, Point> widgetMinSize, + Point sourceSize, Point targetSize) { mContext = context; + mValidPackages = validPackages; + mWidgetMinSize.putAll(widgetMinSize); + mIdp = idp; - SharedPreferences prefs = Utilities.getPrefs(context); - Point sourceSize = parsePoint(prefs.getString(KEY_MIGRATION_SOURCE_SIZE, "")); mSrcX = sourceSize.x; mSrcY = sourceSize.y; - mWidgetMinSize = new HashMap<String, Point>(); - for (String s : prefs.getStringSet(KEY_MIGRATION_WIDGET_MINSIZE, - Collections.<String>emptySet())) { - String[] parts = s.split("#"); - mWidgetMinSize.put(parts[0], parsePoint(parts[1])); - } + mTrgX = targetSize.x; + mTrgY = targetSize.y; - mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile(); - mTrgX = mIdp.numColumns; - mTrgY = mIdp.numRows; mShouldRemoveX = mTrgX < mSrcX; mShouldRemoveY = mTrgY < mSrcY; + + // Non-used variables + mSrcHotseatSize = mSrcAllAppsRank = mDestHotseatSize = mDestAllAppsRank = -1; } - public void execute() throws Exception { - mEntryToRemove = new ArrayList<>(); - mCarryOver = new ArrayList<>(); - mUpdateOperations = new ArrayList<>(); + protected GridSizeMigrationTask(Context context, + InvariantDeviceProfile idp, HashSet<String> validPackages, + int srcHotseatSize, int srcAllAppsRank, + int destHotseatSize, int destAllAppsRank) { + mContext = context; + mIdp = idp; + mValidPackages = validPackages; + + mSrcHotseatSize = srcHotseatSize; + mSrcAllAppsRank = srcAllAppsRank; + + mDestHotseatSize = destHotseatSize; + mDestAllAppsRank = destAllAppsRank; + + // Non-used variables + mSrcX = mSrcY = mTrgX = mTrgY = -1; + mShouldRemoveX = mShouldRemoveY = false; + } + + /** + * Applied all the pending DB operations + * @return true if any DB operation was commited. + */ + private boolean applyOperations() throws Exception { + // Update items + if (!mUpdateOperations.isEmpty()) { + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations); + } - // Initialize list of valid packages. This contain all the packages which are already on - // the device and packages which are being installed. Any item which doesn't belong to - // this set is removed. - // Since the loader removes such items anyway, removing these items here doesn't cause any - // extra data loss and gives us more free space on the grid for better migration. - mValidPackages = new HashSet<>(); - for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) { - mValidPackages.add(info.packageName); + if (!mEntryToRemove.isEmpty()) { + if (DEBUG) { + Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove)); + } + mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI, + Utilities.createDbSelectionQuery( + LauncherSettings.Favorites._ID, mEntryToRemove), null); } - mValidPackages.addAll(PackageInstallerCompat.getInstance(mContext) - .updateAndGetActiveSessionCache().keySet()); + return !mUpdateOperations.isEmpty() || !mEntryToRemove.isEmpty(); + } + + /** + * To migrate hotseat, we load all the entries in order (LTR or RTL) and arrange them + * in the order in the new hotseat while keeping an empty space for all-apps. If the number of + * entries is more than what can fit in the new hotseat, we drop the entries with least weight. + * For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION} + * & {@see #WT_FOLDER_FACTOR}. + * @return true if any DB change was made + */ + protected boolean migrateHotseat() throws Exception { + ArrayList<DbEntry> items = loadHotseatEntries(); + + int requiredCount = mDestHotseatSize - 1; + + while (items.size() > requiredCount) { + // Pick the center item by default. + DbEntry toRemove = items.get(items.size() / 2); + + // Find the item with least weight. + for (DbEntry entry : items) { + if (entry.weight < toRemove.weight) { + toRemove = entry; + } + } + + mEntryToRemove.add(toRemove.id); + items.remove(toRemove); + } + + // Update screen IDS + int newScreenId = 0; + for (DbEntry entry : items) { + if (entry.screenId != newScreenId) { + entry.screenId = newScreenId; + + // These values does not affect the item position, but we should set them + // to something other than -1. + entry.cellX = newScreenId; + entry.cellY = 0; + + update(entry); + } + + newScreenId++; + if (newScreenId == mDestAllAppsRank) { + newScreenId++; + } + } + + return applyOperations(); + } + + /** + * @return true if any DB change was made + */ + protected boolean migrateWorkspace() throws Exception { ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext); if (allScreens.isEmpty()) { throw new Exception("Unable to get workspace screens"); @@ -135,7 +223,11 @@ public class MigrateFromRestoreTask { new boolean[mTrgX][mTrgY], deepCopy(mCarryOver), true); placement.find(); if (placement.finalPlacedItems.size() > 0) { - long newScreenId = LauncherAppState.getLauncherProvider().generateNewScreenId(); + long newScreenId = LauncherSettings.Settings.call( + mContext.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) + .getLong(LauncherSettings.Settings.EXTRA_VALUE); + allScreens.add(newScreenId); for (DbEntry item : placement.finalPlacedItems) { if (!mCarryOver.remove(itemMap.get(item.id))) { @@ -150,31 +242,19 @@ public class MigrateFromRestoreTask { } while (!mCarryOver.isEmpty()); - - LauncherAppState.getInstance().getModel() - .updateWorkspaceScreenOrder(mContext, allScreens); - } - - // Update items - mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations); - - if (!mEntryToRemove.isEmpty()) { - if (DEBUG) { - Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove)); + // Update screens + final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; + mUpdateOperations.add(ContentProviderOperation.newDelete(uri).build()); + int count = allScreens.size(); + for (int i = 0; i < count; i++) { + ContentValues v = new ContentValues(); + long screenId = allScreens.get(i); + v.put(LauncherSettings.WorkspaceScreens._ID, screenId); + v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); + mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); } - mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI, - Utilities.createDbSelectionQuery( - LauncherSettings.Favorites._ID, mEntryToRemove), null); - } - - // Make sure we haven't removed everything. - final Cursor c = mContext.getContentResolver().query( - LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); - boolean hasData = c.moveToNext(); - c.close(); - if (!hasData) { - throw new Exception("Removed every thing during grid resize"); } + return applyOperations(); } /** @@ -188,7 +268,7 @@ public class MigrateFromRestoreTask { * (otherwise they are placed on a new screen). */ private void migrateScreen(long screenId) { - ArrayList<DbEntry> items = loadEntries(screenId); + ArrayList<DbEntry> items = loadWorkspaceEntries(screenId); int removedCol = Integer.MAX_VALUE; int removedRow = Integer.MAX_VALUE; @@ -326,7 +406,7 @@ public class MigrateFromRestoreTask { return finalItems; } - @Thunk void markCells(boolean[][] occupied, DbEntry item, boolean val) { + private void markCells(boolean[][] occupied, DbEntry item, boolean val) { for (int i = item.cellX; i < (item.cellX + item.spanX); i++) { for (int j = item.cellY; j < (item.cellY + item.spanY); j++) { occupied[i][j] = val; @@ -334,7 +414,7 @@ public class MigrateFromRestoreTask { } } - @Thunk boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) { + private boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) { if (x + w > mTrgX) return false; if (y + h > mTrgY) return false; @@ -542,100 +622,161 @@ public class MigrateFromRestoreTask { } } + private ArrayList<DbEntry> loadHotseatEntries() { + Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI, + new String[]{ + Favorites._ID, // 0 + Favorites.ITEM_TYPE, // 1 + Favorites.INTENT, // 2 + Favorites.SCREEN}, // 3 + Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT, null, null, null); + + final int indexId = c.getColumnIndexOrThrow(Favorites._ID); + final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE); + final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT); + final int indexScreen = c.getColumnIndexOrThrow(Favorites.SCREEN); + + ArrayList<DbEntry> entries = new ArrayList<>(); + while (c.moveToNext()) { + DbEntry entry = new DbEntry(); + entry.id = c.getLong(indexId); + entry.itemType = c.getInt(indexItemType); + entry.screenId = c.getLong(indexScreen); + + if (entry.screenId >= mSrcHotseatSize) { + mEntryToRemove.add(entry.id); + continue; + } + + try { + // calculate weight + switch (entry.itemType) { + case Favorites.ITEM_TYPE_SHORTCUT: + case Favorites.ITEM_TYPE_APPLICATION: { + verifyIntent(c.getString(indexIntent)); + entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT + ? WT_SHORTCUT : WT_APPLICATION; + break; + } + case Favorites.ITEM_TYPE_FOLDER: { + int total = getFolderItemsCount(entry.id); + if (total == 0) { + throw new Exception("Folder is empty"); + } + entry.weight = WT_FOLDER_FACTOR * total; + break; + } + default: + throw new Exception("Invalid item type"); + } + } catch (Exception e) { + if (DEBUG) { + Log.d(TAG, "Removing item " + entry.id, e); + } + mEntryToRemove.add(entry.id); + continue; + } + entries.add(entry); + } + c.close(); + return entries; + } + + /** * Loads entries for a particular screen id. */ - public ArrayList<DbEntry> loadEntries(long screen) { - Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { - Favorites._ID, // 0 - Favorites.ITEM_TYPE, // 1 - Favorites.CELLX, // 2 - Favorites.CELLY, // 3 - Favorites.SPANX, // 4 - Favorites.SPANY, // 5 - Favorites.INTENT, // 6 - Favorites.APPWIDGET_PROVIDER}, // 7 + private ArrayList<DbEntry> loadWorkspaceEntries(long screen) { + Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI, + new String[]{ + Favorites._ID, // 0 + Favorites.ITEM_TYPE, // 1 + Favorites.CELLX, // 2 + Favorites.CELLY, // 3 + Favorites.SPANX, // 4 + Favorites.SPANY, // 5 + Favorites.INTENT, // 6 + Favorites.APPWIDGET_PROVIDER}, // 7 Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP - + " AND " + Favorites.SCREEN + " = " + screen, null, null, null); - - final int indexId = c.getColumnIndexOrThrow(Favorites._ID); - final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE); - final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX); - final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY); - final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX); - final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY); - final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT); - final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER); - - ArrayList<DbEntry> entries = new ArrayList<>(); - while (c.moveToNext()) { - DbEntry entry = new DbEntry(); - entry.id = c.getLong(indexId); - entry.itemType = c.getInt(indexItemType); - entry.cellX = c.getInt(indexCellX); - entry.cellY = c.getInt(indexCellY); - entry.spanX = c.getInt(indexSpanX); - entry.spanY = c.getInt(indexSpanY); - entry.screenId = screen; - - try { - // calculate weight - switch (entry.itemType) { - case Favorites.ITEM_TYPE_SHORTCUT: - case Favorites.ITEM_TYPE_APPLICATION: { - verifyIntent(c.getString(indexIntent)); - entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT - ? WT_SHORTCUT : WT_APPLICATION; - break; - } - case Favorites.ITEM_TYPE_APPWIDGET: { - String provider = c.getString(indexAppWidgetProvider); - ComponentName cn = ComponentName.unflattenFromString(provider); - verifyPackage(cn.getPackageName()); - entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR - * entry.spanX * entry.spanY); - - // Migration happens for current user only. - LauncherAppWidgetProviderInfo pInfo = LauncherModel.getProviderInfo( - mContext, cn, UserHandleCompat.myUserHandle()); - Point spans = pInfo == null ? - mWidgetMinSize.get(provider) : pInfo.getMinSpans(mIdp, mContext); - if (spans != null) { - entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX; - entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY; - } else { - // Assume that the widget be resized down to 2x2 - entry.minSpanX = entry.minSpanY = 2; - } - - if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) { - throw new Exception("Widget can't be resized down to fit the grid"); - } - break; - } - case Favorites.ITEM_TYPE_FOLDER: { - int total = getFolderItemsCount(entry.id); - if (total == 0) { - throw new Exception("Folder is empty"); - } - entry.weight = WT_FOLDER_FACTOR * total; - break; - } - default: - throw new Exception("Invalid item type"); - } - } catch (Exception e) { - if (DEBUG) { - Log.d(TAG, "Removing item " + entry.id, e); - } - mEntryToRemove.add(entry.id); - continue; - } - entries.add(entry); - } - c.close(); - return entries; + + " AND " + Favorites.SCREEN + " = " + screen, null, null, null); + + final int indexId = c.getColumnIndexOrThrow(Favorites._ID); + final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE); + final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX); + final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY); + final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX); + final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY); + final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT); + final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER); + + ArrayList<DbEntry> entries = new ArrayList<>(); + while (c.moveToNext()) { + DbEntry entry = new DbEntry(); + entry.id = c.getLong(indexId); + entry.itemType = c.getInt(indexItemType); + entry.cellX = c.getInt(indexCellX); + entry.cellY = c.getInt(indexCellY); + entry.spanX = c.getInt(indexSpanX); + entry.spanY = c.getInt(indexSpanY); + entry.screenId = screen; + + try { + // calculate weight + switch (entry.itemType) { + case Favorites.ITEM_TYPE_SHORTCUT: + case Favorites.ITEM_TYPE_APPLICATION: { + verifyIntent(c.getString(indexIntent)); + entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT + ? WT_SHORTCUT : WT_APPLICATION; + break; + } + case Favorites.ITEM_TYPE_APPWIDGET: { + String provider = c.getString(indexAppWidgetProvider); + ComponentName cn = ComponentName.unflattenFromString(provider); + verifyPackage(cn.getPackageName()); + entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR + * entry.spanX * entry.spanY); + + // Migration happens for current user only. + LauncherAppWidgetProviderInfo pInfo = LauncherModel.getProviderInfo( + mContext, cn, UserHandleCompat.myUserHandle()); + Point spans = pInfo == null ? + mWidgetMinSize.get(provider) : pInfo.getMinSpans(mIdp, mContext); + if (spans != null) { + entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX; + entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY; + } else { + // Assume that the widget be resized down to 2x2 + entry.minSpanX = entry.minSpanY = 2; + } + + if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) { + throw new Exception("Widget can't be resized down to fit the grid"); + } + break; + } + case Favorites.ITEM_TYPE_FOLDER: { + int total = getFolderItemsCount(entry.id); + if (total == 0) { + throw new Exception("Folder is empty"); + } + entry.weight = WT_FOLDER_FACTOR * total; + break; + } + default: + throw new Exception("Invalid item type"); + } + } catch (Exception e) { + if (DEBUG) { + Log.d(TAG, "Removing item " + entry.id, e); + } + mEntryToRemove.add(entry.id); + continue; + } + entries.add(entry); + } + c.close(); + return entries; } /** @@ -643,7 +784,7 @@ public class MigrateFromRestoreTask { */ private int getFolderItemsCount(long folderId) { Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI, - new String[] {Favorites._ID, Favorites.INTENT}, + new String[]{Favorites._ID, Favorites.INTENT}, Favorites.CONTAINER + " = " + folderId, null, null, null); int total = 0; @@ -730,7 +871,7 @@ public class MigrateFromRestoreTask { } } - @Thunk static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) { + private static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) { ArrayList<DbEntry> dup = new ArrayList<DbEntry>(src.size()); for (DbEntry e : src) { dup.add(e.copy()); @@ -743,21 +884,147 @@ public class MigrateFromRestoreTask { return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1])); } - public static void markForMigration(Context context, int srcX, int srcY, - HashSet<String> widgets) { + private static String getPointString(int x, int y) { + return String.format(Locale.ENGLISH, "%d,%d", x, y); + } + + public static void markForMigration( + Context context, HashSet<String> widgets, BackupProtos.DeviceProfieData srcProfile) { Utilities.getPrefs(context).edit() - .putString(KEY_MIGRATION_SOURCE_SIZE, srcX + "," + srcY) + .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, + getPointString((int) srcProfile.desktopCols, (int) srcProfile.desktopRows)) + .putString(KEY_MIGRATION_SRC_HOTSEAT_SIZE, + getPointString((int) srcProfile.hotseatCount, srcProfile.allappsRank)) .putStringSet(KEY_MIGRATION_WIDGET_MINSIZE, widgets) .apply(); } - public static boolean shouldRunTask(Context context) { - return !TextUtils.isEmpty(Utilities.getPrefs(context).getString(KEY_MIGRATION_SOURCE_SIZE, "")); - } + /** + * Migrates the workspace and hotseat in case their sizes changed. + * @return false if the migration failed. + */ + public static boolean migrateGridIfNeeded(Context context) { + SharedPreferences prefs = Utilities.getPrefs(context); + InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile(); - public static void clearFlags(Context context) { - Utilities.getPrefs(context).edit().remove(KEY_MIGRATION_SOURCE_SIZE) - .remove(KEY_MIGRATION_WIDGET_MINSIZE).commit(); - } + String gridSizeString = getPointString(idp.numColumns, idp.numRows); + String hotseatSizeString = getPointString(idp.numHotseatIcons, idp.hotseatAllAppsRank); + + if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) && + hotseatSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_HOTSEAT_SIZE, ""))) { + // Skip if workspace and hotseat sizes have not changed. + return true; + } + long migrationStartTime = System.currentTimeMillis(); + try { + boolean dbChanged = false; + + // Initialize list of valid packages. This contain all the packages which are already on + // the device and packages which are being installed. Any item which doesn't belong to + // this set is removed. + // Since the loader removes such items anyway, removing these items here doesn't cause + // any extra data loss and gives us more free space on the grid for better migration. + HashSet validPackages = new HashSet<>(); + for (PackageInfo info : context.getPackageManager().getInstalledPackages(0)) { + validPackages.add(info.packageName); + } + validPackages.addAll(PackageInstallerCompat.getInstance(context) + .updateAndGetActiveSessionCache().keySet()); + + // Hotseat + Point srcHotseatSize = parsePoint(prefs.getString( + KEY_MIGRATION_SRC_HOTSEAT_SIZE, hotseatSizeString)); + if (srcHotseatSize.x != idp.numHotseatIcons || + srcHotseatSize.y != idp.hotseatAllAppsRank) { + // Migrate hotseat. + + dbChanged = new GridSizeMigrationTask(context, + LauncherAppState.getInstance().getInvariantDeviceProfile(), + validPackages, + srcHotseatSize.x, srcHotseatSize.y, + idp.numHotseatIcons, idp.hotseatAllAppsRank).migrateHotseat(); + } + + // Grid size + Point targetSize = new Point(idp.numColumns, idp.numRows); + Point sourceSize = parsePoint(prefs.getString( + KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)); + + if (!targetSize.equals(sourceSize)) { + + // The following list defines all possible grid sizes (and intermediate steps + // during migration). Note that at each step, dx <= 1 && dy <= 1. Any grid size + // which is not in this list is not migrated. + ArrayList<Point> gridSizeSteps = new ArrayList<>(); + gridSizeSteps.add(new Point(2, 3)); + gridSizeSteps.add(new Point(3, 3)); + gridSizeSteps.add(new Point(3, 4)); + gridSizeSteps.add(new Point(4, 4)); + gridSizeSteps.add(new Point(5, 5)); + gridSizeSteps.add(new Point(5, 6)); + gridSizeSteps.add(new Point(6, 6)); + gridSizeSteps.add(new Point(7, 7)); + + int sourceSizeIndex = gridSizeSteps.indexOf(sourceSize); + int targetSizeIndex = gridSizeSteps.indexOf(targetSize); + + if (sourceSizeIndex <= -1 || targetSizeIndex <= -1) { + throw new Exception("Unable to migrate grid size from " + sourceSize + + " to " + targetSize); + } + + // Min widget sizes + HashMap<String, Point> widgetMinSize = new HashMap<>(); + for (String s : Utilities.getPrefs(context).getStringSet(KEY_MIGRATION_WIDGET_MINSIZE, + Collections.<String>emptySet())) { + String[] parts = s.split("#"); + widgetMinSize.put(parts[0], parsePoint(parts[1])); + } + + // Migrate the workspace grid, step by step. + while (targetSizeIndex < sourceSizeIndex ) { + // We only need to migrate the grid if source size is greater + // than the target size. + Point stepTargetSize = gridSizeSteps.get(sourceSizeIndex - 1); + Point stepSourceSize = gridSizeSteps.get(sourceSizeIndex); + + if (new GridSizeMigrationTask(context, + LauncherAppState.getInstance().getInvariantDeviceProfile(), + validPackages, widgetMinSize, + stepSourceSize, stepTargetSize).migrateWorkspace()) { + dbChanged = true; + } + sourceSizeIndex--; + } + } + + if (dbChanged) { + // Make sure we haven't removed everything. + final Cursor c = context.getContentResolver().query( + LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); + boolean hasData = c.moveToNext(); + c.close(); + if (!hasData) { + throw new Exception("Removed every thing during grid resize"); + } + } + + return true; + } catch (Exception e) { + Log.e(TAG, "Error during grid migration", e); + + return false; + } finally { + Log.v(TAG, "Workspace migration completed in " + + (System.currentTimeMillis() - migrationStartTime)); + + // Save current configuration, so that the migration does not run again. + prefs.edit() + .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString) + .putString(KEY_MIGRATION_SRC_HOTSEAT_SIZE, hotseatSizeString) + .remove(KEY_MIGRATION_WIDGET_MINSIZE) + .apply(); + } + } } diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java index 30f228c68..ddc9cbfd9 100644 --- a/src/com/android/launcher3/model/PackageItemInfo.java +++ b/src/com/android/launcher3/model/PackageItemInfo.java @@ -20,8 +20,6 @@ import android.graphics.Bitmap; import com.android.launcher3.ItemInfo; -import java.util.Arrays; - /** * Represents a {@link Package} in the widget tray section. */ @@ -59,7 +57,6 @@ public class PackageItemInfo extends ItemInfo { return "PackageItemInfo(title=" + title + " id=" + this.id + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY - + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) - + " user=" + user + ")"; + + " spanX=" + spanX + " spanY=" + spanY + " user=" + user + ")"; } } diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java index 1bb57874e..89821768c 100644 --- a/src/com/android/launcher3/testing/LauncherExtension.java +++ b/src/com/android/launcher3/testing/LauncherExtension.java @@ -373,7 +373,8 @@ public class LauncherExtension extends Launcher { @Override public void onScrollInteractionEnd() { if (mProgress > 25 && mLauncherOverlayCallbacks.enterFullImmersion()) { - ObjectAnimator oa = LauncherAnimUtils.ofFloat(mSearchOverlay, "translationX", 0); + ObjectAnimator oa = LauncherAnimUtils.ofFloat( + mSearchOverlay, View.TRANSLATION_X, 0); oa.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator arg0) { diff --git a/src/com/android/launcher3/userevent/Logger.java b/src/com/android/launcher3/userevent/Logger.java new file mode 100644 index 000000000..ae9041a4b --- /dev/null +++ b/src/com/android/launcher3/userevent/Logger.java @@ -0,0 +1,208 @@ +package com.android.launcher3.userevent; + +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.Stats; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action; +import com.android.launcher3.userevent.nano.LauncherLogProto.Target; +import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; + +import java.util.Locale; + +/** + * Creates {@LauncherLogProto} nano protobuf object that can be used for user event + * metrics analysis. + */ +public class Logger { + + private static final String TAG = "UserEventLogger"; + private static final boolean DEBUG = false; + + private long mActionDurationMillis; + private long mElapsedContainerMillis; + private long mElapsedSessionMillis; + + private Context mContext; + + public Logger(Context context) { + mContext = context; + } + + public void logAppLaunch(String provider, ShortcutInfo shortcut, Bundle bundle) { + LauncherEvent event = new LauncherEvent(); + event.action = new Action(); + event.action.type = Action.TOUCH; + event.action.touch = Action.TAP; + + event.srcTarget = new Target(); + event.srcTarget.type = Target.ITEM; + event.srcTarget.itemType = LauncherLogProto.APP_ICON; + // TODO: package hash name should be different per device. + event.srcTarget.packageNameHash = provider.hashCode(); + + event.srcTarget.parent = new Target(); + String subContainer = bundle.getString(Stats.SOURCE_EXTRA_SUB_CONTAINER); + + if (shortcut != null) { + event.srcTarget.parent.containerType = getContainerType(shortcut); + event.srcTarget.pageIndex = (int) shortcut.screenId; + event.srcTarget.gridX = shortcut.cellX; + event.srcTarget.gridX = shortcut.cellY; + } + if (subContainer != null) { + event.srcTarget.parent.type = Target.CONTAINER; + if (subContainer.equals(Stats.SUB_CONTAINER_FOLDER)) { + event.srcTarget.parent.containerType = LauncherLogProto.FOLDER; + } else if (subContainer.equals(Stats.SUB_CONTAINER_ALL_APPS_A_Z)) { + event.srcTarget.parent.containerType = LauncherLogProto.ALLAPPS; + } else if (subContainer.equals(Stats.CONTAINER_HOTSEAT)) { + event.srcTarget.parent.containerType = LauncherLogProto.HOTSEAT; + } else if (subContainer.equals(Stats.SUB_CONTAINER_ALL_APPS_PREDICTION)) { + event.srcTarget.parent.containerType = LauncherLogProto.PREDICTION; + } + + if (DEBUG) { + Log.d(TAG, String.format("parent bundle: %s %s %s %s", + bundle.getString(Stats.SOURCE_EXTRA_CONTAINER), + bundle.getString(Stats.SOURCE_EXTRA_CONTAINER_PAGE), + bundle.getString(Stats.SOURCE_EXTRA_SUB_CONTAINER), + bundle.getString(Stats.SOURCE_EXTRA_SUB_CONTAINER_PAGE))); + } + } + + + // Assign timeToAction + event.elapsedContainerMillis = System.currentTimeMillis() - mElapsedContainerMillis; + event.elapsedSessionMillis = System.currentTimeMillis() - mElapsedSessionMillis; + + // Debug + processLauncherEvent(event); + } + + public void resetElapsedContainerMillis() { + mElapsedContainerMillis = System.currentTimeMillis(); + } + + public void resetElapsedSessionMillis() { + mElapsedSessionMillis = System.currentTimeMillis(); + } + + // + // Debugging helper methods. + // toString() cannot be overriden inside auto generated {@link LauncherLogProto}. + // Note: switch statement cannot be replaced with reflection as proguard strips the constants + + private static void processLauncherEvent(LauncherEvent ev) { + if (DEBUG) { + if (ev.action.touch == Action.TAP && ev.srcTarget.itemType == LauncherLogProto.APP_ICON) { + Log.d(TAG, String.format(Locale.US, "action:%s target:%s\n\telapsed container %d ms session %d ms", + getActionStr(ev.action), + getTargetStr(ev.srcTarget), + ev.elapsedContainerMillis, + ev.elapsedSessionMillis)); + } + } + } + + private static int getContainerType(ShortcutInfo shortcut) { + switch ((int) shortcut.container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: return LauncherLogProto.WORKSPACE; + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: return LauncherLogProto.HOTSEAT; + default: + return (int) shortcut.container; + } + } + + private static String getActionStr(Action action) { + switch(action.touch) { + case Action.TAP: return "TAP"; + case Action.LONGPRESS: return "LONGPRESS"; + case Action.DRAGDROP: return "DRAGDROP"; + case Action.PINCH: return "PINCH"; + default: return "UNKNOWN"; + } + } + + private static String getTargetStr(Target t) { + String typeStr; + switch (t.type) { + case LauncherLogProto.Target.ITEM: + return getItemStr(t); + case LauncherLogProto.Target.CONTROL: + return getControlStr(t); + case LauncherLogProto.Target.CONTAINER: + return getContainerStr(t); + default: + return "UNKNOWN TARGET TYPE"; + } + } + + private static String getItemStr(Target t) { + String typeStr = ""; + switch(t.itemType){ + case LauncherLogProto.APP_ICON: typeStr = "ICON"; break; + case LauncherLogProto.SHORTCUT: typeStr = "SHORTCUT"; break; + case LauncherLogProto.WIDGET: typeStr = "WIDGET"; break; + default: typeStr = "UNKNOWN"; + } + + return typeStr + " " + t.packageNameHash + " grid=(" + t.gridX + "," + t.gridY + ") " + + getContainerStr(t.parent); + } + + private static String getControlStr(Target t) { + switch(t.controlType) { + case LauncherLogProto.ALL_APPS_BUTTON: return "ALL_APPS_BUTTON"; + case LauncherLogProto.WIDGETS_BUTTON: return "WIDGETS_BUTTON"; + case LauncherLogProto.WALLPAPER_BUTTON: return "WALLPAPER_BUTTON"; + case LauncherLogProto.SETTINGS_BUTTON: return "SETTINGS_BUTTON"; + case LauncherLogProto.REMOVE_TARGET: return "REMOVE_TARGET"; + case LauncherLogProto.UNINSTALL_TARGET: return "UNINSTALL_TARGET"; + case LauncherLogProto.APPINFO_TARGET: return "APPINFO_TARGET"; + case LauncherLogProto.RESIZE_HANDLE: return "RESIZE_HANDLE"; + case LauncherLogProto.FAST_SCROLL_HANDLE: return "FAST_SCROLL_HANDLE"; + default: return "UNKNOWN"; + } + } + + private static String getContainerStr(Target t) { + String str; + Log.d(TAG, "t.containerType" + t.containerType); + switch (t.containerType) { + case LauncherLogProto.WORKSPACE: + str = "WORKSPACE"; + break; + case LauncherLogProto.HOTSEAT: + str = "HOTSEAT"; + break; + case LauncherLogProto.FOLDER: + str = "FOLDER"; + break; + case LauncherLogProto.ALLAPPS: + str = "ALLAPPS"; + break; + case LauncherLogProto.WIDGETS: + str = "WIDGETS"; + break; + case LauncherLogProto.OVERVIEW: + str = "OVERVIEW"; + break; + case LauncherLogProto.PREDICTION: + str = "PREDICTION"; + break; + case LauncherLogProto.SEARCHRESULT: + str = "SEARCHRESULT"; + break; + default: + str = "UNKNOWN"; + } + return str + " id=" + t.pageIndex; + } +} + diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java index c61fa8859..bdb16390b 100644 --- a/src/com/android/launcher3/util/ConfigMonitor.java +++ b/src/com/android/launcher3/util/ConfigMonitor.java @@ -23,26 +23,30 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.util.Log; +import com.android.launcher3.Utilities; + /** * {@link BroadcastReceiver} which watches configuration changes and - * restarts the process in case changes which affect the device profile. + * restarts the process in case changes which affect the device profile occur. */ public class ConfigMonitor extends BroadcastReceiver { private final Context mContext; private final float mFontScale; + private final int mDensity; public ConfigMonitor(Context context) { mContext = context; Configuration config = context.getResources().getConfiguration(); mFontScale = config.fontScale; + mDensity = getDensity(config); } @Override public void onReceive(Context context, Intent intent) { Configuration config = context.getResources().getConfiguration(); - if (mFontScale != config.fontScale) { + if (mFontScale != config.fontScale || mDensity != getDensity(config)) { Log.d("ConfigMonitor", "Configuration changed, restarting launcher"); mContext.unregisterReceiver(this); android.os.Process.killProcess(android.os.Process.myPid()); @@ -52,4 +56,8 @@ public class ConfigMonitor extends BroadcastReceiver { public void register() { mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); } + + private static int getDensity(Configuration config) { + return Utilities.ATLEAST_JB_MR1 ? config.densityDpi : 0; + } } diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java index 55c5d7dc2..da8bae712 100644 --- a/src/com/android/launcher3/util/FlingAnimation.java +++ b/src/com/android/launcher3/util/FlingAnimation.java @@ -7,9 +7,9 @@ import android.graphics.PointF; import android.graphics.Rect; import android.view.animation.DecelerateInterpolator; -import com.android.launcher3.DragLayer; -import com.android.launcher3.DragView; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; public class FlingAnimation implements AnimatorUpdateListener { @@ -51,7 +51,7 @@ public class FlingAnimation implements AnimatorUpdateListener { mFrom.top += yOffset; mFrom.bottom -= yOffset; - mDuration = initDuration(); + mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration(); mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY); } @@ -62,7 +62,7 @@ public class FlingAnimation implements AnimatorUpdateListener { * - Calculate a constant acceleration in x direction such that the object reaches * {@link #mIconRect} in the given time. */ - protected int initDuration() { + protected int initFlingUpDuration() { float sY = -mFrom.bottom; float d = mUY * mUY + 2 * sY * MAX_ACCELERATION; @@ -83,6 +83,34 @@ public class FlingAnimation implements AnimatorUpdateListener { return (int) Math.round(t); } + /** + * The fling animation is based on the following system + * - Apply a constant force in the x direction to causing the fling to decelerate. + * - The animation runs for the time taken by the object to go out of the screen. + * - Calculate a constant acceleration in y direction such that the object reaches + * {@link #mIconRect} in the given time. + */ + protected int initFlingLeftDuration() { + float sX = -mFrom.right; + + float d = mUX * mUX + 2 * sX * MAX_ACCELERATION; + if (d >= 0) { + // sX can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for x direction. + mAX = MAX_ACCELERATION; + } else { + // sX is not reachable, decrease the acceleration so that sX is almost reached. + d = 0; + mAX = mUX * mUX / (2 * -sX); + } + double t = (-mUX - Math.sqrt(d)) / mAX; + + float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY(); + + // Find vertical acceleration such that: u*t + a*t*t/2 = s + mAY = (float) ((sY - t * mUY) * 2 / (t * t)); + return (int) Math.round(t); + } + public final int getDuration() { return mDuration + DRAG_END_DELAY; } diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java new file mode 100644 index 000000000..01808ba57 --- /dev/null +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.util; + +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewTreeObserver.OnDrawListener; + +import com.android.launcher3.DeferredHandler; +import com.android.launcher3.Launcher; + +import java.util.ArrayList; +import java.util.concurrent.Executor; + +/** + * An executor which runs all the tasks after the first onDraw is called on the target view. + */ +public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, + OnAttachStateChangeListener { + + private final ArrayList<Runnable> mTasks = new ArrayList<>(); + private final DeferredHandler mHandler; + + private Launcher mLauncher; + private View mAttachedView; + private boolean mCompleted; + + public ViewOnDrawExecutor(DeferredHandler handler) { + mHandler = handler; + } + + public void attachTo(Launcher launcher) { + mLauncher = launcher; + mAttachedView = launcher.getWorkspace(); + mAttachedView.addOnAttachStateChangeListener(this); + + attachObserver(); + } + + private void attachObserver() { + if (!mCompleted) { + mAttachedView.getViewTreeObserver().addOnDrawListener(this); + } + } + + @Override + public void execute(Runnable command) { + mTasks.add(command); + } + + @Override + public void onViewAttachedToWindow(View v) { + attachObserver(); + } + + @Override + public void onViewDetachedFromWindow(View v) { } + + @Override + public void onDraw() { + mAttachedView.post(this); + } + + @Override + public void run() { + for (final Runnable r : mTasks) { + mHandler.post(r); + } + markCompleted(); + } + + public void markCompleted() { + mTasks.clear(); + if (mAttachedView != null) { + mAttachedView.getViewTreeObserver().removeOnDrawListener(this); + mAttachedView.removeOnAttachStateChangeListener(this); + } + if (mLauncher != null) { + mLauncher.clearPendingExecutor(this); + } + } +} diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java new file mode 100644 index 000000000..1fbd0dc13 --- /dev/null +++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java @@ -0,0 +1,225 @@ +package com.android.launcher3.util; + +import android.app.WallpaperManager; +import android.os.IBinder; +import android.util.Log; +import android.view.Choreographer; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; + +/** + * Utility class to handle wallpaper scrolling along with workspace. + */ +public class WallpaperOffsetInterpolator implements Choreographer.FrameCallback { + private static final String TAG = "WPOffsetInterpolator"; + private static final int ANIMATION_DURATION = 250; + + // Don't use all the wallpaper for parallax until you have at least this many pages + private static final int MIN_PARALLAX_PAGE_SPAN = 3; + + private final Choreographer mChoreographer; + private final Interpolator mInterpolator; + private final WallpaperManager mWallpaperManager; + private final Workspace mWorkspace; + private final boolean mIsRtl; + + private IBinder mWindowToken; + private boolean mWallpaperIsLiveWallpaper; + private float mLastSetWallpaperOffsetSteps = 0; + + private float mFinalOffset = 0.0f; + private float mCurrentOffset = 0.5f; // to force an initial update + private boolean mWaitingForUpdate; + + private boolean mAnimating; + private long mAnimationStartTime; + private float mAnimationStartOffset; + int mNumScreens; + int mNumPagesForWallpaperParallax; + + public WallpaperOffsetInterpolator(Workspace workspace) { + mChoreographer = Choreographer.getInstance(); + mInterpolator = new DecelerateInterpolator(1.5f); + + mWorkspace = workspace; + mWallpaperManager = WallpaperManager.getInstance(workspace.getContext()); + mIsRtl = Utilities.isRtl(workspace.getResources()); + } + + @Override + public void doFrame(long frameTimeNanos) { + updateOffset(false); + } + + private void updateOffset(boolean force) { + if (mWaitingForUpdate || force) { + mWaitingForUpdate = false; + if (computeScrollOffset() && mWindowToken != null) { + try { + mWallpaperManager.setWallpaperOffsets(mWindowToken, getCurrX(), 0.5f); + setWallpaperOffsetSteps(); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Error updating wallpaper offset: " + e); + } + } + } + } + + public boolean computeScrollOffset() { + final float oldOffset = mCurrentOffset; + if (mAnimating) { + long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime; + float t0 = durationSinceAnimation / (float) ANIMATION_DURATION; + float t1 = mInterpolator.getInterpolation(t0); + mCurrentOffset = mAnimationStartOffset + + (mFinalOffset - mAnimationStartOffset) * t1; + mAnimating = durationSinceAnimation < ANIMATION_DURATION; + } else { + mCurrentOffset = mFinalOffset; + } + + if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) { + scheduleUpdate(); + } + if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) { + return true; + } + return false; + } + + public float wallpaperOffsetForScroll(int scroll) { + // TODO: do different behavior if it's a live wallpaper? + // Don't use up all the wallpaper parallax until you have at least + // MIN_PARALLAX_PAGE_SPAN pages + int numScrollingPages = getNumScreensExcludingEmptyAndCustom(); + int parallaxPageSpan; + if (mWallpaperIsLiveWallpaper) { + parallaxPageSpan = numScrollingPages - 1; + } else { + parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1); + } + mNumPagesForWallpaperParallax = parallaxPageSpan; + + if (mWorkspace.getChildCount() <= 1) { + if (mIsRtl) { + return 1 - 1.0f/mNumPagesForWallpaperParallax; + } + return 0; + } + + // Exclude the leftmost page + int emptyExtraPages = numEmptyScreensToIgnore(); + int firstIndex = mWorkspace.numCustomPages(); + // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages) + int lastIndex = mWorkspace.getChildCount() - 1 - emptyExtraPages; + if (mIsRtl) { + int temp = firstIndex; + firstIndex = lastIndex; + lastIndex = temp; + } + + int firstPageScrollX = mWorkspace.getScrollForPage(firstIndex); + int scrollRange = mWorkspace.getScrollForPage(lastIndex) - firstPageScrollX; + if (scrollRange == 0) { + return 0; + } else { + // Sometimes the left parameter of the pages is animated during a layout transition; + // this parameter offsets it to keep the wallpaper from animating as well + int adjustedScroll = + scroll - firstPageScrollX - mWorkspace.getLayoutTransitionOffsetForPage(0); + float offset = Math.min(1, adjustedScroll / (float) scrollRange); + offset = Math.max(0, offset); + + // On RTL devices, push the wallpaper offset to the right if we don't have enough + // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN) + if (!mWallpaperIsLiveWallpaper && numScrollingPages < MIN_PARALLAX_PAGE_SPAN + && mIsRtl) { + return offset * (parallaxPageSpan - numScrollingPages + 1) / parallaxPageSpan; + } + return offset * (numScrollingPages - 1) / parallaxPageSpan; + } + } + + private float wallpaperOffsetForCurrentScroll() { + return wallpaperOffsetForScroll(mWorkspace.getScrollX()); + } + + private int numEmptyScreensToIgnore() { + int numScrollingPages = mWorkspace.getChildCount() - mWorkspace.numCustomPages(); + if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) { + return 1; + } else { + return 0; + } + } + + private int getNumScreensExcludingEmptyAndCustom() { + return mWorkspace.getChildCount() - numEmptyScreensToIgnore() - mWorkspace.numCustomPages(); + } + + public void syncWithScroll() { + float offset = wallpaperOffsetForCurrentScroll(); + setFinalX(offset); + updateOffset(true); + } + + public float getCurrX() { + return mCurrentOffset; + } + + public float getFinalX() { + return mFinalOffset; + } + + private void animateToFinal() { + mAnimating = true; + mAnimationStartOffset = mCurrentOffset; + mAnimationStartTime = System.currentTimeMillis(); + } + + private void setWallpaperOffsetSteps() { + // Set wallpaper offset steps (1 / (number of screens - 1)) + float xOffset = 1.0f / mNumPagesForWallpaperParallax; + if (xOffset != mLastSetWallpaperOffsetSteps) { + mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f); + mLastSetWallpaperOffsetSteps = xOffset; + } + } + + public void setFinalX(float x) { + scheduleUpdate(); + mFinalOffset = Math.max(0f, Math.min(x, 1.0f)); + if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) { + if (mNumScreens > 0) { + // Don't animate if we're going from 0 screens + animateToFinal(); + } + mNumScreens = getNumScreensExcludingEmptyAndCustom(); + } + } + + private void scheduleUpdate() { + if (!mWaitingForUpdate) { + mChoreographer.postFrameCallback(this); + mWaitingForUpdate = true; + } + } + + public void jumpToFinal() { + mCurrentOffset = mFinalOffset; + } + + public void onResume() { + mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null; + // Force the wallpaper offset steps to be set again, because another app might have changed + // them + mLastSetWallpaperOffsetSteps = 0f; + } + + public void setWindowToken(IBinder token) { + mWindowToken = token; + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java deleted file mode 100644 index b9fccbcfd..000000000 --- a/src/com/android/launcher3/util/WallpaperUtils.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.util; - -import android.annotation.TargetApi; -import android.app.WallpaperManager; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.graphics.Point; -import android.os.Build; -import android.view.WindowManager; - -import com.android.launcher3.Utilities; - -/** - * Utility methods for wallpaper management. - */ -public final class WallpaperUtils { - - public static final String WALLPAPER_WIDTH_KEY = "wallpaper.width"; - public static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height"; - public static final float WALLPAPER_SCREENS_SPAN = 2f; - - public static void suggestWallpaperDimension(Resources res, - final SharedPreferences sharedPrefs, - WindowManager windowManager, - final WallpaperManager wallpaperManager, boolean fallBackToDefaults) { - final Point defaultWallpaperSize = WallpaperUtils.getDefaultWallpaperSize(res, windowManager); - // If we have saved a wallpaper width/height, use that instead - - int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1); - int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1); - - if (savedWidth == -1 || savedHeight == -1) { - if (!fallBackToDefaults) { - return; - } else { - savedWidth = defaultWallpaperSize.x; - savedHeight = defaultWallpaperSize.y; - } - } - - if (savedWidth != wallpaperManager.getDesiredMinimumWidth() || - savedHeight != wallpaperManager.getDesiredMinimumHeight()) { - wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight); - } - } - - /** - * As a ratio of screen height, the total distance we want the parallax effect to span - * horizontally - */ - public static float wallpaperTravelToScreenWidthRatio(int width, int height) { - float aspectRatio = width / (float) height; - - // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width - // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width - // We will use these two data points to extrapolate how much the wallpaper parallax effect - // to span (ie travel) at any aspect ratio: - - final float ASPECT_RATIO_LANDSCAPE = 16/10f; - final float ASPECT_RATIO_PORTRAIT = 10/16f; - final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; - final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; - - // To find out the desired width at different aspect ratios, we use the following two - // formulas, where the coefficient on x is the aspect ratio (width/height): - // (16/10)x + y = 1.5 - // (10/16)x + y = 1.2 - // We solve for x and y and end up with a final formula: - final float x = - (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / - (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); - final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; - return x * aspectRatio + y; - } - - private static Point sDefaultWallpaperSize; - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - public static Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) { - if (sDefaultWallpaperSize == null) { - Point minDims = new Point(); - Point maxDims = new Point(); - windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims); - - int maxDim = Math.max(maxDims.x, maxDims.y); - int minDim = Math.max(minDims.x, minDims.y); - - if (Utilities.ATLEAST_JB_MR1) { - Point realSize = new Point(); - windowManager.getDefaultDisplay().getRealSize(realSize); - maxDim = Math.max(realSize.x, realSize.y); - minDim = Math.min(realSize.x, realSize.y); - } - - // We need to ensure that there is enough extra space in the wallpaper - // for the intended parallax effects - final int defaultWidth, defaultHeight; - if (res.getConfiguration().smallestScreenWidthDp >= 720) { - defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); - defaultHeight = maxDim; - } else { - defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); - defaultHeight = maxDim; - } - sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight); - } - return sDefaultWallpaperSize; - } -} diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 94bbd929f..70eceb9ce 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -36,6 +36,7 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.SimpleOnStylusPressListener; import com.android.launcher3.R; import com.android.launcher3.StylusEventHelper; import com.android.launcher3.WidgetPreviewLoader; @@ -93,7 +94,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { final Resources r = context.getResources(); mLauncher = (Launcher) context; - mStylusEventHelper = new StylusEventHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mDimensionsFormatString = r.getString(R.string.widget_dims_format); setContainerWidth(); @@ -211,7 +212,7 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { @Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = super.onTouchEvent(ev); - if (mStylusEventHelper.checkAndPerformStylusEvent(ev)) { + if (mStylusEventHelper.onMotionEvent(ev)) { return true; } return handled; diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java index 5d3af5254..b47ba84fc 100644 --- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java +++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java @@ -10,13 +10,14 @@ import android.util.Log; import android.view.View; import com.android.launcher3.AppWidgetResizeFrame; -import com.android.launcher3.DragController; -import com.android.launcher3.DragLayer; import com.android.launcher3.DragSource; +import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.AppWidgetManagerCompat; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Thunk; public class WidgetHostViewLoader implements DragController.DragListener { @@ -46,7 +47,7 @@ public class WidgetHostViewLoader implements DragController.DragListener { } @Override - public void onDragStart(DragSource source, Object info, int dragAction) { } + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } @Override public void onDragEnd() { diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 0173eb771..4c901c2be 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView.State; import android.util.AttributeSet; @@ -31,10 +32,9 @@ import com.android.launcher3.BaseContainerView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.DragController; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.Folder; +import com.android.launcher3.folder.Folder; import com.android.launcher3.IconCache; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; @@ -44,6 +44,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.Workspace; +import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.util.Thunk; @@ -85,7 +86,7 @@ public class WidgetsContainerView extends BaseContainerView super(context, attrs, defStyleAttr); mLauncher = (Launcher) context; mDragController = mLauncher.getDragController(); - mAdapter = new WidgetsListAdapter(context, this, this, mLauncher); + mAdapter = new WidgetsListAdapter(this, this, mLauncher); mIconCache = (LauncherAppState.getInstance()).getIconCache(); if (LOGD) { Log.d(TAG, "WidgetsContainerView constructor"); @@ -239,9 +240,11 @@ public class WidgetsContainerView extends BaseContainerView // Start the drag mLauncher.lockScreenOrientation(); - mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha); mDragController.startDrag(image, preview, this, createItemInfo, bounds, DragController.DRAG_ACTION_COPY, scale); + // This call expects the extra empty screen to already be created, which is why we call it + // after mDragController.startDrag(). + mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha); preview.recycle(); return true; @@ -253,7 +256,7 @@ public class WidgetsContainerView extends BaseContainerView @Override public boolean supportsFlingToDelete() { - return false; + return true; } @Override @@ -306,7 +309,7 @@ public class WidgetsContainerView extends BaseContainerView int currentScreen = mLauncher.getCurrentWorkspaceScreen(); Workspace workspace = (Workspace) target; CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); - ItemInfo itemInfo = (ItemInfo) d.dragInfo; + ItemInfo itemInfo = d.dragInfo; if (layout != null) { showOutOfSpaceMessage = !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java index 885d96fe5..ac9d62e9a 100644 --- a/src/com/android/launcher3/widget/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java @@ -64,20 +64,17 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { private View.OnClickListener mIconClickListener; private View.OnLongClickListener mIconLongClickListener; - private static final int PRESET_INDENT_SIZE_TABLET = 56; - private int mIndent = 0; + private final int mIndent; - public WidgetsListAdapter(Context context, - View.OnClickListener iconClickListener, + public WidgetsListAdapter(View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener, Launcher launcher) { - mLayoutInflater = LayoutInflater.from(context); + mLayoutInflater = launcher.getLayoutInflater(); mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; mLauncher = launcher; - - setContainerHeight(); + mIndent = launcher.getResources().getDimensionPixelSize(R.dimen.widget_section_indent); } public void setWidgetsModel(WidgetsModel w) { @@ -206,12 +203,4 @@ public class WidgetsListAdapter extends Adapter<WidgetsRowViewHolder> { } return mWidgetPreviewLoader; } - - private void setContainerHeight() { - Resources r = mLauncher.getResources(); - DeviceProfile profile = mLauncher.getDeviceProfile(); - if (profile.isLargeTablet || profile.isTablet) { - mIndent = Utilities.pxFromDp(PRESET_INDENT_SIZE_TABLET, r.getDisplayMetrics()); - } - } } diff --git a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java index 249559ab9..19bc868a4 100644 --- a/src/com/android/launcher3/widget/WidgetsRowViewHolder.java +++ b/src/com/android/launcher3/widget/WidgetsRowViewHolder.java @@ -16,10 +16,7 @@ package com.android.launcher3.widget; import android.support.v7.widget.RecyclerView.ViewHolder; -import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; public class WidgetsRowViewHolder extends ViewHolder { |