Snap for 7678535 from 1021d42bb9 to tm-release

Change-Id: I77d93f8ef6b20bc80f2c2e76edfab2a11fac8ea7
This commit is contained in:
Android Build Coastguard Worker
2021-08-27 03:08:10 +00:00
43 changed files with 839 additions and 158 deletions
+7
View File
@@ -19,6 +19,7 @@
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3">
<!--
@@ -176,5 +177,11 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
</manifest>
@@ -46,6 +46,23 @@
android:layout_height="1dp"
android:layout_weight="1" />
<Button
android:id="@+id/action_split"
style="@style/OverviewActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_split_screen"
android:text="@string/action_split"
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
<Space
android:id="@+id/action_split_space"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:visibility="gone" />
<Button
android:id="@+id/action_share"
style="@style/OverviewActionButton"
+2 -1
View File
@@ -166,5 +166,6 @@
<dimen name="taskbar_stashed_size">24dp</dimen>
<dimen name="taskbar_stashed_handle_width">220dp</dimen>
<dimen name="taskbar_stashed_handle_height">6dp</dimen>
<dimen name="taskbar_edu_bg_corner_radius">28dp</dimen>
<dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
<dimen name="taskbar_edu_wave_anim_trans_y_return_overshoot">4dp</dimen>
</resources>
+2
View File
@@ -191,6 +191,8 @@
<string name="action_share">Share</string>
<!-- Label for a button that causes a screen shot of the current app to be taken. [CHAR_LIMIT=40] -->
<string name="action_screenshot">Screenshot</string>
<!-- Label for a button that enters split screen selection mode. [CHAR_LIMIT=20] -->
<string name="action_split">Split</string>
<!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
<string name="blocked_by_policy">This action isn\'t allowed by the app or your organization</string>
@@ -200,6 +200,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
int predictionIndex = 0;
int numViewsAnimated = 0;
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
// make sure predicted icon removal and filling predictions don't step on each other
if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
@@ -233,7 +234,11 @@ public class HotseatPredictionController implements DragController.DragListener,
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
icon.applyFromWorkspaceItem(predictedItem);
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
if (animateIconChange) {
numViewsAnimated++;
}
icon.finishBinding(mPredictionLongClickListener);
} else {
newItems.add(predictedItem);
@@ -34,6 +34,7 @@ import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.OnboardingPrefs;
@@ -45,6 +46,9 @@ import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* A data source which integrates with a Launcher instance
*/
@@ -268,6 +272,11 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
}
@Override
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
}
/**
* Starts the taskbar education flow, if the user hasn't seen it yet.
*/
@@ -260,6 +260,13 @@ public class NavbarButtonsViewController {
return (mState & FLAG_IME_VISIBLE) != 0;
}
/**
* Returns true if the recents (overview) button is disabled
*/
public boolean isRecentsDisabled() {
return (mState & FLAG_DISABLE_RECENTS) != 0;
}
/**
* Adds the bounds corresponding to all visible buttons to provided region
*/
@@ -236,6 +236,8 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
systemUiStateFlags, forceUpdate);
mControllers.taskbarViewController.setImeIsVisible(
mControllers.navbarButtonsViewController.isImeVisible());
mControllers.taskbarViewController.setRecentsButtonDisabled(
mControllers.navbarButtonsViewController.isRecentsDisabled());
mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags);
}
@@ -78,6 +78,7 @@ public class TaskbarControllers {
taskbarKeyguardController.init(navbarButtonsViewController);
stashedHandleViewController.init(this);
taskbarStashController.init(this);
taskbarEduController.init(this);
}
/**
@@ -15,16 +15,72 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
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.DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/** Handles the Taskbar Education flow. */
public class TaskbarEduController {
private static final long WAVE_ANIM_DELAY = 250;
private static final long WAVE_ANIM_STAGGER = 50;
private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
// The fraction of each icon's animation at which we reach the top point of the wave.
private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
// The fraction of each icon's animation at which we reach the bottom, before overshooting.
private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
// How many icons to cycle through in the slot machine (+ the original icon at each end).
private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;
private final TaskbarActivityContext mActivity;
private final float mWaveAnimTranslationY;
private final float mWaveAnimTranslationYReturnOvershoot;
// Initialized in init.
TaskbarControllers mControllers;
private TaskbarEduView mTaskbarEduView;
private Animator mAnim;
public TaskbarEduController(TaskbarActivityContext activity) {
mActivity = activity;
final Resources resources = activity.getResources();
mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
}
public void init(TaskbarControllers controllers) {
mControllers = controllers;
}
void showEdu() {
@@ -35,6 +91,7 @@ public class TaskbarEduController {
mTaskbarEduView.init(new TaskbarEduCallbacks());
mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
mTaskbarEduView.show();
startAnim(createWaveAnim());
});
}
@@ -44,6 +101,90 @@ public class TaskbarEduController {
}
}
/**
* Starts the given animation, ending the previous animation first if it's still playing.
*/
private void startAnim(Animator anim) {
if (mAnim != null) {
mAnim.end();
}
mAnim = anim;
mAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnim = null;
}
});
mAnim.start();
}
/**
* Creates a staggered "wave" animation where each icon translates and scales up in succession.
*/
private Animator createWaveAnim() {
AnimatorSet waveAnim = new AnimatorSet();
View[] icons = mControllers.taskbarViewController.getIconViews();
for (int i = 0; i < icons.length; i++) {
View icon = icons[i];
AnimatorSet iconAnim = new AnimatorSet();
Keyframe[] scaleKeyframes = new Keyframe[] {
Keyframe.ofFloat(0, 1f),
Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
Keyframe.ofFloat(1f, 1f)
};
scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
Keyframe[] translationYKeyframes = new Keyframe[] {
Keyframe.ofFloat(0, 0f),
Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
// Half of the remaining fraction overshoots, then the other half returns to 0.
Keyframe.ofFloat(
WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
mWaveAnimTranslationYReturnOvershoot),
Keyframe.ofFloat(1f, 0f)
};
translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);
iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
.setDuration(WAVE_ANIM_EACH_ICON_DURATION));
iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
.setDuration(WAVE_ANIM_EACH_ICON_DURATION));
if (icon instanceof PredictedAppIcon) {
// Play slot machine animation through random icons from AllAppsList.
PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
ItemInfo itemInfo = (ItemInfo) icon.getTag();
List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
.filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
.map(appInfo -> appInfo.bitmap)
.filter(bitmap -> !bitmap.isNullOrLowRes())
.collect(Collectors.toList());
// Pick n icons at random.
Collections.shuffle(iconsToAnimate);
if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
}
Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
if (slotMachineAnim != null) {
iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
}
}
iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
waveAnim.play(iconAnim);
}
waveAnim.setStartDelay(WAVE_ANIM_DELAY);
return waveAnim;
}
/**
* Callbacks for {@link TaskbarEduView} to interact with its controller.
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import android.animation.PropertyValuesHolder;
import android.content.Context;
@@ -33,6 +33,7 @@ import com.android.launcher3.views.AbstractSlideInView;
public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
implements Insettable {
private static final int DEFAULT_OPEN_DURATION = 500;
private static final int DEFAULT_CLOSE_DURATION = 200;
private final Rect mInsets = new Rect();
@@ -129,8 +130,8 @@ public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
mIsOpen = true;
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
mOpenCloseAnimator.start();
mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
}
void snapToPage(int page) {
@@ -17,6 +17,10 @@ package com.android.launcher3.taskbar;
import android.graphics.Rect;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import java.util.stream.Stream;
/**
* Base class for providing different taskbar UI
*/
@@ -35,4 +39,8 @@ public class TaskbarUIController {
protected void updateContentInsets(Rect outContentInsets) { }
protected void onStashedInAppChanged() { }
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
return Stream.empty();
}
}
@@ -90,6 +90,9 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// We layout the icons to be of mIconTouchSize in width and height
mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
mItemPadding = (mIconTouchSize - actualIconSize) / 2;
// Needed to draw folder leave-behind when opening one.
setWillNotDraw(false);
}
protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
@@ -115,6 +118,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
*/
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
int nextViewIndex = 0;
int numViewsAnimated = 0;
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
@@ -170,8 +174,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
if (hotseatView instanceof BubbleTextView
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
((BubbleTextView) hotseatView).applyFromWorkspaceItem(
(WorkspaceItemInfo) hotseatItemInfo);
BubbleTextView btv = (BubbleTextView) hotseatView;
WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo;
boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
if (animate) {
numViewsAnimated++;
}
}
setClickAndLongClickListenersForIcon(hotseatView);
nextViewIndex++;
@@ -256,6 +266,18 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
return mIconLayoutBounds;
}
/**
* Returns the app icons currently shown in the taskbar.
*/
public View[] getIconViews() {
final int count = getChildCount();
View[] icons = new View[count];
for (int i = 0; i < count; i++) {
icons[i] = getChildAt(i);
}
return icons;
}
// FolderIconParent implemented methods.
@Override
@@ -44,6 +44,8 @@ public class TaskbarViewController {
public static final int ALPHA_INDEX_IME = 1;
public static final int ALPHA_INDEX_KEYGUARD = 2;
public static final int ALPHA_INDEX_STASH = 3;
public static final int ALPHA_INDEX_RECENTS_DISABLED = 4;
private static final int NUM_ALPHA_CHANNELS = 5;
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
@@ -67,7 +69,7 @@ public class TaskbarViewController {
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTaskbarView = taskbarView;
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4);
mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
mTaskbarIconAlpha.setUpdateVisibility(true);
mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
}
@@ -100,6 +102,14 @@ public class TaskbarViewController {
mTaskbarView.setTouchesEnabled(!isImeVisible);
}
/**
* Should be called when the recents button is disabled, so we can hide taskbar icons as well.
*/
public void setRecentsButtonDisabled(boolean isDisabled) {
// TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha.
mTaskbarIconAlpha.getProperty(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1);
}
/**
* Sets OnClickListener and OnLongClickListener for the given view.
*/
@@ -111,6 +121,10 @@ public class TaskbarViewController {
return mTaskbarView.getIconLayoutBounds();
}
public View[] getIconViews() {
return mTaskbarView.getIconViews();
}
public AnimatedFloat getTaskbarIconScaleForStash() {
return mTaskbarIconScaleForStash;
}
@@ -15,6 +15,16 @@
*/
package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -23,8 +33,10 @@ import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -35,6 +47,8 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
@@ -45,6 +59,10 @@ import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A BubbleTextView with a ring around it's drawable
*/
@@ -53,6 +71,9 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.095f;
private static final long ICON_CHANGE_ANIM_DURATION = 360;
private static final long ICON_CHANGE_ANIM_STAGGER = 50;
boolean mIsDrawingDot = false;
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -67,6 +88,25 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private int mPlateColor;
boolean mDrawForDrag = false;
// Used for the "slot-machine" education animation.
private List<Drawable> mSlotMachineIcons;
private Animator mSlotMachineAnim;
private float mSlotMachineIconTranslationY;
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
@Override
public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
predictedAppIcon.mSlotMachineIconTranslationY = transY;
predictedAppIcon.invalidate();
}
@Override
public Float get(PredictedAppIcon predictedAppIcon) {
return predictedAppIcon.mSlotMachineIconTranslationY;
}
};
public PredictedAppIcon(Context context) {
this(context, null, 0);
}
@@ -88,15 +128,38 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
if (!mIsPinned) {
drawEffect(canvas);
if (isSlotMachineAnimRunning) {
// Clip to to outside of the ring during the slot machine animation.
canvas.clipPath(mRingPath);
}
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);
if (isSlotMachineAnimRunning) {
drawSlotMachineIcons(canvas);
} else {
super.onDraw(canvas);
}
canvas.restoreToCount(count);
}
private void drawSlotMachineIcons(Canvas canvas) {
canvas.translate((getWidth() - getIconSize()) / 2f,
(getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
for (Drawable icon : mSlotMachineIcons) {
icon.setBounds(0, 0, getIconSize(), getIconSize());
icon.draw(canvas);
canvas.translate(0, getSlotMachineIconPlusSpacingSize());
}
}
private float getSlotMachineIconPlusSpacingSize() {
return getIconSize() + getOutlineOffsetY();
}
@Override
protected void drawDotIfNecessary(Canvas canvas) {
mIsDrawingDot = true;
@@ -109,9 +172,17 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
}
@Override
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
super.applyFromWorkspaceItem(info);
mPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
// Create the slot machine animation first, since it uses the current icon to start.
Animator slotMachineAnim = animate
? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
: null;
super.applyFromWorkspaceItem(info, animate, staggerIndex);
int oldPlateColor = mPlateColor;
int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
if (!animate) {
mPlateColor = newPlateColor;
}
if (mIsPinned) {
setContentDescription(info.contentDescription);
} else {
@@ -119,6 +190,76 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
getContext().getString(R.string.hotseat_prediction_content_description,
info.contentDescription));
}
if (animate) {
ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
oldPlateColor, newPlateColor);
plateColorAnim.addUpdateListener(valueAnimator -> {
mPlateColor = (int) valueAnimator.getAnimatedValue();
invalidate();
});
AnimatorSet changeIconAnim = new AnimatorSet();
if (slotMachineAnim != null) {
changeIconAnim.play(slotMachineAnim);
}
changeIconAnim.play(plateColorAnim);
changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
}
}
/**
* Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
* and ending with the original icon.
*/
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
return createSlotMachineAnim(iconsToAnimate, true);
}
/**
* Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
* with the original icon, then cycling through the given icons, optionally ending back with
* the original icon.
* @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
* than the last item in iconsToAnimate.
*/
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
boolean endWithOriginalIcon) {
if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
return null;
}
if (mSlotMachineAnim != null) {
mSlotMachineAnim.end();
}
// Bookend the other animating icons with the original icon on both ends.
mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
mSlotMachineIcons.add(getIcon());
iconsToAnimate.stream()
.map(iconInfo -> iconInfo.newThemedIcon(mContext))
.forEach(mSlotMachineIcons::add);
if (endWithOriginalIcon) {
mSlotMachineIcons.add(getIcon());
}
float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
Keyframe[] keyframes = new Keyframe[] {
Keyframe.ofFloat(0f, 0f),
Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
};
keyframes[1].setInterpolator(ACCEL_DEACCEL);
keyframes[2].setInterpolator(ACCEL_DEACCEL);
mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
mSlotMachineIcons = null;
mSlotMachineAnim = null;
mSlotMachineIconTranslationY = 0;
invalidate();
}));
return mSlotMachineAnim;
}
/**
@@ -43,8 +43,12 @@ public class SplitScreenSelectState extends OverviewState {
@Override
public float getSplitSelectTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
int splitPosition = recentsView.getSplitPlaceholder()
.getActiveSplitPositionOption().mStagePosition;
//<<<<<<< HEAD
// int splitPosition = recentsView.getSplitPlaceholder()
// .getActiveSplitPositionOption().stagePosition;
//=======
int splitPosition = recentsView.getSplitPlaceholder().getActiveSplitStagePosition();
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) {
return 0f;
}
@@ -1194,9 +1194,6 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
Log.d(TestProtocol.L3_SWIPE_TO_HOME, "7");
}
maybeUpdateRecentsAttachedState();
// If we are transitioning to launcher, then listen for the activity to be restarted while
@@ -1219,6 +1216,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
}
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
Log.d(TestProtocol.L3_SWIPE_TO_HOME, "7, end target=" + mGestureState.getEndTarget());
}
if (mGestureState.getEndTarget() == HOME) {
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
@@ -1233,6 +1233,9 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
&& runningTaskTarget.allowEnterPip
&& runningTaskTarget.taskInfo.pictureInPictureParams != null
&& runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
Log.d(TestProtocol.L3_SWIPE_TO_HOME, "8, class=" + getClass().getSimpleName());
}
HomeAnimationFactory homeAnimFactory =
createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
runningTaskTarget);
@@ -53,7 +53,6 @@ import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -202,33 +201,6 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
recentsView.switchToScreenshot(thumbnailData, runnable);
}
/**
* Sets the task size in {@param outRect} taking split screened windows into account.
* We assume combined height of both tasks will be same as one normal task, then we'll modify
* the task height/width based on the ratio of task screen space bounds from
* {@param splitInfo}
*
* @param desiredStageBounds whether task size for top/left or bottom/right needs to be computed
*/
public final void calculateStagedSplitTaskSize(Context context, DeviceProfile dp, Rect outRect,
SplitConfigurationOptions.StagedSplitBounds splitInfo,
@SplitConfigurationOptions.StagePosition int desiredStageBounds) {
calculateTaskSize(context, dp, outRect);
// TODO(b/181705607) Change for landscape vs portrait
float totalHeight = splitInfo.mLeftTopBounds.height()
+ splitInfo.mRightBottomBounds.height()
+ splitInfo.mDividerBounds.height() / 2f;
float topTaskPercent = splitInfo.mLeftTopBounds.height() / totalHeight;
if (desiredStageBounds == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
float diff = outRect.height() * (1f - topTaskPercent);
outRect.bottom -= diff;
} else {
float diff = outRect.height() * topTaskPercent;
outRect.top += diff;
}
}
/**
* Calculates the taskView size for the provided device configuration.
*/
@@ -218,6 +218,11 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
}
}
private void enterSplitSelect() {
RecentsView overviewPanel = mThumbnailView.getTaskView().getRecentsView();
overviewPanel.initiateSplitSelect(mThumbnailView.getTaskView());
}
/**
* Called when the overlay is no longer used.
*/
@@ -323,6 +328,10 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
public void onScreenshot() {
endLiveTileMode(() -> saveScreenshot(mTask));
}
public void onSplit() {
endLiveTileMode(TaskOverlay.this::enterSplitSelect);
}
}
}
@@ -336,5 +345,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
/** User has indicated they want to screenshot the current task. */
void onScreenshot();
/** User wants to start split screen with current app. */
void onSplit();
}
}
@@ -100,7 +100,7 @@ public interface TaskShortcutFactory {
private SplitPositionOption mSplitPositionOption;
public SplitSelectSystemShortcut(BaseDraggingActivity target, TaskView taskView,
SplitPositionOption option) {
super(option.mIconResId, option.mTextResId, target, taskView.getItemInfo());
super(option.iconResId, option.textResId, target, taskView.getItemInfo());
mTaskView = taskView;
mSplitPositionOption = option;
setEnabled(taskView.getRecentsView().getTaskViewCount() > 1);
@@ -32,12 +32,11 @@ import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskViewUtils;
@@ -57,7 +56,7 @@ public class SplitSelectStateController {
private final SystemUiProxy mSystemUiProxy;
private TaskView mInitialTaskView;
private TaskView mSecondTaskView;
private SplitPositionOption mInitialPosition;
private @StagePosition int mStagePosition;
private Rect mInitialBounds;
private final Handler mHandler;
@@ -69,10 +68,10 @@ public class SplitSelectStateController {
/**
* To be called after first task selected
*/
public void setInitialTaskSelect(TaskView taskView, SplitPositionOption positionOption,
public void setInitialTaskSelect(TaskView taskView, @StagePosition int stagePosition,
Rect initialBounds) {
mInitialTaskView = taskView;
mInitialPosition = positionOption;
mStagePosition = stagePosition;
mInitialBounds = initialBounds;
}
@@ -83,7 +82,11 @@ public class SplitSelectStateController {
mSecondTaskView = taskView;
// Assume initial task is for top/left part of screen
final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
//<<<<<<< HEAD
// final int[] taskIds = mInitialPosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT
//=======
final int[] taskIds = mStagePosition == STAGE_POSITION_TOP_OR_LEFT
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
: new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
@@ -113,7 +116,11 @@ public class SplitSelectStateController {
DeviceProfile deviceProfile) {
InsettableFrameLayout.LayoutParams params =
new InsettableFrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
boolean topLeftPosition = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT;
//<<<<<<< HEAD
// boolean topLeftPosition = mInitialPosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT;
//=======
boolean topLeftPosition = mStagePosition == STAGE_POSITION_TOP_OR_LEFT;
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
if (deviceProfile.isLandscape) {
params.width = (int) resources.getDimension(R.dimen.split_placeholder_size);
params.gravity = topLeftPosition ? Gravity.START : Gravity.END;
@@ -125,9 +132,8 @@ public class SplitSelectStateController {
return params;
}
@Nullable
public SplitPositionOption getActiveSplitPositionOption() {
return mInitialPosition;
public @StagePosition int getActiveSplitStagePosition() {
return mStagePosition;
}
/**
@@ -189,7 +195,7 @@ public class SplitSelectStateController {
public void resetState() {
mInitialTaskView = null;
mSecondTaskView = null;
mInitialPosition = null;
mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
mInitialBounds = null;
}
@@ -145,8 +145,9 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
// The task rect changes according to the staged split task sizes, but recents
// fullscreen scale and pivot remains the same since the task fits into the existing
// sized task space bounds
mSizeStrategy.calculateStagedSplitTaskSize(mContext, mDp, mTaskRect, mStagedSplitBounds,
mStagePosition);
mSizeStrategy.calculateTaskSize(mContext, mDp, mTaskRect);
mOrientationState.getOrientationHandler()
.setSplitTaskSwipeRect(mDp, mTaskRect, mStagedSplitBounds, mStagePosition);
} else {
mTaskRect.set(fullTaskSize);
}
@@ -175,7 +176,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
mStagePosition = STAGE_POSITION_UNDEFINED;
return;
}
mStagePosition = mThumbnailPosition.equals(splitInfo.mLeftTopBounds) ?
mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds) ?
STAGE_POSITION_TOP_OR_LEFT :
STAGE_POSITION_BOTTOM_OR_RIGHT;
}
@@ -277,13 +278,9 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
getFullScreenScale();
mThumbnailData.rotation = mOrientationState.getDisplayRotation();
// TODO(b/195145340) handle non 50-50 split scenarios
if (mStagedSplitBounds != null) {
if (mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
// The preview set is for the bottom/right, inset by top/left task
mSplitOffset.y = mStagedSplitBounds.mLeftTopBounds.height() +
mStagedSplitBounds.mDividerBounds.height() / 2;
}
mOrientationState.getOrientationHandler().setLeashSplitOffset(mSplitOffset, mDp,
mStagedSplitBounds, mStagePosition);
}
// mIsRecentsRtl is the inverse of TaskView RTL.
@@ -331,7 +328,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
applyWindowToHomeRotation(mMatrix);
// Move lower/right split window into correct position
mMatrix.postTranslate(0, mSplitOffset.y);
mMatrix.postTranslate(mSplitOffset.x, mSplitOffset.y);
// Crop rect is the inverse of thumbnail matrix
mTempRectF.set(-insets.left, -insets.top,
@@ -1,13 +1,10 @@
package com.android.quickstep.views;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.util.CancellableTask;
@@ -48,13 +45,14 @@ public class GroupedTaskView extends TaskView {
mSnapshotView2 = findViewById(R.id.bottomright_snapshot);
}
public void bind(Task primary, Task secondary, RecentsOrientedState orientedState) {
public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig) {
super.bind(primary, orientedState);
mSecondaryTask = secondary;
mTaskIdContainer[1] = secondary.key.id;
mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2);
mSnapshotView2.bind(secondary);
adjustThumbnailBoundsForSplit();
adjustThumbnailBoundsForSplit(splitBoundsConfig, orientedState);
}
@Override
@@ -108,30 +106,15 @@ public class GroupedTaskView extends TaskView {
mSnapshotView2.setOverlayEnabled(overlayEnabled);
}
private void adjustThumbnailBoundsForSplit() {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
primaryLp.width = mSecondaryTask == null ?
MATCH_PARENT :
getWidth();
int spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx;
// TODO get divider height
int dividerBar = 20;
primaryLp.height = mSecondaryTask == null ?
MATCH_PARENT :
(getHeight() - spaceAboveSnapshot - dividerBar) / 2;
mSnapshotView.setLayoutParams(primaryLp);
if (mSecondaryTask == null) {
mSnapshotView2.setVisibility(GONE);
private void adjustThumbnailBoundsForSplit(
SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
RecentsOrientedState orientedState) {
if (splitBoundsConfig == null) {
return;
}
mSnapshotView2.setVisibility(VISIBLE);
ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
secondaryLp.width = getWidth();
secondaryLp.height = primaryLp.height;
mSnapshotView2.setLayoutParams(secondaryLp);
mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
orientedState.getOrientationHandler().setGroupedTaskViewThumbnailBounds(
mSnapshotView, mSnapshotView2, this, splitBoundsConfig,
mActivity.getDeviceProfile());
}
}
@@ -245,8 +245,8 @@ public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher, Laun
@Override
public void initiateSplitSelect(TaskView taskView,
SplitConfigurationOptions.SplitPositionOption splitPositionOption) {
super.initiateSplitSelect(taskView, splitPositionOption);
@SplitConfigurationOptions.StagePosition int stagePosition) {
super.initiateSplitSelect(taskView, stagePosition);
mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
@@ -79,6 +79,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
private static final int INDEX_SCROLL_ALPHA = 4;
private final MultiValueAlpha mMultiValueAlpha;
private View mSplitButton;
@ActionsHiddenFlags
private int mHiddenFlags;
@@ -110,6 +111,8 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
View share = findViewById(R.id.action_share);
share.setOnClickListener(this);
findViewById(R.id.action_screenshot).setOnClickListener(this);
mSplitButton = findViewById(R.id.action_split);
mSplitButton.setOnClickListener(this);
if (ENABLE_OVERVIEW_SHARE.get()) {
share.setVisibility(VISIBLE);
findViewById(R.id.oav_three_button_space).setVisibility(VISIBLE);
@@ -135,6 +138,8 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
mCallbacks.onShare();
} else if (id == R.id.action_screenshot) {
mCallbacks.onScreenshot();
} else if (id == R.id.action_split) {
mCallbacks.onSplit();
}
}
@@ -218,6 +223,16 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
mDp = dp;
updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
requestLayout();
setSplitButtonVisible(mDp.isTablet);
}
public void setSplitButtonVisible(boolean visible) {
if (mSplitButton == null) {
return;
}
mSplitButton.setVisibility(visible ? VISIBLE : GONE);
findViewById(R.id.action_split_space).setVisibility(visible ? VISIBLE : GONE);
}
/** Get the top margin associated with the action buttons in Overview. */
@@ -133,7 +133,7 @@ import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TranslateEdgeEffect;
import com.android.launcher3.util.ViewPool;
@@ -575,6 +575,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
*/
private TaskView mSplitHiddenTaskView;
private TaskView mSecondSplitHiddenTaskView;
private SplitConfigurationOptions.StagedSplitBounds mSplitBoundsConfig;
/**
* Keeps track of the index of the TaskView that split screen was initialized with so we know
@@ -1314,7 +1315,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
taskDataIndex--;
leftTop = tasks.get(taskDataIndex);
}
((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState);
((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState,
mSplitBoundsConfig);
} else {
taskView.bind(task, mOrientationState);
}
@@ -2087,8 +2089,11 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
Task.from(new TaskKey(secondaryTaskInfo), secondaryTaskInfo, false)
};
addView(taskView, mTaskViewStartIndex);
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
// load later anyways
((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
mOrientationState);
mOrientationState, mSplitBoundsConfig);
} else {
taskView = getTaskViewFromPool(false);
addView(taskView, mTaskViewStartIndex);
@@ -2567,8 +2572,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
private void createInitialSplitSelectAnimation(PendingAnimation anim) {
float placeholderHeight = getResources().getDimension(R.dimen.split_placeholder_size);
mOrientationHandler.getInitialSplitPlaceholderBounds((int) placeholderHeight,
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitPositionOption(), mTempRect);
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect);
RectF startingTaskRect = new RectF();
mSplitHiddenTaskView.setVisibility(INVISIBLE);
@@ -2577,6 +2582,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mFirstFloatingTaskView.setAlpha(1);
mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
anim.addEndListener(aBoolean -> mActionsView.setSplitButtonVisible(false));
}
/**
@@ -3009,8 +3015,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
* @return {@code true} if one of the task thumbnails would intersect/overlap with the
* {@link #mFirstFloatingTaskView}
*/
public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition
int stagePosition) {
public boolean shouldShiftThumbnailsForSplitSelect(@StagePosition int stagePosition) {
if (!mActivity.getDeviceProfile().isTablet) {
// Never enough space on phones
return true;
@@ -3542,12 +3547,17 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
}
public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
public void initiateSplitSelect(TaskView taskView) {
int defaultSplitPosition = mOrientationHandler
.getDefaultSplitPosition(mActivity.getDeviceProfile());
initiateSplitSelect(taskView, defaultSplitPosition);
}
public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition) {
mSplitHiddenTaskView = taskView;
Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
taskView.getBottom());
mSplitSelectStateController.setInitialTaskSelect(taskView,
splitPositionOption, initialBounds);
mSplitSelectStateController.setInitialTaskSelect(taskView, stagePosition, initialBounds);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
finishRecentsAnimation(true, null);
@@ -3573,7 +3583,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
mActivity.getDeviceProfile(),
mSplitSelectStateController.getActiveSplitPositionOption(), firstTaskEndingBounds,
mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
secondTaskEndingBounds);
mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
@@ -3598,7 +3608,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
public PendingAnimation cancelSplitSelect(boolean animate) {
SplitSelectStateController splitController = mSplitSelectStateController;
SplitPositionOption splitOption = splitController.getActiveSplitPositionOption();
@StagePosition int stagePosition = splitController.getActiveSplitStagePosition();
Rect initialBounds = splitController.getInitialBounds();
splitController.resetState();
int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
@@ -3624,7 +3634,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
if (child == mSplitHiddenTaskView) {
TaskView taskView = (TaskView) child;
int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitOption,
int dir = mOrientationHandler.getSplitTaskViewDismissDirection(stagePosition,
mActivity.getDeviceProfile());
FloatProperty<TaskView> dismissingTaskViewTranslate;
Rect hiddenBounds = new Rect(taskView.getLeft(), taskView.getTop(),
@@ -3696,10 +3706,10 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
return pendingAnim;
}
/** TODO(b/181707736) More gracefully handle exiting split selection state */
private void resetFromSplitSelectionState() {
mSplitHiddenTaskView.setTranslationY(0);
if (!showAsGrid()) {
// TODO(b/186800707)
int pageToSnapTo = mCurrentPage;
if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
pageToSnapTo += 1;
@@ -3723,6 +3733,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
mSecondSplitHiddenTaskView = null;
}
mActionsView.setSplitButtonVisible(true);
}
private void updateDeadZoneRects() {
@@ -4038,12 +4049,13 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mRemoteTargetHandles[1] = new RemoteTargetHandle(secondaryTvs, new TransformParams());
RemoteAnimationTargetCompat primaryTaskTarget = recentsAnimationTargets.apps[0];
RemoteAnimationTargetCompat secondaryTaskTarget = recentsAnimationTargets.apps[1];
SplitConfigurationOptions.StagedSplitBounds
info = new SplitConfigurationOptions.StagedSplitBounds(
mSplitBoundsConfig = new SplitConfigurationOptions.StagedSplitBounds(
primaryTaskTarget.screenSpaceBounds,
secondaryTaskTarget.screenSpaceBounds, dividerTarget.screenSpaceBounds);
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget, info);
mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget, info);
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
mSplitBoundsConfig);
mRemoteTargetHandles[1].mTaskViewSimulator.setPreview(secondaryTaskTarget,
mSplitBoundsConfig);
RemoteAnimationTargets rats = new RemoteAnimationTargets(
new RemoteAnimationTargetCompat[]{primaryTaskTarget},
recentsAnimationTargets.wallpapers, recentsAnimationTargets.nonApps,
@@ -4084,6 +4096,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
Runnable onFinishComplete) {
// TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
mRemoteTargetHandles = null;
if (!toRecents && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// Reset the minimized state since we force-toggled the minimized state when entering
// overview, but never actually finished the recents animation. This is a catch all for
@@ -4131,7 +4145,6 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
setCurrentTask(-1);
mRecentsAnimationController = null;
executeSideTaskLaunchCallback();
mRemoteTargetHandles = null;
}
public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
@@ -1514,7 +1514,7 @@ public class TaskView extends FrameLayout implements Reusable {
public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_TASK_MENU);
getRecentsView().initiateSplitSelect(this, splitPositionOption);
getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition);
}
/**
@@ -42,6 +42,7 @@ import com.android.quickstep.views.RecentsView;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -212,6 +213,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
}
@Test
@Ignore("b/197802324")
@PortraitLandscape
public void testAllAppsFromHome() throws Exception {
// Test opening all apps
+1 -1
View File
@@ -14,5 +14,5 @@
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral1_800" />
<item android:color="@android:color/system_neutral2_500" android:lStar="35" />
</selector>
@@ -256,9 +256,27 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
@UiThread
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
applyFromWorkspaceItem(info, /* animate = */ false, /* staggerIndex = */ 0);
}
@UiThread
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
applyFromWorkspaceItem(info, false);
}
/**
* Returns whether the newInfo differs from the current getTag().
*/
public boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
WorkspaceItemInfo oldInfo = getTag() instanceof WorkspaceItemInfo
? (WorkspaceItemInfo) getTag()
: null;
boolean changedIcons = oldInfo != null && oldInfo.getTargetComponent() != null
&& newInfo.getTargetComponent() != null
&& !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
return changedIcons && isShown();
}
@Override
public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
if (delegate instanceof LauncherAccessibilityDelegate) {
+18 -1
View File
@@ -2186,6 +2186,24 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
}
private void bindAddScreens(IntArray orderedScreenIds) {
if (mDeviceProfile.isTwoPanels) {
// Some empty pages might have been removed while the phone was in a single panel
// mode, so we want to add those empty pages back.
IntSet screenIds = IntSet.wrap(orderedScreenIds);
for (int i = 0; i < orderedScreenIds.size(); i++) {
int screenId = orderedScreenIds.get(i);
// Don't add the page pair if the page is the last one and if the pair is on the
// right, because that would cause a bug when adding new pages.
// TODO: (b/196376162) remove this when the new screen id logic is fixed for two
// panel in Workspace::commitExtraEmptyScreen
if (i == orderedScreenIds.size() - 1 && screenId % 2 == 0) {
continue;
}
screenIds.add(mWorkspace.getPagePair(screenId));
}
orderedScreenIds = screenIds.getArray();
}
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
int screenId = orderedScreenIds.get(i);
@@ -2193,7 +2211,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
// No need to bind the first screen, as its always bound.
continue;
}
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
}
+32
View File
@@ -123,6 +123,7 @@ import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverla
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -800,6 +801,21 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return mScreenOrder;
}
/**
* Returns the page that is shown together with the given page when two panel is enabled.
* @throws IllegalStateException if called while two panel home isn't enabled.
*/
public int getPagePair(int page) {
if (!isTwoPanelEnabled()) {
throw new IllegalStateException("Two panel home isn't enabled.");
}
if (page % 2 == 0) {
return page + 1;
} else {
return page - 1;
}
}
public void stripEmptyScreens() {
if (mLauncher.isWorkspaceLoading()) {
// Don't strip empty screens if the workspace is still loading.
@@ -825,6 +841,22 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
}
// When two panel home is enabled we only remove an empty page if both visible pages are
// empty.
if (isTwoPanelEnabled()) {
// We go through all the pages that were marked as removable and check their page pair
Iterator<Integer> removeScreensIterator = removeScreens.iterator();
while (removeScreensIterator.hasNext()) {
int pageToRemove = removeScreensIterator.next();
int pagePair = getPagePair(pageToRemove);
if (!removeScreens.contains(pagePair)) {
// The page pair isn't empty so we want to remove the current page from the
// removable pages' collection
removeScreensIterator.remove();
}
}
}
// We enforce at least one page to add new items to. In the case that we remove the last
// such screen, we convert the last screen to the empty screen
int minScreens = 1;
@@ -32,6 +32,8 @@ public interface WorkspaceLayoutManager {
int EXTRA_EMPTY_SCREEN_ID = -201;
// The is the first screen. It is always present, even if its empty.
int FIRST_SCREEN_ID = 0;
// This is the second page. On two panel home it is always present, even if its empty.
int SECOND_SCREEN_ID = 1;
/**
* At bind time, we use the rank (screenId) to compute x and y for hotseat items.
@@ -65,6 +65,7 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
@@ -279,7 +280,7 @@ public class LauncherPreviewRenderer extends ContextWrapper
mDp.workspacePadding.top,
mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
mDp.workspacePadding.bottom);
mWorkspaceScreens.put(PreviewSurfaceRenderer.SECOND_SCREEN_ID, rightPanel);
mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel);
}
if (Utilities.ATLEAST_S) {
@@ -66,9 +66,6 @@ public class PreviewSurfaceRenderer {
private static final int FADE_IN_ANIMATION_DURATION = 200;
// The is the second screen. It is always present in two panel, even if its empty.
static final int SECOND_SCREEN_ID = 1;
private static final String KEY_HOST_TOKEN = "host_token";
private static final String KEY_VIEW_WIDTH = "width";
private static final String KEY_VIEW_HEIGHT = "height";
@@ -172,7 +169,7 @@ public class PreviewSurfaceRenderer {
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT;
if (deviceProfile.isTwoPanels) {
query += " or " + LauncherSettings.Favorites.SCREEN + " = "
+ SECOND_SCREEN_ID;
+ Workspace.SECOND_SCREEN_ID;
}
loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
query);
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.WorkspaceLayoutManager.SECOND_SCREEN_ID;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
@@ -299,6 +300,11 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
IntSet screensToExclude = new IntSet();
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
screensToExclude.add(FIRST_SCREEN_ID);
// On split display we don't want to add the new items onto the second screen.
if (app.getInvariantDeviceProfile().isSplitDisplay) {
screensToExclude.add(SECOND_SCREEN_ID);
}
}
for (int screen = 0; screen < screenCount; screen++) {
@@ -26,6 +26,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -35,13 +36,16 @@ import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.views.BaseDragLayer;
import java.util.Collections;
@@ -162,16 +166,24 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler {
}
@Override
public int getSplitTaskViewDismissDirection(SplitPositionOption splitPosition,
public int getSplitTaskViewDismissDirection(@StagePosition int stagePosition,
DeviceProfile dp) {
// Don't use device profile here because we know we're in fake landscape, only split option
// available is top/left
if (splitPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
//<<<<<<< HEAD
// if (splitPosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
// // Top (visually left) side
// return SPLIT_TRANSLATE_PRIMARY_NEGATIVE;
// }
// throw new IllegalStateException("Invalid split stage position: " +
// splitPosition.stagePosition);
//=======
if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
// Top (visually left) side
return SPLIT_TRANSLATE_PRIMARY_NEGATIVE;
}
throw new IllegalStateException("Invalid split stage position: " +
splitPosition.mStagePosition);
throw new IllegalStateException("Invalid split stage position: " + stagePosition);
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
}
@Override
@@ -364,7 +376,7 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler {
@Override
public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
SplitPositionOption splitPositionOption, Rect out) {
@StagePosition int stagePosition, Rect out) {
// In fake land/seascape, the placeholder always needs to go to the "top" of the device,
// which is the same bounds as 0 rotation.
int width = dp.widthPx;
@@ -373,7 +385,7 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler {
@Override
public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
@StagePosition int stagePosition, Rect out1, Rect out2) {
// In fake land/seascape, the window bounds are always top and bottom half
int screenHeight = dp.heightPx;
int screenWidth = dp.widthPx;
@@ -381,6 +393,55 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler {
out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
}
@Override
//<<<<<<< HEAD
public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
float diff;
if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
diff = outRect.height() * (1f - splitInfo.leftTaskPercent);
outRect.bottom -= diff;
} else {
diff = outRect.height() * splitInfo.leftTaskPercent;
outRect.top += diff;
}
}
@Override
public void setLeashSplitOffset(Point splitOffset, DeviceProfile dp,
SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
if (desiredStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
// The preview set is for the bottom/right, inset by top/left task
splitOffset.x = splitInfo.leftTopBounds.width() + splitInfo.dividerBounds.width() / 2;
}
}
@Override
public void setGroupedTaskViewThumbnailBounds(View mSnapshotView, View mSnapshotView2,
View taskParent, SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
DeviceProfile dp) {
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
int totalThumbnailWidth = taskParent.getWidth();
int dividerBar = splitBoundsConfig.dividerBounds.width() / 2;
ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
primaryLp.width = totalThumbnailWidth;
primaryLp.height = (int) (totalThumbnailHeight * splitBoundsConfig.leftTaskPercent);
secondaryLp.width = totalThumbnailWidth;
secondaryLp.height = totalThumbnailHeight - primaryLp.height - dividerBar;
mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
}
//=======
@Override
public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
throw new IllegalStateException("Default position not available in fake landscape");
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
}
@Override
public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
DeviceProfile deviceProfile) {
@@ -19,6 +19,7 @@ package com.android.launcher3.touch;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -31,6 +32,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -78,11 +80,11 @@ public interface PagedOrientationHandler {
FloatProperty<View> getSecondaryViewTranslate();
/**
* @param splitPosition The position where the view to be split will go
* @param stagePosition The position where the view to be split will go
* @return {@link #SPLIT_TRANSLATE_*} constants to indicate which direction the
* dismissal should happen
*/
int getSplitTaskViewDismissDirection(SplitPositionOption splitPosition, DeviceProfile dp);
int getSplitTaskViewDismissDirection(@StagePosition int stagePosition, DeviceProfile dp);
int getPrimaryScroll(View view);
float getPrimaryScale(View view);
int getChildStart(View view);
@@ -120,18 +122,48 @@ public interface PagedOrientationHandler {
* @param splitholderSize height of placeholder view in portrait, width in landscape
*/
void getInitialSplitPlaceholderBounds(int splitholderSize, DeviceProfile dp,
SplitPositionOption splitPositionOption, Rect out);
@StagePosition int stagePosition, Rect out);
/**
* @param splitDividerSize height of split screen drag handle in portrait, width in landscape
* @param initialSplitOption the split position option (top/left, bottom/right) of the first
* @param stagePosition the split position option (top/left, bottom/right) of the first
* task selected for entering split
* @param out1 the bounds for where the first selected app will be
* @param out2 the bounds for where the second selected app will be, complimentary to
* {@param out1} based on {@param initialSplitOption}
*/
void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
SplitPositionOption initialSplitOption, Rect out1, Rect out2);
@StagePosition int stagePosition, Rect out1, Rect out2);
int getDefaultSplitPosition(DeviceProfile deviceProfile);
/**
* @param outRect This is expected to be the rect that has the dimensions for a non-split,
* fullscreen task in overview. This will directly be modified.
* @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
* outRect for
*/
void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
SplitConfigurationOptions.StagedSplitBounds splitInfo,
@SplitConfigurationOptions.StagePosition int desiredStagePosition);
/**
* It's important to note that {@link #setSplitTaskSwipeRect(DeviceProfile, Rect,
* SplitConfigurationOptions.StagedSplitBounds, int)} above operates on the outRect based on
* launcher's coordinate system, meaning it will treat the outRect as portrait if home rotation
* is not allowed.
*
* However, here the splitOffset is from perspective of TaskViewSimulator, which is in display
* orientation coordinates. So, for example, for the fake landscape scenario, even though
* launcher is portrait, we inset the bottom/right task by an X coordinate instead of the
* usual Y
*/
void setLeashSplitOffset(Point splitOffset, DeviceProfile dp,
SplitConfigurationOptions.StagedSplitBounds splitInfo,
@SplitConfigurationOptions.StagePosition int desiredStagePosition);
void setGroupedTaskViewThumbnailBounds(View mSnapshot1, View mSnapshot2, View taskParent,
SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp);
// Overview TaskMenuView methods
float getTaskMenuX(float x, View thumbnailView, int overScroll);
@@ -25,6 +25,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MA
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -34,13 +35,16 @@ import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
@@ -162,9 +166,13 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
}
@Override
public int getSplitTaskViewDismissDirection(SplitPositionOption splitPosition,
public int getSplitTaskViewDismissDirection(@StagePosition int stagePosition,
DeviceProfile dp) {
if (splitPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
//<<<<<<< HEAD
// if (splitPosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
//=======
if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
if (dp.isLandscape) {
// Left side
return SPLIT_TRANSLATE_PRIMARY_NEGATIVE;
@@ -172,12 +180,20 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
// Top side
return SPLIT_TRANSLATE_SECONDARY_NEGATIVE;
}
} else if (splitPosition.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
//<<<<<<< HEAD
// } else if (splitPosition.stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
// We don't have a bottom option, so should be right
// return SPLIT_TRANSLATE_PRIMARY_POSITIVE;
// }
// throw new IllegalStateException("Invalid split stage position: " +
// splitPosition.stagePosition);
//=======
} else if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
// We don't have a bottom option, so should be right
return SPLIT_TRANSLATE_PRIMARY_POSITIVE;
}
throw new IllegalStateException("Invalid split stage position: " +
splitPosition.mStagePosition);
throw new IllegalStateException("Invalid split stage position: " + stagePosition);
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
}
@Override
@@ -413,7 +429,7 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
@Override
public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
SplitPositionOption splitPositionOption, Rect out) {
@StagePosition int stagePosition, Rect out) {
int width = dp.widthPx;
out.set(0, 0, width, placeholderHeight);
if (!dp.isLandscape) {
@@ -422,7 +438,11 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
}
// Now we rotate the portrait rect depending on what side we want pinned
boolean pinToRight = splitPositionOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
//<<<<<<< HEAD
// boolean pinToRight = splitPositionOption.stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
//=======
boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
int screenHeight = dp.heightPx;
float postRotateScale = (float) screenHeight / width;
@@ -439,7 +459,7 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
@Override
public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
@StagePosition int stagePosition, Rect out1, Rect out2) {
int screenHeight = dp.heightPx;
int screenWidth = dp.widthPx;
out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
@@ -450,7 +470,11 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
}
// Now we rotate the portrait rect depending on what side we want pinned
boolean pinToRight = initialSplitOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
//<<<<<<< HEAD
// boolean pinToRight = initialSplitOption.stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
//=======
boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
float postRotateScale = (float) screenHeight / screenWidth;
mTmpMatrix.reset();
@@ -467,6 +491,92 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler {
mTmpRectF.roundOut(out2);
}
@Override
//<<<<<<< HEAD
public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
boolean isLandscape = dp.isLandscape;
float diff;
if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
if (isLandscape) {
diff = outRect.width() * (1f - splitInfo.leftTaskPercent);
outRect.right -= diff;
} else {
diff = outRect.height() * (1f - splitInfo.topTaskPercent);
outRect.bottom -= diff;
}
} else {
if (isLandscape) {
diff = outRect.width() * splitInfo.leftTaskPercent;
outRect.left += diff;
} else {
diff = outRect.height() * splitInfo.topTaskPercent;
outRect.top += diff;
}
}
}
@Override
public void setLeashSplitOffset(Point splitOffset, DeviceProfile dp,
SplitConfigurationOptions.StagedSplitBounds splitInfo, int desiredStagePosition) {
if (desiredStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
if (dp.isLandscape) {
splitOffset.x = splitInfo.leftTopBounds.width() +
splitInfo.dividerBounds.width() / 2;
} else {
splitOffset.y = splitInfo.leftTopBounds.height() +
splitInfo.dividerBounds.height() / 2;
}
}
}
@Override
public void setGroupedTaskViewThumbnailBounds(View mSnapshotView, View mSnapshotView2,
View taskParent, SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig,
DeviceProfile dp) {
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
int totalThumbnailHeight = taskParent.getHeight() - spaceAboveSnapshot;
int totalThumbnailWidth = taskParent.getWidth();
int dividerBar = (dp.isLandscape ?
splitBoundsConfig.dividerBounds.width() :
splitBoundsConfig.dividerBounds.height())
/ 2;
ViewGroup.LayoutParams primaryLp = mSnapshotView.getLayoutParams();
ViewGroup.LayoutParams secondaryLp = mSnapshotView2.getLayoutParams();
if (dp.isLandscape) {
primaryLp.height = totalThumbnailHeight;
primaryLp.width = (int) (totalThumbnailWidth * splitBoundsConfig.leftTaskPercent);
secondaryLp.height = totalThumbnailHeight;
secondaryLp.width = totalThumbnailWidth - primaryLp.width - dividerBar;
mSnapshotView2.setTranslationX(primaryLp.width + dividerBar);
mSnapshotView2.setTranslationY(spaceAboveSnapshot);
} else {
primaryLp.width = totalThumbnailWidth;
primaryLp.height = (int) (totalThumbnailHeight * splitBoundsConfig.topTaskPercent);
secondaryLp.width = totalThumbnailWidth;
secondaryLp.height = totalThumbnailHeight - primaryLp.height - dividerBar;
mSnapshotView2.setTranslationY(primaryLp.height + spaceAboveSnapshot + dividerBar);
mSnapshotView2.setTranslationX(0);
}
}
//=======
@Override
public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
if (!deviceProfile.isTablet) {
throw new IllegalStateException("Default position available only for large screens");
}
if (deviceProfile.isLandscape) {
return STAGE_POSITION_BOTTOM_OR_RIGHT;
} else {
return STAGE_POSITION_TOP_OR_LEFT;
//>>>>>>> f6769c8532 (Add Split button in OverviewActions)
}
}
@Override
public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
DeviceProfile dp) {
+1 -1
View File
@@ -296,7 +296,7 @@ public class IntArray implements Cloneable, Iterable<Integer> {
@Override
public void remove() {
throw new UnsupportedOperationException();
removeIndex(--mNextIndex);
}
}
}
@@ -69,32 +69,44 @@ public final class SplitConfigurationOptions {
///////////////////////////////////
public static class SplitPositionOption {
public final int mIconResId;
public final int mTextResId;
public final int iconResId;
public final int textResId;
@StagePosition
public final int mStagePosition;
public final int stagePosition;
@StageType
public final int mStageType;
public SplitPositionOption(int iconResId, int textResId, int stagePosition, int stageType) {
mIconResId = iconResId;
mTextResId = textResId;
mStagePosition = stagePosition;
this.iconResId = iconResId;
this.textResId = textResId;
this.stagePosition = stagePosition;
mStageType = stageType;
}
}
public static class StagedSplitBounds {
public final Rect mLeftTopBounds;
public final Rect mRightBottomBounds;
public final Rect mDividerBounds;
public final Rect leftTopBounds;
public final Rect rightBottomBounds;
public final Rect dividerBounds;
// This class is orientation-agnostic, so we compute both for later use
public final float topTaskPercent;
public final float leftTaskPercent;
public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, Rect dividerBounds) {
mLeftTopBounds = leftTopBounds;
mRightBottomBounds = rightBottomBounds;
mDividerBounds = dividerBounds;
this.leftTopBounds = leftTopBounds;
this.rightBottomBounds = rightBottomBounds;
this.dividerBounds = dividerBounds;
float totalHeight = this.leftTopBounds.height()
+ this.rightBottomBounds.height()
+ this.dividerBounds.height();
float totalWidth = this.leftTopBounds.width()
+ this.rightBottomBounds.width()
+ this.dividerBounds.width();
leftTaskPercent = this.leftTopBounds.width() / totalWidth;
topTaskPercent = this.leftTopBounds.height() / totalHeight;
}
}
+7
View File
@@ -16,6 +16,7 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3.tests">
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
@@ -235,5 +236,11 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>
</manifest>
@@ -250,6 +250,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
}
@Test
@Ignore("b/197802324")
@PortraitLandscape
public void testWidgets() throws Exception {
// Test opening widgets.