From fc956e5a2a818c06ed3424e15b0aa20a3f604658 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 17 Feb 2016 13:24:20 -0800 Subject: Reverting refactoring of Folder and FolderIcon to avoid dependencies breakage Change-Id: Ifd0d717d70aff4c83c9eb5cba397d04500b2c869 --- src/com/android/launcher3/BubbleTextView.java | 1 - src/com/android/launcher3/CellLayout.java | 4 +- src/com/android/launcher3/DeleteDropTarget.java | 1 - src/com/android/launcher3/FocusHelper.java | 1 - src/com/android/launcher3/Folder.java | 1490 +++++++++++++++++++ src/com/android/launcher3/FolderIcon.java | 756 ++++++++++ src/com/android/launcher3/Launcher.java | 2 - src/com/android/launcher3/LauncherModel.java | 2 - src/com/android/launcher3/ShortcutInfo.java | 4 - src/com/android/launcher3/Workspace.java | 4 +- .../LauncherAccessibilityDelegate.java | 2 +- .../launcher3/allapps/AllAppsContainerView.java | 2 +- src/com/android/launcher3/dragndrop/DragLayer.java | 4 +- .../folder/ClippedFolderIconLayoutRule.java | 7 +- src/com/android/launcher3/folder/Folder.java | 1506 -------------------- src/com/android/launcher3/folder/FolderIcon.java | 776 ---------- .../android/launcher3/folder/FolderPagedView.java | 2 + .../folder/StackFolderIconLayoutRule.java | 5 +- .../launcher3/widget/WidgetsContainerView.java | 3 +- 19 files changed, 2260 insertions(+), 2312 deletions(-) create mode 100644 src/com/android/launcher3/Folder.java create mode 100644 src/com/android/launcher3/FolderIcon.java delete mode 100644 src/com/android/launcher3/folder/Folder.java delete mode 100644 src/com/android/launcher3/folder/FolderIcon.java (limited to 'src/com') diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index dddd826c4..cc6df599a 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -40,7 +40,6 @@ 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; diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 5832b9f0d..9370e57b5 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -32,7 +32,6 @@ 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; @@ -54,8 +53,7 @@ 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.FolderIcon.FolderRingAnimator; import com.android.launcher3.util.ParcelableSparseArray; import com.android.launcher3.util.Thunk; diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 997ded2d3..173e6ab65 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -24,7 +24,6 @@ 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; diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index f99c08a59..95b562328 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -23,7 +23,6 @@ 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; diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java new file mode 100644 index 000000000..f84bee27b --- /dev/null +++ b/src/com/android/launcher3/Folder.java @@ -0,0 +1,1490 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.PropertyValuesHolder; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.text.InputType; +import android.text.Selection; +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.ViewDebug; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AnimationUtils; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.launcher3.CellLayout.CellInfo; +import com.android.launcher3.FolderInfo.FolderListener; +import com.android.launcher3.UninstallDropTarget.UninstallSource; +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.folder.FolderPagedView; +import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.UiThreadCircularReveal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Represents a set of icons chosen by the user or generated by the system. + */ +public class Folder extends LinearLayout implements DragSource, View.OnClickListener, + View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, + View.OnFocusChangeListener, DragListener, UninstallSource, AccessibilityDragSource, + Stats.LaunchSourceProvider { + private static final String TAG = "Launcher.Folder"; + + /** + * We avoid measuring {@link #mContentWrapper} with a 0 width or height, as this + * results in CellLayout being measured as UNSPECIFIED, which it does not support. + */ + private static final int MIN_CONTENT_DIMEN = 5; + + static final int STATE_NONE = -1; + static final int STATE_SMALL = 0; + static final int STATE_ANIMATING = 1; + static final int STATE_OPEN = 2; + + /** + * Time for which the scroll hint is shown before automatically changing page. + */ + public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY; + + /** + * Fraction of icon width which behave as scroll region. + */ + private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f; + + private static final int FOLDER_NAME_ANIMATION_DURATION = 633; + + private static final int REORDER_DELAY = 250; + private static final int ON_EXIT_CLOSE_DELAY = 400; + private static final Rect sTempRect = new Rect(); + + private static String sDefaultFolderName; + private static String sHintText; + + private final Alarm mReorderAlarm = new Alarm(); + private final Alarm mOnExitAlarm = new Alarm(); + private final Alarm mOnScrollHintAlarm = new Alarm(); + @Thunk final Alarm mScrollPauseAlarm = new Alarm(); + + @Thunk final ArrayList mItemsInReadingOrder = new ArrayList(); + + private final int mExpandDuration; + private final int mMaterialExpandDuration; + private final int mMaterialExpandStagger; + + private final InputMethodManager mInputMethodManager; + + public final Launcher mLauncher; + protected DragController mDragController; + public FolderInfo mInfo; + + @Thunk + FolderIcon mFolderIcon; + + @Thunk + FolderPagedView mContent; + @Thunk View mContentWrapper; + public ExtendedEditText mFolderName; + + private View mFooter; + private int mFooterHeight; + + // 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; + private View mCurrentDragView; + private boolean mIsExternalDrag; + boolean mSuppressOnAdd = false; + private boolean mDragInProgress = false; + private boolean mDeleteFolderOnDropCompleted = false; + private boolean mSuppressFolderDeletion = false; + private boolean mItemAddedBackToSelfViaIcon = false; + @Thunk float mFolderIconPivotX; + @Thunk float mFolderIconPivotY; + private boolean mIsEditingName = false; + + @ViewDebug.ExportedProperty(category = "launcher") + private boolean mDestroyed; + + @Thunk Runnable mDeferredAction; + private boolean mDeferDropAfterUninstall; + private boolean mUninstallSuccessful; + + // Folder scrolling + private int mScrollAreaOffset; + + @Thunk int mScrollHintDir = DragController.SCROLL_NONE; + @Thunk int mCurrentScrollDir = DragController.SCROLL_NONE; + + /** + * Used to inflate the Workspace from XML. + * + * @param context The application's context. + * @param attrs The attributes set containing the Workspace's customization values. + */ + public Folder(Context context, AttributeSet attrs) { + super(context, attrs); + setAlwaysDrawnWithCacheEnabled(false); + mInputMethodManager = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + Resources res = getResources(); + mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration); + mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration); + mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger); + + if (sDefaultFolderName == null) { + sDefaultFolderName = res.getString(R.string.folder_name); + } + if (sHintText == null) { + sHintText = res.getString(R.string.folder_hint_text); + } + mLauncher = (Launcher) context; + // We need this view to be focusable in touch mode so that when text editing of the folder + // name is complete, we have something to focus on, thus hiding the cursor and giving + // reliable behavior when clicking the text field (since it will always gain focus on click). + setFocusableInTouchMode(true); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mContentWrapper = findViewById(R.id.folder_content_wrapper); + mContent = (FolderPagedView) findViewById(R.id.folder_content); + mContent.setFolder(this); + + mFolderName = (ExtendedEditText) findViewById(R.id.folder_name); + mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() { + @Override + public boolean onBackKey() { + // Close the activity on back key press + doneEditingFolderName(true); + return false; + } + }); + mFolderName.setOnFocusChangeListener(this); + + 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() | + InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); + + mFooter = findViewById(R.id.folder_footer); + + // We find out how tall footer wants to be (it is set to wrap_content), so that + // we can allocate the appropriate amount of space for it. + int measureSpec = MeasureSpec.UNSPECIFIED; + mFooter.measure(measureSpec, measureSpec); + mFooterHeight = mFooter.getMeasuredHeight(); + } + + public void onClick(View v) { + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + mLauncher.onClick(v); + } + } + + public boolean onLongClick(View v) { + // Return if global dragging is not enabled + if (!mLauncher.isDraggingEnabled()) return true; + return beginDrag(v, false); + } + + private boolean beginDrag(View v, boolean accessible) { + Object tag = v.getTag(); + if (tag instanceof ShortcutInfo) { + ShortcutInfo item = (ShortcutInfo) tag; + if (!v.isInTouchMode()) { + return false; + } + + mLauncher.getWorkspace().beginDragShared(v, new Point(), this, accessible); + + mCurrentDragInfo = item; + mEmptyCellRank = item.rank; + mCurrentDragView = v; + + mContent.removeItem(mCurrentDragView); + mInfo.remove(mCurrentDragInfo); + mDragInProgress = true; + mItemAddedBackToSelfViaIcon = false; + } + return true; + } + + @Override + public void startDrag(CellInfo cellInfo, boolean accessible) { + beginDrag(cellInfo.cell, accessible); + } + + @Override + public void enableAccessibleDrag(boolean enable) { + mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable); + for (int i = 0; i < mContent.getChildCount(); i++) { + mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG); + } + + mFooter.setImportantForAccessibility(enable ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS : + IMPORTANT_FOR_ACCESSIBILITY_AUTO); + mLauncher.getWorkspace().setAddNewPageOnDrag(!enable); + } + + public boolean isEditingName() { + return mIsEditingName; + } + + public void startEditingFolderName() { + mFolderName.setHint(""); + mIsEditingName = true; + } + + public void dismissEditingName() { + mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + doneEditingFolderName(true); + } + + public void doneEditingFolderName(boolean commit) { + mFolderName.setHint(sHintText); + // Convert to a string here to ensure that no other state associated with the text field + // gets saved. + String newTitle = mFolderName.getText().toString(); + mInfo.setTitle(newTitle); + LauncherModel.updateItemInDatabase(mLauncher, mInfo); + + if (commit) { + sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + getContext().getString(R.string.folder_renamed, newTitle)); + } + + // This ensures that focus is gained every time the field is clicked, which selects all + // the text and brings up the soft keyboard if necessary. + mFolderName.clearFocus(); + + Selection.setSelection((Spannable) mFolderName.getText(), 0, 0); + mIsEditingName = false; + } + + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + dismissEditingName(); + return true; + } + return false; + } + + public View getEditTextRegion() { + return mFolderName; + } + + /** + * We need to handle touch events to prevent them from falling through to the workspace below. + */ + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent ev) { + return true; + } + + public void setDragController(DragController dragController) { + mDragController = dragController; + } + + public void setFolderIcon(FolderIcon icon) { + mFolderIcon = icon; + } + + @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 + */ + public FolderInfo getInfo() { + return mInfo; + } + + void bind(FolderInfo info) { + mInfo = info; + ArrayList children = info.contents; + Collections.sort(children, ITEM_POS_COMPARATOR); + + ArrayList overflow = mContent.bindItems(children); + + // If our folder has too many items we prune them from the list. This is an issue + // when upgrading from the old Folders implementation which could contain an unlimited + // number of items. + for (ShortcutInfo item: overflow) { + mInfo.remove(item); + LauncherModel.deleteItemFromDatabase(mLauncher, item); + } + + DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + if (lp == null) { + lp = new DragLayer.LayoutParams(0, 0); + lp.customPosition = true; + setLayoutParams(lp); + } + centerAboutIcon(); + + mItemsInvalidated = true; + updateTextViewFocus(); + mInfo.addListener(this); + + if (!sDefaultFolderName.contentEquals(mInfo.title)) { + mFolderName.setText(mInfo.title); + } else { + mFolderName.setText(""); + } + + // In case any children didn't come across during loading, clean up the folder accordingly + mFolderIcon.post(new Runnable() { + public void run() { + if (getItemCount() <= 1) { + replaceFolderWithFinalItem(); + } + } + }); + } + + /** + * Creates a new UserFolder, inflated from R.layout.user_folder. + * + * @param launcher The main activity. + * + * @return A new UserFolder. + */ + @SuppressLint("InflateParams") + static Folder fromXml(Launcher launcher) { + return (Folder) launcher.getLayoutInflater().inflate( + FeatureFlags.LAUNCHER3_ICON_NORMALIZATION + ? R.layout.user_folder_icon_normalized : R.layout.user_folder, null); + } + + /** + * This method is intended to make the UserFolder to be visually identical in size and position + * to its associated FolderIcon. This allows for a seamless transition into the expanded state. + */ + private void positionAndSizeAsIcon() { + if (!(getParent() instanceof DragLayer)) return; + setScaleX(0.8f); + setScaleY(0.8f); + setAlpha(0f); + mState = STATE_SMALL; + } + + private void prepareReveal() { + setScaleX(1f); + setScaleY(1f); + setAlpha(1f); + mState = STATE_SMALL; + } + + public void animateOpen() { + if (!(getParent() instanceof DragLayer)) return; + + mContent.completePendingPageChanges(); + if (!mDragInProgress) { + // Open on the first page. + mContent.snapToPageImmediately(0); + } + + // This is set to true in close(), but isn't reset to false until onDropCompleted(). This + // leads to an consistent state if you drag out of the folder and drag back in without + // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. + mDeleteFolderOnDropCompleted = false; + + Animator openFolderAnim = null; + final Runnable onCompleteRunnable; + if (!Utilities.ATLEAST_LOLLIPOP) { + positionAndSizeAsIcon(); + centerAboutIcon(); + + final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1); + oa.setDuration(mExpandDuration); + openFolderAnim = oa; + + setLayerType(LAYER_TYPE_HARDWARE, null); + onCompleteRunnable = new Runnable() { + @Override + public void run() { + setLayerType(LAYER_TYPE_NONE, null); + } + }; + } else { + prepareReveal(); + centerAboutIcon(); + + AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); + int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); + int height = getFolderHeight(); + + float transX = - 0.075f * (width / 2 - getPivotX()); + float transY = - 0.075f * (height / 2 - getPivotY()); + setTranslationX(transX); + setTranslationY(transY); + 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); + drift.setStartDelay(mMaterialExpandStagger); + drift.setInterpolator(new LogDecelerateInterpolator(100, 0)); + + int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX()); + int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY()); + float radius = (float) Math.hypot(rx, ry); + + Animator reveal = UiThreadCircularReveal.createCircularReveal(this, (int) getPivotX(), + (int) getPivotY(), 0, radius); + reveal.setDuration(mMaterialExpandDuration); + reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); + + mContentWrapper.setAlpha(0f); + Animator iconsAlpha = ObjectAnimator.ofFloat(mContentWrapper, "alpha", 0f, 1f); + iconsAlpha.setDuration(mMaterialExpandDuration); + iconsAlpha.setStartDelay(mMaterialExpandStagger); + iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); + + mFooter.setAlpha(0f); + Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f); + textAlpha.setDuration(mMaterialExpandDuration); + textAlpha.setStartDelay(mMaterialExpandStagger); + textAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); + + anim.play(drift); + anim.play(iconsAlpha); + anim.play(textAlpha); + anim.play(reveal); + + openFolderAnim = anim; + + mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null); + mFooter.setLayerType(LAYER_TYPE_HARDWARE, null); + onCompleteRunnable = new Runnable() { + @Override + public void run() { + mContentWrapper.setLayerType(LAYER_TYPE_NONE, null); + mContentWrapper.setLayerType(LAYER_TYPE_NONE, null); + } + }; + } + openFolderAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + mContent.getAccessibilityDescription()); + mState = STATE_ANIMATING; + } + @Override + public void onAnimationEnd(Animator animation) { + mState = STATE_OPEN; + + onCompleteRunnable.run(); + mContent.setFocusOnFirstChild(); + } + }); + + // Footer animation + if (mContent.getPageCount() > 1 && !mInfo.hasOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION)) { + int footerWidth = mContent.getDesiredWidth() + - mFooter.getPaddingLeft() - mFooter.getPaddingRight(); + + float textWidth = mFolderName.getPaint().measureText(mFolderName.getText().toString()); + float translation = (footerWidth - textWidth) / 2; + mFolderName.setTranslationX(mContent.mIsRtl ? -translation : translation); + mContent.setMarkerScale(0); + + // Do not update the flag if we are in drag mode. The flag will be updated, when we + // actually drop the icon. + final boolean updateAnimationFlag = !mDragInProgress; + openFolderAnim.addListener(new AnimatorListenerAdapter() { + + @SuppressLint("InlinedApi") + @Override + public void onAnimationEnd(Animator animation) { + mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION) + .translationX(0) + .setInterpolator(Utilities.ATLEAST_LOLLIPOP ? + AnimationUtils.loadInterpolator(mLauncher, + android.R.interpolator.fast_out_slow_in) + : new LogDecelerateInterpolator(100, 0)); + mContent.animateMarkers(); + + if (updateAnimationFlag) { + mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher); + } + } + }); + } else { + mFolderName.setTranslationX(0); + mContent.setMarkerScale(1); + } + + openFolderAnim.start(); + + // 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()); + } + + /** + * 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) { + mCurrentDragInfo = item; + mEmptyCellRank = mContent.allocateRankForNewItem(item); + mIsExternalDrag = true; + mDragInProgress = true; + + // Since this folder opened by another controller, it might not get onDrop or + // onDropComplete. Perform cleanup once drag-n-drop ends. + mDragController.addDragListener(this); + } + + @Override + public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } + + @Override + public void onDragEnd() { + if (mIsExternalDrag && mDragInProgress) { + completeDragExit(); + } + mDragController.removeDragListener(this); + } + + @Thunk void sendCustomAccessibilityEvent(int type, String text) { + AccessibilityManager accessibilityManager = (AccessibilityManager) + getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(type); + onInitializeAccessibilityEvent(event); + event.getText().add(text); + accessibilityManager.sendAccessibilityEvent(event); + } + } + + public void animateClosed() { + if (!(getParent() instanceof DragLayer)) return; + final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f); + oa.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setLayerType(LAYER_TYPE_NONE, null); + close(true); + } + @Override + public void onAnimationStart(Animator animation) { + sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + getContext().getString(R.string.folder_closed)); + mState = STATE_ANIMATING; + } + }); + oa.setDuration(mExpandDuration); + setLayerType(LAYER_TYPE_HARDWARE, null); + oa.start(); + } + + public void close(boolean wasAnimated) { + // TODO: Clear all active animations. + DragLayer parent = (DragLayer) getParent(); + if (parent != null) { + parent.removeView(this); + } + mDragController.removeDropTarget(this); + clearFocus(); + if (wasAnimated) { + mFolderIcon.requestFocus(); + } + + if (mRearrangeOnClose) { + rearrangeChildren(); + mRearrangeOnClose = false; + } + if (getItemCount() <= 1) { + if (!mDragInProgress && !mSuppressFolderDeletion) { + replaceFolderWithFinalItem(); + } else if (mDragInProgress) { + mDeleteFolderOnDropCompleted = true; + } + } + mSuppressFolderDeletion = false; + clearDragInfo(); + mState = STATE_SMALL; + } + + public boolean acceptDrop(DragObject d) { + final ItemInfo item = d.dragInfo; + final int itemType = item.itemType; + return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || + itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && + !isFull()); + } + + public void onDragEnter(DragObject d) { + mPrevTargetRank = -1; + mOnExitAlarm.cancelAlarm(); + // Get the area offset such that the folder only closes if half the drag icon width + // is outside the folder area + mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset; + } + + OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + mContent.realTimeReorder(mEmptyCellRank, mTargetRank); + mEmptyCellRank = mTargetRank; + } + }; + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public boolean isLayoutRtl() { + return (getLayoutDirection() == LAYOUT_DIRECTION_RTL); + } + + @Override + public void onDragOver(DragObject d) { + onDragOver(d, REORDER_DELAY); + } + + private int getTargetRank(DragObject d, float[] recycle) { + recycle = d.getVisualCenter(recycle); + return mContent.findNearestArea( + (int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop()); + } + + @Thunk void onDragOver(DragObject d, int reorderDelay) { + if (mScrollPauseAlarm.alarmPending()) { + return; + } + final float[] r = new float[2]; + mTargetRank = getTargetRank(d, r); + + if (mTargetRank != mPrevTargetRank) { + mReorderAlarm.cancelAlarm(); + mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); + mReorderAlarm.setAlarm(REORDER_DELAY); + mPrevTargetRank = mTargetRank; + + if (d.stateAnnouncer != null) { + d.stateAnnouncer.announce(getContext().getString(R.string.move_to_position, + mTargetRank + 1)); + } + } + + float x = r[0]; + int currentPage = mContent.getNextPage(); + + float cellOverlap = mContent.getCurrentCellLayout().getCellWidth() + * ICON_OVERSCROLL_WIDTH_FACTOR; + boolean isOutsideLeftEdge = x < cellOverlap; + boolean isOutsideRightEdge = x > (getWidth() - cellOverlap); + + if (currentPage > 0 && (mContent.mIsRtl ? isOutsideRightEdge : isOutsideLeftEdge)) { + showScrollHint(DragController.SCROLL_LEFT, d); + } else if (currentPage < (mContent.getPageCount() - 1) + && (mContent.mIsRtl ? isOutsideLeftEdge : isOutsideRightEdge)) { + showScrollHint(DragController.SCROLL_RIGHT, d); + } else { + mOnScrollHintAlarm.cancelAlarm(); + if (mScrollHintDir != DragController.SCROLL_NONE) { + mContent.clearScrollHint(); + mScrollHintDir = DragController.SCROLL_NONE; + } + } + } + + private void showScrollHint(int direction, DragObject d) { + // Show scroll hint on the right + if (mScrollHintDir != direction) { + mContent.showScrollHint(direction); + mScrollHintDir = direction; + } + + // Set alarm for when the hint is complete + if (!mOnScrollHintAlarm.alarmPending() || mCurrentScrollDir != direction) { + mCurrentScrollDir = direction; + mOnScrollHintAlarm.cancelAlarm(); + mOnScrollHintAlarm.setOnAlarmListener(new OnScrollHintListener(d)); + mOnScrollHintAlarm.setAlarm(SCROLL_HINT_DURATION); + + mReorderAlarm.cancelAlarm(); + mTargetRank = mEmptyCellRank; + } + } + + OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + completeDragExit(); + } + }; + + public void completeDragExit() { + if (mInfo.opened) { + mLauncher.closeFolder(); + mRearrangeOnClose = true; + } else if (mState == STATE_ANIMATING) { + mRearrangeOnClose = true; + } else { + rearrangeChildren(); + clearDragInfo(); + } + } + + private void clearDragInfo() { + mCurrentDragInfo = null; + mCurrentDragView = null; + mSuppressOnAdd = false; + mIsExternalDrag = false; + } + + public void onDragExit(DragObject d) { + // We only close the folder if this is a true drag exit, ie. not because + // a drop has occurred above the folder. + if (!d.dragComplete) { + mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); + mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); + } + mReorderAlarm.cancelAlarm(); + + mOnScrollHintAlarm.cancelAlarm(); + mScrollPauseAlarm.cancelAlarm(); + if (mScrollHintDir != DragController.SCROLL_NONE) { + mContent.clearScrollHint(); + mScrollHintDir = DragController.SCROLL_NONE; + } + } + + /** + * When performing an accessibility drop, onDrop is sent immediately after onDragEnter. So we + * need to complete all transient states based on timers. + */ + @Override + public void prepareAccessibilityDrop() { + if (mReorderAlarm.alarmPending()) { + mReorderAlarm.cancelAlarm(); + mReorderAlarmListener.onAlarm(mReorderAlarm); + } + } + + public void onDropCompleted(final View target, final DragObject d, + final boolean isFlingToDelete, final boolean success) { + if (mDeferDropAfterUninstall) { + Log.d(TAG, "Deferred handling drop because waiting for uninstall."); + mDeferredAction = new Runnable() { + public void run() { + onDropCompleted(target, d, isFlingToDelete, success); + mDeferredAction = null; + } + }; + return; + } + + boolean beingCalledAfterUninstall = mDeferredAction != null; + boolean successfulDrop = + success && (!beingCalledAfterUninstall || mUninstallSuccessful); + + if (successfulDrop) { + if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { + replaceFolderWithFinalItem(); + } + } else { + // The drag failed, we need to return the item to the folder + ShortcutInfo info = (ShortcutInfo) d.dragInfo; + View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) + ? mCurrentDragView : mContent.createNewView(info); + ArrayList views = getItemsInReadingOrder(); + views.add(info.rank, icon); + mContent.arrangeChildren(views, views.size()); + mItemsInvalidated = true; + + mSuppressOnAdd = true; + mFolderIcon.onDrop(d); + mSuppressOnAdd = false; + } + + if (target != this) { + if (mOnExitAlarm.alarmPending()) { + mOnExitAlarm.cancelAlarm(); + if (!successfulDrop) { + mSuppressFolderDeletion = true; + } + mScrollPauseAlarm.cancelAlarm(); + completeDragExit(); + } + } + + mDeleteFolderOnDropCompleted = false; + mDragInProgress = false; + mItemAddedBackToSelfViaIcon = false; + mCurrentDragInfo = null; + mCurrentDragView = null; + mSuppressOnAdd = false; + + // Reordering may have occured, and we need to save the new item locations. We do this once + // at the end to prevent unnecessary database operations. + updateItemLocationsInDatabaseBatch(); + + // Use the item count to check for multi-page as the folder UI may not have + // been refreshed yet. + if (getItemCount() <= mContent.itemsPerPage()) { + // 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 + public void deferCompleteDropAfterUninstallActivity() { + mDeferDropAfterUninstall = true; + } + + @Override + public void onUninstallActivityReturned(boolean success) { + mDeferDropAfterUninstall = false; + mUninstallSuccessful = success; + if (mDeferredAction != null) { + mDeferredAction.run(); + } + } + + @Override + public float getIntrinsicIconScaleFactor() { + return 1f; + } + + @Override + public boolean supportsFlingToDelete() { + return true; + } + + @Override + public boolean supportsAppInfoDropTarget() { + return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND; + } + + @Override + public boolean supportsDeleteDropTarget() { + return true; + } + + @Override + public void onFlingToDelete(DragObject d, PointF vec) { + // Do nothing + } + + @Override + public void onFlingToDeleteCompleted() { + // Do nothing + } + + private void updateItemLocationsInDatabaseBatch() { + ArrayList list = getItemsInReadingOrder(); + ArrayList items = new ArrayList(); + for (int i = 0; i < list.size(); i++) { + View v = list.get(i); + ItemInfo info = (ItemInfo) v.getTag(); + info.rank = i; + items.add(info); + } + + LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0); + } + + public void notifyDrop() { + if (mDragInProgress) { + mItemAddedBackToSelfViaIcon = true; + } + } + + public boolean isDropEnabled() { + return true; + } + + public boolean isFull() { + return mContent.isFull(); + } + + private void centerAboutIcon() { + DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + + DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); + int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); + int height = getFolderHeight(); + + float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect); + + DeviceProfile grid = mLauncher.getDeviceProfile(); + + int centerX = (int) (sTempRect.left + sTempRect.width() * scale / 2); + int centerY = (int) (sTempRect.top + sTempRect.height() * scale / 2); + int centeredLeft = centerX - width / 2; + int centeredTop = centerY - height / 2; + + // We need to bound the folder to the currently visible workspace area + mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect); + int left = Math.min(Math.max(sTempRect.left, centeredLeft), + sTempRect.left + sTempRect.width() - width); + int top = Math.min(Math.max(sTempRect.top, centeredTop), + sTempRect.top + sTempRect.height() - height); + if (grid.isPhone && (grid.availableWidthPx - width) < grid.iconSizePx) { + // Center the folder if it is full (on phones only) + left = (grid.availableWidthPx - width) / 2; + } else if (width >= sTempRect.width()) { + // If the folder doesn't fit within the bounds, center it about the desired bounds + left = sTempRect.left + (sTempRect.width() - width) / 2; + } + if (height >= sTempRect.height()) { + top = sTempRect.top + (sTempRect.height() - height) / 2; + } + + int folderPivotX = width / 2 + (centeredLeft - left); + int folderPivotY = height / 2 + (centeredTop - top); + setPivotX(folderPivotX); + setPivotY(folderPivotY); + mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * + (1.0f * folderPivotX / width)); + mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * + (1.0f * folderPivotY / height)); + + lp.width = width; + lp.height = height; + lp.x = left; + lp.y = top; + } + + public float getPivotXForIconAnimation() { + return mFolderIconPivotX; + } + public float getPivotYForIconAnimation() { + return mFolderIconPivotY; + } + + private int getContentAreaHeight() { + DeviceProfile grid = mLauncher.getDeviceProfile(); + Rect workspacePadding = grid.getWorkspacePadding(mContent.mIsRtl); + int maxContentAreaHeight = grid.availableHeightPx - + workspacePadding.top - workspacePadding.bottom - + mFooterHeight; + int height = Math.min(maxContentAreaHeight, + mContent.getDesiredHeight()); + return Math.max(height, MIN_CONTENT_DIMEN); + } + + private int getContentAreaWidth() { + return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN); + } + + private int getFolderHeight() { + return getFolderHeight(getContentAreaHeight()); + } + + private int getFolderHeight(int contentAreaHeight) { + return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight; + } + + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int contentWidth = getContentAreaWidth(); + int contentHeight = getContentAreaHeight(); + + int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY); + int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY); + + mContent.setFixedSize(contentWidth, contentHeight); + mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec); + + if (mContent.getChildCount() > 0) { + int cellIconGap = (mContent.getPageAt(0).getCellWidth() + - mLauncher.getDeviceProfile().iconSizePx) / 2; + mFooter.setPadding(mContent.getPaddingLeft() + cellIconGap, + mFooter.getPaddingTop(), + mContent.getPaddingRight() + cellIconGap, + mFooter.getPaddingBottom()); + } + mFooter.measure(contentAreaWidthSpec, + MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY)); + + int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth; + int folderHeight = getFolderHeight(contentHeight); + setMeasuredDimension(folderWidth, folderHeight); + } + + /** + * Rearranges the children based on their rank. + */ + public void rearrangeChildren() { + rearrangeChildren(-1); + } + + /** + * Rearranges the children based on their rank. + * @param itemCount if greater than the total children count, empty spaces are left at the end, + * otherwise it is ignored. + */ + public void rearrangeChildren(int itemCount) { + ArrayList views = getItemsInReadingOrder(); + mContent.arrangeChildren(views, Math.max(itemCount, views.size())); + mItemsInvalidated = true; + } + + public int getItemCount() { + return mContent.getItemCount(); + } + + @Thunk void replaceFolderWithFinalItem() { + // Add the last remaining child to the workspace in place of the folder + Runnable onCompleteRunnable = new Runnable() { + @Override + public void run() { + int itemCount = mInfo.contents.size(); + if (itemCount <= 1) { + View newIcon = null; + + if (itemCount == 1) { + // Move the item from the folder to the workspace, in the position of the + // folder + CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, + mInfo.screenId); + ShortcutInfo finalItem = mInfo.contents.remove(0); + newIcon = mLauncher.createShortcut(cellLayout, finalItem); + LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container, + mInfo.screenId, mInfo.cellX, mInfo.cellY); + } + + // Remove the folder + mLauncher.removeItem(mFolderIcon, mInfo, true /* deleteFromDb */); + if (mFolderIcon instanceof DropTarget) { + mDragController.removeDropTarget((DropTarget) mFolderIcon); + } + + if (newIcon != null) { + // We add the child after removing the folder to prevent both from existing + // at the same time in the CellLayout. We need to add the new item with + // addInScreenFromBind() to ensure that hotseat items are placed correctly. + mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo.container, + mInfo.screenId, mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY); + + // Focus the newly created child + newIcon.requestFocus(); + } + } + } + }; + View finalChild = mContent.getLastItem(); + if (finalChild != null) { + mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable); + } else { + onCompleteRunnable.run(); + } + mDestroyed = true; + } + + public boolean isDestroyed() { + return mDestroyed; + } + + // This method keeps track of the first and last item in the folder for the purposes + // of keyboard focus + public void updateTextViewFocus() { + final View firstChild = mContent.getFirstItem(); + final View lastChild = mContent.getLastItem(); + if (firstChild != null && lastChild != null) { + mFolderName.setNextFocusDownId(lastChild.getId()); + mFolderName.setNextFocusRightId(lastChild.getId()); + mFolderName.setNextFocusLeftId(lastChild.getId()); + mFolderName.setNextFocusUpId(lastChild.getId()); + // Hitting TAB from the folder name wraps around to the first item on the current + // folder page, and hitting SHIFT+TAB from that item wraps back to the folder name. + mFolderName.setNextFocusForwardId(firstChild.getId()); + // When clicking off the folder when editing the name, this Folder gains focus. When + // pressing an arrow key from that state, give the focus to the first item. + this.setNextFocusDownId(firstChild.getId()); + this.setNextFocusRightId(firstChild.getId()); + this.setNextFocusLeftId(firstChild.getId()); + this.setNextFocusUpId(firstChild.getId()); + // When pressing shift+tab in the above state, give the focus to the last item. + setOnKeyListener(new OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + boolean isShiftPlusTab = keyCode == KeyEvent.KEYCODE_TAB && + event.hasModifiers(KeyEvent.META_SHIFT_ON); + if (isShiftPlusTab && Folder.this.isFocused()) { + return lastChild.requestFocus(); + } + return false; + } + }); + } + } + + public void onDrop(DragObject d) { + Runnable cleanUpRunnable = null; + + // If we are coming from All Apps space, we defer removing the extra empty screen + // until the folder closes + if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) { + cleanUpRunnable = new Runnable() { + @Override + public void run() { + mLauncher.exitSpringLoadedDragModeDelayed(true, + Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, + null); + } + }; + } + + // If the icon was dropped while the page was being scrolled, we need to compute + // the target location again such that the icon is placed of the final page. + if (!mContent.rankOnCurrentPage(mEmptyCellRank)) { + // Reorder again. + mTargetRank = getTargetRank(d, null); + + // Rearrange items immediately. + mReorderAlarmListener.onAlarm(mReorderAlarm); + + mOnScrollHintAlarm.cancelAlarm(); + mScrollPauseAlarm.cancelAlarm(); + } + mContent.completePendingPageChanges(); + + View currentDragView; + ShortcutInfo si = mCurrentDragInfo; + if (mIsExternalDrag) { + currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank); + // Actually move the item in the database if it was an external drag. Call this + // before creating the view, so that ShortcutInfo is updated appropriately. + LauncherModel.addOrMoveItemInDatabase( + mLauncher, si, mInfo.id, 0, si.cellX, si.cellY); + + // We only need to update the locations if it doesn't get handled in #onDropCompleted. + if (d.dragSource != this) { + updateItemLocationsInDatabaseBatch(); + } + mIsExternalDrag = false; + } else { + currentDragView = mCurrentDragView; + mContent.addViewForRank(currentDragView, si, mEmptyCellRank); + } + + if (d.dragView.hasDrawn()) { + + // Temporarily reset the scale such that the animation target gets calculated correctly. + float scaleX = getScaleX(); + float scaleY = getScaleY(); + setScaleX(1.0f); + setScaleY(1.0f); + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, + cleanUpRunnable, null); + setScaleX(scaleX); + setScaleY(scaleY); + } else { + d.deferDragViewCleanupPostAnimation = false; + currentDragView.setVisibility(VISIBLE); + } + mItemsInvalidated = true; + rearrangeChildren(); + + // Temporarily suppress the listener, as we did all the work already here. + mSuppressOnAdd = true; + mInfo.add(si); + mSuppressOnAdd = false; + // Clear the drag info, as it is no longer being dragged. + mCurrentDragInfo = null; + mDragInProgress = false; + + if (mContent.getPageCount() > 1) { + // The animation has already been shown while opening the folder. + mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher); + } + } + + // This is used so the item doesn't immediately appear in the folder when added. In one case + // we need to create the illusion that the item isn't added back to the folder yet, to + // to correspond to the animation of the icon back into the folder. This is + public void hideItem(ShortcutInfo info) { + View v = getViewForInfo(info); + v.setVisibility(INVISIBLE); + } + public void showItem(ShortcutInfo info) { + View v = getViewForInfo(info); + v.setVisibility(VISIBLE); + } + + @Override + public void onAdd(ShortcutInfo item) { + // If the item was dropped onto this open folder, we have done the work associated + // with adding the item to the folder, as indicated by mSuppressOnAdd being set + if (mSuppressOnAdd) return; + mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem(item)); + mItemsInvalidated = true; + LauncherModel.addOrMoveItemInDatabase( + mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); + } + + public void onRemove(ShortcutInfo item) { + mItemsInvalidated = true; + // If this item is being dragged from this open folder, we have already handled + // the work associated with removing the item, so we don't have to do anything here. + if (item == mCurrentDragInfo) return; + View v = getViewForInfo(item); + mContent.removeItem(v); + if (mState == STATE_ANIMATING) { + mRearrangeOnClose = true; + } else { + rearrangeChildren(); + } + if (getItemCount() <= 1) { + if (mInfo.opened) { + mLauncher.closeFolder(this, true); + } else { + replaceFolderWithFinalItem(); + } + } + } + + private View getViewForInfo(final ShortcutInfo item) { + return mContent.iterateOverItems(new ItemOperator() { + + @Override + public boolean evaluate(ItemInfo info, View view, View parent) { + return info == item; + } + }); + } + + public void onItemsChanged() { + updateTextViewFocus(); + } + + public void onTitleChanged(CharSequence title) { + } + + public ArrayList getItemsInReadingOrder() { + if (mItemsInvalidated) { + mItemsInReadingOrder.clear(); + mContent.iterateOverItems(new ItemOperator() { + + @Override + public boolean evaluate(ItemInfo info, View view, View parent) { + mItemsInReadingOrder.add(view); + return false; + } + }); + mItemsInvalidated = false; + } + return mItemsInReadingOrder; + } + + public void onFocusChange(View v, boolean hasFocus) { + if (v == mFolderName) { + if (hasFocus) { + startEditingFolderName(); + } else { + dismissEditingName(); + } + } + } + + @Override + public void getHitRectRelativeToDragLayer(Rect outRect) { + getHitRect(outRect); + outRect.left -= mScrollAreaOffset; + outRect.right += mScrollAreaOffset; + } + + @Override + public void fillInLaunchSourceData(View v, Bundle sourceData) { + // Fill in from the folder icon's launch source provider first + Stats.LaunchSourceUtils.populateSourceDataFromAncestorProvider(mFolderIcon, sourceData); + sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER, Stats.SUB_CONTAINER_FOLDER); + sourceData.putInt(Stats.SOURCE_EXTRA_SUB_CONTAINER_PAGE, mContent.getCurrentPage()); + } + + private class OnScrollHintListener implements OnAlarmListener { + + private final DragObject mDragObject; + + OnScrollHintListener(DragObject object) { + mDragObject = object; + } + + /** + * Scroll hint has been shown long enough. Now scroll to appropriate page. + */ + @Override + public void onAlarm(Alarm alarm) { + if (mCurrentScrollDir == DragController.SCROLL_LEFT) { + mContent.scrollLeft(); + mScrollHintDir = DragController.SCROLL_NONE; + } else if (mCurrentScrollDir == DragController.SCROLL_RIGHT) { + mContent.scrollRight(); + mScrollHintDir = DragController.SCROLL_NONE; + } else { + // This should not happen + return; + } + mCurrentScrollDir = DragController.SCROLL_NONE; + + // Pause drag event until the scrolling is finished + mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject)); + mScrollPauseAlarm.setAlarm(DragController.RESCROLL_DELAY); + } + } + + private class OnScrollFinishedListener implements OnAlarmListener { + + private final DragObject mDragObject; + + OnScrollFinishedListener(DragObject object) { + mDragObject = object; + } + + /** + * Page scroll is complete. + */ + @Override + public void onAlarm(Alarm alarm) { + // Reorder immediately on page change. + onDragOver(mDragObject, 1); + } + } + + // Compares item position based on rank and position giving priority to the rank. + public static final Comparator ITEM_POS_COMPARATOR = new Comparator() { + + @Override + public int compare(ItemInfo lhs, ItemInfo rhs) { + if (lhs.rank != rhs.rank) { + return lhs.rank - rhs.rank; + } else if (lhs.cellY != rhs.cellY) { + return lhs.cellY - rhs.cellY; + } else { + return lhs.cellX - rhs.cellX; + } + } + }; +} diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java new file mode 100644 index 000000000..d61022f4d --- /dev/null +++ b/src/com/android/launcher3/FolderIcon.java @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +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; +import android.os.Looper; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.FolderInfo.FolderListener; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.folder.ClippedFolderIconLayoutRule; +import com.android.launcher3.folder.StackFolderIconLayoutRule; +import com.android.launcher3.util.Thunk; + +import java.util.ArrayList; + +/** + * An icon that can appear on in the workspace representing an {@link Folder}. + */ +public class FolderIcon extends FrameLayout implements FolderListener { + @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 + 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; + private static final int FINAL_ITEM_ANIMATION_DURATION = 200; + + // The degree to which the inner ring grows when accepting drop + private static final float INNER_RING_GROWTH_FACTOR = 0.15f; + + // The degree to which the outer ring is scaled in its natural state + private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; + + // 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; + + // 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; + + 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 int mAvailableSpaceInPreview; + private int mPreviewOffsetX; + private int mPreviewOffsetY; + private int mTotalWidth; + + private PreviewLayoutRule mPreviewLayoutRule; + + boolean mAnimating = false; + private Rect mOldBounds = new Rect(); + + private float mSlop; + + private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0); + @Thunk PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0); + @Thunk ArrayList mHiddenItems = new ArrayList(); + + private Alarm mOpenAlarm = new Alarm(); + @Thunk + ItemInfo mDragInfo; + + public FolderIcon(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FolderIcon(Context context) { + super(context); + init(); + } + + private void init() { + mLongPressHelper = new CheckLongPressHelper(this); + mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); + mPreviewLayoutRule = FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON ? + new ClippedFolderIconLayoutRule() : + new StackFolderIconLayoutRule(); + + setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); + } + + public boolean isDropEnabled() { + final ViewGroup cellLayoutChildren = (ViewGroup) getParent(); + final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent(); + final Workspace workspace = (Workspace) cellLayout.getParent(); + return !workspace.workspaceInModalState(); + } + + 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; + if (error) { + throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " + + "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " + + "is dependent on this"); + } + + DeviceProfile grid = launcher.getDeviceProfile(); + + FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); + icon.setClipToPadding(false); + icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); + icon.mFolderName.setText(folderInfo.title); + icon.mFolderName.setCompoundDrawablePadding(0); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams(); + lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; + + // Offset the preview background to center this view accordingly + icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); + lp = (FrameLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams(); + lp.topMargin = grid.folderBackgroundOffset; + lp.width = grid.folderIconSizePx; + lp.height = grid.folderIconSizePx; + + icon.setTag(folderInfo); + icon.setOnClickListener(launcher); + icon.mInfo = folderInfo; + icon.mLauncher = launcher; + icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title)); + Folder folder = Folder.fromXml(launcher); + folder.setDragController(launcher.getDragController()); + folder.setFolderIcon(icon); + folder.bind(folderInfo); + icon.mFolder = folder; + + icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon); + folderInfo.addListener(icon); + + icon.setOnFocusChangeListener(launcher.mFocusHandler); + return icon; + } + + @Override + protected Parcelable onSaveInstanceState() { + sStaticValuesDirty = true; + return super.onSaveInstanceState(); + } + + public static class FolderRingAnimator { + public int mCellX; + public int mCellY; + @Thunk + CellLayout mCellLayout; + public float mOuterRingSize; + public float mInnerRingSize; + public FolderIcon mFolderIcon = null; + public static Drawable sSharedOuterRingDrawable = null; + public static Drawable sSharedInnerRingDrawable = null; + public static int sPreviewSize = -1; + public static int sPreviewPadding = -1; + + private ValueAnimator mAcceptAnimator; + private ValueAnimator mNeutralAnimator; + + public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { + mFolderIcon = folderIcon; + Resources res = launcher.getResources(); + + // We need to reload the static values when configuration changes in case they are + // different in another configuration + if (sStaticValuesDirty) { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread " + + Thread.currentThread()); + } + + DeviceProfile grid = launcher.getDeviceProfile(); + sPreviewSize = grid.folderIconSizePx; + sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); + sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer); + sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip); + sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); + sStaticValuesDirty = false; + } + } + + public void animateToAcceptState() { + if (mNeutralAnimator != null) { + mNeutralAnimator.cancel(); + } + mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); + mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); + + final int previewSize = sPreviewSize; + mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float percent = (Float) animation.getAnimatedValue(); + mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize; + mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize; + if (mCellLayout != null) { + mCellLayout.invalidate(); + } + } + }); + mAcceptAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (mFolderIcon != null) { + mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); + } + } + }); + mAcceptAnimator.start(); + } + + public void animateToNaturalState() { + if (mAcceptAnimator != null) { + mAcceptAnimator.cancel(); + } + mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); + mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); + + final int previewSize = sPreviewSize; + mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + final float percent = (Float) animation.getAnimatedValue(); + mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize; + mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize; + if (mCellLayout != null) { + mCellLayout.invalidate(); + } + } + }); + mNeutralAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mCellLayout != null) { + mCellLayout.hideFolderAccept(FolderRingAnimator.this); + } + if (mFolderIcon != null) { + mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); + } + } + }); + mNeutralAnimator.start(); + } + + // Location is expressed in window coordinates + public void getCell(int[] loc) { + loc[0] = mCellX; + loc[1] = mCellY; + } + + // Location is expressed in window coordinates + public void setCell(int x, int y) { + mCellX = x; + mCellY = y; + } + + public void setCellLayout(CellLayout layout) { + mCellLayout = layout; + } + + public float getOuterRingSize() { + return mOuterRingSize; + } + + public float getInnerRingSize() { + return mInnerRingSize; + } + } + + public Folder getFolder() { + return mFolder; + } + + public FolderInfo getFolderInfo() { + return mInfo; + } + + private boolean willAcceptItem(ItemInfo item) { + final int itemType = item.itemType; + return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || + itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && + !mFolder.isFull() && item != mInfo && !mInfo.opened); + } + + public boolean acceptDrop(ItemInfo dragInfo) { + final ItemInfo item = dragInfo; + return !mFolder.isDestroyed() && willAcceptItem(item); + } + + public void addItem(ShortcutInfo item) { + mInfo.add(item); + } + + 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); + mFolderRingAnimator.setCellLayout(layout); + mFolderRingAnimator.animateToAcceptState(); + layout.showFolderAccept(mFolderRingAnimator); + mOpenAlarm.setOnAlarmListener(mOnOpenListener); + if (SPRING_LOADING_ENABLED && + ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) { + // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even + // though widget-style shortcuts can be added to folders. The issue is that we need + // to deal with configuration activities which are currently handled in + // Workspace#onDropExternal. + mOpenAlarm.setAlarm(ON_OPEN_DELAY); + } + mDragInfo = dragInfo; + } + + OnAlarmListener mOnOpenListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + ShortcutInfo item; + if (mDragInfo instanceof AppInfo) { + // Came from all apps -- make a copy. + item = ((AppInfo) mDragInfo).makeShortcut(); + item.spanX = 1; + item.spanY = 1; + } else { + // ShortcutInfo + item = (ShortcutInfo) mDragInfo; + } + mFolder.beginExternalDrag(item); + mLauncher.openFolder(FolderIcon.this, true); + } + }; + + public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, + final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, + float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { + + // These correspond two the drawable and view that the icon was dropped _onto_ + Drawable animateDrawable = getTopDrawable((TextView) destView); + computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), + destView.getMeasuredWidth()); + + // This will animate the first item from it's position as an icon into its + // position as the first item in the preview + animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null); + addItem(destInfo); + + // This will animate the dragView (srcView) into the new folder + onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null); + } + + public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) { + Drawable animateDrawable = getTopDrawable((TextView) finalView); + computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), + finalView.getMeasuredWidth()); + + // This will animate the first item from it's position as an icon into its + // position as the first item in the preview + animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true, + onCompleteRunnable); + } + + public void onDragExit(Object dragInfo) { + onDragExit(); + } + + public void onDragExit() { + mFolderRingAnimator.animateToNaturalState(); + mOpenAlarm.cancelAlarm(); + } + + private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, + float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, + DragObject d) { + item.cellX = -1; + item.cellY = -1; + + // Typically, the animateView corresponds to the DragView; however, if this is being done + // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we + // will not have a view to animate + if (animateView != null) { + DragLayer dragLayer = mLauncher.getDragLayer(); + Rect from = new Rect(); + dragLayer.getViewRectRelativeToSelf(animateView, from); + Rect to = finalRect; + if (to == null) { + to = new Rect(); + Workspace workspace = mLauncher.getWorkspace(); + // Set cellLayout and this to it's final state to compute final animation locations + workspace.setFinalTransitionTransform((CellLayout) getParent().getParent()); + float scaleX = getScaleX(); + float scaleY = getScaleY(); + setScaleX(1.0f); + setScaleY(1.0f); + scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to); + // Finished computing final animation locations, restore current state + setScaleX(scaleX); + setScaleY(scaleY); + workspace.resetTransitionTransform((CellLayout) getParent().getParent()); + } + + int[] center = new int[2]; + 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 < mPreviewLayoutRule.numItems() ? 0.5f : 0f; + + float finalScale = scale * scaleRelativeToDragLayer; + dragLayer.animateView(animateView, from, to, finalAlpha, + 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION, + new DecelerateInterpolator(2), new AccelerateInterpolator(2), + postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); + addItem(item); + mHiddenItems.add(item); + mFolder.hideItem(item); + postDelayed(new Runnable() { + public void run() { + mHiddenItems.remove(item); + mFolder.showItem(item); + invalidate(); + } + }, DROP_IN_ANIMATION_DURATION); + } else { + addItem(item); + } + } + + public void onDrop(DragObject d) { + ShortcutInfo item; + if (d.dragInfo instanceof AppInfo) { + // Came from all apps -- make a copy + item = ((AppInfo) d.dragInfo).makeShortcut(); + } else { + item = (ShortcutInfo) d.dragInfo; + } + mFolder.notifyDrop(); + onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); + } + + private void computePreviewDrawingParams(int drawableSize, int totalSize) { + if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { + DeviceProfile grid = mLauncher.getDeviceProfile(); + + mIntrinsicIconSize = drawableSize; + mTotalWidth = totalSize; + + final int previewSize = FolderRingAnimator.sPreviewSize; + final int previewPadding = FolderRingAnimator.sPreviewPadding; + + mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); + + mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; + mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset + getPaddingTop(); + + mPreviewLayoutRule.init(mAvailableSpaceInPreview, mIntrinsicIconSize, + Utilities.isRtl(getResources())); + } + } + + private void computePreviewDrawingParams(Drawable d) { + computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); + } + + public static class PreviewItemDrawingParams { + public PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) { + this.transX = transX; + this.transY = transY; + this.scale = scale; + this.overlayAlpha = overlayAlpha; + } + public float transX; + public float transY; + public float scale; + public float overlayAlpha; + public Drawable drawable; + } + + private float getLocalCenterForIndex(int index, int curNumItems, int[] center) { + mParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index), + curNumItems, mParams); + + mParams.transX += mPreviewOffsetX; + mParams.transY += mPreviewOffsetY; + float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2; + float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2; + + center[0] = (int) Math.round(offsetX); + center[1] = (int) Math.round(offsetY); + return mParams.scale; + } + + private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, + PreviewItemDrawingParams params) { + return mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params); + } + + private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { + canvas.save(); + canvas.translate(params.transX, params.transY); + canvas.scale(params.scale, params.scale); + Drawable d = params.drawable; + + if (d != null) { + mOldBounds.set(d.getBounds()); + d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); + if (d instanceof FastBitmapDrawable) { + FastBitmapDrawable fd = (FastBitmapDrawable) d; + float oldBrightness = fd.getBrightness(); + fd.setBrightness(params.overlayAlpha); + d.draw(canvas); + fd.setBrightness(oldBrightness); + } else { + d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255), + PorterDuff.Mode.SRC_ATOP); + d.draw(canvas); + d.clearColorFilter(); + } + d.setBounds(mOldBounds); + } + canvas.restore(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + if (mFolder == null) return; + if (mFolder.getItemCount() == 0 && !mAnimating) return; + + ArrayList items = mFolder.getItemsInReadingOrder(); + Drawable d; + TextView v; + + // Update our drawing parameters if necessary + if (mAnimating) { + computePreviewDrawingParams(mAnimParams.drawable); + } else { + v = (TextView) items.get(0); + d = getTopDrawable(v); + computePreviewDrawingParams(d); + } + + 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, nItemsInPreview, mParams); + mParams.drawable = d; + drawPreviewItem(canvas, mParams); + } + } + } else { + drawPreviewItem(canvas, mAnimParams); + } + canvas.restore(); + } + + private Drawable getTopDrawable(TextView v) { + Drawable d = v.getCompoundDrawables()[1]; + return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d; + } + + private void animateFirstItem(final Drawable d, int duration, final boolean reverse, + final Runnable onCompleteRunnable) { + + 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; + mAnimParams.drawable = d; + + ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f); + va.addUpdateListener(new AnimatorUpdateListener(){ + public void onAnimationUpdate(ValueAnimator animation) { + float progress = (Float) animation.getAnimatedValue(); + if (reverse) { + progress = 1 - progress; + mPreviewBackground.setAlpha(progress); + } + + mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0); + mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0); + mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0); + invalidate(); + } + }); + va.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mAnimating = true; + } + @Override + public void onAnimationEnd(Animator animation) { + mAnimating = false; + if (onCompleteRunnable != null) { + onCompleteRunnable.run(); + } + } + }); + va.setDuration(duration); + va.start(); + } + + public void setTextVisible(boolean visible) { + if (visible) { + mFolderName.setVisibility(VISIBLE); + } else { + mFolderName.setVisibility(INVISIBLE); + } + } + + public boolean getTextVisible() { + return mFolderName.getVisibility() == VISIBLE; + } + + public void onItemsChanged() { + invalidate(); + requestLayout(); + } + + public void onAdd(ShortcutInfo item) { + invalidate(); + requestLayout(); + } + + public void onRemove(ShortcutInfo item) { + invalidate(); + requestLayout(); + } + + public void onTitleChanged(CharSequence title) { + mFolderName.setText(title); + setContentDescription(getContext().getString(R.string.folder_name_format, title)); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // Call the superclass onTouchEvent first, because sometimes it changes the state to + // isPressed() on an ACTION_UP + boolean result = super.onTouchEvent(event); + + // Check for a stylus button press, if it occurs cancel any long press checks. + if (mStylusEventHelper.onMotionEvent(event)) { + mLongPressHelper.cancelLongPress(); + return true; + } + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mLongPressHelper.postCheckForLongPress(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mLongPressHelper.cancelLongPress(); + break; + case MotionEvent.ACTION_MOVE: + if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) { + mLongPressHelper.cancelLongPress(); + } + break; + } + return result; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + } + + @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/Launcher.java b/src/com/android/launcher3/Launcher.java index 545768f51..bda0ad9b2 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -107,8 +107,6 @@ 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.util.ComponentKey; import com.android.launcher3.util.LongArrayMap; diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index f707ec501..dcfd65b82 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -57,8 +57,6 @@ import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; 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; diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 128d695eb..9c6d71b37 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -21,15 +21,11 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.util.Log; 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; /** * Represents a launchable icon on the workspaces and in folders. diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 5d2f451bd..e6ab11160 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -53,9 +53,7 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.TextView; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.folder.FolderIcon.FolderRingAnimator; +import com.android.launcher3.FolderIcon.FolderRingAnimator; import com.android.launcher3.Launcher.CustomContentCallbacks; import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.UninstallDropTarget.UninstallSource; diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 8560b2167..03731d175 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -21,7 +21,7 @@ import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.CellLayout; import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DragSource; -import com.android.launcher3.folder.Folder; +import com.android.launcher3.Folder; import com.android.launcher3.FolderInfo; import com.android.launcher3.InfoDropTarget; import com.android.launcher3.ItemInfo; diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 36acedee7..920e954e0 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -40,7 +40,7 @@ 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.Folder; +import com.android.launcher3.Folder; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherTransitionable; diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 3128db21e..743ba3705 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -53,8 +53,8 @@ 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.Folder; +import com.android.launcher3.FolderIcon; import com.android.launcher3.util.Thunk; import java.util.ArrayList; diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index 44d7ac6e9..b73c04f81 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -1,15 +1,12 @@ 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; +import com.android.launcher3.FolderIcon; public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { - static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; + public 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; diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java deleted file mode 100644 index a411c481c..000000000 --- a/src/com/android/launcher3/folder/Folder.java +++ /dev/null @@ -1,1506 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Build; -import android.os.Bundle; -import android.text.InputType; -import android.text.Selection; -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.ViewDebug; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.AnimationUtils; -import android.view.inputmethod.EditorInfo; -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.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; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -/** - * Represents a set of icons chosen by the user or generated by the system. - */ -public class Folder extends LinearLayout implements DragSource, View.OnClickListener, - View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, - View.OnFocusChangeListener, DragListener, UninstallSource, AccessibilityDragSource, - Stats.LaunchSourceProvider { - private static final String TAG = "Launcher.Folder"; - - /** - * We avoid measuring {@link #mContentWrapper} with a 0 width or height, as this - * results in CellLayout being measured as UNSPECIFIED, which it does not support. - */ - private static final int MIN_CONTENT_DIMEN = 5; - - static final int STATE_NONE = -1; - static final int STATE_SMALL = 0; - static final int STATE_ANIMATING = 1; - static final int STATE_OPEN = 2; - - /** - * Time for which the scroll hint is shown before automatically changing page. - */ - public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY; - - /** - * Fraction of icon width which behave as scroll region. - */ - private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f; - - private static final int FOLDER_NAME_ANIMATION_DURATION = 633; - - private static final int REORDER_DELAY = 250; - private static final int ON_EXIT_CLOSE_DELAY = 400; - private static final Rect sTempRect = new Rect(); - - private static String sDefaultFolderName; - private static String sHintText; - - private final Alarm mReorderAlarm = new Alarm(); - private final Alarm mOnExitAlarm = new Alarm(); - private final Alarm mOnScrollHintAlarm = new Alarm(); - @Thunk final Alarm mScrollPauseAlarm = new Alarm(); - - @Thunk final ArrayList mItemsInReadingOrder = new ArrayList(); - - private final int mExpandDuration; - private final int mMaterialExpandDuration; - private final int mMaterialExpandStagger; - - private final InputMethodManager mInputMethodManager; - - protected final Launcher mLauncher; - protected DragController mDragController; - public FolderInfo mInfo; - - @Thunk FolderIcon mFolderIcon; - - @Thunk FolderPagedView mContent; - @Thunk View mContentWrapper; - public ExtendedEditText mFolderName; - - private View mFooter; - private int mFooterHeight; - - // 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; - private View mCurrentDragView; - private boolean mIsExternalDrag; - boolean mSuppressOnAdd = false; - private boolean mDragInProgress = false; - private boolean mDeleteFolderOnDropCompleted = false; - private boolean mSuppressFolderDeletion = false; - private boolean mItemAddedBackToSelfViaIcon = false; - @Thunk float mFolderIconPivotX; - @Thunk float mFolderIconPivotY; - private boolean mIsEditingName = false; - - @ViewDebug.ExportedProperty(category = "launcher") - private boolean mDestroyed; - - @Thunk Runnable mDeferredAction; - private boolean mDeferDropAfterUninstall; - private boolean mUninstallSuccessful; - - // Folder scrolling - private int mScrollAreaOffset; - - @Thunk int mScrollHintDir = DragController.SCROLL_NONE; - @Thunk int mCurrentScrollDir = DragController.SCROLL_NONE; - - /** - * Used to inflate the Workspace from XML. - * - * @param context The application's context. - * @param attrs The attributes set containing the Workspace's customization values. - */ - public Folder(Context context, AttributeSet attrs) { - super(context, attrs); - setAlwaysDrawnWithCacheEnabled(false); - mInputMethodManager = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - - Resources res = getResources(); - mExpandDuration = res.getInteger(R.integer.config_folderExpandDuration); - mMaterialExpandDuration = res.getInteger(R.integer.config_materialFolderExpandDuration); - mMaterialExpandStagger = res.getInteger(R.integer.config_materialFolderExpandStagger); - - if (sDefaultFolderName == null) { - sDefaultFolderName = res.getString(R.string.folder_name); - } - if (sHintText == null) { - sHintText = res.getString(R.string.folder_hint_text); - } - mLauncher = (Launcher) context; - // We need this view to be focusable in touch mode so that when text editing of the folder - // name is complete, we have something to focus on, thus hiding the cursor and giving - // reliable behavior when clicking the text field (since it will always gain focus on click). - setFocusableInTouchMode(true); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContentWrapper = findViewById(R.id.folder_content_wrapper); - mContent = (FolderPagedView) findViewById(R.id.folder_content); - mContent.setFolder(this); - - mFolderName = (ExtendedEditText) findViewById(R.id.folder_name); - mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() { - @Override - public boolean onBackKey() { - // Close the activity on back key press - doneEditingFolderName(true); - return false; - } - }); - mFolderName.setOnFocusChangeListener(this); - - 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() | - InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); - - mFooter = findViewById(R.id.folder_footer); - - // We find out how tall footer wants to be (it is set to wrap_content), so that - // we can allocate the appropriate amount of space for it. - int measureSpec = MeasureSpec.UNSPECIFIED; - mFooter.measure(measureSpec, measureSpec); - mFooterHeight = mFooter.getMeasuredHeight(); - } - - public void onClick(View v) { - Object tag = v.getTag(); - if (tag instanceof ShortcutInfo) { - mLauncher.onClick(v); - } - } - - public boolean onLongClick(View v) { - // Return if global dragging is not enabled - if (!mLauncher.isDraggingEnabled()) return true; - return beginDrag(v, false); - } - - private boolean beginDrag(View v, boolean accessible) { - Object tag = v.getTag(); - if (tag instanceof ShortcutInfo) { - ShortcutInfo item = (ShortcutInfo) tag; - if (!v.isInTouchMode()) { - return false; - } - - mLauncher.getWorkspace().beginDragShared(v, new Point(), this, accessible); - - mCurrentDragInfo = item; - mEmptyCellRank = item.rank; - mCurrentDragView = v; - - mContent.removeItem(mCurrentDragView); - mInfo.remove(mCurrentDragInfo); - mDragInProgress = true; - mItemAddedBackToSelfViaIcon = false; - } - return true; - } - - @Override - public void startDrag(CellInfo cellInfo, boolean accessible) { - beginDrag(cellInfo.cell, accessible); - } - - @Override - public void enableAccessibleDrag(boolean enable) { - mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable); - for (int i = 0; i < mContent.getChildCount(); i++) { - mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG); - } - - mFooter.setImportantForAccessibility(enable ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS : - IMPORTANT_FOR_ACCESSIBILITY_AUTO); - mLauncher.getWorkspace().setAddNewPageOnDrag(!enable); - } - - public boolean isEditingName() { - return mIsEditingName; - } - - public void startEditingFolderName() { - mFolderName.setHint(""); - mIsEditingName = true; - } - - public void dismissEditingName() { - mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); - doneEditingFolderName(true); - } - - public void doneEditingFolderName(boolean commit) { - mFolderName.setHint(sHintText); - // Convert to a string here to ensure that no other state associated with the text field - // gets saved. - String newTitle = mFolderName.getText().toString(); - mInfo.setTitle(newTitle); - LauncherModel.updateItemInDatabase(mLauncher, mInfo); - - if (commit) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - getContext().getString(R.string.folder_renamed, newTitle)); - } - - // This ensures that focus is gained every time the field is clicked, which selects all - // the text and brings up the soft keyboard if necessary. - mFolderName.clearFocus(); - - Selection.setSelection((Spannable) mFolderName.getText(), 0, 0); - mIsEditingName = false; - } - - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) { - dismissEditingName(); - return true; - } - return false; - } - - public View getEditTextRegion() { - return mFolderName; - } - - /** - * We need to handle touch events to prevent them from falling through to the workspace below. - */ - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(MotionEvent ev) { - return true; - } - - public void setDragController(DragController dragController) { - mDragController = dragController; - } - - public void setFolderIcon(FolderIcon icon) { - mFolderIcon = icon; - } - - @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 - */ - public FolderInfo getInfo() { - return mInfo; - } - - void bind(FolderInfo info) { - mInfo = info; - ArrayList children = info.contents; - Collections.sort(children, ITEM_POS_COMPARATOR); - - ArrayList overflow = mContent.bindItems(children); - - // If our folder has too many items we prune them from the list. This is an issue - // when upgrading from the old Folders implementation which could contain an unlimited - // number of items. - for (ShortcutInfo item: overflow) { - mInfo.remove(item); - LauncherModel.deleteItemFromDatabase(mLauncher, item); - } - - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - if (lp == null) { - lp = new DragLayer.LayoutParams(0, 0); - lp.customPosition = true; - setLayoutParams(lp); - } - centerAboutIcon(); - - mItemsInvalidated = true; - updateTextViewFocus(); - mInfo.addListener(this); - - if (!sDefaultFolderName.contentEquals(mInfo.title)) { - mFolderName.setText(mInfo.title); - } else { - mFolderName.setText(""); - } - - // In case any children didn't come across during loading, clean up the folder accordingly - mFolderIcon.post(new Runnable() { - public void run() { - if (getItemCount() <= 1) { - replaceFolderWithFinalItem(); - } - } - }); - } - - /** - * Creates a new UserFolder, inflated from R.layout.user_folder. - * - * @param launcher The main activity. - * - * @return A new UserFolder. - */ - @SuppressLint("InflateParams") - static Folder fromXml(Launcher launcher) { - return (Folder) launcher.getLayoutInflater().inflate( - FeatureFlags.LAUNCHER3_ICON_NORMALIZATION - ? R.layout.user_folder_icon_normalized : R.layout.user_folder, null); - } - - /** - * This method is intended to make the UserFolder to be visually identical in size and position - * to its associated FolderIcon. This allows for a seamless transition into the expanded state. - */ - private void positionAndSizeAsIcon() { - if (!(getParent() instanceof DragLayer)) return; - setScaleX(0.8f); - setScaleY(0.8f); - setAlpha(0f); - mState = STATE_SMALL; - } - - private void prepareReveal() { - setScaleX(1f); - setScaleY(1f); - setAlpha(1f); - mState = STATE_SMALL; - } - - public void animateOpen() { - if (!(getParent() instanceof DragLayer)) return; - - mContent.completePendingPageChanges(); - if (!mDragInProgress) { - // Open on the first page. - mContent.snapToPageImmediately(0); - } - - // This is set to true in close(), but isn't reset to false until onDropCompleted(). This - // leads to an consistent state if you drag out of the folder and drag back in without - // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. - mDeleteFolderOnDropCompleted = false; - - Animator openFolderAnim = null; - final Runnable onCompleteRunnable; - if (!Utilities.ATLEAST_LOLLIPOP) { - positionAndSizeAsIcon(); - centerAboutIcon(); - - final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1); - oa.setDuration(mExpandDuration); - openFolderAnim = oa; - - setLayerType(LAYER_TYPE_HARDWARE, null); - onCompleteRunnable = new Runnable() { - @Override - public void run() { - setLayerType(LAYER_TYPE_NONE, null); - } - }; - } else { - prepareReveal(); - centerAboutIcon(); - - AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); - int height = getFolderHeight(); - - float transX = - 0.075f * (width / 2 - getPivotX()); - float transY = - 0.075f * (height / 2 - getPivotY()); - setTranslationX(transX); - setTranslationY(transY); - 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); - drift.setStartDelay(mMaterialExpandStagger); - drift.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX()); - int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY()); - float radius = (float) Math.hypot(rx, ry); - - Animator reveal = UiThreadCircularReveal.createCircularReveal(this, (int) getPivotX(), - (int) getPivotY(), 0, radius); - reveal.setDuration(mMaterialExpandDuration); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - mContentWrapper.setAlpha(0f); - Animator iconsAlpha = ObjectAnimator.ofFloat(mContentWrapper, "alpha", 0f, 1f); - iconsAlpha.setDuration(mMaterialExpandDuration); - iconsAlpha.setStartDelay(mMaterialExpandStagger); - iconsAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - - mFooter.setAlpha(0f); - Animator textAlpha = ObjectAnimator.ofFloat(mFooter, "alpha", 0f, 1f); - textAlpha.setDuration(mMaterialExpandDuration); - textAlpha.setStartDelay(mMaterialExpandStagger); - textAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); - - anim.play(drift); - anim.play(iconsAlpha); - anim.play(textAlpha); - anim.play(reveal); - - openFolderAnim = anim; - - mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null); - mFooter.setLayerType(LAYER_TYPE_HARDWARE, null); - onCompleteRunnable = new Runnable() { - @Override - public void run() { - mContentWrapper.setLayerType(LAYER_TYPE_NONE, null); - mContentWrapper.setLayerType(LAYER_TYPE_NONE, null); - } - }; - } - openFolderAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - mContent.getAccessibilityDescription()); - mState = STATE_ANIMATING; - } - @Override - public void onAnimationEnd(Animator animation) { - mState = STATE_OPEN; - - onCompleteRunnable.run(); - mContent.setFocusOnFirstChild(); - } - }); - - // Footer animation - if (mContent.getPageCount() > 1 && !mInfo.hasOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION)) { - int footerWidth = mContent.getDesiredWidth() - - mFooter.getPaddingLeft() - mFooter.getPaddingRight(); - - float textWidth = mFolderName.getPaint().measureText(mFolderName.getText().toString()); - float translation = (footerWidth - textWidth) / 2; - mFolderName.setTranslationX(mContent.mIsRtl ? -translation : translation); - mContent.setMarkerScale(0); - - // Do not update the flag if we are in drag mode. The flag will be updated, when we - // actually drop the icon. - final boolean updateAnimationFlag = !mDragInProgress; - openFolderAnim.addListener(new AnimatorListenerAdapter() { - - @SuppressLint("InlinedApi") - @Override - public void onAnimationEnd(Animator animation) { - mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION) - .translationX(0) - .setInterpolator(Utilities.ATLEAST_LOLLIPOP ? - AnimationUtils.loadInterpolator(mLauncher, - android.R.interpolator.fast_out_slow_in) - : new LogDecelerateInterpolator(100, 0)); - mContent.animateMarkers(); - - if (updateAnimationFlag) { - mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher); - } - } - }); - } else { - mFolderName.setTranslationX(0); - mContent.setMarkerScale(1); - } - - openFolderAnim.start(); - - // 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()); - } - - /** - * 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) { - mCurrentDragInfo = item; - mEmptyCellRank = mContent.allocateRankForNewItem(item); - mIsExternalDrag = true; - mDragInProgress = true; - - // Since this folder opened by another controller, it might not get onDrop or - // onDropComplete. Perform cleanup once drag-n-drop ends. - mDragController.addDragListener(this); - } - - @Override - public void onDragStart(DragSource source, ItemInfo info, int dragAction) { } - - @Override - public void onDragEnd() { - if (mIsExternalDrag && mDragInProgress) { - completeDragExit(); - } - mDragController.removeDragListener(this); - } - - @Thunk void sendCustomAccessibilityEvent(int type, String text) { - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager.isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain(type); - onInitializeAccessibilityEvent(event); - event.getText().add(text); - accessibilityManager.sendAccessibilityEvent(event); - } - } - - public void animateClosed() { - if (!(getParent() instanceof DragLayer)) return; - final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f); - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setLayerType(LAYER_TYPE_NONE, null); - close(true); - } - @Override - public void onAnimationStart(Animator animation) { - sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - getContext().getString(R.string.folder_closed)); - mState = STATE_ANIMATING; - } - }); - oa.setDuration(mExpandDuration); - setLayerType(LAYER_TYPE_HARDWARE, null); - oa.start(); - } - - public void close(boolean wasAnimated) { - // TODO: Clear all active animations. - DragLayer parent = (DragLayer) getParent(); - if (parent != null) { - parent.removeView(this); - } - mDragController.removeDropTarget(this); - clearFocus(); - if (wasAnimated) { - mFolderIcon.requestFocus(); - } - - if (mRearrangeOnClose) { - rearrangeChildren(); - mRearrangeOnClose = false; - } - if (getItemCount() <= 1) { - if (!mDragInProgress && !mSuppressFolderDeletion) { - replaceFolderWithFinalItem(); - } else if (mDragInProgress) { - mDeleteFolderOnDropCompleted = true; - } - } - mSuppressFolderDeletion = false; - clearDragInfo(); - mState = STATE_SMALL; - } - - public boolean acceptDrop(DragObject d) { - final ItemInfo item = d.dragInfo; - final int itemType = item.itemType; - return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || - itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && - !isFull()); - } - - public void onDragEnter(DragObject d) { - mPrevTargetRank = -1; - mOnExitAlarm.cancelAlarm(); - // Get the area offset such that the folder only closes if half the drag icon width - // is outside the folder area - mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset; - } - - OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { - public void onAlarm(Alarm alarm) { - mContent.realTimeReorder(mEmptyCellRank, mTargetRank); - mEmptyCellRank = mTargetRank; - } - }; - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - public boolean isLayoutRtl() { - return (getLayoutDirection() == LAYOUT_DIRECTION_RTL); - } - - @Override - public void onDragOver(DragObject d) { - onDragOver(d, REORDER_DELAY); - } - - private int getTargetRank(DragObject d, float[] recycle) { - recycle = d.getVisualCenter(recycle); - return mContent.findNearestArea( - (int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop()); - } - - @Thunk void onDragOver(DragObject d, int reorderDelay) { - if (mScrollPauseAlarm.alarmPending()) { - return; - } - final float[] r = new float[2]; - mTargetRank = getTargetRank(d, r); - - if (mTargetRank != mPrevTargetRank) { - mReorderAlarm.cancelAlarm(); - mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); - mReorderAlarm.setAlarm(REORDER_DELAY); - mPrevTargetRank = mTargetRank; - - if (d.stateAnnouncer != null) { - d.stateAnnouncer.announce(getContext().getString(R.string.move_to_position, - mTargetRank + 1)); - } - } - - float x = r[0]; - int currentPage = mContent.getNextPage(); - - float cellOverlap = mContent.getCurrentCellLayout().getCellWidth() - * ICON_OVERSCROLL_WIDTH_FACTOR; - boolean isOutsideLeftEdge = x < cellOverlap; - boolean isOutsideRightEdge = x > (getWidth() - cellOverlap); - - if (currentPage > 0 && (mContent.mIsRtl ? isOutsideRightEdge : isOutsideLeftEdge)) { - showScrollHint(DragController.SCROLL_LEFT, d); - } else if (currentPage < (mContent.getPageCount() - 1) - && (mContent.mIsRtl ? isOutsideLeftEdge : isOutsideRightEdge)) { - showScrollHint(DragController.SCROLL_RIGHT, d); - } else { - mOnScrollHintAlarm.cancelAlarm(); - if (mScrollHintDir != DragController.SCROLL_NONE) { - mContent.clearScrollHint(); - mScrollHintDir = DragController.SCROLL_NONE; - } - } - } - - private void showScrollHint(int direction, DragObject d) { - // Show scroll hint on the right - if (mScrollHintDir != direction) { - mContent.showScrollHint(direction); - mScrollHintDir = direction; - } - - // Set alarm for when the hint is complete - if (!mOnScrollHintAlarm.alarmPending() || mCurrentScrollDir != direction) { - mCurrentScrollDir = direction; - mOnScrollHintAlarm.cancelAlarm(); - mOnScrollHintAlarm.setOnAlarmListener(new OnScrollHintListener(d)); - mOnScrollHintAlarm.setAlarm(SCROLL_HINT_DURATION); - - mReorderAlarm.cancelAlarm(); - mTargetRank = mEmptyCellRank; - } - } - - OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { - public void onAlarm(Alarm alarm) { - completeDragExit(); - } - }; - - public void completeDragExit() { - if (mInfo.opened) { - mLauncher.closeFolder(); - mRearrangeOnClose = true; - } else if (mState == STATE_ANIMATING) { - mRearrangeOnClose = true; - } else { - rearrangeChildren(); - clearDragInfo(); - } - } - - private void clearDragInfo() { - mCurrentDragInfo = null; - mCurrentDragView = null; - mSuppressOnAdd = false; - mIsExternalDrag = false; - } - - public void onDragExit(DragObject d) { - // We only close the folder if this is a true drag exit, ie. not because - // a drop has occurred above the folder. - if (!d.dragComplete) { - mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); - mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); - } - mReorderAlarm.cancelAlarm(); - - mOnScrollHintAlarm.cancelAlarm(); - mScrollPauseAlarm.cancelAlarm(); - if (mScrollHintDir != DragController.SCROLL_NONE) { - mContent.clearScrollHint(); - mScrollHintDir = DragController.SCROLL_NONE; - } - } - - /** - * When performing an accessibility drop, onDrop is sent immediately after onDragEnter. So we - * need to complete all transient states based on timers. - */ - @Override - public void prepareAccessibilityDrop() { - if (mReorderAlarm.alarmPending()) { - mReorderAlarm.cancelAlarm(); - mReorderAlarmListener.onAlarm(mReorderAlarm); - } - } - - public void onDropCompleted(final View target, final DragObject d, - final boolean isFlingToDelete, final boolean success) { - if (mDeferDropAfterUninstall) { - Log.d(TAG, "Deferred handling drop because waiting for uninstall."); - mDeferredAction = new Runnable() { - public void run() { - onDropCompleted(target, d, isFlingToDelete, success); - mDeferredAction = null; - } - }; - return; - } - - boolean beingCalledAfterUninstall = mDeferredAction != null; - boolean successfulDrop = - success && (!beingCalledAfterUninstall || mUninstallSuccessful); - - if (successfulDrop) { - if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { - replaceFolderWithFinalItem(); - } - } else { - // The drag failed, we need to return the item to the folder - ShortcutInfo info = (ShortcutInfo) d.dragInfo; - View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) - ? mCurrentDragView : mContent.createNewView(info); - ArrayList views = getItemsInReadingOrder(); - views.add(info.rank, icon); - mContent.arrangeChildren(views, views.size()); - mItemsInvalidated = true; - - mSuppressOnAdd = true; - mFolderIcon.onDrop(d); - mSuppressOnAdd = false; - } - - if (target != this) { - if (mOnExitAlarm.alarmPending()) { - mOnExitAlarm.cancelAlarm(); - if (!successfulDrop) { - mSuppressFolderDeletion = true; - } - mScrollPauseAlarm.cancelAlarm(); - completeDragExit(); - } - } - - mDeleteFolderOnDropCompleted = false; - mDragInProgress = false; - mItemAddedBackToSelfViaIcon = false; - mCurrentDragInfo = null; - mCurrentDragView = null; - mSuppressOnAdd = false; - - // Reordering may have occured, and we need to save the new item locations. We do this once - // at the end to prevent unnecessary database operations. - updateItemLocationsInDatabaseBatch(); - - // Use the item count to check for multi-page as the folder UI may not have - // been refreshed yet. - if (getItemCount() <= mContent.itemsPerPage()) { - // 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 - public void deferCompleteDropAfterUninstallActivity() { - mDeferDropAfterUninstall = true; - } - - @Override - public void onUninstallActivityReturned(boolean success) { - mDeferDropAfterUninstall = false; - mUninstallSuccessful = success; - if (mDeferredAction != null) { - mDeferredAction.run(); - } - } - - @Override - public float getIntrinsicIconScaleFactor() { - return 1f; - } - - @Override - public boolean supportsFlingToDelete() { - return true; - } - - @Override - public boolean supportsAppInfoDropTarget() { - return !FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND; - } - - @Override - public boolean supportsDeleteDropTarget() { - return true; - } - - @Override - public void onFlingToDelete(DragObject d, PointF vec) { - // Do nothing - } - - @Override - public void onFlingToDeleteCompleted() { - // Do nothing - } - - private void updateItemLocationsInDatabaseBatch() { - ArrayList list = getItemsInReadingOrder(); - ArrayList items = new ArrayList(); - for (int i = 0; i < list.size(); i++) { - View v = list.get(i); - ItemInfo info = (ItemInfo) v.getTag(); - info.rank = i; - items.add(info); - } - - LauncherModel.moveItemsInDatabase(mLauncher, items, mInfo.id, 0); - } - - public void notifyDrop() { - if (mDragInProgress) { - mItemAddedBackToSelfViaIcon = true; - } - } - - public boolean isDropEnabled() { - return true; - } - - public boolean isFull() { - return mContent.isFull(); - } - - private void centerAboutIcon() { - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - - DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); - int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); - int height = getFolderHeight(); - - float scale = parent.getDescendantRectRelativeToSelf(mFolderIcon, sTempRect); - - DeviceProfile grid = mLauncher.getDeviceProfile(); - - int centerX = (int) (sTempRect.left + sTempRect.width() * scale / 2); - int centerY = (int) (sTempRect.top + sTempRect.height() * scale / 2); - int centeredLeft = centerX - width / 2; - int centeredTop = centerY - height / 2; - - // We need to bound the folder to the currently visible workspace area - mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect); - int left = Math.min(Math.max(sTempRect.left, centeredLeft), - sTempRect.left + sTempRect.width() - width); - int top = Math.min(Math.max(sTempRect.top, centeredTop), - sTempRect.top + sTempRect.height() - height); - if (grid.isPhone && (grid.availableWidthPx - width) < grid.iconSizePx) { - // Center the folder if it is full (on phones only) - left = (grid.availableWidthPx - width) / 2; - } else if (width >= sTempRect.width()) { - // If the folder doesn't fit within the bounds, center it about the desired bounds - left = sTempRect.left + (sTempRect.width() - width) / 2; - } - if (height >= sTempRect.height()) { - top = sTempRect.top + (sTempRect.height() - height) / 2; - } - - int folderPivotX = width / 2 + (centeredLeft - left); - int folderPivotY = height / 2 + (centeredTop - top); - setPivotX(folderPivotX); - setPivotY(folderPivotY); - mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * - (1.0f * folderPivotX / width)); - mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * - (1.0f * folderPivotY / height)); - - lp.width = width; - lp.height = height; - lp.x = left; - lp.y = top; - } - - public float getPivotXForIconAnimation() { - return mFolderIconPivotX; - } - public float getPivotYForIconAnimation() { - return mFolderIconPivotY; - } - - private int getContentAreaHeight() { - DeviceProfile grid = mLauncher.getDeviceProfile(); - Rect workspacePadding = grid.getWorkspacePadding(mContent.mIsRtl); - int maxContentAreaHeight = grid.availableHeightPx - - workspacePadding.top - workspacePadding.bottom - - mFooterHeight; - int height = Math.min(maxContentAreaHeight, - mContent.getDesiredHeight()); - return Math.max(height, MIN_CONTENT_DIMEN); - } - - private int getContentAreaWidth() { - return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN); - } - - private int getFolderHeight() { - return getFolderHeight(getContentAreaHeight()); - } - - private int getFolderHeight(int contentAreaHeight) { - return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight; - } - - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int contentWidth = getContentAreaWidth(); - int contentHeight = getContentAreaHeight(); - - int contentAreaWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY); - int contentAreaHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY); - - mContent.setFixedSize(contentWidth, contentHeight); - mContentWrapper.measure(contentAreaWidthSpec, contentAreaHeightSpec); - - if (mContent.getChildCount() > 0) { - int cellIconGap = (mContent.getPageAt(0).getCellWidth() - - mLauncher.getDeviceProfile().iconSizePx) / 2; - mFooter.setPadding(mContent.getPaddingLeft() + cellIconGap, - mFooter.getPaddingTop(), - mContent.getPaddingRight() + cellIconGap, - mFooter.getPaddingBottom()); - } - mFooter.measure(contentAreaWidthSpec, - MeasureSpec.makeMeasureSpec(mFooterHeight, MeasureSpec.EXACTLY)); - - int folderWidth = getPaddingLeft() + getPaddingRight() + contentWidth; - int folderHeight = getFolderHeight(contentHeight); - setMeasuredDimension(folderWidth, folderHeight); - } - - /** - * Rearranges the children based on their rank. - */ - public void rearrangeChildren() { - rearrangeChildren(-1); - } - - /** - * Rearranges the children based on their rank. - * @param itemCount if greater than the total children count, empty spaces are left at the end, - * otherwise it is ignored. - */ - public void rearrangeChildren(int itemCount) { - ArrayList views = getItemsInReadingOrder(); - mContent.arrangeChildren(views, Math.max(itemCount, views.size())); - mItemsInvalidated = true; - } - - public int getItemCount() { - return mContent.getItemCount(); - } - - @Thunk void replaceFolderWithFinalItem() { - // Add the last remaining child to the workspace in place of the folder - Runnable onCompleteRunnable = new Runnable() { - @Override - public void run() { - int itemCount = mInfo.contents.size(); - if (itemCount <= 1) { - View newIcon = null; - - if (itemCount == 1) { - // Move the item from the folder to the workspace, in the position of the - // folder - CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, - mInfo.screenId); - ShortcutInfo finalItem = mInfo.contents.remove(0); - newIcon = mLauncher.createShortcut(cellLayout, finalItem); - LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container, - mInfo.screenId, mInfo.cellX, mInfo.cellY); - } - - // Remove the folder - mLauncher.removeItem(mFolderIcon, mInfo, true /* deleteFromDb */); - if (mFolderIcon instanceof DropTarget) { - mDragController.removeDropTarget((DropTarget) mFolderIcon); - } - - if (newIcon != null) { - // We add the child after removing the folder to prevent both from existing - // at the same time in the CellLayout. We need to add the new item with - // addInScreenFromBind() to ensure that hotseat items are placed correctly. - mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo.container, - mInfo.screenId, mInfo.cellX, mInfo.cellY, mInfo.spanX, mInfo.spanY); - - // Focus the newly created child - newIcon.requestFocus(); - } - } - } - }; - View finalChild = mContent.getLastItem(); - if (finalChild != null) { - mFolderIcon.performDestroyAnimation(finalChild, onCompleteRunnable); - } else { - onCompleteRunnable.run(); - } - mDestroyed = true; - } - - public boolean isDestroyed() { - return mDestroyed; - } - - // This method keeps track of the first and last item in the folder for the purposes - // of keyboard focus - public void updateTextViewFocus() { - final View firstChild = mContent.getFirstItem(); - final View lastChild = mContent.getLastItem(); - if (firstChild != null && lastChild != null) { - mFolderName.setNextFocusDownId(lastChild.getId()); - mFolderName.setNextFocusRightId(lastChild.getId()); - mFolderName.setNextFocusLeftId(lastChild.getId()); - mFolderName.setNextFocusUpId(lastChild.getId()); - // Hitting TAB from the folder name wraps around to the first item on the current - // folder page, and hitting SHIFT+TAB from that item wraps back to the folder name. - mFolderName.setNextFocusForwardId(firstChild.getId()); - // When clicking off the folder when editing the name, this Folder gains focus. When - // pressing an arrow key from that state, give the focus to the first item. - this.setNextFocusDownId(firstChild.getId()); - this.setNextFocusRightId(firstChild.getId()); - this.setNextFocusLeftId(firstChild.getId()); - this.setNextFocusUpId(firstChild.getId()); - // When pressing shift+tab in the above state, give the focus to the last item. - setOnKeyListener(new OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - boolean isShiftPlusTab = keyCode == KeyEvent.KEYCODE_TAB && - event.hasModifiers(KeyEvent.META_SHIFT_ON); - if (isShiftPlusTab && Folder.this.isFocused()) { - return lastChild.requestFocus(); - } - return false; - } - }); - } - } - - public void onDrop(DragObject d) { - Runnable cleanUpRunnable = null; - - // If we are coming from All Apps space, we defer removing the extra empty screen - // until the folder closes - if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) { - cleanUpRunnable = new Runnable() { - @Override - public void run() { - mLauncher.exitSpringLoadedDragModeDelayed(true, - Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, - null); - } - }; - } - - // If the icon was dropped while the page was being scrolled, we need to compute - // the target location again such that the icon is placed of the final page. - if (!mContent.rankOnCurrentPage(mEmptyCellRank)) { - // Reorder again. - mTargetRank = getTargetRank(d, null); - - // Rearrange items immediately. - mReorderAlarmListener.onAlarm(mReorderAlarm); - - mOnScrollHintAlarm.cancelAlarm(); - mScrollPauseAlarm.cancelAlarm(); - } - mContent.completePendingPageChanges(); - - View currentDragView; - ShortcutInfo si = mCurrentDragInfo; - if (mIsExternalDrag) { - currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank); - // Actually move the item in the database if it was an external drag. Call this - // before creating the view, so that ShortcutInfo is updated appropriately. - LauncherModel.addOrMoveItemInDatabase( - mLauncher, si, mInfo.id, 0, si.cellX, si.cellY); - - // We only need to update the locations if it doesn't get handled in #onDropCompleted. - if (d.dragSource != this) { - updateItemLocationsInDatabaseBatch(); - } - mIsExternalDrag = false; - } else { - currentDragView = mCurrentDragView; - mContent.addViewForRank(currentDragView, si, mEmptyCellRank); - } - - if (d.dragView.hasDrawn()) { - - // Temporarily reset the scale such that the animation target gets calculated correctly. - float scaleX = getScaleX(); - float scaleY = getScaleY(); - setScaleX(1.0f); - setScaleY(1.0f); - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, - cleanUpRunnable, null); - setScaleX(scaleX); - setScaleY(scaleY); - } else { - d.deferDragViewCleanupPostAnimation = false; - currentDragView.setVisibility(VISIBLE); - } - mItemsInvalidated = true; - rearrangeChildren(); - - // Temporarily suppress the listener, as we did all the work already here. - mSuppressOnAdd = true; - mInfo.add(si); - mSuppressOnAdd = false; - // Clear the drag info, as it is no longer being dragged. - mCurrentDragInfo = null; - mDragInProgress = false; - - if (mContent.getPageCount() > 1) { - // The animation has already been shown while opening the folder. - mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher); - } - } - - // This is used so the item doesn't immediately appear in the folder when added. In one case - // we need to create the illusion that the item isn't added back to the folder yet, to - // to correspond to the animation of the icon back into the folder. This is - public void hideItem(ShortcutInfo info) { - View v = getViewForInfo(info); - v.setVisibility(INVISIBLE); - } - public void showItem(ShortcutInfo info) { - View v = getViewForInfo(info); - v.setVisibility(VISIBLE); - } - - @Override - public void onAdd(ShortcutInfo item) { - // If the item was dropped onto this open folder, we have done the work associated - // with adding the item to the folder, as indicated by mSuppressOnAdd being set - if (mSuppressOnAdd) return; - mContent.createAndAddViewForRank(item, mContent.allocateRankForNewItem(item)); - mItemsInvalidated = true; - LauncherModel.addOrMoveItemInDatabase( - mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); - } - - public void onRemove(ShortcutInfo item) { - mItemsInvalidated = true; - // If this item is being dragged from this open folder, we have already handled - // the work associated with removing the item, so we don't have to do anything here. - if (item == mCurrentDragInfo) return; - View v = getViewForInfo(item); - mContent.removeItem(v); - if (mState == STATE_ANIMATING) { - mRearrangeOnClose = true; - } else { - rearrangeChildren(); - } - if (getItemCount() <= 1) { - if (mInfo.opened) { - mLauncher.closeFolder(this, true); - } else { - replaceFolderWithFinalItem(); - } - } - } - - private View getViewForInfo(final ShortcutInfo item) { - return mContent.iterateOverItems(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View view, View parent) { - return info == item; - } - }); - } - - public void onItemsChanged() { - updateTextViewFocus(); - } - - public void onTitleChanged(CharSequence title) { - } - - public ArrayList getItemsInReadingOrder() { - if (mItemsInvalidated) { - mItemsInReadingOrder.clear(); - mContent.iterateOverItems(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View view, View parent) { - mItemsInReadingOrder.add(view); - return false; - } - }); - mItemsInvalidated = false; - } - return mItemsInReadingOrder; - } - - public void onFocusChange(View v, boolean hasFocus) { - if (v == mFolderName) { - if (hasFocus) { - startEditingFolderName(); - } else { - dismissEditingName(); - } - } - } - - @Override - public void getHitRectRelativeToDragLayer(Rect outRect) { - getHitRect(outRect); - outRect.left -= mScrollAreaOffset; - outRect.right += mScrollAreaOffset; - } - - @Override - public void fillInLaunchSourceData(View v, Bundle sourceData) { - // Fill in from the folder icon's launch source provider first - Stats.LaunchSourceUtils.populateSourceDataFromAncestorProvider(mFolderIcon, sourceData); - sourceData.putString(Stats.SOURCE_EXTRA_SUB_CONTAINER, Stats.SUB_CONTAINER_FOLDER); - sourceData.putInt(Stats.SOURCE_EXTRA_SUB_CONTAINER_PAGE, mContent.getCurrentPage()); - } - - private class OnScrollHintListener implements OnAlarmListener { - - private final DragObject mDragObject; - - OnScrollHintListener(DragObject object) { - mDragObject = object; - } - - /** - * Scroll hint has been shown long enough. Now scroll to appropriate page. - */ - @Override - public void onAlarm(Alarm alarm) { - if (mCurrentScrollDir == DragController.SCROLL_LEFT) { - mContent.scrollLeft(); - mScrollHintDir = DragController.SCROLL_NONE; - } else if (mCurrentScrollDir == DragController.SCROLL_RIGHT) { - mContent.scrollRight(); - mScrollHintDir = DragController.SCROLL_NONE; - } else { - // This should not happen - return; - } - mCurrentScrollDir = DragController.SCROLL_NONE; - - // Pause drag event until the scrolling is finished - mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject)); - mScrollPauseAlarm.setAlarm(DragController.RESCROLL_DELAY); - } - } - - private class OnScrollFinishedListener implements OnAlarmListener { - - private final DragObject mDragObject; - - OnScrollFinishedListener(DragObject object) { - mDragObject = object; - } - - /** - * Page scroll is complete. - */ - @Override - public void onAlarm(Alarm alarm) { - // Reorder immediately on page change. - onDragOver(mDragObject, 1); - } - } - - // Compares item position based on rank and position giving priority to the rank. - public static final Comparator ITEM_POS_COMPARATOR = new Comparator() { - - @Override - public int compare(ItemInfo lhs, ItemInfo rhs) { - if (lhs.rank != rhs.rank) { - return lhs.rank - rhs.rank; - } else if (lhs.cellY != rhs.cellY) { - return lhs.cellY - rhs.cellY; - } else { - return lhs.cellX - rhs.cellX; - } - } - }; -} diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java deleted file mode 100644 index 5c084d949..000000000 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ /dev/null @@ -1,776 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * 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.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -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; -import android.os.Looper; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -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 Folder}. - */ -public class FolderIcon extends FrameLayout implements FolderListener { - @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 - 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; - private static final int FINAL_ITEM_ANIMATION_DURATION = 200; - - // The degree to which the inner ring grows when accepting drop - private static final float INNER_RING_GROWTH_FACTOR = 0.15f; - - // The degree to which the outer ring is scaled in its natural state - private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; - - // 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; - - // 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; - - 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 int mAvailableSpaceInPreview; - private int mPreviewOffsetX; - private int mPreviewOffsetY; - private int mTotalWidth; - - private PreviewLayoutRule mPreviewLayoutRule; - - boolean mAnimating = false; - private Rect mOldBounds = new Rect(); - - private float mSlop; - - private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0); - @Thunk PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0); - @Thunk ArrayList mHiddenItems = new ArrayList(); - - private Alarm mOpenAlarm = new Alarm(); - @Thunk - ItemInfo mDragInfo; - - public FolderIcon(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public FolderIcon(Context context) { - super(context); - init(); - } - - private void init() { - mLongPressHelper = new CheckLongPressHelper(this); - mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); - mPreviewLayoutRule = FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON ? - new ClippedFolderIconLayoutRule() : - new StackFolderIconLayoutRule(); - - setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); - } - - public boolean isDropEnabled() { - final ViewGroup cellLayoutChildren = (ViewGroup) getParent(); - final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent(); - final Workspace workspace = (Workspace) cellLayout.getParent(); - return !workspace.workspaceInModalState(); - } - - 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; - if (error) { - throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " + - "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " + - "is dependent on this"); - } - - DeviceProfile grid = launcher.getDeviceProfile(); - - FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); - icon.setClipToPadding(false); - icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); - icon.mFolderName.setText(folderInfo.title); - icon.mFolderName.setCompoundDrawablePadding(0); - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams(); - lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; - - // Offset the preview background to center this view accordingly - icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); - lp = (FrameLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams(); - lp.topMargin = grid.folderBackgroundOffset; - lp.width = grid.folderIconSizePx; - lp.height = grid.folderIconSizePx; - - icon.setTag(folderInfo); - icon.setOnClickListener(launcher); - icon.mInfo = folderInfo; - icon.mLauncher = launcher; - icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title)); - Folder folder = Folder.fromXml(launcher); - folder.setDragController(launcher.getDragController()); - folder.setFolderIcon(icon); - folder.bind(folderInfo); - icon.mFolder = folder; - - icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon); - folderInfo.addListener(icon); - - icon.setOnFocusChangeListener(launcher.mFocusHandler); - return icon; - } - - @Override - protected Parcelable onSaveInstanceState() { - sStaticValuesDirty = true; - return super.onSaveInstanceState(); - } - - public static class FolderRingAnimator { - public int mCellX; - public int mCellY; - @Thunk - CellLayout mCellLayout; - public float mOuterRingSize; - public float mInnerRingSize; - public FolderIcon mFolderIcon = null; - public static Drawable sSharedOuterRingDrawable = null; - public static Drawable sSharedInnerRingDrawable = null; - public static int sPreviewSize = -1; - public static int sPreviewPadding = -1; - - private ValueAnimator mAcceptAnimator; - private ValueAnimator mNeutralAnimator; - - public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { - mFolderIcon = folderIcon; - Resources res = launcher.getResources(); - - // We need to reload the static values when configuration changes in case they are - // different in another configuration - if (sStaticValuesDirty) { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread " - + Thread.currentThread()); - } - - DeviceProfile grid = launcher.getDeviceProfile(); - sPreviewSize = grid.folderIconSizePx; - sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); - sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer); - sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip); - sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); - sStaticValuesDirty = false; - } - } - - public void animateToAcceptState() { - if (mNeutralAnimator != null) { - mNeutralAnimator.cancel(); - } - mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); - mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); - - final int previewSize = sPreviewSize; - mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = (Float) animation.getAnimatedValue(); - mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize; - mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize; - if (mCellLayout != null) { - mCellLayout.invalidate(); - } - } - }); - mAcceptAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (mFolderIcon != null) { - mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); - } - } - }); - mAcceptAnimator.start(); - } - - public void animateToNaturalState() { - if (mAcceptAnimator != null) { - mAcceptAnimator.cancel(); - } - mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); - mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); - - final int previewSize = sPreviewSize; - mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = (Float) animation.getAnimatedValue(); - mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize; - mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize; - if (mCellLayout != null) { - mCellLayout.invalidate(); - } - } - }); - mNeutralAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mCellLayout != null) { - mCellLayout.hideFolderAccept(FolderRingAnimator.this); - } - if (mFolderIcon != null) { - mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); - } - } - }); - mNeutralAnimator.start(); - } - - // Location is expressed in window coordinates - public void getCell(int[] loc) { - loc[0] = mCellX; - loc[1] = mCellY; - } - - // Location is expressed in window coordinates - public void setCell(int x, int y) { - mCellX = x; - mCellY = y; - } - - public void setCellLayout(CellLayout layout) { - mCellLayout = layout; - } - - public float getOuterRingSize() { - return mOuterRingSize; - } - - public float getInnerRingSize() { - return mInnerRingSize; - } - } - - public Folder getFolder() { - return mFolder; - } - - public FolderInfo getFolderInfo() { - return mInfo; - } - - private boolean willAcceptItem(ItemInfo item) { - final int itemType = item.itemType; - return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || - itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && - !mFolder.isFull() && item != mInfo && !mInfo.opened); - } - - public boolean acceptDrop(ItemInfo dragInfo) { - final ItemInfo item = dragInfo; - return !mFolder.isDestroyed() && willAcceptItem(item); - } - - public void addItem(ShortcutInfo item) { - mInfo.add(item); - } - - 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); - mFolderRingAnimator.setCellLayout(layout); - mFolderRingAnimator.animateToAcceptState(); - layout.showFolderAccept(mFolderRingAnimator); - mOpenAlarm.setOnAlarmListener(mOnOpenListener); - if (SPRING_LOADING_ENABLED && - ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) { - // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even - // though widget-style shortcuts can be added to folders. The issue is that we need - // to deal with configuration activities which are currently handled in - // Workspace#onDropExternal. - mOpenAlarm.setAlarm(ON_OPEN_DELAY); - } - mDragInfo = dragInfo; - } - - OnAlarmListener mOnOpenListener = new OnAlarmListener() { - public void onAlarm(Alarm alarm) { - ShortcutInfo item; - if (mDragInfo instanceof AppInfo) { - // Came from all apps -- make a copy. - item = ((AppInfo) mDragInfo).makeShortcut(); - item.spanX = 1; - item.spanY = 1; - } else { - // ShortcutInfo - item = (ShortcutInfo) mDragInfo; - } - mFolder.beginExternalDrag(item); - mLauncher.openFolder(FolderIcon.this, true); - } - }; - - public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, - final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, - float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { - - // These correspond two the drawable and view that the icon was dropped _onto_ - Drawable animateDrawable = getTopDrawable((TextView) destView); - computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), - destView.getMeasuredWidth()); - - // This will animate the first item from it's position as an icon into its - // position as the first item in the preview - animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null); - addItem(destInfo); - - // This will animate the dragView (srcView) into the new folder - onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null); - } - - public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) { - Drawable animateDrawable = getTopDrawable((TextView) finalView); - computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), - finalView.getMeasuredWidth()); - - // This will animate the first item from it's position as an icon into its - // position as the first item in the preview - animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true, - onCompleteRunnable); - } - - public void onDragExit(Object dragInfo) { - onDragExit(); - } - - public void onDragExit() { - mFolderRingAnimator.animateToNaturalState(); - mOpenAlarm.cancelAlarm(); - } - - private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, - float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable, - DragObject d) { - item.cellX = -1; - item.cellY = -1; - - // Typically, the animateView corresponds to the DragView; however, if this is being done - // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we - // will not have a view to animate - if (animateView != null) { - DragLayer dragLayer = mLauncher.getDragLayer(); - Rect from = new Rect(); - dragLayer.getViewRectRelativeToSelf(animateView, from); - Rect to = finalRect; - if (to == null) { - to = new Rect(); - Workspace workspace = mLauncher.getWorkspace(); - // Set cellLayout and this to it's final state to compute final animation locations - workspace.setFinalTransitionTransform((CellLayout) getParent().getParent()); - float scaleX = getScaleX(); - float scaleY = getScaleY(); - setScaleX(1.0f); - setScaleY(1.0f); - scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to); - // Finished computing final animation locations, restore current state - setScaleX(scaleX); - setScaleY(scaleY); - workspace.resetTransitionTransform((CellLayout) getParent().getParent()); - } - - int[] center = new int[2]; - 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 < mPreviewLayoutRule.numItems() ? 0.5f : 0f; - - float finalScale = scale * scaleRelativeToDragLayer; - dragLayer.animateView(animateView, from, to, finalAlpha, - 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION, - new DecelerateInterpolator(2), new AccelerateInterpolator(2), - postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null); - addItem(item); - mHiddenItems.add(item); - mFolder.hideItem(item); - postDelayed(new Runnable() { - public void run() { - mHiddenItems.remove(item); - mFolder.showItem(item); - invalidate(); - } - }, DROP_IN_ANIMATION_DURATION); - } else { - addItem(item); - } - } - - public void onDrop(DragObject d) { - ShortcutInfo item; - if (d.dragInfo instanceof AppInfo) { - // Came from all apps -- make a copy - item = ((AppInfo) d.dragInfo).makeShortcut(); - } else { - item = (ShortcutInfo) d.dragInfo; - } - mFolder.notifyDrop(); - onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d); - } - - private void computePreviewDrawingParams(int drawableSize, int totalSize) { - if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) { - DeviceProfile grid = mLauncher.getDeviceProfile(); - - mIntrinsicIconSize = drawableSize; - mTotalWidth = totalSize; - - final int previewSize = FolderRingAnimator.sPreviewSize; - final int previewPadding = FolderRingAnimator.sPreviewPadding; - - mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); - - mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; - mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset + getPaddingTop(); - - mPreviewLayoutRule.init(mAvailableSpaceInPreview, mIntrinsicIconSize, - Utilities.isRtl(getResources())); - } - } - - private void computePreviewDrawingParams(Drawable d) { - computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth()); - } - - static class PreviewItemDrawingParams { - PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) { - this.transX = transX; - this.transY = transY; - this.scale = scale; - this.overlayAlpha = overlayAlpha; - } - float transX; - float transY; - float scale; - float overlayAlpha; - Drawable drawable; - } - - private float getLocalCenterForIndex(int index, int curNumItems, int[] center) { - mParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index), - curNumItems, mParams); - - mParams.transX += mPreviewOffsetX; - mParams.transY += mPreviewOffsetY; - float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2; - float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2; - - center[0] = (int) Math.round(offsetX); - center[1] = (int) Math.round(offsetY); - return mParams.scale; - } - - private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, - PreviewItemDrawingParams params) { - return mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params); - } - - private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) { - canvas.save(); - canvas.translate(params.transX, params.transY); - canvas.scale(params.scale, params.scale); - Drawable d = params.drawable; - - if (d != null) { - mOldBounds.set(d.getBounds()); - d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize); - if (d instanceof FastBitmapDrawable) { - FastBitmapDrawable fd = (FastBitmapDrawable) d; - float oldBrightness = fd.getBrightness(); - fd.setBrightness(params.overlayAlpha); - d.draw(canvas); - fd.setBrightness(oldBrightness); - } else { - d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255), - PorterDuff.Mode.SRC_ATOP); - d.draw(canvas); - d.clearColorFilter(); - } - d.setBounds(mOldBounds); - } - canvas.restore(); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - if (mFolder == null) return; - if (mFolder.getItemCount() == 0 && !mAnimating) return; - - ArrayList items = mFolder.getItemsInReadingOrder(); - Drawable d; - TextView v; - - // Update our drawing parameters if necessary - if (mAnimating) { - computePreviewDrawingParams(mAnimParams.drawable); - } else { - v = (TextView) items.get(0); - d = getTopDrawable(v); - computePreviewDrawingParams(d); - } - - 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, nItemsInPreview, mParams); - mParams.drawable = d; - drawPreviewItem(canvas, mParams); - } - } - } else { - drawPreviewItem(canvas, mAnimParams); - } - canvas.restore(); - } - - private Drawable getTopDrawable(TextView v) { - Drawable d = v.getCompoundDrawables()[1]; - return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d; - } - - private void animateFirstItem(final Drawable d, int duration, final boolean reverse, - final Runnable onCompleteRunnable) { - - 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; - mAnimParams.drawable = d; - - ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f); - va.addUpdateListener(new AnimatorUpdateListener(){ - public void onAnimationUpdate(ValueAnimator animation) { - float progress = (Float) animation.getAnimatedValue(); - if (reverse) { - progress = 1 - progress; - mPreviewBackground.setAlpha(progress); - } - - mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0); - mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0); - mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0); - invalidate(); - } - }); - va.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mAnimating = true; - } - @Override - public void onAnimationEnd(Animator animation) { - mAnimating = false; - if (onCompleteRunnable != null) { - onCompleteRunnable.run(); - } - } - }); - va.setDuration(duration); - va.start(); - } - - public void setTextVisible(boolean visible) { - if (visible) { - mFolderName.setVisibility(VISIBLE); - } else { - mFolderName.setVisibility(INVISIBLE); - } - } - - public boolean getTextVisible() { - return mFolderName.getVisibility() == VISIBLE; - } - - public void onItemsChanged() { - invalidate(); - requestLayout(); - } - - public void onAdd(ShortcutInfo item) { - invalidate(); - requestLayout(); - } - - public void onRemove(ShortcutInfo item) { - invalidate(); - requestLayout(); - } - - public void onTitleChanged(CharSequence title) { - mFolderName.setText(title); - setContentDescription(getContext().getString(R.string.folder_name_format, title)); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // Call the superclass onTouchEvent first, because sometimes it changes the state to - // isPressed() on an ACTION_UP - boolean result = super.onTouchEvent(event); - - // Check for a stylus button press, if it occurs cancel any long press checks. - if (mStylusEventHelper.onMotionEvent(event)) { - mLongPressHelper.cancelLongPress(); - return true; - } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mLongPressHelper.postCheckForLongPress(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mLongPressHelper.cancelLongPress(); - break; - case MotionEvent.ACTION_MOVE: - if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) { - mLongPressHelper.cancelLongPress(); - } - break; - } - return result; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - } - - @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/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index c25444e06..7fc5d2b04 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -33,6 +33,8 @@ 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.Folder; +import com.android.launcher3.FolderIcon; import com.android.launcher3.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java index 87f5f897b..01eeecd43 100644 --- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java @@ -18,11 +18,12 @@ package com.android.launcher3.folder; import android.graphics.Path; -import com.android.launcher3.folder.FolderIcon.PreviewItemDrawingParams; +import com.android.launcher3.FolderIcon; +import com.android.launcher3.FolderIcon.PreviewItemDrawingParams; public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { - static final int MAX_NUM_ITEMS_IN_PREVIEW = 3; + public 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) diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 7ad478ead..9a0c50bac 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -20,7 +20,6 @@ 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; @@ -34,7 +33,7 @@ import com.android.launcher3.DeleteDropTarget; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.folder.Folder; +import com.android.launcher3.Folder; import com.android.launcher3.IconCache; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -- cgit v1.2.3