Merging from ub-launcher3-master @ build 6048063

Test: manual, presubmit on the source branch
http://x20/teams/android-launcher/merge/ub-launcher3-master_6048063.html

Change-Id: Ieee38cc301364f96cf252b2387142b0aa1b75317
This commit is contained in:
Hyunyoung Song
2019-12-04 00:42:32 -08:00
47 changed files with 1379 additions and 336 deletions
+16
View File
@@ -30,3 +30,19 @@ android_library {
manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
}
java_library_static {
name: "launcher-log-protos-lite",
srcs: [
"protos/*.proto",
"proto_overrides/*.proto",
],
sdk_version: "current",
proto: {
type: "lite",
local_include_dirs:[
"protos",
"proto_overrides",
],
},
}
@@ -24,4 +24,8 @@ public class ShelfPeekAnim {
public enum ShelfAnimState {
}
public boolean isPeeking() {
return false;
}
}
@@ -0,0 +1,17 @@
<?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.
-->
<com.android.launcher3.uioverrides.PredictedAppIcon style="@style/BaseIcon.Workspace" />
@@ -18,6 +18,7 @@ package com.android.launcher3;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
@@ -40,10 +41,9 @@ import com.android.launcher3.appprediction.DynamicItemCache;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ComponentKey;
@@ -66,6 +66,9 @@ public class HotseatPredictionController implements DragController.DragListener,
//TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
private static final int APPTARGET_ACTION_UNPIN = 4;
private static final String APP_LOCATION_HOTSEAT = "hotseat";
private static final String APP_LOCATION_WORKSPACE = "workspace";
private static final String PREDICTION_CLIENT = "hotseat";
private DropTarget.DragObject mDragObject;
@@ -81,12 +84,14 @@ public class HotseatPredictionController implements DragController.DragListener,
private AppPredictor mAppPredictor;
private AllAppsStore mAllAppsStore;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
public HotseatPredictionController(Launcher launcher) {
mLauncher = launcher;
mHotseat = launcher.getHotseat();
mAllAppsStore = mLauncher.getAppsView().getAppsStore();
mAllAppsStore.addUpdateListener(this);
mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false));
mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
launcher.getDeviceProfile().inv.addOnChangeListener(this);
mHotseat.addOnAttachStateChangeListener(this);
@@ -102,16 +107,17 @@ public class HotseatPredictionController implements DragController.DragListener,
mLauncher.getDragController().removeDragListener(this);
}
/**
* Fills gaps in the hotseat with predictions
*/
public void fillGapsWithPrediction(boolean animate) {
private void fillGapsWithPrediction() {
fillGapsWithPrediction(false, null);
}
private void fillGapsWithPrediction(boolean animate, Runnable callback) {
if (mDragObject != null) {
return;
}
List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
int predictionIndex = 0;
ArrayList<ItemInfo> newItemsToAdd = new ArrayList<>();
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
View child = mHotseat.getChildAt(
mHotseat.getCellXFromOrder(rank),
@@ -130,21 +136,37 @@ public class HotseatPredictionController implements DragController.DragListener,
WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
if (isPredictedIcon(child)) {
BubbleTextView icon = (BubbleTextView) child;
PredictedAppIcon icon = (PredictedAppIcon) child;
icon.applyFromWorkspaceItem(predictedItem);
icon.finishBinding();
} else {
newItemsToAdd.add(predictedItem);
newItems.add(predictedItem);
}
preparePredictionInfo(predictedItem, rank);
}
mLauncher.bindItems(newItemsToAdd, animate);
for (BubbleTextView icon : getPredictedIcons()) {
icon.verifyHighRes();
icon.setOnLongClickListener((v) -> {
PopupContainerWithArrow.showForIcon((BubbleTextView) v);
return true;
bindItems(newItems, animate, callback);
}
private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate, Runnable callback) {
AnimatorSet animationSet = new AnimatorSet();
for (WorkspaceItemInfo item : itemsToAdd) {
PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
mLauncher.getWorkspace().addInScreenFromBind(icon, item);
icon.finishBinding();
if (animate) {
animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
}
}
if (animate) {
animationSet.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (callback != null) callback.run();
}
});
icon.setBackgroundResource(R.drawable.predicted_icon_background);
animationSet.start();
} else {
if (callback != null) callback.run();
}
}
@@ -179,22 +201,30 @@ public class HotseatPredictionController implements DragController.DragListener,
.build());
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
mAppPredictor.requestPredictionUpdate();
}
private Bundle getAppPredictionContextExtra() {
Bundle bundle = new Bundle();
ViewGroup vg = mHotseat.getShortcutsAndWidgets();
bundle.putParcelableArrayList(APP_LOCATION_HOTSEAT,
getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets())));
bundle.putParcelableArrayList(APP_LOCATION_WORKSPACE, getPinnedAppTargetsInViewGroup(
mLauncher.getWorkspace().getScreenWithId(
Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets()));
return bundle;
}
private ArrayList<AppTarget> getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) {
ArrayList<AppTarget> pinnedApps = new ArrayList<>();
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (isPinnedIcon(child)) {
WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag();
pinnedApps.add(getAppTargetFromItemInfo(itemInfo));
}
}
bundle.putParcelableArrayList("pinned_apps", pinnedApps);
return bundle;
return pinnedApps;
}
private void setPredictedApps(List<AppTarget> appTargets) {
@@ -210,7 +240,7 @@ public class HotseatPredictionController implements DragController.DragListener,
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
updateDependencies();
fillGapsWithPrediction(false);
fillGapsWithPrediction();
}
private void updateDependencies() {
@@ -219,7 +249,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
private void pinPrediction(ItemInfo info) {
BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt(
PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
mHotseat.getCellXFromOrder(info.rank),
mHotseat.getCellYFromOrder(info.rank));
if (icon == null) {
@@ -230,11 +260,9 @@ public class HotseatPredictionController implements DragController.DragListener,
LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
workspaceItemInfo.cellX, workspaceItemInfo.cellY);
ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
icon.reset();
icon.applyFromWorkspaceItem(workspaceItemInfo);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
icon.pin(workspaceItemInfo);
AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
notifyItemAction(appTarget, AppTargetEvent.ACTION_PIN);
notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
}
private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
@@ -265,47 +293,51 @@ public class HotseatPredictionController implements DragController.DragListener,
return predictedApps;
}
private List<BubbleTextView> getPredictedIcons() {
List<BubbleTextView> icons = new ArrayList<>();
private List<PredictedAppIcon> getPredictedIcons() {
List<PredictedAppIcon> icons = new ArrayList<>();
ViewGroup vg = mHotseat.getShortcutsAndWidgets();
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
if (isPredictedIcon(child)) {
icons.add((BubbleTextView) child);
icons.add((PredictedAppIcon) child);
}
}
return icons;
}
private void removePredictedApps(boolean animate) {
for (BubbleTextView icon : getPredictedIcons()) {
if (animate) {
icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (icon.getParent() != null) {
mHotseat.removeView(icon);
}
private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines) {
for (PredictedAppIcon icon : getPredictedIcons()) {
int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (icon.getParent() != null) {
mHotseat.removeView(icon);
}
});
} else {
if (icon.getParent() != null) {
mHotseat.removeView(icon);
}
}
});
}
}
private void notifyItemAction(AppTarget target, int action) {
private void notifyItemAction(AppTarget target, String location, int action) {
if (mAppPredictor != null) {
mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, action).build());
mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target,
action).setLaunchLocation(location).build());
}
}
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
removePredictedApps(true);
removePredictedApps(mOutlineDrawings);
mDragObject = dragObject;
if (mOutlineDrawings.isEmpty()) return;
for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
mHotseat.addDelegatedCellDrawing(outlineDrawing);
}
mHotseat.invalidate();
}
@Override
@@ -315,14 +347,29 @@ public class HotseatPredictionController implements DragController.DragListener,
}
ItemInfo dragInfo = mDragObject.dragInfo;
if (dragInfo instanceof WorkspaceItemInfo && dragInfo.getTargetComponent() != null) {
AppTarget appTarget = getAppTargetFromItemInfo(dragInfo);
if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) {
notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
}
if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) {
notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
}
if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) {
notifyItemAction(getAppTargetFromItemInfo(dragInfo), AppTargetEvent.ACTION_PIN);
} else if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) {
notifyItemAction(getAppTargetFromItemInfo(dragInfo), APPTARGET_ACTION_UNPIN);
notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
}
if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) {
notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
}
}
mDragObject = null;
fillGapsWithPrediction(true);
fillGapsWithPrediction(true, () -> {
if (mOutlineDrawings.isEmpty()) return;
for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
mHotseat.removeDelegatedCellDrawing(outlineDrawing);
}
mHotseat.invalidate();
mOutlineDrawings.clear();
});
}
@Nullable
@@ -351,8 +398,7 @@ public class HotseatPredictionController implements DragController.DragListener,
@Override
public void onAppsUpdated() {
updateDependencies();
fillGapsWithPrediction(false);
fillGapsWithPrediction();
}
@Override
@@ -375,7 +421,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
private static boolean isPredictedIcon(View view) {
return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo
return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) view.getTag()).container
== LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
}
@@ -385,7 +431,7 @@ public class HotseatPredictionController implements DragController.DragListener,
return false;
}
ItemInfo info = (ItemInfo) view.getTag();
return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && (
return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && (
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
}
@@ -394,11 +440,15 @@ public class HotseatPredictionController implements DragController.DragListener,
return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
}
private static boolean isInFirstPage(ItemInfo itemInfo) {
return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
&& itemInfo.screenId == Workspace.FIRST_SCREEN_ID;
}
private static AppTarget getAppTargetFromItemInfo(ItemInfo info) {
if (info.getTargetComponent() == null) return null;
return new AppTarget.Builder(
new AppTargetId("app:" + info.getTargetComponent().getPackageName()),
info.getTargetComponent().getPackageName(), info.user).setClassName(
info.getTargetComponent().getClassName()).build();
ComponentName cn = info.getTargetComponent();
return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()),
cn.getPackageName(), info.user).setClassName(cn.getClassName()).build();
}
}
@@ -21,8 +21,15 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
@@ -40,6 +47,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.quickstep.util.AppWindowAnimationHelper;
@@ -56,6 +64,9 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
public static final int INDEX_SHELF_ANIM = 0;
public static final int INDEX_RECENTS_FADE_ANIM = 1;
public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
public LauncherAppTransitionManagerImpl(Context context) {
super(context);
@@ -145,7 +156,7 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
@Override
public int getStateElementAnimationsCount() {
return 3;
return 4;
}
@Override
@@ -191,6 +202,20 @@ public final class LauncherAppTransitionManagerImpl extends QuickstepAppTransiti
.setStiffness(250)
.setValues(values)
.build(mLauncher);
case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
AnimatorSetBuilder builder = new AnimatorSetBuilder();
builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
}
LauncherStateManager stateManager = mLauncher.getStateManager();
return stateManager.createAtomicAnimation(
stateManager.getCurrentStableState(), OVERVIEW, builder,
ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
}
default:
return super.createStateElementAnimation(index, values);
}
@@ -276,7 +276,7 @@ public class PredictionRowView extends LinearLayout implements
boolean predictionsEnabled = predictionCount > 0;
if (predictionsEnabled != mPredictionsEnabled) {
mPredictionsEnabled = predictionsEnabled;
mLauncher.reapplyUi();
mLauncher.reapplyUi(false /* cancelCurrentAnimation */);
updateVisibility();
}
mParent.onHeightUpdated();
@@ -0,0 +1,189 @@
/*
* 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.uioverrides;
import static com.android.launcher3.graphics.IconShape.getShape;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
/**
* A BubbleTextView with a ring around it's drawable
*/
public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private static final float RING_EFFECT_RATIO = 0.12f;
private DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean mIsPinned = false;
private int mNormalizedIconRadius;
public PredictedAppIcon(Context context) {
this(context, null, 0);
}
public PredictedAppIcon(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDeviceProfile = Launcher.getLauncher(context).getDeviceProfile();
mNormalizedIconRadius = IconNormalizer.getNormalizedCircleSize(getIconSize()) / 2;
setOnClickListener(ItemClickHandler.INSTANCE);
setOnFocusChangeListener(Launcher.getLauncher(context).mFocusHandler);
}
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
if (!mIsPinned) {
drawEffect(canvas);
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
}
super.onDraw(canvas);
canvas.restoreToCount(count);
}
@Override
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
super.applyFromWorkspaceItem(info);
int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200));
}
/**
* Removes prediction ring from app icon
*/
public void pin(WorkspaceItemInfo info) {
if (mIsPinned) return;
applyFromWorkspaceItem(info);
setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
mIsPinned = true;
invalidate();
}
/**
* prepares prediction icon for usage after bind
*/
public void finishBinding() {
setOnLongClickListener((v) -> {
PopupContainerWithArrow.showForIcon((BubbleTextView) v);
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return true;
});
setTextVisibility(false);
verifyHighRes();
}
@Override
public void getIconBounds(Rect outBounds) {
super.getIconBounds(outBounds);
if (!mIsPinned) {
int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO);
outBounds.inset(predictionInset, predictionInset);
}
}
private int getOutlineOffsetX() {
return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
}
private int getOutlineOffsetY() {
return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
}
private void drawEffect(Canvas canvas) {
getShape().drawShape(canvas, getOutlineOffsetX(), getOutlineOffsetY(),
mNormalizedIconRadius, mIconRingPaint);
}
/**
* Creates and returns a new instance of PredictedAppIcon from WorkspaceItemInfo
*/
public static PredictedAppIcon createIcon(ViewGroup parent, WorkspaceItemInfo info) {
PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext())
.inflate(R.layout.predicted_app_icon, parent, false);
icon.applyFromWorkspaceItem(info);
return icon;
}
/**
* Draws Predicted Icon outline on cell layout
*/
public static class PredictedIconOutlineDrawing extends CellLayout.DelegatedCellDrawing {
private int mOffsetX;
private int mOffsetY;
private int mIconRadius;
private Paint mOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public PredictedIconOutlineDrawing(int cellX, int cellY, PredictedAppIcon icon) {
mDelegateCellX = cellX;
mDelegateCellY = cellY;
mOffsetX = icon.getOutlineOffsetX();
mOffsetY = icon.getOutlineOffsetY();
mIconRadius = icon.mNormalizedIconRadius;
mOutlinePaint.setStyle(Paint.Style.STROKE);
mOutlinePaint.setStrokeWidth(5);
mOutlinePaint.setPathEffect(new DashPathEffect(new float[]{15, 15}, 0));
mOutlinePaint.setColor(Color.argb(100, 245, 245, 245));
}
/**
* Draws predicted app icon outline under CellLayout
*/
@Override
public void drawUnderItem(Canvas canvas) {
getShape().drawShape(canvas, mOffsetX, mOffsetY, mIconRadius, mOutlinePaint);
}
/**
* Draws PredictedAppIcon outline over CellLayout
*/
@Override
public void drawOverItem(Canvas canvas) {
// Does nothing
}
}
}
@@ -37,6 +37,7 @@ import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
@@ -215,7 +216,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
ArrayList<TouchController> list = new ArrayList<>();
list.add(getDragController());
if (mode == NO_BUTTON) {
list.add(new QuickSwitchTouchController(this));
list.add(new NoButtonQuickSwitchTouchController(this));
list.add(new NavBarToHomeTouchController(this));
list.add(new FlingAndHoldTouchController(this));
} else {
@@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
@@ -205,6 +206,7 @@ public class OverviewState extends LauncherState {
builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
}
}
@@ -16,25 +16,20 @@
package com.android.launcher3.uioverrides.touchcontrollers;
import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
@@ -46,12 +41,13 @@ import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppTransitionManagerImpl;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.quickstep.SystemUiProxy;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.views.RecentsView;
@@ -79,7 +75,7 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
@Override
protected long getAtomicDuration() {
return 300;
return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
}
@Override
@@ -179,15 +175,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController {
mPeekAnim.cancel();
}
AnimatorSetBuilder builder = new AnimatorSetBuilder();
builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
}
AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
INDEX_PAUSE_TO_OVERVIEW_ANIM);
overviewAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -0,0 +1,470 @@
/*
* 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.uioverrides.touchcontrollers;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.views.LauncherRecentsView;
/**
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
* the user as possible, also handles swipe up and hold to go to overview and swiping back home.
*/
public class NoButtonQuickSwitchTouchController implements TouchController,
BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener {
/** The minimum progress of the scale/translationY animation until drag end. */
private static final float Y_ANIM_MIN_PROGRESS = 0.15f;
private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5;
private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
private final BaseQuickstepLauncher mLauncher;
private final BothAxesSwipeDetector mSwipeDetector;
private final ShelfPeekAnim mShelfPeekAnim;
private final float mXRange;
private final float mYRange;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
private boolean mNoIntercept;
private LauncherState mStartState;
private boolean mIsHomeScreenVisible = true;
// As we drag, we control 3 animations: one to get non-overview components out of the way,
// and the other two to set overview properties based on x and y progress.
private AnimatorPlaybackController mNonOverviewAnim;
private AnimatorPlaybackController mXOverviewAnim;
private AnimatorPlaybackController mYOverviewAnim;
public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
mShelfPeekAnim = mLauncher.getShelfPeekAnim();
mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile());
mMotionPauseDetector = new MotionPauseDetector(mLauncher);
mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
}
// Only detect horizontal swipe for intercept, then we will allow swipe up as well.
mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
false /* ignoreSlopWhenSettling */);
}
if (mNoIntercept) {
return false;
}
onControllerTouchEvent(ev);
return mSwipeDetector.isDraggingOrSettling();
}
@Override
public boolean onControllerTouchEvent(MotionEvent ev) {
return mSwipeDetector.onTouchEvent(ev);
}
private boolean canInterceptTouch(MotionEvent ev) {
if (!mLauncher.isInState(LauncherState.NORMAL)) {
return false;
}
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return false;
}
return true;
}
@Override
public void onDragStart(boolean start) {
mMotionPauseDetector.clear();
if (start) {
mStartState = mLauncher.getStateManager().getState();
mMotionPauseDetector.setOnMotionPauseListener(this);
// We have detected horizontal drag start, now allow swipe up as well.
mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP,
false /* ignoreSlopWhenSettling */);
setupAnimators();
}
}
@Override
public void onMotionPauseChanged(boolean isPaused) {
ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
if (shelfState == PEEK) {
// Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
AnimatorSetBuilder builder = new AnimatorSetBuilder();
AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher),
new AnimationConfig(), builder);
builder.build().setDuration(0).start();
if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
// Hotseat was hidden, but we need it visible when peeking.
mLauncher.getHotseat().setAlpha(1);
}
}
mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
ShelfPeekAnim.DURATION);
VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
}
private void setupAnimators() {
// Animate the non-overview components (e.g. workspace, shelf) out of the way.
AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder();
nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL);
mNonOverviewAnim.dispatchOnStart();
setupOverviewAnimators();
}
/** Create state animation to control non-overview components. */
private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
@LauncherStateManager.AnimationComponents int animComponents) {
builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
builder, accuracy, this::clearState, animComponents);
}
private void setupOverviewAnimators() {
final LauncherState fromState = QUICK_SWITCH;
final LauncherState toState = OVERVIEW;
LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState
.getOverviewScaleAndTranslation(mLauncher);
LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
.getOverviewScaleAndTranslation(mLauncher);
// Update RecentView's translationX to have it start offscreen.
LauncherRecentsView recentsView = mLauncher.getOverviewPanel();
float startScale = Utilities.mapRange(
SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
fromScaleAndTranslation.scale,
toScaleAndTranslation.scale);
fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale);
// Set RecentView's initial properties.
recentsView.setScaleX(fromScaleAndTranslation.scale);
recentsView.setScaleY(fromScaleAndTranslation.scale);
recentsView.setTranslationX(fromScaleAndTranslation.translationX);
recentsView.setTranslationY(fromScaleAndTranslation.translationY);
recentsView.setContentAlpha(1);
// As we drag right, animate the following properties:
// - RecentsView translationX
// - OverviewScrim
AnimatorSet xOverviewAnim = new AnimatorSet();
xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X,
toScaleAndTranslation.translationX));
xOverviewAnim.play(ObjectAnimator.ofFloat(
mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
toState.getOverviewScrimAlpha(mLauncher)));
long xAccuracy = (long) (mXRange * 2);
xOverviewAnim.setDuration(xAccuracy);
mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy);
mXOverviewAnim.dispatchOnStart();
// As we drag up, animate the following properties:
// - RecentsView translationY
// - RecentsView scale
// - RecentsView fullscreenProgress
AnimatorSet yAnimation = new AnimatorSet();
Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y,
toScaleAndTranslation.translationY);
Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
toScaleAndTranslation.scale);
Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS,
fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
yAnimation.play(translateYAnim);
yAnimation.play(scaleAnim);
yAnimation.play(fullscreenProgressAnim);
long yAccuracy = (long) (mYRange * 2);
yAnimation.setDuration(yAccuracy);
mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy);
mYOverviewAnim.dispatchOnStart();
}
@Override
public boolean onDrag(PointF displacement, MotionEvent ev) {
float xProgress = Math.max(0, displacement.x) / mXRange;
float yProgress = Math.max(0, -displacement.y) / mYRange;
yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f);
boolean wasHomeScreenVisible = mIsHomeScreenVisible;
if (wasHomeScreenVisible && mNonOverviewAnim != null) {
mNonOverviewAnim.setPlayFraction(xProgress);
}
mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
<= 1 - ALPHA_CUTOFF_THRESHOLD;
if (wasHomeScreenVisible && !mIsHomeScreenVisible) {
// Get the shelf all the way offscreen so it pops up when we decide to peek it.
mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0);
}
// Only allow motion pause if the home screen is invisible, since some
// home screen elements will appear in the shelf on motion pause.
mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
|| -displacement.y < mMotionPauseMinDisplacement);
mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime());
if (mIsHomeScreenVisible) {
// Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0);
}
if (mXOverviewAnim != null) {
mXOverviewAnim.setPlayFraction(xProgress);
}
if (mYOverviewAnim != null) {
mYOverviewAnim.setPlayFraction(yProgress);
}
return true;
}
@Override
public void onDragEnd(PointF velocity) {
boolean horizontalFling = mSwipeDetector.isFling(velocity.x);
boolean verticalFling = mSwipeDetector.isFling(velocity.y);
boolean noFling = !horizontalFling && !verticalFling;
int logAction = noFling ? Touch.SWIPE : Touch.FLING;
if (mMotionPauseDetector.isPaused() && noFling) {
cancelAnimations();
Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
INDEX_PAUSE_TO_OVERVIEW_ANIM);
overviewAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
onAnimationToStateCompleted(OVERVIEW, logAction);
}
});
overviewAnim.start();
return;
}
final LauncherState targetState;
if (horizontalFling && verticalFling) {
if (velocity.x < 0) {
// Flinging left and up or down both go back home.
targetState = NORMAL;
} else {
if (velocity.y > 0) {
// Flinging right and down goes to quick switch.
targetState = QUICK_SWITCH;
} else {
// Flinging up and right could go either home or to quick switch.
// Determine the target based on the higher velocity.
targetState = Math.abs(velocity.x) > Math.abs(velocity.y)
? QUICK_SWITCH : NORMAL;
}
}
} else if (horizontalFling) {
targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL;
} else if (verticalFling) {
targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
} else {
// If user isn't flinging, just snap to the closest state based on x progress.
boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL;
}
// Animate the various components to the target state.
float xProgress = mXOverviewAnim.getProgressFraction();
float startXProgress = Utilities.boundToRange(xProgress
+ velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f);
final float endXProgress = targetState == NORMAL ? 0 : 1;
long xDuration = BaseSwipeDetector.calculateDuration(velocity.x,
Math.abs(endXProgress - startXProgress));
ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer();
xOverviewAnim.setFloatValues(startXProgress, endXProgress);
xOverviewAnim.setDuration(xDuration)
.setInterpolator(scrollInterpolatorForVelocity(velocity.x));
mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x);
boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
float yProgress = mYOverviewAnim.getProgressFraction();
float startYProgress = Utilities.boundToRange(yProgress
- velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f);
final float endYProgress;
if (flingUpToNormal) {
endYProgress = 1;
} else if (targetState == NORMAL) {
// Keep overview at its current scale/translationY as it slides off the screen.
endYProgress = startYProgress;
} else {
endYProgress = 0;
}
long yDuration = BaseSwipeDetector.calculateDuration(velocity.y,
Math.abs(endYProgress - startYProgress));
ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
yOverviewAnim.setFloatValues(startYProgress, endYProgress);
yOverviewAnim.setDuration(yDuration);
mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y);
ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
if (flingUpToNormal && !mIsHomeScreenVisible) {
// We are flinging to home while workspace is invisible, run the same staggered
// animation as from an app.
// Update mNonOverviewAnim to do nothing so it doesn't interfere.
updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */);
nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */)
.start();
} else {
boolean canceled = targetState == NORMAL;
if (canceled) {
// Let the state manager know that the animation didn't go to the target state,
// but don't clean up yet (we already clean up when the animation completes).
mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable();
}
float startProgress = mNonOverviewAnim.getProgressFraction();
float endProgress = canceled ? 0 : 1;
nonOverviewAnim.setFloatValues(startProgress, endProgress);
mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress,
horizontalFling ? velocity.x : velocity.y);
}
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction));
cancelAnimations();
xOverviewAnim.start();
yOverviewAnim.start();
nonOverviewAnim.start();
}
private void onAnimationToStateCompleted(LauncherState targetState, int logAction) {
mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(),
LauncherLogProto.ContainerType.NAVBAR,
mStartState.containerType,
targetState.containerType,
mLauncher.getWorkspace().getCurrentPage());
mLauncher.getStateManager().goToState(targetState, false, this::clearState);
}
private int getDirectionForLog() {
return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT;
}
private void cancelAnimations() {
if (mNonOverviewAnim != null) {
mNonOverviewAnim.getAnimationPlayer().cancel();
}
if (mXOverviewAnim != null) {
mXOverviewAnim.getAnimationPlayer().cancel();
}
if (mYOverviewAnim != null) {
mYOverviewAnim.getAnimationPlayer().cancel();
}
mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
mMotionPauseDetector.clear();
}
private void clearState() {
cancelAnimations();
mNonOverviewAnim = null;
mXOverviewAnim = null;
mYOverviewAnim = null;
mIsHomeScreenVisible = true;
mSwipeDetector.finishedScrolling();
}
}
@@ -24,7 +24,6 @@ import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -52,10 +51,8 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.FloatingIconView;
@@ -194,18 +191,8 @@ public final class LauncherActivityInterface implements BaseActivityInterface<La
@Override
public void playAtomicAnimation(float velocity) {
// Setup workspace with 0 duration to prepare for our staggered animation.
LauncherStateManager stateManager = launcher.getStateManager();
AnimatorSetBuilder builder = new AnimatorSetBuilder();
// setRecentsAttachedToAppWindow() will animate recents out.
builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
builder.build().start();
// Stop scrolling so that it doesn't interfere with the translation offscreen.
recentsView.getScroller().forceFinished(true);
new StaggeredWorkspaceAnim(launcher, workspaceView, velocity).start();
new StaggeredWorkspaceAnim(launcher, velocity, true /* animateOverviewScrim */)
.start();
}
};
}
@@ -167,8 +167,6 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
private boolean mIsShelfPeeking;
private boolean mContinuingLastGesture;
// To avoid UI jump when gesture is started, we offset the animation by the threshold.
private float mShiftAtGestureStart = 0;
private ThumbnailData mTaskSnapshot;
@@ -574,9 +572,7 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
// Normalize the progress to 0 to 1, as the animation controller will clamp it to that
// anyway. The controller mimics the drag length factor by applying it to its interpolators.
float progress = mCurrentShift.value / mDragLengthFactor;
mLauncherTransitionController.setPlayFraction(
progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1
? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
mLauncherTransitionController.setPlayFraction(progress);
}
/**
@@ -623,7 +619,6 @@ public class LauncherSwipeHandler<T extends BaseDraggingActivity>
@Override
public void onGestureStarted() {
notifyGestureStartedAsync();
mShiftAtGestureStart = mCurrentShift.value;
mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
}
@@ -18,6 +18,7 @@ package com.android.quickstep.util;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
@@ -27,12 +28,11 @@ import android.animation.ObjectAnimator;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -41,7 +41,7 @@ import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.views.IconLabelDotView;
import com.android.quickstep.views.RecentsView;
/**
* Creates an animation where all the workspace items are moved into their final location,
@@ -61,18 +61,12 @@ public class StaggeredWorkspaceAnim {
private final float mVelocity;
private final float mSpringTransY;
// The original view of the {@link FloatingIconView}.
private final View mOriginalView;
private final AnimatorSet mAnimators = new AnimatorSet();
/**
* @param floatingViewOriginalView The FloatingIconView's original view.
*/
public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView,
float velocity) {
public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) {
prepareToAnimate(launcher);
mVelocity = velocity;
mOriginalView = floatingViewOriginalView;
// Scale the translationY based on the initial velocity to better sync the workspace items
// with the floating view.
@@ -128,8 +122,10 @@ public class StaggeredWorkspaceAnim {
addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
}
addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
if (animateOverviewScrim) {
addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
}
mAnimators.addListener(new AnimatorListenerAdapter() {
@Override
@@ -144,6 +140,21 @@ public class StaggeredWorkspaceAnim {
});
}
/**
* Setup workspace with 0 duration to prepare for our staggered animation.
*/
private void prepareToAnimate(Launcher launcher) {
LauncherStateManager stateManager = launcher.getStateManager();
AnimatorSetBuilder builder = new AnimatorSetBuilder();
// setRecentsAttachedToAppWindow() will animate recents out.
builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
builder.build().start();
// Stop scrolling so that it doesn't interfere with the translation offscreen.
launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
}
/**
* Starts the animation.
*/
@@ -176,35 +187,12 @@ public class StaggeredWorkspaceAnim {
springTransY.setStartDelay(startDelay);
mAnimators.play(springTransY);
ObjectAnimator alpha = getAlphaAnimator(v, startDelay);
if (v == mOriginalView) {
// For IconLabelDotViews, we just want the label to fade in.
// Icon, badge, and dots will animate in separately (controlled via FloatingIconView)
if (v instanceof IconLabelDotView) {
alpha.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
IconLabelDotView view = (IconLabelDotView) v;
view.setIconVisible(false);
view.setForceHideDot(true);
}
});
} else {
return;
}
}
v.setAlpha(0);
mAnimators.play(alpha);
}
private ObjectAnimator getAlphaAnimator(View v, long startDelay) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
alpha.setInterpolator(LINEAR);
alpha.setDuration(ALPHA_DURATION_MS);
alpha.setStartDelay(startDelay);
return alpha;
mAnimators.play(alpha);
}
private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
@@ -176,6 +176,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
private final float mFastFlingVelocity;
private final RecentsModel mModel;
private final int mTaskTopMargin;
private final int mTaskBottomMargin;
private final ClearAllButton mClearAllButton;
private final Rect mClearAllButtonDeadZoneRect = new Rect();
private final Rect mTaskViewDeadZoneRect = new Rect();
@@ -342,6 +343,8 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mTaskTopMargin = getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
mTaskBottomMargin = getResources().getDimensionPixelSize(
R.dimen.task_thumbnail_bottom_margin);
mSquaredTouchSlop = squaredTouchSlop(context);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -695,6 +698,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
mTaskHeight = mTempRect.height();
mTempRect.top -= mTaskTopMargin;
mTempRect.bottom += mTaskBottomMargin;
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
dp.widthPx - mInsets.right - mTempRect.right,
dp.heightPx - mInsets.bottom - mTempRect.bottom);
@@ -1466,7 +1470,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
// Set the pivot points to match the task preview center
setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin)
+ (getHeight() - mInsets.bottom - getPaddingBottom())) / 2);
+ (getHeight() - mInsets.bottom - getPaddingBottom() - mTaskBottomMargin)) / 2);
setPivotX(((mInsets.left + getPaddingLeft())
+ (getWidth() - mInsets.right - getPaddingRight())) / 2);
}
@@ -540,8 +540,11 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
}
addView(view, indexToAdd);
((LayoutParams) view.getLayoutParams()).gravity =
LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
layoutParams.gravity =
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
layoutParams.bottomMargin =
((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin;
view.setAlpha(mFooterAlpha);
mFooters[index] = new FooterWrapper(view);
if (shouldAnimateEntry) {
@@ -618,10 +621,12 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
private static final class TaskOutlineProvider extends ViewOutlineProvider {
private final int mMarginTop;
private final int mMarginBottom;
private FullscreenDrawParams mFullscreenParams;
TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) {
mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
mMarginBottom = res.getDimensionPixelSize(R.dimen.task_thumbnail_bottom_margin);
mFullscreenParams = fullscreenParams;
}
@@ -636,7 +641,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
outline.setRoundRect(0,
(int) (mMarginTop * scale),
(int) ((insets.left + view.getWidth() + insets.right) * scale),
(int) ((insets.top + view.getHeight() + insets.bottom) * scale),
(int) ((insets.top + view.getHeight() + insets.bottom - mMarginBottom) * scale),
mFullscreenParams.mCurrentDrawnCornerRadius);
}
}
+2 -1
View File
@@ -25,7 +25,8 @@
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/task_thumbnail_top_margin"/>
android:layout_marginTop="@dimen/task_thumbnail_top_margin"
android:layout_marginBottom="@dimen/task_thumbnail_bottom_margin"/>
<com.android.quickstep.views.IconView
android:id="@+id/icon"
+2
View File
@@ -18,6 +18,8 @@
<dimen name="task_thumbnail_top_margin">24dp</dimen>
<dimen name="task_thumbnail_half_top_margin">12dp</dimen>
<!-- Can be overridden in overlays. -->
<dimen name="task_thumbnail_bottom_margin">0dp</dimen>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
@@ -105,6 +105,7 @@ public class LayoutUtils {
}
float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
float bottomMargin = res.getDimension(R.dimen.task_thumbnail_bottom_margin);
float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
// Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
@@ -113,7 +114,7 @@ public class LayoutUtils {
int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
float availableHeight = launcherVisibleHeight
- topIconMargin - extraVerticalSpace - paddingVert;
- topIconMargin - extraVerticalSpace - paddingVert - bottomMargin;
float availableWidth = launcherVisibleWidth - paddingHorz;
float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
@@ -18,6 +18,7 @@ package com.android.quickstep.views;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -34,7 +35,9 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
@@ -52,7 +55,8 @@ import com.android.quickstep.util.LayoutUtils;
* From normal state to overview state, the shelf just fades in and does not move
* From overview state to all-apps state the shelf moves up and fades in to cover the screen
*/
public class ShelfScrimView extends ScrimView implements NavigationModeChangeListener {
public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
implements NavigationModeChangeListener {
// If the progress is more than this, shelf follows the finger, otherwise it moves faster to
// cover the whole screen
@@ -193,8 +197,10 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis
if (mProgress >= 1) {
mRemainingScreenColor = 0;
mShelfColor = 0;
LauncherState state = mLauncher.getStateManager().getState();
if (mSysUINavigationMode == Mode.NO_BUTTON
&& mLauncher.getStateManager().getState() == BACKGROUND_APP) {
&& (state == BACKGROUND_APP || state == QUICK_SWITCH)
&& mLauncher.getShelfPeekAnim().isPeeking()) {
// Show the shelf background when peeking during swipe up.
mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
}
@@ -209,7 +209,6 @@ public class NavigationModeSwitchRule implements TestRule {
final String error = mLauncher.getNavigationModeMismatchError();
assertTrue("Switching nav mode: " + error, error == null);
Thread.sleep(5000);
return true;
}
-3
View File
@@ -15,9 +15,6 @@
-->
<resources>
<!-- Container -->
<item name="container_margin" format="fraction" type="fraction">12%</item>
<!-- Fast scroll -->
<dimen name="fastscroll_popup_width">58dp</dimen>
<dimen name="fastscroll_popup_height">48dp</dimen>
+3
View File
@@ -114,4 +114,7 @@
<!-- Recents -->
<item type="id" name="overview_panel"/>
<!-- Configuration resources -->
<array name="dynamic_resources"> </array>
</resources>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<DynamicResources>
<entry id="@color/delete_target_hover_tint" />
<entry id="@color/delete_target_hover_tint" />
<entry id="@color/delete_target_hover_tint" />
<entry id="@color/delete_target_hover_tint" />
</DynamicResources>
+50 -30
View File
@@ -110,7 +110,7 @@ public class CellLayout extends ViewGroup implements Transposable {
private OnTouchListener mInterceptTouchListener;
private final ArrayList<PreviewBackground> mFolderBackgrounds = new ArrayList<>();
private final ArrayList<DelegatedCellDrawing> mDelegatedCellDrawings = new ArrayList<>();
final PreviewBackground mFolderLeaveBehind = new PreviewBackground();
private static final int[] BACKGROUND_STATE_ACTIVE = new int[] { android.R.attr.state_active };
@@ -219,8 +219,8 @@ public class CellLayout extends ViewGroup implements Transposable {
mPreviousReorderDirection[0] = INVALID_DIRECTION;
mPreviousReorderDirection[1] = INVALID_DIRECTION;
mFolderLeaveBehind.delegateCellX = -1;
mFolderLeaveBehind.delegateCellY = -1;
mFolderLeaveBehind.mDelegateCellX = -1;
mFolderLeaveBehind.mDelegateCellY = -1;
setAlwaysDrawnWithCacheEnabled(false);
final Resources res = getResources();
@@ -466,21 +466,18 @@ public class CellLayout extends ViewGroup implements Transposable {
}
}
for (int i = 0; i < mFolderBackgrounds.size(); i++) {
PreviewBackground bg = mFolderBackgrounds.get(i);
cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
bg.drawBackground(canvas);
if (!bg.isClipping) {
bg.drawBackgroundStroke(canvas);
}
cellDrawing.drawUnderItem(canvas);
canvas.restore();
}
if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) {
cellToPoint(mFolderLeaveBehind.delegateCellX,
mFolderLeaveBehind.delegateCellY, mTempLocation);
if (mFolderLeaveBehind.mDelegateCellX >= 0 && mFolderLeaveBehind.mDelegateCellY >= 0) {
cellToPoint(mFolderLeaveBehind.mDelegateCellX,
mFolderLeaveBehind.mDelegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
mFolderLeaveBehind.drawLeaveBehind(canvas);
@@ -492,23 +489,28 @@ public class CellLayout extends ViewGroup implements Transposable {
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
for (int i = 0; i < mFolderBackgrounds.size(); i++) {
PreviewBackground bg = mFolderBackgrounds.get(i);
if (bg.isClipping) {
cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
bg.drawBackgroundStroke(canvas);
canvas.restore();
}
for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
canvas.save();
canvas.translate(mTempLocation[0], mTempLocation[1]);
bg.drawOverItem(canvas);
canvas.restore();
}
}
public void addFolderBackground(PreviewBackground bg) {
mFolderBackgrounds.add(bg);
/**
* Add Delegated cell drawing
*/
public void addDelegatedCellDrawing(DelegatedCellDrawing bg) {
mDelegatedCellDrawings.add(bg);
}
public void removeFolderBackground(PreviewBackground bg) {
mFolderBackgrounds.remove(bg);
/**
* Remove item from DelegatedCellDrawings
*/
public void removeDelegatedCellDrawing(DelegatedCellDrawing bg) {
mDelegatedCellDrawings.remove(bg);
}
public void setFolderLeaveBehindCell(int x, int y) {
@@ -516,14 +518,14 @@ public class CellLayout extends ViewGroup implements Transposable {
mFolderLeaveBehind.setup(getContext(), mActivity, null,
child.getMeasuredWidth(), child.getPaddingTop());
mFolderLeaveBehind.delegateCellX = x;
mFolderLeaveBehind.delegateCellY = y;
mFolderLeaveBehind.mDelegateCellX = x;
mFolderLeaveBehind.mDelegateCellY = y;
invalidate();
}
public void clearFolderLeaveBehind() {
mFolderLeaveBehind.delegateCellX = -1;
mFolderLeaveBehind.delegateCellY = -1;
mFolderLeaveBehind.mDelegateCellX = -1;
mFolderLeaveBehind.mDelegateCellY = -1;
invalidate();
}
@@ -2743,6 +2745,24 @@ public class CellLayout extends ViewGroup implements Transposable {
}
}
/**
* A Delegated cell Drawing for drawing on CellLayout
*/
public abstract static class DelegatedCellDrawing {
public int mDelegateCellX;
public int mDelegateCellY;
/**
* Draw under CellLayout
*/
public abstract void drawUnderItem(Canvas canvas);
/**
* Draw over CellLayout
*/
public abstract void drawOverItem(Canvas canvas);
}
/**
* Returns whether an item can be placed in this CellLayout (after rearranging and/or resizing
* if necessary).
@@ -113,6 +113,7 @@ public class DeleteDropTarget extends ButtonDropTarget {
public void onDrop(DragObject d, DragOptions options) {
if (canRemove(d.dragInfo)) {
mLauncher.getModelWriter().prepareToUndoDelete();
d.dragInfo.container = NO_ID;
}
super.onDrop(d, options);
}
@@ -21,14 +21,19 @@ import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.util.UiThreadHelper;
import java.util.List;
/**
* The edit text that reports back when the back key has been pressed.
* Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion
*/
public class ExtendedEditText extends EditText {
@@ -85,12 +90,9 @@ public class ExtendedEditText extends EditText {
super.onLayout(changed, left, top, right, bottom);
if (mShowImeAfterFirstLayout) {
// soft input only shows one frame after the layout of the EditText happens,
post(new Runnable() {
@Override
public void run() {
showSoftInput();
mShowImeAfterFirstLayout = false;
}
post(() -> {
showSoftInput();
mShowImeAfterFirstLayout = false;
});
}
}
@@ -103,9 +105,27 @@ public class ExtendedEditText extends EditText {
UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
}
@Override
public void onCommitCompletion(CompletionInfo text) {
setText(text.getText());
}
/**
* Currently only used for folder name suggestion.
*/
public void displayCompletions(List<String> suggestList) {
int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX);
CompletionInfo[] cInfo = new CompletionInfo[cnt];
for (int i = 0; i < cnt; i++) {
cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
}
post(() -> getContext().getSystemService(InputMethodManager.class)
.displayCompletions(this, cInfo));
}
private boolean showSoftInput() {
return requestFocus() &&
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
getContext().getSystemService(InputMethodManager.class)
.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
}
+5 -1
View File
@@ -508,12 +508,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
@Override
public void reapplyUi() {
reapplyUi(true /* cancelCurrentAnimation */);
}
public void reapplyUi(boolean cancelCurrentAnimation) {
if (supportsFakeLandscapeUI()) {
mRotationMode = mStableDeviceProfile == null
? RotationMode.NORMAL : getFakeRotationMode(mDeviceProfile);
}
getRootView().dispatchInsets();
getStateManager().reapplyState(true /* cancelCurrentAnimation */);
getStateManager().reapplyState(cancelCurrentAnimation);
}
@Override
@@ -27,7 +27,7 @@ import android.view.ViewGroup;
*/
public class AlphaUpdateListener extends AnimationSuccessListener
implements AnimatorUpdateListener {
private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
private View mView;
@@ -26,15 +26,15 @@ import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.util.Log;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
/**
* Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
* and durations.
@@ -250,6 +250,17 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat
}
}
/**
* Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
* is intended to be used only if you need to cancel but want to defer cleaning up yourself.
*/
public void dispatchOnCancelWithoutCancelRunnable() {
Runnable onCancel = mOnCancelRunnable;
setOnCancelRunnable(null);
dispatchOnCancel();
setOnCancelRunnable(onCancel);
}
public void dispatchOnCancel() {
dispatchOnCancelRecursively(mAnim);
}
@@ -283,10 +294,6 @@ public abstract class AnimatorPlaybackController implements ValueAnimator.Animat
mOnCancelRunnable = runnable;
}
public Runnable getOnCancelRunnable() {
return mOnCancelRunnable;
}
public void skipToEnd() {
mSkipToEnd = true;
for (SpringAnimation spring : mSprings) {
@@ -39,6 +39,7 @@ public class Interpolators {
public static final Interpolator LINEAR = new LinearInterpolator();
public static final Interpolator ACCEL = new AccelerateInterpolator();
public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f);
public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f);
public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2);
@@ -48,6 +49,7 @@ public class Interpolators {
public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f);
public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
@@ -69,16 +69,8 @@ public final class FeatureFlags {
*
* To add a new flag that can be toggled through the flags UI:
*
* 1. Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
* Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
* and set a default value for the flag. This will be the default value on Debug builds.
*
* 2. Add your flag to mTogglableFlags.
*
* 3. Create a getter method (an 'is' method) for the flag by copying an existing one.
*
* 4. Create a getter method with the same name in the release flags copy of FeatureFlags.java.
* This should returns a constant (true/false). This will be the value of the flag used on
* release builds.
*/
// When enabled the promise icon is visible in all apps while installation an app.
public static final TogglableFlag PROMISE_APPS_IN_ALL_APPS = new TogglableFlag(
+12 -10
View File
@@ -84,6 +84,7 @@ import com.android.launcher3.views.ClipPathView;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -148,7 +149,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
public ExtendedEditText mFolderName;
private PageIndicatorDots mPageIndicator;
private View mFooter;
protected View mFooter;
private int mFooterHeight;
// Cell ranks used for drag and drop
@@ -217,7 +218,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
& ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
& ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mFolderName.forceDisableSuggestions(true);
mFolderName.forceDisableSuggestions(!FeatureFlags.FOLDER_NAME_SUGGEST.get());
mFooter = findViewById(R.id.folder_footer);
@@ -412,19 +413,20 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
});
}
/**
* Show suggested folder title.
*/
public void showSuggestedTitle(CharSequence suggestName) {
public void showSuggestedTitle(String[] suggestName) {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
if (!TextUtils.isEmpty(suggestName)) {
mFolderName.setHint(suggestName);
mFolderName.setText(suggestName);
if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
mFolderName.setHint(suggestName[0]);
mFolderName.setText(suggestName[0]);
mInfo.title = suggestName[0];
animateOpen();
mFolderName.showKeyboard();
mInfo.title = suggestName;
mFolderName.displayCompletions(
Arrays.asList(suggestName).subList(1, suggestName.length));
}
animateOpen();
}
}
@@ -991,7 +993,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
lp.y = top;
}
private int getContentAreaHeight() {
protected int getContentAreaHeight() {
DeviceProfile grid = mLauncher.getDeviceProfile();
int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
- mFooterHeight;
@@ -59,6 +59,8 @@ import java.util.List;
*/
public class FolderAnimationManager {
private static final int FOLDER_NAME_ALPHA_DURATION = 32;
private Folder mFolder;
private FolderPagedView mContent;
private GradientDrawable mFolderBackground;
@@ -130,11 +132,19 @@ public class FolderAnimationManager {
* scaleRelativeToDragLayer;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
mFolder.setScaleX(scale);
mFolder.setScaleY(scale);
mFolder.setPivotX(0);
mFolder.setPivotY(0);
// Scale the contents of the folder.
mFolder.mContent.setScaleX(scale);
mFolder.mContent.setScaleY(scale);
mFolder.mContent.setPivotX(0);
mFolder.mContent.setPivotY(0);
mFolder.mFooter.setScaleX(scale);
mFolder.mFooter.setScaleY(scale);
mFolder.mFooter.setPivotX(0);
mFolder.mFooter.setPivotY(0);
// We want to create a small X offset for the preview items, so that they follow their
// expected path to their final locations. ie. an icon should not move right, if it's final
// location is to its left. This value is arbitrarily defined.
@@ -143,14 +153,13 @@ public class FolderAnimationManager {
previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX);
}
final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft())
* initialScale);
final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop())
* initialScale);
final int paddingOffsetX = (int) (mContent.getPaddingLeft() * initialScale);
final int paddingOffsetY = (int) (mContent.getPaddingTop() * initialScale);
int initialX = folderIconPos.left + mPreviewBackground.getOffsetX() - paddingOffsetX
- previewItemOffsetX;
int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY;
int initialX = folderIconPos.left + mFolder.getPaddingLeft()
+ mPreviewBackground.getOffsetX() - paddingOffsetX - previewItemOffsetX;
int initialY = folderIconPos.top + mFolder.getPaddingTop()
+ mPreviewBackground.getOffsetY() - paddingOffsetY;
final float xDistance = initialX - lp.x;
final float yDistance = initialY - lp.y;
@@ -164,11 +173,10 @@ public class FolderAnimationManager {
// Set up the reveal animation that clips the Folder.
int totalOffsetX = paddingOffsetX + previewItemOffsetX;
Rect startRect = new Rect(
Math.round(totalOffsetX / initialScale),
Math.round(paddingOffsetY / initialScale),
Math.round((totalOffsetX + initialSize) / initialScale),
Math.round((paddingOffsetY + initialSize) / initialScale));
Rect startRect = new Rect(totalOffsetX,
paddingOffsetY,
Math.round((totalOffsetX + initialSize)),
Math.round((paddingOffsetY + initialSize)));
Rect endRect = new Rect(0, 0, lp.width, lp.height);
float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics());
@@ -189,17 +197,46 @@ public class FolderAnimationManager {
play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f));
play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f));
play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
play(a, getAnimator(mFolder.mContent, SCALE_PROPERTY, initialScale, finalScale));
play(a, getAnimator(mFolder.mFooter, SCALE_PROPERTY, initialScale, finalScale));
play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening));
play(a, getShape().createRevealAnimator(
mFolder, startRect, endRect, finalRadius, !mIsOpening));
// Fade in the folder name, as the text can overlap the icons when grid size is small.
mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f);
play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1),
mIsOpening ? FOLDER_NAME_ALPHA_DURATION : 0,
mIsOpening ? mDuration - FOLDER_NAME_ALPHA_DURATION : FOLDER_NAME_ALPHA_DURATION);
// Translate the footer so that it tracks the bottom of the content.
float normalHeight = mFolder.getContentAreaHeight();
float scaledHeight = normalHeight * initialScale;
float diff = normalHeight - scaledHeight;
play(a, getAnimator(mFolder.mFooter, View.TRANSLATION_Y, -diff, 0f));
// Animate the elevation midway so that the shadow is not noticeable in the background.
int midDuration = mDuration / 2;
Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
play(a, z, mIsOpening ? midDuration : 0, midDuration);
// Store clip variables
CellLayout cellLayout = mContent.getCurrentCellLayout();
boolean folderClipChildren = mFolder.getClipChildren();
boolean folderClipToPadding = mFolder.getClipToPadding();
boolean contentClipChildren = mContent.getClipChildren();
boolean contentClipToPadding = mContent.getClipToPadding();
boolean cellLayoutClipChildren = cellLayout.getClipChildren();
boolean cellLayoutClipPadding = cellLayout.getClipToPadding();
mFolder.setClipChildren(false);
mFolder.setClipToPadding(false);
mContent.setClipChildren(false);
mContent.setClipToPadding(false);
cellLayout.setClipChildren(false);
cellLayout.setClipToPadding(false);
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -207,8 +244,20 @@ public class FolderAnimationManager {
mFolder.setTranslationX(0.0f);
mFolder.setTranslationY(0.0f);
mFolder.setTranslationZ(0.0f);
mFolder.setScaleX(1f);
mFolder.setScaleY(1f);
mFolder.mContent.setScaleX(1f);
mFolder.mContent.setScaleY(1f);
mFolder.mFooter.setScaleX(1f);
mFolder.mFooter.setScaleY(1f);
mFolder.mFooter.setTranslationX(0f);
mFolder.mFolderName.setAlpha(1f);
mFolder.setClipChildren(folderClipChildren);
mFolder.setClipToPadding(folderClipToPadding);
mContent.setClipChildren(contentClipChildren);
mContent.setClipToPadding(contentClipToPadding);
cellLayout.setClipChildren(cellLayoutClipChildren);
cellLayout.setClipToPadding(cellLayoutClipPadding);
}
});
@@ -371,22 +371,31 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
final int finalIndex = index;
String[] suggestedNameOut = new String[1];
String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
.getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
Executors.UI_HELPER_EXECUTOR.post(() -> {
mLauncher.getFolderNameProvider().getSuggestedFolderName(
getContext(), mInfo.contents, suggestedNameOut);
showFinalView(finalIndex, item, suggestedNameOut);
});
} else {
showFinalView(finalIndex, item, suggestedNameOut);
}
postDelayed(() -> {
mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
invalidate();
mFolder.showSuggestedTitle(suggestedNameOut[0]);
}, DROP_IN_ANIMATION_DURATION);
} else {
addItem(item);
}
}
private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
String[] suggestedNameOut) {
postDelayed(() -> {
mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
invalidate();
mFolder.showSuggestedTitle(suggestedNameOut);
}, DROP_IN_ANIMATION_DURATION);
}
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
WorkspaceItemInfo item;
if (d.dragInfo instanceof AppInfo) {
@@ -28,6 +28,12 @@ import java.util.ArrayList;
*/
public class FolderNameProvider {
/**
* IME usually has up to 3 suggest slots. Adding one as in Launcher, there are folder
* name edit box that we can also provide suggestion.
*/
public static final int SUGGEST_MAX = 4;
/**
* Returns suggested folder name.
*/
@@ -48,7 +48,7 @@ import com.android.launcher3.views.ActivityContext;
* This object represents a FolderIcon preview background. It stores drawing / measurement
* information, handles drawing, and animation (accept state <--> rest state).
*/
public class PreviewBackground {
public class PreviewBackground extends CellLayout.DelegatedCellDrawing {
private static final int CONSUMPTION_ANIMATION_DURATION = 100;
@@ -76,8 +76,6 @@ public class PreviewBackground {
int basePreviewOffsetY;
private CellLayout mDrawingDelegate;
public int delegateCellX;
public int delegateCellY;
// When the PreviewBackground is drawn under an icon (for creating a folder) the border
// should not occlude the icon
@@ -124,6 +122,27 @@ public class PreviewBackground {
}
};
/**
* Draws folder background under cell layout
*/
@Override
public void drawUnderItem(Canvas canvas) {
drawBackground(canvas);
if (!isClipping) {
drawBackgroundStroke(canvas);
}
}
/**
* Draws folder background on cell layout
*/
@Override
public void drawOverItem(Canvas canvas) {
if (isClipping) {
drawBackgroundStroke(canvas);
}
}
public void setup(Context context, ActivityContext activity, View invalidateDelegate,
int availableSpaceX, int topPadding) {
mInvalidateDelegate = invalidateDelegate;
@@ -317,19 +336,19 @@ public class PreviewBackground {
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
if (mDrawingDelegate != delegate) {
delegate.addFolderBackground(this);
delegate.addDelegatedCellDrawing(this);
}
mDrawingDelegate = delegate;
delegateCellX = cellX;
delegateCellY = cellY;
mDelegateCellX = cellX;
mDelegateCellY = cellY;
invalidate();
}
private void clearDrawingDelegate() {
if (mDrawingDelegate != null) {
mDrawingDelegate.removeFolderBackground(this);
mDrawingDelegate.removeDelegatedCellDrawing(this);
}
mDrawingDelegate = null;
@@ -395,8 +414,8 @@ public class PreviewBackground {
// is saved and restored at the beginning of the animation, since cancelling the
// existing animation can clear the delgate.
CellLayout cl = mDrawingDelegate;
int cellX = delegateCellX;
int cellY = delegateCellY;
int cellX = mDelegateCellX;
int cellY = mDelegateCellY;
animateScale(1f, 1f, () -> delegateDrawing(cl, cellX, cellY), this::clearDrawingDelegate);
}
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.settings;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -24,28 +26,21 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.FlagTogglerPrefUi;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import java.util.List;
import java.util.Set;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceDataStore;
@@ -54,6 +49,16 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.FlagTogglerPrefUi;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Dev-build only UI allowing developers to toggle flag settings and plugins.
* See {@link FeatureFlags}.
@@ -154,44 +159,53 @@ public class DeveloperOptionsFragment extends PreferenceFragment {
PackageManager pm = getContext().getPackageManager();
Set<String> pluginActions = manager.getPluginActions();
ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>();
ArrayMap<Pair<String, String>, ArrayList<Pair<String, ServiceInfo>>> plugins =
new ArrayMap<>();
Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
.stream()
.map(pi -> pi.packageName)
.collect(Collectors.toSet());
for (String action : pluginActions) {
String name = toName(action);
List<ResolveInfo> result = pm.queryIntentServices(
new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS);
new Intent(action), MATCH_DISABLED_COMPONENTS);
for (ResolveInfo info : result) {
String packageName = info.serviceInfo.packageName;
if (!plugins.containsKey(packageName)) {
plugins.put(packageName, new ArraySet<>());
if (!pluginPermissionApps.contains(packageName)) {
continue;
}
plugins.get(packageName).add(name);
Pair<String, String> key = Pair.create(packageName, info.serviceInfo.processName);
if (!plugins.containsKey(key)) {
plugins.put(key, new ArrayList<>());
}
plugins.get(key).add(Pair.create(name, info.serviceInfo));
}
}
List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION},
PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES);
PreferenceDataStore enabled = manager.getPluginEnabler();
apps.forEach(app -> {
if (!plugins.containsKey(app.packageName)) return;
SwitchPreference pref = new PluginPreference(prefContext, app, enabled);
pref.setSummary("Plugins: " + toString(plugins.get(app.packageName)));
mPluginsCategory.addPreference(pref);
PreferenceDataStore enabler = manager.getPluginEnabler();
plugins.forEach((key, si) -> {
String packageName = key.first;
List<ComponentName> componentNames = si.stream()
.map(p -> new ComponentName(packageName, p.second.name))
.collect(Collectors.toList());
if (!componentNames.isEmpty()) {
SwitchPreference pref = new PluginPreference(
prefContext, si.get(0).second.applicationInfo, enabler, componentNames);
pref.setSummary("Plugins: "
+ si.stream().map(p -> p.first).collect(Collectors.joining(", ")));
mPluginsCategory.addPreference(pref);
}
});
}
private String toString(ArraySet<String> plugins) {
StringBuilder b = new StringBuilder();
for (String string : plugins) {
if (b.length() != 0) {
b.append(", ");
}
b.append(string);
}
return b.toString();
}
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "");
String str = action.replace("com.android.systemui.action.PLUGIN_", "")
.replace("com.android.launcher3.action.PLUGIN_", "");
StringBuilder b = new StringBuilder();
for (String s : str.split("_")) {
if (b.length() != 0) {
@@ -205,18 +219,20 @@ public class DeveloperOptionsFragment extends PreferenceFragment {
private static class PluginPreference extends SwitchPreference {
private final boolean mHasSettings;
private final PackageInfo mInfo;
private final PreferenceDataStore mPluginEnabler;
private final String mPackageName;
private final List<ComponentName> mComponentNames;
public PluginPreference(Context prefContext, PackageInfo info,
PreferenceDataStore pluginEnabler) {
PluginPreference(Context prefContext, ApplicationInfo info,
PreferenceDataStore pluginEnabler, List<ComponentName> componentNames) {
super(prefContext);
PackageManager pm = prefContext.getPackageManager();
mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
.setPackage(info.packageName), 0) != null;
mInfo = info;
mPackageName = info.packageName;
mComponentNames = componentNames;
mPluginEnabler = pluginEnabler;
setTitle(info.applicationInfo.loadLabel(pm));
setTitle(info.loadLabel(pm));
setChecked(isPluginEnabled());
setWidgetLayoutResource(R.layout.switch_preference_with_settings);
}
@@ -227,9 +243,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment {
}
private boolean isPluginEnabled() {
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
for (ComponentName componentName : mComponentNames) {
if (!isEnabled(componentName)) {
return false;
}
@@ -240,17 +254,14 @@ public class DeveloperOptionsFragment extends PreferenceFragment {
@Override
protected boolean persistBoolean(boolean isEnabled) {
boolean shouldSendBroadcast = false;
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
for (ComponentName componentName : mComponentNames) {
if (isEnabled(componentName) != isEnabled) {
mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled);
shouldSendBroadcast = true;
}
}
if (shouldSendBroadcast) {
final String pkg = mInfo.packageName;
final String pkg = mPackageName;
final Intent intent = new Intent(PLUGIN_CHANGED,
pkg != null ? Uri.fromParts("package", pkg, null) : null);
getContext().sendBroadcast(intent);
@@ -268,8 +279,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment {
: View.GONE);
holder.findViewById(R.id.settings).setOnClickListener(v -> {
ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
new Intent(ACTION_PLUGIN_SETTINGS).setPackage(
mInfo.packageName), 0);
new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName), 0);
if (result != null) {
v.getContext().startActivity(new Intent().setComponent(
new ComponentName(result.activityInfo.packageName,
@@ -278,7 +288,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment {
});
holder.itemView.setOnLongClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", mInfo.packageName, null));
intent.setData(Uri.fromParts("package", mPackageName, null));
getContext().startActivity(intent);
return true;
});
@@ -413,10 +413,7 @@ public abstract class AbstractStateChangeTouchController
} else {
// Let the state manager know that the animation didn't go to the target state,
// but don't cancel ourselves (we already clean up when the animation completes).
Runnable onCancel = mCurrentAnimation.getOnCancelRunnable();
mCurrentAnimation.setOnCancelRunnable(null);
mCurrentAnimation.dispatchOnCancel();
mCurrentAnimation.setOnCancelRunnable(onCancel);
mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable();
endProgress = 0;
if (progress <= 0) {
@@ -0,0 +1,89 @@
/*
* 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.util;
import android.content.Context;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.FractionRes;
import androidx.annotation.IntegerRes;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.ResourceProvider;
/**
* Utility class to support customizing resource values using plugins
*
* To load resources, call
* DynamicResource.provider(context).getInt(resId) or any other supported methods
*
* To allow customization for a particular resource, add them to dynamic_resources.xml
*/
public class DynamicResource implements ResourceProvider, PluginListener<ResourceProvider> {
private static final MainThreadInitializedObject<DynamicResource> INSTANCE =
new MainThreadInitializedObject<>(DynamicResource::new);
private final Context mContext;
private ResourceProvider mPlugin;
private DynamicResource(Context context) {
mContext = context;
PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this,
ResourceProvider.class, false /* allowedMultiple */);
}
@Override
public int getInt(@IntegerRes int resId) {
return mContext.getResources().getInteger(resId);
}
@Override
public float getFraction(@FractionRes int resId) {
return mContext.getResources().getFraction(resId, 1, 1);
}
@Override
public float getDimension(@DimenRes int resId) {
return mContext.getResources().getDimension(resId);
}
@Override
public int getColor(@ColorRes int resId) {
return mContext.getResources().getColor(resId, null);
}
@Override
public void onPluginConnected(ResourceProvider plugin, Context context) {
mPlugin = plugin;
}
@Override
public void onPluginDisconnected(ResourceProvider plugin) {
mPlugin = null;
}
/**
* Returns the currently active or default provider
*/
public static ResourceProvider provider(Context context) {
DynamicResource dr = DynamicResource.INSTANCE.get(context);
ResourceProvider plugin = dr.mPlugin;
return plugin == null ? dr : plugin;
}
}
@@ -560,7 +560,7 @@ public class FloatingIconView extends View implements
* Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a
* callback to set the icon once the icon result is loaded.
*/
private void checkIconResult(View originalView, boolean isOpening) {
private void checkIconResult(View originalView) {
CancellationSignal cancellationSignal = new CancellationSignal();
if (mIconLoadResult == null) {
@@ -572,9 +572,7 @@ public class FloatingIconView extends View implements
if (mIconLoadResult.isIconLoaded) {
setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
mIconLoadResult.iconOffset);
if (isOpening) {
hideOriginalView(originalView);
}
hideOriginalView(originalView);
} else {
mIconLoadResult.onIconLoaded = () -> {
if (cancellationSignal.isCanceled()) {
@@ -585,10 +583,7 @@ public class FloatingIconView extends View implements
mIconLoadResult.iconOffset);
setVisibility(VISIBLE);
if (isOpening) {
// Delay swapping views until the icon is loaded to prevent a flash.
hideOriginalView(originalView);
}
hideOriginalView(originalView);
};
mLoadIconSignal = cancellationSignal;
}
@@ -596,9 +591,9 @@ public class FloatingIconView extends View implements
}
private void hideOriginalView(View originalView) {
if (originalView instanceof BubbleTextView) {
((BubbleTextView) originalView).setIconVisible(false);
((BubbleTextView) originalView).setForceHideDot(true);
if (originalView instanceof IconLabelDotView) {
((IconLabelDotView) originalView).setIconVisible(false);
((IconLabelDotView) originalView).setForceHideDot(true);
} else {
originalView.setVisibility(INVISIBLE);
}
@@ -674,6 +669,9 @@ public class FloatingIconView extends View implements
}
public void fastFinish() {
if (mLoadIconSignal != null) {
mLoadIconSignal.cancel();
}
if (mEndRunnable != null) {
mEndRunnable.run();
mEndRunnable = null;
@@ -689,6 +687,10 @@ public class FloatingIconView extends View implements
if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) {
setVisibility(View.VISIBLE);
}
if (!mIsOpening) {
// When closing an app, we want the item on the workspace to be invisible immediately
hideOriginalView(mOriginalIcon);
}
}
@Override
@@ -798,7 +800,7 @@ public class FloatingIconView extends View implements
// Must be called after the fastFinish listener and end runnable is created so that
// the icon is not left in a hidden state.
if (shouldLoadIcon) {
view.checkIconResult(originalView, isOpening);
view.checkIconResult(originalView);
}
return view;
@@ -842,6 +844,7 @@ public class FloatingIconView extends View implements
@Override
public void onAnimationStart(Animator animation) {
btv.setIconVisible(true);
btv.setForceHideDot(true);
}
});
fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255));
@@ -77,7 +77,7 @@ import java.util.List;
/**
* Simple scrim which draws a flat color
*/
public class ScrimView extends View implements Insettable, OnChangeListener,
public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener,
AccessibilityStateChangeListener, StateListener {
public static final Property<ScrimView, Integer> DRAG_HANDLE_ALPHA =
@@ -101,7 +101,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener,
private final Rect mTempRect = new Rect();
private final int[] mTempPos = new int[2];
protected final Launcher mLauncher;
protected final T mLauncher;
private final WallpaperColorInfo mWallpaperColorInfo;
private final AccessibilityManager mAM;
protected final int mEndScrim;
@@ -130,7 +130,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener,
public ScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
mLauncher = Launcher.getLauncher(context);
mLauncher = Launcher.cast(Launcher.getLauncher(context));
mWallpaperColorInfo = WallpaperColorInfo.getInstance(context);
mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
@@ -0,0 +1,47 @@
/*
* 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.systemui.plugins;
import com.android.systemui.plugins.annotations.ProvidesInterface;
/**
* Plugin to support customizing resource
*/
@ProvidesInterface(action = ResourceProvider.ACTION, version = ResourceProvider.VERSION)
public interface ResourceProvider extends Plugin {
String ACTION = "com.android.launcher3.action.PLUGIN_DYNAMIC_RESOURCE";
int VERSION = 1;
/**
* @see android.content.res.Resources#getInteger(int)
*/
int getInt(int resId);
/**
* @see android.content.res.Resources#getFraction(int, int, int)
*/
float getFraction(int resId);
/**
* @see android.content.res.Resources#getDimension(int)
*/
float getDimension(int resId);
/**
* @see android.content.res.Resources#getColor(int)
*/
int getColor(int resId);
}
+2 -1
View File
@@ -50,7 +50,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
androidx.test.runner \
androidx.test.rules \
androidx.test.uiautomator_uiautomator \
mockito-target-minus-junit4
mockito-target-minus-junit4 \
launcher-log-protos-lite
ifneq (,$(wildcard frameworks/base))
LOCAL_PRIVATE_PLATFORM_APIS := true
@@ -122,10 +122,6 @@ public abstract class AbstractLauncherUiTest {
protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@Rule
public ShellCommandRule mDefaultLauncherRule =
TestHelpers.isInLauncherProcess() ? ShellCommandRule.setDefaultLauncher() : null;
@Rule
public ShellCommandRule mDisableHeadsUpNotification =
ShellCommandRule.disableHeadsUpNotification();
@@ -154,9 +150,13 @@ public abstract class AbstractLauncherUiTest {
}
protected TestRule getRulesInsideActivityMonitor() {
return RuleChain.
outerRule(new PortraitLandscapeRunner(this)).
around(new FailureWatcher(mDevice));
final RuleChain inner = RuleChain.outerRule(new PortraitLandscapeRunner(this))
.around(new FailureWatcher(mDevice));
return TestHelpers.isInLauncherProcess()
? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher())
.around(inner) :
inner;
}
@Rule
@@ -560,10 +560,12 @@ public final class LauncherInstrumentation {
final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName()))
? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL;
swipeToState(
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState);
try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
swipeToState(
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState);
}
}
} else {
log(action = "clicking home button");
@@ -873,12 +875,17 @@ public final class LauncherInstrumentation {
final Point start = new Point(startX, startY);
final Point end = new Point(endX, endY);
sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, start);
final long endTime = movePointer(start, end, steps, downTime, slowDown);
sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end);
}
long movePointer(Point start, Point end, int steps, long downTime, boolean slowDown) {
long endTime = movePointer(downTime, downTime, steps * GESTURE_STEP_MS, start, end);
if (slowDown) {
endTime = movePointer(downTime, endTime + GESTURE_STEP_MS, 5 * GESTURE_STEP_MS, end,
end);
}
sendPointer(downTime, endTime, MotionEvent.ACTION_UP, end);
return endTime;
}
void waitForIdle() {
@@ -39,7 +39,6 @@ import com.android.launcher3.testing.TestProtocol;
* Operations on the workspace screen.
*/
public final class Workspace extends Home {
private static final int DRAG_DURATION = 500;
private static final int FLING_STEPS = 10;
private final UiObject2 mHotseat;
@@ -170,8 +169,7 @@ public final class Workspace extends Home {
LauncherInstrumentation.log("dragIconToWorkspace: sent down");
launcher.waitForLauncherObject(longPressIndicator);
LauncherInstrumentation.log("dragIconToWorkspace: indicator");
launcher.movePointer(
downTime, SystemClock.uptimeMillis(), DRAG_DURATION, launchableCenter, dest);
launcher.movePointer(launchableCenter, dest, 10, downTime, true);
LauncherInstrumentation.log("dragIconToWorkspace: moved pointer");
launcher.sendPointer(
downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);