Merge "Add clear all view that scales off device height" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 6730e97..d20910f 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -51,6 +52,9 @@
public void onStateEnabled(Launcher launcher) {
IconRecentsView recentsView = launcher.getOverviewPanel();
recentsView.onBeginTransitionToOverview();
+ // Request orientation be set to unspecified, letting the system decide the best
+ // orientation.
+ launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
}
@Override
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index ec02e9d..b7ed5b5 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.views;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
@@ -99,6 +101,7 @@
private final Context mContext;
private final TaskListLoader mTaskLoader;
private final TaskAdapter mTaskAdapter;
+ private final LinearLayoutManager mTaskLayoutManager;
private final TaskActionController mTaskActionController;
private final DefaultItemAnimator mDefaultItemAnimator = new DefaultItemAnimator();
private final ContentFillItemAnimator mLoadingContentItemAnimator =
@@ -138,6 +141,7 @@
mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks());
mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter);
mTaskAdapter.setActionController(mTaskActionController);
+ mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */);
RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener);
}
@@ -147,8 +151,7 @@
if (mTaskRecyclerView == null) {
mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
mTaskRecyclerView.setAdapter(mTaskAdapter);
- mTaskRecyclerView.setLayoutManager(
- new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
+ mTaskRecyclerView.setLayoutManager(mTaskLayoutManager);
ItemTouchHelper helper = new ItemTouchHelper(
new TaskSwipeCallback(mTaskActionController));
helper.attachToRecyclerView(mTaskRecyclerView);
@@ -211,6 +214,12 @@
* becomes visible.
*/
public void onBeginTransitionToOverview() {
+ if (mContext.getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
+ // Scroll to bottom of task in landscape mode. This is a non-issue in portrait mode as
+ // all tasks should be visible to fill up the screen in portrait mode and the view will
+ // not be scrollable.
+ mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */);
+ }
scheduleFadeInLayoutAnimation();
// Load any task changes
if (!mTaskLoader.needsToLoad()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 5337c39..20a2487 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -75,7 +75,8 @@
* @return the animation
*/
PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
- TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
+ mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
+ TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getCurrentPage());
if (taskView == null) {
throw new IllegalStateException("There is no task view to animate to.");
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
index d919a3e..7fd09f7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
@@ -17,7 +17,9 @@
import android.content.Context;
import android.content.Intent;
+import android.graphics.PointF;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
/**
* A dummy input consumer used when the device is still locked, e.g. from secure camera.
@@ -25,9 +27,13 @@
public class DeviceLockedInputConsumer implements InputConsumer {
private final Context mContext;
+ private final float mTouchSlopSquared;
+ private final PointF mTouchDown = new PointF();
public DeviceLockedInputConsumer(Context context) {
mContext = context;
+ float touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mTouchSlopSquared = touchSlop * touchSlop;
}
@Override
@@ -37,11 +43,19 @@
@Override
public void onMotionEvent(MotionEvent ev) {
- // For now, just start the home intent so user is prompted to unlock the device.
+ float x = ev.getX();
+ float y = ev.getY();
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mContext.startActivity(new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ mTouchDown.set(x, y);
+ } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
+ float xSquared = (x - mTouchDown.x) * (x - mTouchDown.x);
+ float ySquared = (y - mTouchDown.y) * (y - mTouchDown.y);
+ if (xSquared + ySquared > mTouchSlopSquared) {
+ // For now, just start the home intent so user is prompted to unlock the device.
+ mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
}
}
}
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
deleted file mode 100644
index b68d615..0000000
--- a/res/values/overlayable.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-<resources>
- <overlayable name="SystemIcons">
- <policy type="product|system|vendor">
- <item type="drawable" name="ic_corp"/>
- <item type="drawable" name="ic_info_no_shadow"/>
- <item type="drawable" name="ic_remove_no_shadow"/>
- <item type="drawable" name="ic_setting"/>
- <item type="drawable" name="ic_wallpaper"/>
- <item type="drawable" name="ic_warning"/>
- <item type="drawable" name="ic_widget"/>
- </policy>
- </overlayable>
-</resources>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 0e08276..3cb6ba6 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -30,6 +30,7 @@
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.IntDef;
@@ -58,6 +59,7 @@
TYPE_ON_BOARD_POPUP,
TYPE_DISCOVERY_BOUNCE,
TYPE_SNACKBAR,
+ TYPE_LISTENER,
TYPE_TASK_MENU,
TYPE_OPTIONS_POPUP
@@ -72,15 +74,16 @@
public static final int TYPE_ON_BOARD_POPUP = 1 << 5;
public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
public static final int TYPE_SNACKBAR = 1 << 7;
+ public static final int TYPE_LISTENER = 1 << 8;
// Popups related to quickstep UI
- public static final int TYPE_TASK_MENU = 1 << 8;
- public static final int TYPE_OPTIONS_POPUP = 1 << 9;
+ public static final int TYPE_TASK_MENU = 1 << 9;
+ public static final int TYPE_OPTIONS_POPUP = 1 << 10;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
- | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR;
+ | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
@@ -90,7 +93,7 @@
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
| TYPE_SNACKBAR;
- public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE;
+ public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER;
// These view all have particular operation associated with swipe down interaction.
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f96652e..f2fc718 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -70,7 +70,7 @@
public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
public static final float SHAPE_PROGRESS_DURATION = 0.15f;
-
+ private static final int FADE_DURATION_MS = 200;
private static final Rect sTmpRect = new Rect();
private Runnable mEndRunnable;
@@ -93,10 +93,15 @@
private float mBgDrawableStartScale = 1f;
private float mBgDrawableEndScale = 1f;
+ private AnimatorSet mFadeAnimatorSet;
+ private ListenerView mListenerView;
+
private FloatingIconView(Context context) {
super(context);
+
mBlurSizeOutline = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_medium_outline);
+ mListenerView = new ListenerView(context, null);
}
/**
@@ -138,6 +143,12 @@
if (mRevealAnimator == null) {
mRevealAnimator = (ValueAnimator) FolderShape.getShape().createRevealAnimator(this,
mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, !isOpening);
+ mRevealAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRevealAnimator = null;
+ }
+ });
mRevealAnimator.start();
// We pause here so we can set the current fraction ourselves.
mRevealAnimator.pause();
@@ -314,7 +325,7 @@
@WorkerThread
private int getOffsetForIconBounds(Drawable drawable) {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O ||
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
!(drawable instanceof AdaptiveIconDrawable)) {
return 0;
}
@@ -364,6 +375,18 @@
}
}
+ public void onListenerViewClosed() {
+ // Fast finish here.
+ if (mEndRunnable != null) {
+ mEndRunnable.run();
+ mEndRunnable = null;
+ }
+ if (mFadeAnimatorSet != null) {
+ mFadeAnimatorSet.end();
+ mFadeAnimatorSet = null;
+ }
+ }
+
@Override
public void onAnimationStart(Animator animator) {}
@@ -410,54 +433,71 @@
// We need to add it to the overlay, but keep it invisible until animation starts..
final DragLayer dragLayer = launcher.getDragLayer();
view.setVisibility(INVISIBLE);
- ((ViewGroup) dragLayer.getParent()).getOverlay().add(view);
+ ((ViewGroup) dragLayer.getParent()).addView(view);
+ dragLayer.addView(view.mListenerView);
+ view.mListenerView.setListener(view::onListenerViewClosed);
- if (hideOriginal) {
- view.mEndRunnable = () -> {
- AnimatorSet fade = new AnimatorSet();
- fade.setDuration(200);
- fade.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- originalView.setVisibility(VISIBLE);
- }
+ view.mEndRunnable = () -> {
+ view.mEndRunnable = null;
- @Override
- public void onAnimationEnd(Animator animation) {
- ((ViewGroup) dragLayer.getParent()).getOverlay().remove(view);
-
- if (view.mRevealAnimator != null) {
- view.mRevealAnimator.end();
- }
- }
- });
-
- if (originalView instanceof FolderIcon) {
- FolderIcon folderIcon = (FolderIcon) originalView;
- folderIcon.setBackgroundVisible(false);
- folderIcon.getFolderName().setTextVisibility(false);
- fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
- fade.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- folderIcon.setBackgroundVisible(true);
- folderIcon.animateBgShadowAndStroke();
- if (folderIcon.hasDot()) {
- folderIcon.animateDotScale(0, 1f);
- }
- }
- });
+ if (hideOriginal) {
+ if (isOpening) {
+ originalView.setVisibility(VISIBLE);
+ view.finish(dragLayer);
} else {
- fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
+ view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
+ view.mFadeAnimatorSet.start();
}
- fade.start();
- // TODO: Do not run fade animation until we fix b/129421279.
- fade.end();
- };
- }
+ } else {
+ view.finish(dragLayer);
+ }
+ };
return view;
}
+ private AnimatorSet createFadeAnimation(View originalView, DragLayer dragLayer) {
+ AnimatorSet fade = new AnimatorSet();
+ fade.setDuration(FADE_DURATION_MS);
+ fade.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ originalView.setVisibility(VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finish(dragLayer);
+ }
+ });
+
+ if (originalView instanceof FolderIcon) {
+ FolderIcon folderIcon = (FolderIcon) originalView;
+ folderIcon.setBackgroundVisible(false);
+ folderIcon.getFolderName().setTextVisibility(false);
+ fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
+ fade.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ folderIcon.setBackgroundVisible(true);
+ folderIcon.animateBgShadowAndStroke();
+ if (folderIcon.hasDot()) {
+ folderIcon.animateDotScale(0, 1f);
+ }
+ }
+ });
+ } else {
+ fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
+ }
+
+ return fade;
+ }
+
+ private void finish(DragLayer dragLayer) {
+ ((ViewGroup) dragLayer.getParent()).removeView(this);
+ dragLayer.removeView(mListenerView);
+ recycle();
+ }
+
private void recycle() {
setTranslationX(0);
setTranslationY(0);
@@ -475,10 +515,15 @@
mBackground = null;
mClipPath = null;
mFinalDrawableBounds.setEmpty();
- mBgDrawableBounds.setEmpty();;
+ mBgDrawableBounds.setEmpty();
if (mRevealAnimator != null) {
mRevealAnimator.cancel();
}
mRevealAnimator = null;
+ if (mFadeAnimatorSet != null) {
+ mFadeAnimatorSet.cancel();
+ }
+ mFadeAnimatorSet = null;
+ mListenerView.setListener(null);
}
}
diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java
new file mode 100644
index 0000000..263f7c4
--- /dev/null
+++ b/src/com/android/launcher3/views/ListenerView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.AbstractFloatingView;
+
+/**
+ * An invisible AbstractFloatingView that can run a callback when it is being closed.
+ */
+public class ListenerView extends AbstractFloatingView {
+
+ public Runnable mCloseListener;
+
+ public ListenerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setVisibility(View.GONE);
+ }
+
+ public void setListener(Runnable listener) {
+ mCloseListener = listener;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mIsOpen = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mIsOpen = false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (mIsOpen) {
+ if (mCloseListener != null) {
+ mCloseListener.run();
+ } else {
+ if (getParent() instanceof ViewGroup) {
+ ((ViewGroup) getParent()).removeView(this);
+ }
+ }
+ }
+ mIsOpen = false;
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // Users do not interact with FloatingIconView, so there is nothing to log here.
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_LISTENER) != 0;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ handleClose(false);
+ }
+ // We want other views to be able to intercept the touch so we return false here.
+ return false;
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 1efdee8..8c64c8e 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -33,6 +33,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -103,6 +104,7 @@
}
@Test
+ @Ignore
public void testCustomProfileLoaded_with_folder() throws Exception {
writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
.addApp(SETTINGS_APP, SETTINGS_APP)