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:
+16
@@ -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" />
|
||||
+110
-60
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+26
-1
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+189
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
+2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-17
@@ -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) {
|
||||
|
||||
+470
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-15
@@ -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;
|
||||
}
|
||||
|
||||
+26
-38
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -114,4 +114,7 @@
|
||||
|
||||
<!-- Recents -->
|
||||
<item type="id" name="overview_panel"/>
|
||||
|
||||
<!-- Configuration resources -->
|
||||
<array name="dynamic_resources"> </array>
|
||||
</resources>
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user