diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2016-10-10 21:00:09 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2016-10-10 21:00:10 +0000 |
commit | 631ffbda64b92b843f18af6756b52b1ac4c22461 (patch) | |
tree | 6c9c56068f427cfcf5851e1bca0d8714ab554be6 /src | |
parent | 51e15402faabf708fa707cefcbb28d7883fed1d0 (diff) | |
parent | 740ac7f00e0b847b8e392800f7948d93493e11d6 (diff) | |
download | android_packages_apps_Trebuchet-631ffbda64b92b843f18af6756b52b1ac4c22461.tar.gz android_packages_apps_Trebuchet-631ffbda64b92b843f18af6756b52b1ac4c22461.tar.bz2 android_packages_apps_Trebuchet-631ffbda64b92b843f18af6756b52b1ac4c22461.zip |
Merge "Refactoring floating view opening/closing logic" into ub-launcher3-master
Diffstat (limited to 'src')
15 files changed, 536 insertions, 490 deletions
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java new file mode 100644 index 000000000..65da00211 --- /dev/null +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.support.annotation.IntDef; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +import com.android.launcher3.dragndrop.DragLayer; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Base class for a View which shows a floating UI on top of the launcher UI. + */ +public abstract class AbstractFloatingView extends LinearLayout { + + @IntDef(flag = true, value = {TYPE_FOLDER, TYPE_DEEPSHORTCUT_CONTAINER}) + @Retention(RetentionPolicy.SOURCE) + public @interface FloatingViewType {} + public static final int TYPE_FOLDER = 1 << 0; + public static final int TYPE_DEEPSHORTCUT_CONTAINER = 1 << 1; + + protected boolean mIsOpen; + + public AbstractFloatingView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public final void close(boolean animate) { + animate &= !Utilities.isPowerSaverOn(getContext()); + handleClose(animate); + Launcher.getLauncher(getContext()).getUserEventDispatcher().resetElapsedContainerMillis(); + } + + protected abstract void handleClose(boolean animate); + + /** + * If the view is current handling keyboard, return the active target, null otherwise + */ + public ExtendedEditText getActiveTextView() { + return null; + } + + + /** + * Any additional view (outside of this container) where touch should be allowed while this + * view is visible. + */ + public View getExtendedTouchView() { + return null; + } + + public final boolean isOpen() { + return mIsOpen; + } + + protected abstract boolean isOfType(@FloatingViewType int type); + + protected static <T extends AbstractFloatingView> T getOpenView( + Launcher launcher, @FloatingViewType int type) { + DragLayer dragLayer = launcher.getDragLayer(); + // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer, + // and will be one of the last views. + for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { + View child = dragLayer.getChildAt(i); + if (child instanceof AbstractFloatingView) { + AbstractFloatingView view = (AbstractFloatingView) child; + if (view.isOfType(type) && view.isOpen()) { + return (T) view; + } + } + } + return null; + } + + protected static void closeOpenContainer(Launcher launcher, @FloatingViewType int type) { + AbstractFloatingView view = getOpenView(launcher, type); + if (view != null) { + view.close(true); + } + } + + public static void closeAllOpenViews(Launcher launcher, boolean animate) { + DragLayer dragLayer = launcher.getDragLayer(); + // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer, + // and will be one of the last views. + for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { + View child = dragLayer.getChildAt(i); + if (child instanceof AbstractFloatingView) { + ((AbstractFloatingView) child).close(animate); + } + } + } + + public static void closeAllOpenViews(Launcher launcher) { + closeAllOpenViews(launcher, true); + } + + public static AbstractFloatingView getTopOpenView(Launcher launcher) { + return getOpenView(launcher, TYPE_FOLDER | TYPE_DEEPSHORTCUT_CONTAINER); + } +} diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index c06f727a5..d05673ccc 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -99,4 +99,12 @@ public class ExtendedEditText extends EditText { ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); } + + public void dispatchBackKey() { + ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) + .hideSoftInputFromWindow(getWindowToken(), 0); + if (mBackKeyListener != null) { + mBackKeyListener.onBackKey(); + } + } } diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index c0a8caaa3..8b70d1c70 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -45,11 +45,6 @@ public class FolderInfo extends ItemInfo { */ public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004; - /** - * Whether this folder has been opened - */ - public boolean opened; - public int options; /** diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index b1700ccbb..43a250b3a 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -18,9 +18,7 @@ package com.android.launcher3; import android.Manifest; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -47,8 +45,6 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.AsyncTask; @@ -79,10 +75,8 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.OvershootInterpolator; import android.view.inputmethod.InputMethodManager; -import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -295,13 +289,6 @@ public class Launcher extends Activity // it from the context. private SharedPreferences mSharedPrefs; - // Holds the page that we need to animate to, and the icon views that we need to animate up - // when we scroll to that page on resume. - @Thunk ImageView mFolderIconImageView; - private Bitmap mFolderIconBitmap; - private Canvas mFolderIconCanvas; - private Rect mRectForFolderAnimation = new Rect(); - private DeviceProfile mDeviceProfile; private boolean mMoveToDefaultScreenFromNewIntent; @@ -1231,11 +1218,8 @@ public class Launcher extends Activity if (keyCode == KeyEvent.KEYCODE_MENU) { // Ignore the menu key if we are currently dragging or are on the custom content screen if (!isOnCustomContent() && !mDragController.isDragging()) { - // Close any open folders - closeFolder(); - - // Close any shortcuts containers - closeShortcutsContainer(); + // Close any open floating view + AbstractFloatingView.closeAllOpenViews(this); // Stop resizing any widgets mWorkspace.exitWidgetResizeMode(); @@ -1705,7 +1689,7 @@ public class Launcher extends Activity // Check this condition before handling isActionMain, as this will get reset. boolean shouldMoveToDefaultScreen = alreadyOnHome && - mState == State.WORKSPACE && getTopFloatingView() == null; + mState == State.WORKSPACE && AbstractFloatingView.getTopOpenView(this) == null; boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction()); if (isActionMain) { @@ -1719,8 +1703,7 @@ public class Launcher extends Activity // In all these cases, only animate if we're already on home mWorkspace.exitWidgetResizeMode(); - closeFolder(alreadyOnHome); - closeShortcutsContainer(alreadyOnHome); + AbstractFloatingView.closeAllOpenViews(this, alreadyOnHome); exitSpringLoadedDragMode(); // If we are already on home, then just animate back to the workspace, @@ -1803,11 +1786,9 @@ public class Launcher extends Activity super.onSaveInstanceState(outState); outState.putInt(RUNTIME_STATE, mState.ordinal()); - // We close any open folder since it will not be re-opened, and we need to make sure - // this state is reflected. - // TODO: Move folderInfo.isOpened out of the model and make it a UI state. - closeFolder(false); - closeShortcutsContainer(false); + // We close any open folders and shortcut containers since they will not be re-opened, + // and we need to make sure this state is reflected. + AbstractFloatingView.closeAllOpenViews(this, false); if (mPendingRequestArgs != null) { outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs); @@ -2036,7 +2017,7 @@ public class Launcher extends Activity protected void moveToCustomContentScreen(boolean animate) { // Close any folders that may be open. - closeFolder(); + AbstractFloatingView.closeAllOpenViews(this, animate); mWorkspace.moveToCustomContentScreen(animate); } @@ -2227,21 +2208,19 @@ public class Launcher extends Activity return; } - if (getOpenShortcutsContainer() != null) { - closeShortcutsContainer(); + AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this); + if (topView != null) { + if (topView.getActiveTextView() != null) { + topView.getActiveTextView().dispatchBackKey(); + } else { + topView.close(true); + } } else if (isAppsViewVisible()) { showWorkspace(true); } else if (isWidgetsViewVisible()) { showOverviewMode(true); } else if (mWorkspace.isInOverviewMode()) { showWorkspace(true); - } else if (mWorkspace.getOpenFolder() != null) { - Folder openFolder = mWorkspace.getOpenFolder(); - if (openFolder.isEditingName()) { - openFolder.dismissEditingName(); - } else { - closeFolder(); - } } else { mWorkspace.exitWidgetResizeMode(); @@ -2491,10 +2470,10 @@ public class Launcher extends Activity throw new IllegalArgumentException("Input must be a FolderIcon"); } - FolderIcon folderIcon = (FolderIcon) v; - if (!folderIcon.getFolderInfo().opened && !folderIcon.getFolder().isDestroyed()) { + Folder folder = ((FolderIcon) v).getFolder(); + if (!folder.isOpen() && !folder.isDestroyed()) { // Open the requested folder - openFolder(folderIcon); + folder.animateOpen(); } } @@ -2735,233 +2714,6 @@ public class Launcher extends Activity return false; } - /** - * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView - * in the DragLayer in the exact absolute location of the original FolderIcon. - */ - private void copyFolderIconToImage(FolderIcon fi) { - final int width = fi.getMeasuredWidth(); - final int height = fi.getMeasuredHeight(); - - // Lazy load ImageView, Bitmap and Canvas - if (mFolderIconImageView == null) { - mFolderIconImageView = new ImageView(this); - } - if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width || - mFolderIconBitmap.getHeight() != height) { - mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mFolderIconCanvas = new Canvas(mFolderIconBitmap); - } - - DragLayer.LayoutParams lp; - if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) { - lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams(); - } else { - lp = new DragLayer.LayoutParams(width, height); - } - - // The layout from which the folder is being opened may be scaled, adjust the starting - // view size by this scale factor. - float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation); - lp.customPosition = true; - lp.x = mRectForFolderAnimation.left; - lp.y = mRectForFolderAnimation.top; - lp.width = (int) (scale * width); - lp.height = (int) (scale * height); - - mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR); - fi.draw(mFolderIconCanvas); - mFolderIconImageView.setImageBitmap(mFolderIconBitmap); - if (fi.getFolder() != null) { - mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation()); - mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation()); - } - // Just in case this image view is still in the drag layer from a previous animation, - // we remove it and re-add it. - if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) { - mDragLayer.removeView(mFolderIconImageView); - } - mDragLayer.addView(mFolderIconImageView, lp); - if (fi.getFolder() != null) { - fi.getFolder().bringToFront(); - } - } - - private void growAndFadeOutFolderIcon(FolderIcon fi) { - if (fi == null) return; - FolderInfo info = (FolderInfo) fi.getTag(); - if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - CellLayout cl = (CellLayout) fi.getParent().getParent(); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams(); - cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); - } - - // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original - copyFolderIconToImage(fi); - fi.setVisibility(View.INVISIBLE); - - ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale( - mFolderIconImageView, 0, 1.5f, 1.5f); - if (Utilities.ATLEAST_LOLLIPOP) { - oa.setInterpolator(new LogDecelerateInterpolator(100, 0)); - } - oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); - oa.start(); - } - - private void shrinkAndFadeInFolderIcon(final FolderIcon fi, boolean animate) { - if (fi == null) return; - final CellLayout cl = (CellLayout) fi.getParent().getParent(); - - // We remove and re-draw the FolderIcon in-case it has changed - mDragLayer.removeView(mFolderIconImageView); - copyFolderIconToImage(fi); - - if (cl != null) { - cl.clearFolderLeaveBehind(); - } - - ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1); - oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); - oa.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (cl != null) { - // Remove the ImageView copy of the FolderIcon and make the original visible. - mDragLayer.removeView(mFolderIconImageView); - fi.setVisibility(View.VISIBLE); - } - } - }); - oa.start(); - if (!animate) { - oa.end(); - } - } - - /** - * Opens the user folder described by the specified tag. The opening of the folder - * is animated relative to the specified View. If the View is null, no animation - * is played. - * - * @param folderIcon The FolderIcon describing the folder to open. - */ - public void openFolder(FolderIcon folderIcon) { - - Folder folder = folderIcon.getFolder(); - Folder openFolder = mWorkspace != null ? mWorkspace.getOpenFolder() : null; - if (openFolder != null && openFolder != folder) { - // Close any open folder before opening a folder. - closeFolder(); - } - - FolderInfo info = folder.mInfo; - - info.opened = true; - - // While the folder is open, the position of the icon cannot change. - ((CellLayout.LayoutParams) folderIcon.getLayoutParams()).canReorder = false; - - // Just verify that the folder hasn't already been added to the DragLayer. - // There was a one-off crash where the folder had a parent already. - if (folder.getParent() == null) { - mDragLayer.addView(folder); - mDragController.addDropTarget(folder); - } else { - Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" + - folder.getParent() + ")."); - } - folder.animateOpen(); - - growAndFadeOutFolderIcon(folderIcon); - - // Notify the accessibility manager that this folder "window" has appeared and occluded - // the workspace items - folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - - getUserEventDispatcher().resetElapsedContainerMillis(); - } - - public void closeFolder() { - closeFolder(true); - } - - public void closeFolder(boolean animate) { - Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null; - if (folder != null) { - if (folder.isEditingName()) { - folder.dismissEditingName(); - } - closeFolder(folder, animate); - } - } - - public void closeFolder(Folder folder, boolean animate) { - animate &= !Utilities.isPowerSaverOn(this); - - folder.getInfo().opened = false; - - ViewGroup parent = (ViewGroup) folder.getParent().getParent(); - if (parent != null) { - FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo); - shrinkAndFadeInFolderIcon(fi, animate); - if (fi != null) { - ((CellLayout.LayoutParams) fi.getLayoutParams()).canReorder = true; - } - } - if (animate) { - folder.animateClosed(); - } else { - folder.close(false); - } - - // Notify the accessibility manager that this folder "window" has disappeared and no - // longer occludes the workspace items - getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - - getUserEventDispatcher().resetElapsedContainerMillis(); - } - - public void closeShortcutsContainer() { - closeShortcutsContainer(true); - } - - public void closeShortcutsContainer(boolean animate) { - DeepShortcutsContainer deepShortcutsContainer = getOpenShortcutsContainer(); - if (deepShortcutsContainer != null) { - if (animate) { - deepShortcutsContainer.animateClose(); - } else { - deepShortcutsContainer.close(); - } - } - } - - public View getTopFloatingView() { - View topView = getOpenShortcutsContainer(); - if (topView == null) { - topView = getWorkspace().getOpenFolder(); - } - return topView; - } - - /** - * @return The open shortcuts container, or null if there is none - */ - public DeepShortcutsContainer getOpenShortcutsContainer() { - // Iterate in reverse order. Shortcuts container is added later to the dragLayer, - // and will be one of the last views. - for (int i = mDragLayer.getChildCount() - 1; i >= 0; i--) { - View child = mDragLayer.getChildAt(i); - if (child instanceof DeepShortcutsContainer - && ((DeepShortcutsContainer) child).isOpen()) { - return (DeepShortcutsContainer) child; - } - } - return null; - } - @Override public boolean dispatchTouchEvent(MotionEvent ev) { mLastDispatchTouchEventX = ev.getX(); @@ -3220,9 +2972,7 @@ public class Launcher extends Activity // Change the state *after* we've called all the transition code mState = toState; - - closeFolder(); - closeShortcutsContainer(); + AbstractFloatingView.closeAllOpenViews(this); // Send an accessibility event to announce the context change getWindow().getDecorView() @@ -4379,7 +4129,7 @@ public class Launcher extends Activity && mAccessibilityDelegate.performAction(focusedView, (ItemInfo) focusedView.getTag(), LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { - getOpenShortcutsContainer().requestFocus(); + DeepShortcutsContainer.getOpen(this).requestFocus(); return true; } break; diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java index bc5ac2456..66209bfa1 100644 --- a/src/com/android/launcher3/PinchToOverviewListener.java +++ b/src/com/android/launcher3/PinchToOverviewListener.java @@ -102,7 +102,7 @@ public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleG // once the state switching animation is complete. return false; } - if (mLauncher.getTopFloatingView() != null) { + if (AbstractFloatingView.getTopOpenView(mLauncher) != null) { // Don't listen for the pinch gesture if a floating view is open. return false; } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index e583be483..f5297358b 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -553,24 +553,6 @@ public class Workspace extends PagedView cl.getBackgroundAlpha() > 0); } - /** - * @return The open folder on the current screen, or null if there is none - */ - public Folder getOpenFolder() { - DragLayer dragLayer = mLauncher.getDragLayer(); - // Iterate in reverse order. Folder is added later to the dragLayer, - // and will be one of the last views. - for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) { - View child = dragLayer.getChildAt(i); - if (child instanceof Folder) { - Folder folder = (Folder) child; - if (folder.getInfo().opened) - return folder; - } - } - return null; - } - boolean isTouchActive() { return mTouchState != TOUCH_STATE_REST; } @@ -3811,7 +3793,7 @@ public class Workspace extends PagedView if (!workspaceInModalState() && !mIsSwitchingState) { super.scrollLeft(); } - Folder openFolder = getOpenFolder(); + Folder openFolder = Folder.getOpen(mLauncher); if (openFolder != null) { openFolder.completeDragExit(); } @@ -3822,7 +3804,7 @@ public class Workspace extends PagedView if (!workspaceInModalState() && !mIsSwitchingState) { super.scrollRight(); } - Folder openFolder = getOpenFolder(); + Folder openFolder = Folder.getOpen(mLauncher); if (openFolder != null) { openFolder.completeDragExit(); } @@ -3841,7 +3823,7 @@ public class Workspace extends PagedView } boolean result = false; - if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) { + if (!workspaceInModalState() && !mIsSwitchingState && Folder.getOpen(mLauncher) == null) { mInScrollArea = true; final int page = getNextPage() + diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 439e3145e..83391f3ec 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -192,8 +192,8 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme }); return true; } else if (action == MOVE_TO_WORKSPACE) { - Folder folder = mLauncher.getWorkspace().getOpenFolder(); - mLauncher.closeFolder(folder, true); + Folder folder = Folder.getOpen(mLauncher); + folder.close(true); ShortcutInfo info = (ShortcutInfo) item; folder.getInfo().remove(info, false); @@ -373,12 +373,10 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos); mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY()); - Workspace workspace = mLauncher.getWorkspace(); - - Folder folder = workspace.getOpenFolder(); + Folder folder = Folder.getOpen(mLauncher); if (folder != null) { if (!folder.getItemsInReadingOrder().contains(item)) { - mLauncher.closeFolder(); + folder.close(true); folder = null; } } @@ -390,7 +388,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme if (folder != null) { folder.startDrag(cellInfo.cell, options); } else { - workspace.startDrag(cellInfo, options); + mLauncher.getWorkspace().startDrag(cellInfo, options); } } diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java index 5baa7b59e..f7ca7034d 100644 --- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java @@ -19,6 +19,7 @@ package com.android.launcher3.accessibility; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherModel; @@ -64,7 +65,7 @@ public class ShortcutMenuAccessibilityDelegate extends LauncherAccessibilityDele ArrayList<ItemInfo> itemList = new ArrayList<>(); itemList.add(info); mLauncher.bindItems(itemList, 0, itemList.size(), true); - mLauncher.closeShortcutsContainer(); + AbstractFloatingView.closeAllOpenViews(mLauncher); announceConfirmation(R.string.item_added_to_workspace); } }; diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index cdd2f1d0c..689ee3816 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -52,6 +52,7 @@ import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.keyboard.FocusedItemDecorator; +import com.android.launcher3.shortcuts.DeepShortcutsContainer; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.ComponentKey; @@ -265,7 +266,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } // IF a shortcuts container is open, container should not be pulled down. - if (mLauncher.getOpenShortcutsContainer() != null) { + if (DeepShortcutsContainer.getOpen(mLauncher) != null) { return false; } diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 1bbbd4615..019a1745c 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -49,9 +49,11 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.TextView; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.CellLayout; import com.android.launcher3.DropTargetBar; +import com.android.launcher3.ExtendedEditText; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetHostView; @@ -66,7 +68,6 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.keyboard.ViewGroupFocusHelper; -import com.android.launcher3.shortcuts.DeepShortcutsContainer; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.TouchController; @@ -178,18 +179,13 @@ public class DragLayer extends InsettableFrameLayout { } public boolean isEventOverPageIndicator(MotionEvent ev) { - getDescendantRectRelativeToSelf(mLauncher.getWorkspace().getPageIndicator(), mHitRect); - return mHitRect.contains((int) ev.getX(), (int) ev.getY()); + return isEventOverView(mLauncher.getWorkspace().getPageIndicator(), ev); } public boolean isEventOverHotseat(MotionEvent ev) { return isEventOverView(mLauncher.getHotseat(), ev); } - private boolean isEventOverFolderTextRegion(Folder folder, MotionEvent ev) { - return isEventOverView(folder.getEditTextRegion(), ev); - } - private boolean isEventOverFolder(Folder folder, MotionEvent ev) { return isEventOverView(folder, ev); } @@ -204,45 +200,27 @@ public class DragLayer extends InsettableFrameLayout { } private boolean handleTouchDown(MotionEvent ev, boolean intercept) { - // Remove the shortcuts container when touching outside of it. - DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer(); - if (deepShortcutsContainer != null) { - if (isEventOverView(deepShortcutsContainer, ev)) { - // Let the container handle the event. - return false; - } else { + AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); + if (topView != null && intercept) { + ExtendedEditText textView = topView.getActiveTextView(); + if (textView != null) { + if (!isEventOverView(textView, ev)) { + textView.dispatchBackKey(); + return true; + } + } else if (!isEventOverView(topView, ev)) { if (isInAccessibleDrag()) { // Do not close the container if in drag and drop. if (!isEventOverDropTargetBar(ev)) { return true; } } else { - mLauncher.closeShortcutsContainer(); + topView.close(true); + // We let touches on the original icon go through so that users can launch // the app with one tap if they don't find a shortcut they want. - return !isEventOverView(deepShortcutsContainer.getOriginalIcon(), ev); - } - } - } - - Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); - if (currentFolder != null && intercept) { - if (currentFolder.isEditingName()) { - if (!isEventOverFolderTextRegion(currentFolder, ev)) { - currentFolder.dismissEditingName(); - return true; - } - } - - if (!isEventOverFolder(currentFolder, ev)) { - if (isInAccessibleDrag()) { - // Do not close the folder if in drag and drop. - if (!isEventOverDropTargetBar(ev)) { - return true; - } - } else { - mLauncher.closeFolder(); - return true; + View extendedTouch = topView.getExtendedTouchView(); + return extendedTouch == null || !isEventOverView(extendedTouch, ev); } } } @@ -300,7 +278,7 @@ public class DragLayer extends InsettableFrameLayout { if (mLauncher == null || mLauncher.getWorkspace() == null) { return false; } - Folder currentFolder = mLauncher.getWorkspace().getOpenFolder(); + Folder currentFolder = Folder.getOpen(mLauncher); if (currentFolder == null) { return false; } else { @@ -350,7 +328,7 @@ public class DragLayer extends InsettableFrameLayout { @Override public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { // Shortcuts can appear above folder - View topView = mLauncher.getTopFloatingView(); + View topView = AbstractFloatingView.getTopOpenView(mLauncher); if (topView != null) { if (child == topView) { return super.onRequestSendAccessibilityEvent(child, event); @@ -367,7 +345,7 @@ public class DragLayer extends InsettableFrameLayout { @Override public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) { - View topView = mLauncher.getTopFloatingView(); + View topView = AbstractFloatingView.getTopOpenView(mLauncher); if (topView != null) { // Only add the top view as a child for accessibility when it is open childrenForAccessibility.add(topView); @@ -522,7 +500,7 @@ public class DragLayer extends InsettableFrameLayout { @Override public boolean dispatchUnhandledMove(View focused, int direction) { // Consume the unhandled move if a container is open, to avoid switching pages underneath. - boolean isContainerOpen = mLauncher.getTopFloatingView() != null; + boolean isContainerOpen = AbstractFloatingView.getTopOpenView(mLauncher) != null; return isContainerOpen || mDragController.dispatchUnhandledMove(focused, direction); } @@ -1031,7 +1009,7 @@ public class DragLayer extends InsettableFrameLayout { @Override protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { - View topView = mLauncher.getTopFloatingView(); + View topView = AbstractFloatingView.getTopOpenView(mLauncher); if (topView != null) { return topView.requestFocus(direction, previouslyFocusedRect); } else { @@ -1041,7 +1019,7 @@ public class DragLayer extends InsettableFrameLayout { @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { - View topView = mLauncher.getTopFloatingView(); + View topView = AbstractFloatingView.getTopOpenView(mLauncher); if (topView != null) { topView.addFocusables(views, direction); } else { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 7030b660f..0685ddbe1 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -46,9 +46,9 @@ 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.AbstractFloatingView; import com.android.launcher3.Alarm; import com.android.launcher3.AppInfo; import com.android.launcher3.BubbleTextView; @@ -73,6 +73,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragLayer; @@ -91,9 +92,10 @@ 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, +public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener, View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, - View.OnFocusChangeListener, DragListener, DropTargetSource { + View.OnFocusChangeListener, DragListener, DropTargetSource, + ExtendedEditText.OnBackKeyListener { private static final String TAG = "Launcher.Folder"; /** @@ -227,14 +229,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mPageIndicator = (PageIndicatorDots) findViewById(R.id.folder_page_indicator); 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.setOnBackKeyListener(this); mFolderName.setOnFocusChangeListener(this); if (!Utilities.ATLEAST_MARSHMALLOW) { @@ -357,12 +352,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList }); } - public void dismissEditingName() { - mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); - doneEditingFolderName(true); - } - public void doneEditingFolderName(boolean commit) { + @Override + public boolean onBackKey() { mFolderName.setHint(sHintText); // Convert to a string here to ensure that no other state associated with the text field // gets saved. @@ -370,30 +362,30 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mInfo.setTitle(newTitle); LauncherModel.updateItemInDatabase(mLauncher, mInfo); - if (commit) { - Utilities.sendCustomAccessibilityEvent( - this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - getContext().getString(R.string.folder_renamed, newTitle)); - } + Utilities.sendCustomAccessibilityEvent( + this, 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); + Selection.setSelection(mFolderName.getText(), 0, 0); mIsEditingName = false; + return true; } public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { - dismissEditingName(); + mFolderName.dispatchBackKey(); return true; } return false; } - public View getEditTextRegion() { - return mFolderName; + @Override + public ExtendedEditText getActiveTextView() { + return isEditingName() ? mFolderName : null; } /** @@ -518,8 +510,33 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mState = STATE_SMALL; } + /** + * Opens the user folder described by the specified tag. The opening of the folder + * is animated relative to the specified View. If the View is null, no animation + * is played. + */ public void animateOpen() { - if (!(getParent() instanceof DragLayer)) return; + Folder openFolder = getOpen(mLauncher); + if (openFolder != null && openFolder != this) { + // Close any open folder before opening a folder. + openFolder.close(true); + } + + DragLayer dragLayer = mLauncher.getDragLayer(); + // Just verify that the folder hasn't already been added to the DragLayer. + // There was a one-off crash where the folder had a parent already. + if (getParent() == null) { + dragLayer.addView(this); + mDragController.addDropTarget(this); + } else { + if (ProviderConfig.IS_DOGFOOD_BUILD) { + Log.e(TAG, "Opening folder (" + this + ") which already has a parent:" + + getParent()); + } + } + + mIsOpen = true; + mFolderIcon.growAndFadeOut(); mContent.completePendingPageChanges(); if (!mDragInProgress) { @@ -532,83 +549,63 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // 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(); + prepareReveal(); + centerAboutIcon(); - final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 1, 1, 1); - oa.setDuration(mExpandDuration); - openFolderAnim = oa; + AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); + int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); + int height = getFolderHeight(); - 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 = new CircleRevealOutlineProvider((int) getPivotX(), - (int) getPivotY(), 0, radius).createRevealAnimator(this); - reveal.setDuration(mMaterialExpandDuration); - reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); - - mContent.setAlpha(0f); - Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "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; - - mContent.setLayerType(LAYER_TYPE_HARDWARE, null); - mFooter.setLayerType(LAYER_TYPE_HARDWARE, null); - onCompleteRunnable = new Runnable() { - @Override - public void run() { - mContent.setLayerType(LAYER_TYPE_NONE, null); - mFooter.setLayerType(LAYER_TYPE_NONE, null); - mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); - } - }; - } - openFolderAnim.addListener(new AnimatorListenerAdapter() { + 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 = new CircleRevealOutlineProvider((int) getPivotX(), + (int) getPivotY(), 0, radius).createRevealAnimator(this); + reveal.setDuration(mMaterialExpandDuration); + reveal.setInterpolator(new LogDecelerateInterpolator(100, 0)); + + mContent.setAlpha(0f); + Animator iconsAlpha = ObjectAnimator.ofFloat(mContent, "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); + + mContent.setLayerType(LAYER_TYPE_HARDWARE, null); + mFooter.setLayerType(LAYER_TYPE_HARDWARE, null); + onCompleteRunnable = new Runnable() { + @Override + public void run() { + mContent.setLayerType(LAYER_TYPE_NONE, null); + mFooter.setLayerType(LAYER_TYPE_NONE, null); + mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); + } + }; + anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { Utilities.sendCustomAccessibilityEvent( @@ -639,7 +636,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // 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() { + anim.addListener(new AnimatorListenerAdapter() { @SuppressLint("InlinedApi") @Override @@ -662,7 +659,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } mPageIndicator.stopAllAnimations(); - openFolderAnim.start(); + anim.start(); // Make sure the folder picks up the last drag move even if the finger doesn't move. if (mDragController.isDragging()) { @@ -670,6 +667,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } mContent.verifyVisibleHighResIcons(mContent.getNextPage()); + + // Notify the accessibility manager that this folder "window" has appeared and occluded + // the workspace items + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + dragLayer.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } public void beginExternalDrag() { @@ -682,14 +684,44 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mDragController.addDragListener(this); } - public void animateClosed() { + @Override + protected boolean isOfType(int type) { + return (type & TYPE_FOLDER) != 0; + } + + @Override + protected void handleClose(boolean animate) { + mIsOpen = false; + + if (isEditingName()) { + mFolderName.dispatchBackKey(); + } + + if (mFolderIcon != null) { + mFolderIcon.shrinkAndFadeIn(animate); + } + if (!(getParent() instanceof DragLayer)) return; + + if (animate) { + animateClosed(); + } else { + closeComplete(false); + } + + // Notify the accessibility manager that this folder "window" has disappeared and no + // longer occludes the workspace items + ((DragLayer) getParent()) + .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + + private void animateClosed() { 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); + closeComplete(true); } @Override public void onAnimationStart(Animator animation) { @@ -705,7 +737,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList oa.start(); } - public void close(boolean wasAnimated) { + private void closeComplete(boolean wasAnimated) { // TODO: Clear all active animations. DragLayer parent = (DragLayer) getParent(); if (parent != null) { @@ -840,8 +872,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList }; public void completeDragExit() { - if (mInfo.opened) { - mLauncher.closeFolder(); + if (mIsOpen) { + close(true); mRearrangeOnClose = true; } else if (mState == STATE_ANIMATING) { mRearrangeOnClose = true; @@ -1370,8 +1402,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList rearrangeChildren(); } if (getItemCount() <= 1) { - if (mInfo.opened) { - mLauncher.closeFolder(this, true); + if (mIsOpen) { + close(true); } else { replaceFolderWithFinalItem(); } @@ -1417,7 +1449,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (hasFocus) { startEditingFolderName(); } else { - dismissEditingName(); + mFolderName.dispatchBackKey(); } } } @@ -1516,4 +1548,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList updateTextViewFocus(); } } + + /** + * Returns a folder which is already open or null + */ + public static Folder getOpen(Launcher launcher) { + return getOpenView(launcher, TYPE_FOLDER); + } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 69c2b0fa3..a29a94659 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -18,6 +18,7 @@ package com.android.launcher3.folder; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; @@ -56,7 +57,6 @@ 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; @@ -142,6 +142,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ? new StackFolderIconLayoutRule() : new ClippedFolderIconLayoutRule(); + mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, @@ -202,16 +203,12 @@ public class FolderIcon extends FrameLayout implements FolderListener { updateItemDrawingParams(false); } - 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 || itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) && - !mFolder.isFull() && item != mInfo && !mInfo.opened); + !mFolder.isFull() && item != mInfo && !mFolder.isOpen()); } public boolean acceptDrop(ItemInfo dragInfo) { @@ -243,7 +240,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { OnAlarmListener mOnOpenListener = new OnAlarmListener() { public void onAlarm(Alarm alarm) { mFolder.beginExternalDrag(); - mLauncher.openFolder(FolderIcon.this); + mFolder.animateOpen(); } }; @@ -974,12 +971,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - } - - @Override public void cancelLongPress() { super.cancelLongPress(); mLongPressHelper.cancelLongPress(); @@ -990,13 +981,76 @@ public class FolderIcon extends FrameLayout implements FolderListener { mInfo.removeListener(mFolder); } + public void shrinkAndFadeIn(boolean animate) { + final CellLayout cl = (CellLayout) getParent().getParent(); + ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; + + // We remove and re-draw the FolderIcon in-case it has changed + final PreviewImageView previewImage = PreviewImageView.get(getContext()); + previewImage.removeFromParent(); + copyToPreview(previewImage); + + if (cl != null) { + cl.clearFolderLeaveBehind(); + } + + ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 1, 1, 1); + oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); + oa.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (cl != null) { + // Remove the ImageView copy of the FolderIcon and make the original visible. + previewImage.removeFromParent(); + setVisibility(View.VISIBLE); + } + } + }); + oa.start(); + if (!animate) { + oa.end(); + } + } + + public void growAndFadeOut() { + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); + // While the folder is open, the position of the icon cannot change. + lp.canReorder = false; + if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + CellLayout cl = (CellLayout) getParent().getParent(); + cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY); + } + + // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original + PreviewImageView previewImage = PreviewImageView.get(getContext()); + copyToPreview(previewImage); + setVisibility(View.INVISIBLE); + + ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 0, 1.5f, 1.5f); + oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); + oa.start(); + } + + /** + * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView + * in the DragLayer in the exact absolute location of the original FolderIcon. + */ + private void copyToPreview(PreviewImageView previewImageView) { + previewImageView.copy(this); + if (mFolder != null) { + previewImageView.setPivotX(mFolder.getPivotXForIconAnimation()); + previewImageView.setPivotY(mFolder.getPivotYForIconAnimation()); + mFolder.bringToFront(); + } + } + public interface PreviewLayoutRule { - public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, + PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, PreviewItemDrawingParams params); - public void init(int availableSpace, int intrinsicIconSize, boolean rtl); + void init(int availableSpace, int intrinsicIconSize, boolean rtl); - public int numItems(); - public boolean clipToBackground(); + int numItems(); + boolean clipToBackground(); } } diff --git a/src/com/android/launcher3/folder/PreviewImageView.java b/src/com/android/launcher3/folder/PreviewImageView.java new file mode 100644 index 000000000..c4f3ee15c --- /dev/null +++ b/src/com/android/launcher3/folder/PreviewImageView.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.folder; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.dragndrop.DragLayer; + +/** + * A temporary view which displays the a bitmap (used for folder icon animation) + */ +public class PreviewImageView extends ImageView { + + private final Rect mTempRect = new Rect(); + private final DragLayer mParent; + + private Bitmap mBitmap; + private Canvas mCanvas; + + public PreviewImageView(DragLayer parent) { + super(parent.getContext()); + mParent = parent; + } + + public void copy(View view) { + final int width = view.getMeasuredWidth(); + final int height = view.getMeasuredHeight(); + + if (mBitmap == null || mBitmap.getWidth() != width || mBitmap.getHeight() != height) { + mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBitmap); + } + + DragLayer.LayoutParams lp; + if (getLayoutParams() instanceof DragLayer.LayoutParams) { + lp = (DragLayer.LayoutParams) getLayoutParams(); + } else { + lp = new DragLayer.LayoutParams(width, height); + } + + // The layout from which the folder is being opened may be scaled, adjust the starting + // view size by this scale factor. + float scale = mParent.getDescendantRectRelativeToSelf(view, mTempRect); + lp.customPosition = true; + lp.x = mTempRect.left; + lp.y = mTempRect.top; + lp.width = (int) (scale * width); + lp.height = (int) (scale * height); + + mCanvas.drawColor(0, PorterDuff.Mode.CLEAR); + view.draw(mCanvas); + setImageBitmap(mBitmap); + + // Just in case this image view is still in the drag layer from a previous animation, + // we remove it and re-add it. + removeFromParent(); + mParent.addView(this, lp); + } + + public void removeFromParent() { + if (mParent.indexOfChild(this) != -1) { + mParent.removeView(this); + } + } + + public static PreviewImageView get(Context context) { + DragLayer dragLayer = Launcher.getLauncher(context).getDragLayer(); + PreviewImageView view = (PreviewImageView) dragLayer.getTag(R.id.preview_image_id); + if (view == null) { + view = new PreviewImageView(dragLayer); + dragLayer.setTag(R.id.preview_image_id, view); + } + return view; + } +} diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java index 6056f4c10..6603e93b0 100644 --- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java +++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java @@ -46,7 +46,7 @@ public class CustomActionsPopup implements OnMenuItemClickListener { public CustomActionsPopup(Launcher launcher, View icon) { mLauncher = launcher; mIcon = icon; - DeepShortcutsContainer container = launcher.getOpenShortcutsContainer(); + DeepShortcutsContainer container = DeepShortcutsContainer.getOpen(launcher); if (container != null) { mDelegate = container.getAccessibilityDelegate(); } else { diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java index 9f9a99eb1..b5126e95c 100644 --- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java +++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java @@ -41,6 +41,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.DecelerateInterpolator; import android.widget.LinearLayout; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; @@ -73,9 +74,9 @@ import java.util.List; * A container for shortcuts to deep links within apps. */ @TargetApi(Build.VERSION_CODES.N) -public class DeepShortcutsContainer extends LinearLayout implements View.OnLongClickListener, +public class DeepShortcutsContainer extends AbstractFloatingView + implements View.OnLongClickListener, View.OnTouchListener, DragSource, DragController.DragListener { - private static final String TAG = "ShortcutsContainer"; private final Point mIconShift = new Point(); @@ -94,7 +95,6 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC private Animator mOpenCloseAnimator; private boolean mDeferContainerRemoval; - private boolean mIsOpen; public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); @@ -376,7 +376,8 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC return arrowView; } - public BubbleTextView getOriginalIcon() { + @Override + public View getExtendedTouchView() { return mOriginalIcon; } @@ -444,7 +445,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC dv.animateShift(-mIconShift.x, -mIconShift.y); // TODO: support dragging from within folder without having to close it - mLauncher.closeFolder(); + AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER); return false; } @@ -499,7 +500,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC } else { // Close animation is not running. if (mDeferContainerRemoval) { - close(); + closeComplete(); } } } @@ -512,7 +513,16 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS; } - public void animateClose() { + @Override + protected void handleClose(boolean animate) { + if (animate) { + animateClose(); + } else { + closeComplete(); + } + } + + private void animateClose() { if (!mIsOpen) { return; } @@ -592,7 +602,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC if (mDeferContainerRemoval) { setVisibility(INVISIBLE); } else { - close(); + closeComplete(); } } }); @@ -607,7 +617,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC /** * Closes the folder without animation. */ - public void close() { + private void closeComplete() { if (mOpenCloseAnimator != null) { mOpenCloseAnimator.cancel(); mOpenCloseAnimator = null; @@ -621,8 +631,9 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC mLauncher.getDragLayer().removeView(this); } - public boolean isOpen() { - return mIsOpen; + @Override + protected boolean isOfType(int type) { + return (type & TYPE_DEEPSHORTCUT_CONTAINER) != 0; } /** @@ -631,7 +642,7 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC */ public static DeepShortcutsContainer showForIcon(BubbleTextView icon) { Launcher launcher = Launcher.getLauncher(icon.getContext()); - if (launcher.getOpenShortcutsContainer() != null) { + if (getOpen(launcher) != null) { // There is already a shortcuts container open, so don't open this one. icon.clearFocus(); return null; @@ -666,4 +677,11 @@ public class DeepShortcutsContainer extends LinearLayout implements View.OnLongC return unbadgedBitmap; } } + + /** + * Returns a DeepShortcutsContainer which is already open or null + */ + public static DeepShortcutsContainer getOpen(Launcher launcher) { + return getOpenView(launcher, TYPE_DEEPSHORTCUT_CONTAINER); + } } |