Snap for 12458821 from 644aad280b to 25Q1-release
Change-Id: Ie758ce691e187cd7b1b1f60f3e2363d5428a0853
This commit is contained in:
@@ -36,7 +36,7 @@ flag {
|
||||
name: "enable_large_desktop_windowing_tile"
|
||||
namespace: "launcher_overview"
|
||||
description: "Makes the desktop tiles larger and moves them to the front of the list in Overview."
|
||||
bug: "353947137"
|
||||
bug: "357860832"
|
||||
}
|
||||
|
||||
flag {
|
||||
|
||||
@@ -37,12 +37,13 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.AppShareabilityChecker;
|
||||
import com.android.launcher3.model.AppShareabilityJobService;
|
||||
import com.android.launcher3.model.AppShareabilityManager;
|
||||
import com.android.launcher3.model.AppShareabilityManager.ShareabilityStatus;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
@@ -114,17 +115,17 @@ public final class AppSharing {
|
||||
* The Share App system shortcut, used to initiate p2p sharing of a given app
|
||||
*/
|
||||
public final class Share extends SystemShortcut<Launcher> {
|
||||
private final PopupDataProvider mPopupDataProvider;
|
||||
private final boolean mSharingEnabledForUser;
|
||||
|
||||
private final Set<View> mBoundViews = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private boolean mIsEnabled = true;
|
||||
private StatsLogManager mStatsLogManager;
|
||||
|
||||
public Share(Launcher target, ItemInfo itemInfo, View originalView) {
|
||||
super(R.drawable.ic_share, R.string.app_share_drop_target_label, target, itemInfo,
|
||||
originalView);
|
||||
mPopupDataProvider = target.getPopupDataProvider();
|
||||
|
||||
mStatsLogManager = ActivityContext.lookupContext(originalView.getContext())
|
||||
.getStatsLogManager();
|
||||
mSharingEnabledForUser = bluetoothSharingEnabled(target);
|
||||
if (!mSharingEnabledForUser) {
|
||||
setEnabled(false);
|
||||
@@ -150,8 +151,7 @@ public final class AppSharing {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ActivityContext.lookupContext(view.getContext())
|
||||
.getStatsLogManager().logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
|
||||
mStatsLogManager.logger().log(LAUNCHER_SYSTEM_SHORTCUT_APP_SHARE_TAP);
|
||||
if (!mIsEnabled) {
|
||||
showCannotShareToast(view.getContext());
|
||||
return;
|
||||
@@ -240,6 +240,11 @@ public final class AppSharing {
|
||||
public boolean isEnabled() {
|
||||
return mIsEnabled;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setStatsLogManager(StatsLogManager statsLogManager) {
|
||||
mStatsLogManager = statsLogManager;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -406,6 +406,12 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
}
|
||||
};
|
||||
mSeparateWindowParent.recreateControllers();
|
||||
if (BubbleBarController.isBubbleBarEnabled()) {
|
||||
mNavButtonsView.addOnLayoutChangeListener(
|
||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
|
||||
onLayoutsUpdated()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
|
||||
@@ -1180,15 +1186,20 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
/** Adjusts navigation buttons layout accordingly to the bubble bar position. */
|
||||
@Override
|
||||
public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
|
||||
cancelExistingNavBarAnimation();
|
||||
mBubbleBarTargetLocation = location;
|
||||
boolean locationUpdated = location != mBubbleBarTargetLocation;
|
||||
if (locationUpdated) {
|
||||
cancelExistingNavBarAnimation();
|
||||
} else {
|
||||
endExistingAnimation();
|
||||
}
|
||||
mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
|
||||
mNavButtonContainer.setAlpha(1);
|
||||
mBubbleBarTargetLocation = location;
|
||||
}
|
||||
|
||||
/** Animates navigation buttons accordingly to the bubble bar position. */
|
||||
@Override
|
||||
public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
|
||||
if (location == mBubbleBarTargetLocation) return;
|
||||
cancelExistingNavBarAnimation();
|
||||
mBubbleBarTargetLocation = location;
|
||||
int finalX = getNavBarTranslationX(location);
|
||||
@@ -1199,6 +1210,13 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
mNavBarLocationAnimator.start();
|
||||
}
|
||||
|
||||
private void endExistingAnimation() {
|
||||
if (mNavBarLocationAnimator != null) {
|
||||
mNavBarLocationAnimator.end();
|
||||
mNavBarLocationAnimator = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelExistingNavBarAnimation() {
|
||||
if (mNavBarLocationAnimator != null) {
|
||||
mNavBarLocationAnimator.cancel();
|
||||
@@ -1240,12 +1258,18 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
|
||||
}
|
||||
|
||||
/** Adjusts the navigation buttons layout position according to the bubble bar location. */
|
||||
public void onTaskbarLayoutChanged() {
|
||||
if (mControllers.taskbarViewController.getIconLayoutBounds().isEmpty()) return;
|
||||
public void onLayoutsUpdated() {
|
||||
// no need to do anything if on phone, or if taskbar or navbar views were not placed on
|
||||
// screen.
|
||||
if (mContext.getDeviceProfile().isPhone
|
||||
|| mControllers.taskbarViewController.getIconLayoutBounds().isEmpty()
|
||||
|| mNavButtonsView.getWidth() == 0) {
|
||||
return;
|
||||
}
|
||||
if (enableBubbleBarInPersistentTaskBar()
|
||||
&& mControllers.bubbleControllers.isPresent()) {
|
||||
if (mBubbleBarTargetLocation == null) {
|
||||
// only set bubble bar location if it was not set before, e.g. at device boot
|
||||
// only set bubble bar location if it was not set before
|
||||
mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
|
||||
.bubbleBarViewController.getBubbleBarLocation();
|
||||
}
|
||||
|
||||
@@ -278,7 +278,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
|
||||
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
|
||||
BubbleBarController.onTaskbarRecreated();
|
||||
if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
|
||||
if (BubbleBarController.isBubbleBarEnabled()
|
||||
&& !mDeviceProfile.isPhone
|
||||
&& bubbleBarView != null
|
||||
) {
|
||||
Optional<BubbleStashedHandleViewController> bubbleHandleController = Optional.empty();
|
||||
Optional<BubbleBarSwipeController> bubbleBarSwipeController = Optional.empty();
|
||||
if (isTransientTaskbar) {
|
||||
|
||||
@@ -220,11 +220,12 @@ public class TaskbarControllers {
|
||||
uiController = newUiController;
|
||||
uiController.init(this);
|
||||
uiController.updateStateForSysuiFlags(mSharedState.sysuiStateFlags);
|
||||
bubbleControllers.ifPresent(bubbleControllers -> {
|
||||
// if bubble controllers are present take bubble bar location, else set it to null
|
||||
bubbleControllers.ifPresentOrElse(bubbleControllers -> {
|
||||
BubbleBarLocation location =
|
||||
bubbleControllers.bubbleBarViewController.getBubbleBarLocation();
|
||||
uiController.onBubbleBarLocationUpdated(location);
|
||||
});
|
||||
}, () -> uiController.onBubbleBarLocationUpdated(null));
|
||||
// Notify that the ui controller has changed
|
||||
navbarButtonsViewController.onUiControllerChanged();
|
||||
}
|
||||
|
||||
@@ -866,43 +866,49 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
|
||||
/** Updates launcher home screen appearance accordingly to the bubble bar location. */
|
||||
public void onBubbleBarLocationChanged(BubbleBarLocation location, boolean animate) {
|
||||
DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
|
||||
if (mBubbleBarLocation == location) return;
|
||||
public void onBubbleBarLocationChanged(@Nullable BubbleBarLocation location, boolean animate) {
|
||||
mBubbleBarLocation = location;
|
||||
if (location == null) {
|
||||
// bubble bar is not present, hence no location, resetting the hotseat
|
||||
updateHotseatAndQsbTranslationX(0, animate);
|
||||
mBubbleBarLocation = null;
|
||||
return;
|
||||
}
|
||||
DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
|
||||
if (!deviceProfile.shouldAdjustHotseatOnBubblesLocationUpdate(
|
||||
mControllers.taskbarActivityContext)) {
|
||||
return;
|
||||
}
|
||||
int targetX = 0;
|
||||
if (mBubbleBarLocation != null) {
|
||||
boolean isBubblesOnLeft = location.isOnLeft(isRtl(mLauncher.getResources()));
|
||||
targetX = deviceProfile.getHotseatTranslationXForBubbleBar(/* isNavbarOnRight= */
|
||||
isBubblesOnLeft);
|
||||
}
|
||||
boolean isBubblesOnLeft = location.isOnLeft(isRtl(mLauncher.getResources()));
|
||||
int targetX = deviceProfile
|
||||
.getHotseatTranslationXForBubbleBar(/* isNavbarOnRight= */ isBubblesOnLeft);
|
||||
updateHotseatAndQsbTranslationX(targetX, animate);
|
||||
}
|
||||
|
||||
/** Used to translate hotseat and QSB to make room for bubbles. */
|
||||
private void updateHotseatAndQsbTranslationX(float targetValue, boolean animate) {
|
||||
// cancel existing animation
|
||||
if (mHotseatTranslationXAnimation != null) {
|
||||
mHotseatTranslationXAnimation.cancel();
|
||||
mHotseatTranslationXAnimation = null;
|
||||
}
|
||||
Runnable alignTaskbar = new Runnable() {
|
||||
Runnable postAnimationAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mHotseatTranslationXAnimation = null;
|
||||
// We only need to align the task bar when on launcher home screen
|
||||
if (mControllers.taskbarStashController.isOnHome()) {
|
||||
DeviceProfile dp = mLauncher.getDeviceProfile();
|
||||
mControllers.taskbarViewController
|
||||
.setLauncherIconAlignment(/* alignmentRatio = */ 1, dp);
|
||||
mControllers.taskbarViewController.setLauncherIconAlignment(
|
||||
/* alignmentRatio = */ 1,
|
||||
mLauncher.getDeviceProfile()
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
Hotseat hotseat = mLauncher.getHotseat();
|
||||
AnimatorSet translationXAnimation = new AnimatorSet();
|
||||
MultiProperty iconsTranslationX = hotseat.getIconsTranslationX(
|
||||
Hotseat.ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT);
|
||||
MultiProperty iconsTranslationX = mLauncher.getHotseat()
|
||||
.getIconsTranslationX(Hotseat.ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT);
|
||||
if (animate) {
|
||||
translationXAnimation.playTogether(iconsTranslationX.animateToValue(targetValue));
|
||||
} else {
|
||||
@@ -921,18 +927,17 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
}
|
||||
if (!animate) {
|
||||
alignTaskbar.run();
|
||||
postAnimationAction.run();
|
||||
return;
|
||||
}
|
||||
mHotseatTranslationXAnimation = translationXAnimation;
|
||||
translationXAnimation.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
|
||||
translationXAnimation.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
|
||||
translationXAnimation.setInterpolator(Interpolators.EMPHASIZED);
|
||||
translationXAnimation.addListener(AnimatorListeners.forEndCallback(alignTaskbar));
|
||||
translationXAnimation.addListener(AnimatorListeners.forEndCallback(postAnimationAction));
|
||||
translationXAnimation.start();
|
||||
}
|
||||
|
||||
|
||||
private final class TaskBarRecentsAnimationListener implements
|
||||
RecentsAnimationCallbacks.RecentsAnimationListener {
|
||||
private final RecentsAnimationCallbacks mCallbacks;
|
||||
|
||||
@@ -159,7 +159,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
|
||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
updateTaskbarIconTranslationXForPinning();
|
||||
mControllers.navbarButtonsViewController.onTaskbarLayoutChanged();
|
||||
if (BubbleBarController.isBubbleBarEnabled()) {
|
||||
mControllers.navbarButtonsViewController.onLayoutsUpdated();
|
||||
}
|
||||
};
|
||||
|
||||
// Animation to align icons with Launcher, created lazily. This allows the controller to be
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.annotation.BinderThread
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.internal.jank.Cuj
|
||||
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
|
||||
import com.android.launcher3.Flags.enableOverviewCommandHelperTimeout
|
||||
import com.android.launcher3.PagedView
|
||||
import com.android.launcher3.logger.LauncherAtom
|
||||
@@ -215,13 +216,12 @@ constructor(
|
||||
}
|
||||
}
|
||||
TOGGLE -> {
|
||||
val taskView =
|
||||
if (recentsView.runningTaskView == null) {
|
||||
recentsView.getTaskViewAt(0)
|
||||
} else {
|
||||
recentsView.nextTaskView ?: recentsView.runningTaskView
|
||||
}
|
||||
launchTask(recentsView, taskView, command, onCallbackResult)
|
||||
launchTask(
|
||||
recentsView,
|
||||
getNextToggledTaskView(recentsView),
|
||||
command,
|
||||
onCallbackResult,
|
||||
)
|
||||
}
|
||||
HOME -> {
|
||||
recentsView.startHome()
|
||||
@@ -229,6 +229,27 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNextToggledTaskView(recentsView: RecentsView<*, *>): TaskView? {
|
||||
// When running task view is null we return last large taskView - typically focusView when
|
||||
// grid only is not enabled else last desktop task view.
|
||||
return if (recentsView.runningTaskView == null) {
|
||||
recentsView.lastLargeTaskView ?: recentsView.getTaskViewAt(0)
|
||||
} else {
|
||||
if (
|
||||
enableLargeDesktopWindowingTile() &&
|
||||
recentsView.getTaskViewCount() == recentsView.largeTilesCount &&
|
||||
recentsView.runningTaskView === recentsView.lastLargeTaskView
|
||||
) {
|
||||
// Enables the toggle when only large tiles are in recents view.
|
||||
// We return previous because unlike small tiles, large tiles are always
|
||||
// on the right hand side.
|
||||
recentsView.previousTaskView ?: recentsView.runningTaskView
|
||||
} else {
|
||||
recentsView.nextTaskView ?: recentsView.runningTaskView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchTask(
|
||||
recents: RecentsView<*, *>,
|
||||
taskView: TaskView?,
|
||||
|
||||
@@ -77,6 +77,7 @@ import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
@@ -943,7 +944,16 @@ public class SplitSelectStateController {
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
controller.finish(true /* toRecents */, null /* onFinishComplete */,
|
||||
controller.finish(
|
||||
true /* toRecents */,
|
||||
() -> {
|
||||
LauncherTaskbarUIController controller =
|
||||
mLauncher.getTaskbarUIController();
|
||||
if (controller != null) {
|
||||
controller.updateTaskbarLauncherStateGoingHome();
|
||||
}
|
||||
|
||||
},
|
||||
false /* sendUserLeaveHint */);
|
||||
}
|
||||
@Override
|
||||
@@ -951,7 +961,16 @@ public class SplitSelectStateController {
|
||||
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext())
|
||||
.onDesktopSplitSelectAnimComplete(mTaskInfo);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
mLauncher.getDragLayer().removeView(floatingTaskView);
|
||||
getSplitAnimationController()
|
||||
.removeSplitInstructionsView(mLauncher);
|
||||
resetState();
|
||||
}
|
||||
});
|
||||
anim.add(getSplitAnimationController()
|
||||
.getShowSplitInstructionsAnim(mLauncher).buildAnim());
|
||||
anim.buildAnim().start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.quickstep.views;
|
||||
import android.animation.Animator;
|
||||
import android.animation.Animator.AnimatorListener;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.TaskInfo;
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
@@ -333,11 +334,16 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener,
|
||||
* context's theme background color.
|
||||
*/
|
||||
public static int getDefaultBackgroundColor(
|
||||
Context context, RemoteAnimationTarget target) {
|
||||
return (target != null && target.taskInfo != null
|
||||
&& target.taskInfo.taskDescription != null)
|
||||
? target.taskInfo.taskDescription.getBackgroundColor()
|
||||
: Themes.getColorBackground(context);
|
||||
Context context, @Nullable RemoteAnimationTarget target) {
|
||||
final int fallbackColor = Themes.getColorBackground(context);
|
||||
if (target == null) {
|
||||
return fallbackColor;
|
||||
}
|
||||
final TaskInfo taskInfo = target.taskInfo;
|
||||
if (taskInfo == null) {
|
||||
return fallbackColor;
|
||||
}
|
||||
return taskInfo.taskDescription.getBackgroundColor();
|
||||
}
|
||||
|
||||
private static void getRelativePosition(View descendant, View ancestor, RectF position) {
|
||||
|
||||
@@ -3141,7 +3141,9 @@ public abstract class RecentsView<
|
||||
// Horizontal grid translation for each task
|
||||
float[] gridTranslations = new float[taskCount];
|
||||
|
||||
int lastLargeTaskIndex = Integer.MAX_VALUE;
|
||||
TaskView lastLargeTaskView = mUtils.getLastLargeTaskView(getTaskViews());
|
||||
int lastLargeTaskIndex =
|
||||
(lastLargeTaskView == null) ? Integer.MAX_VALUE : indexOfChild(lastLargeTaskView);
|
||||
Set<Integer> largeTasksIndices = new HashSet<>();
|
||||
int focusedTaskShift = 0;
|
||||
int largeTaskWidthAndSpacing = 0;
|
||||
@@ -3177,7 +3179,6 @@ public abstract class RecentsView<
|
||||
taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
|
||||
- taskView.getLayoutParams().height) / 2f);
|
||||
|
||||
lastLargeTaskIndex = i;
|
||||
largeTasksIndices.add(i);
|
||||
largeTaskWidthAndSpacing = taskWidthAndSpacing;
|
||||
|
||||
@@ -4571,6 +4572,20 @@ public abstract class RecentsView<
|
||||
return getTaskViewAt(getRunningTaskIndex() + 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TaskView getPreviousTaskView() {
|
||||
return getTaskViewAt(getRunningTaskIndex() - 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TaskView getLastLargeTaskView() {
|
||||
return mUtils.getLastLargeTaskView(getTaskViews());
|
||||
}
|
||||
|
||||
public int getLargeTilesCount() {
|
||||
return mUtils.getLargeTileCount(getTaskViews());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TaskView getCurrentPageTaskView() {
|
||||
return getTaskViewAt(getCurrentPage());
|
||||
@@ -5404,6 +5419,7 @@ public abstract class RecentsView<
|
||||
|
||||
int taskIndex = indexOfChild(taskView);
|
||||
int centerTaskIndex = getCurrentPage();
|
||||
boolean isRunningTask = taskView.isRunningTask();
|
||||
|
||||
float toScale = getMaxScaleForFullScreen();
|
||||
boolean showAsGrid = showAsGrid();
|
||||
@@ -5422,13 +5438,16 @@ public abstract class RecentsView<
|
||||
mTempPointF);
|
||||
setPivotX(mTempPointF.x);
|
||||
setPivotY(mTempPointF.y);
|
||||
runActionOnRemoteHandles(
|
||||
remoteTargetHandle -> {
|
||||
remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
|
||||
mTempPointF);
|
||||
remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(
|
||||
false);
|
||||
});
|
||||
|
||||
if (!isRunningTask) {
|
||||
runActionOnRemoteHandles(
|
||||
remoteTargetHandle -> {
|
||||
remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
|
||||
mTempPointF);
|
||||
remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(
|
||||
false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (!showAsGrid) {
|
||||
|
||||
+15
-23
@@ -20,7 +20,6 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -38,14 +37,13 @@ import android.view.SurfaceControl;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.LauncherRootView;
|
||||
import com.android.launcher3.dragndrop.DragLayer;
|
||||
import com.android.launcher3.statemanager.BaseState;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.statemanager.StatefulContainer;
|
||||
import com.android.launcher3.util.SystemUiController;
|
||||
import com.android.quickstep.fallback.window.RecentsWindowManager;
|
||||
@@ -65,11 +63,11 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
public abstract class AbsSwipeUpHandlerTestCase<
|
||||
RECENTS_CONTAINER extends Context & RecentsViewContainer & StatefulContainer<STATE>,
|
||||
STATE extends BaseState<STATE>, RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
|
||||
ACTIVITY_TYPE extends StatefulActivity<STATE> & RecentsViewContainer,
|
||||
ACTIVITY_INTERFACE extends BaseActivityInterface<STATE, ACTIVITY_TYPE>,
|
||||
SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE>> {
|
||||
STATE_TYPE extends BaseState<STATE_TYPE>,
|
||||
RECENTS_CONTAINER extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>,
|
||||
RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE_TYPE>,
|
||||
SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE_TYPE>,
|
||||
CONTAINER_INTERFACE extends BaseContainerInterface<STATE_TYPE, RECENTS_CONTAINER>> {
|
||||
|
||||
protected final Context mContext =
|
||||
InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
@@ -106,13 +104,12 @@ public abstract class AbsSwipeUpHandlerTestCase<
|
||||
/* minimizedHomeBounds= */ null,
|
||||
new Bundle());
|
||||
|
||||
protected RecentsWindowManager mRecentsWindowManager;
|
||||
protected TaskAnimationManager mTaskAnimationManager;
|
||||
|
||||
@Mock protected ACTIVITY_INTERFACE mActivityInterface;
|
||||
@Mock protected CONTAINER_INTERFACE mActivityInterface;
|
||||
@Mock protected ActivityInitListener<?> mActivityInitListener;
|
||||
@Mock protected RecentsAnimationController mRecentsAnimationController;
|
||||
@Mock protected STATE mState;
|
||||
@Mock protected STATE_TYPE mState;
|
||||
@Mock protected ViewTreeObserver mViewTreeObserver;
|
||||
@Mock protected DragLayer mDragLayer;
|
||||
@Mock protected LauncherRootView mRootView;
|
||||
@@ -122,16 +119,6 @@ public abstract class AbsSwipeUpHandlerTestCase<
|
||||
@Rule
|
||||
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Before
|
||||
public void setUpTaskAnimationManager() {
|
||||
runOnMainSync(() -> {
|
||||
if(Flags.enableFallbackOverviewInWindow()){
|
||||
mRecentsWindowManager = new RecentsWindowManager(mContext);
|
||||
}
|
||||
mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsWindowManager);
|
||||
});
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUpRunningTaskInfo() {
|
||||
mRunningTaskInfo.baseIntent = new Intent(Intent.ACTION_MAIN)
|
||||
@@ -162,6 +149,7 @@ public abstract class AbsSwipeUpHandlerTestCase<
|
||||
|
||||
@Before
|
||||
public void setUpRecentsContainer() {
|
||||
mTaskAnimationManager = new TaskAnimationManager(mContext, getRecentsWindowManager());
|
||||
RecentsViewContainer recentsContainer = getRecentsContainer();
|
||||
RECENTS_VIEW recentsView = getRecentsView();
|
||||
|
||||
@@ -263,13 +251,12 @@ public abstract class AbsSwipeUpHandlerTestCase<
|
||||
|
||||
private void onRecentsAnimationStart(SWIPE_HANDLER absSwipeUpHandler) {
|
||||
when(mActivityInterface.getOverviewWindowBounds(any(), any())).thenReturn(new Rect());
|
||||
doNothing().when(mActivityInterface).setOnDeferredActivityLaunchCallback(any());
|
||||
|
||||
runOnMainSync(() -> absSwipeUpHandler.onRecentsAnimationStart(
|
||||
mRecentsAnimationController, mRecentsAnimationTargets));
|
||||
}
|
||||
|
||||
private static void runOnMainSync(Runnable runnable) {
|
||||
protected static void runOnMainSync(Runnable runnable) {
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
|
||||
}
|
||||
|
||||
@@ -278,6 +265,11 @@ public abstract class AbsSwipeUpHandlerTestCase<
|
||||
return createSwipeHandler(SystemClock.uptimeMillis(), false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected RecentsWindowManager getRecentsWindowManager() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected abstract SWIPE_HANDLER createSwipeHandler(
|
||||
long touchTimeMs, boolean continuingLastGesture);
|
||||
|
||||
+8
-6
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit;
|
||||
@@ -28,15 +29,14 @@ import org.mockito.Mock;
|
||||
@SmallTest
|
||||
@RunWith(LauncherMultivalentJUnit.class)
|
||||
public class FallbackSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
|
||||
RecentsActivity,
|
||||
RecentsState,
|
||||
FallbackRecentsView<RecentsActivity>,
|
||||
RecentsActivity,
|
||||
FallbackActivityInterface,
|
||||
FallbackSwipeHandler> {
|
||||
FallbackRecentsView<RecentsActivity>,
|
||||
FallbackSwipeHandler,
|
||||
FallbackActivityInterface> {
|
||||
|
||||
@Mock private RecentsActivity mRecentsActivity;
|
||||
@Mock private FallbackRecentsView mRecentsView;
|
||||
@Mock private FallbackRecentsView<RecentsActivity> mRecentsView;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -52,13 +52,15 @@ public class FallbackSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
|
||||
mInputConsumerController);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected RecentsActivity getRecentsContainer() {
|
||||
return mRecentsActivity;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected FallbackRecentsView getRecentsView() {
|
||||
protected FallbackRecentsView<RecentsActivity> getRecentsView() {
|
||||
return mRecentsView;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -19,6 +19,7 @@ package com.android.quickstep;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.Hotseat;
|
||||
@@ -38,12 +39,11 @@ import org.mockito.Mock;
|
||||
@SmallTest
|
||||
@RunWith(LauncherMultivalentJUnit.class)
|
||||
public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
|
||||
QuickstepLauncher,
|
||||
LauncherState,
|
||||
RecentsView<QuickstepLauncher, LauncherState>,
|
||||
QuickstepLauncher,
|
||||
LauncherActivityInterface,
|
||||
LauncherSwipeHandlerV2> {
|
||||
RecentsView<QuickstepLauncher, LauncherState>,
|
||||
LauncherSwipeHandlerV2,
|
||||
LauncherActivityInterface> {
|
||||
|
||||
@Mock private QuickstepLauncher mQuickstepLauncher;
|
||||
@Mock private RecentsView<QuickstepLauncher, LauncherState> mRecentsView;
|
||||
@@ -67,6 +67,7 @@ public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
|
||||
when(mWorkspace.getStateTransitionAnimation()).thenReturn(mTransitionAnimation);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected LauncherSwipeHandlerV2 createSwipeHandler(
|
||||
long touchTimeMs, boolean continuingLastGesture) {
|
||||
@@ -80,11 +81,13 @@ public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
|
||||
mInputConsumerController);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected QuickstepLauncher getRecentsContainer() {
|
||||
return mQuickstepLauncher;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected RecentsView<QuickstepLauncher, LauncherState> getRecentsView() {
|
||||
return mRecentsView;
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.quickstep;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit;
|
||||
import com.android.quickstep.fallback.FallbackRecentsView;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.fallback.window.RecentsWindowManager;
|
||||
import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
|
||||
import com.android.quickstep.views.RecentsViewContainer;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(LauncherMultivalentJUnit.class)
|
||||
public class RecentsWindowSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
|
||||
RecentsState,
|
||||
RecentsWindowManager,
|
||||
FallbackRecentsView<RecentsWindowManager>,
|
||||
RecentsWindowSwipeHandler,
|
||||
FallbackWindowInterface> {
|
||||
|
||||
@Mock private RecentsWindowManager mRecentsWindowManager;
|
||||
@Mock private FallbackRecentsView<RecentsWindowManager> mRecentsView;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected RecentsWindowSwipeHandler createSwipeHandler(long touchTimeMs,
|
||||
boolean continuingLastGesture) {
|
||||
return new RecentsWindowSwipeHandler(
|
||||
mContext,
|
||||
mRecentsAnimationDeviceState,
|
||||
mTaskAnimationManager,
|
||||
mGestureState,
|
||||
touchTimeMs,
|
||||
continuingLastGesture,
|
||||
mInputConsumerController,
|
||||
mRecentsWindowManager);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected RecentsWindowManager getRecentsWindowManager() {
|
||||
return mRecentsWindowManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected RecentsViewContainer getRecentsContainer() {
|
||||
return mRecentsWindowManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected FallbackRecentsView<RecentsWindowManager> getRecentsView() {
|
||||
return mRecentsView;
|
||||
}
|
||||
}
|
||||
@@ -13,36 +13,25 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/accent_ripple_color">
|
||||
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
|
||||
<solid android:color="@color/accent_ripple_color" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<selector android:enterFadeDuration="100">
|
||||
<item
|
||||
android:id="@+id/unselected"
|
||||
android:state_selected="false">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
|
||||
<solid android:color="?attr/materialColorSurfaceBright" />
|
||||
</shape>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/all_apps_tabs_background_unselected_focused" android:state_focused="true" android:state_selected="false" />
|
||||
<item android:drawable="@drawable/all_apps_tabs_background_selected_focused" android:state_focused="true" android:state_selected="true" />
|
||||
<item android:id="@+id/unselected" android:state_focused="false" android:state_selected="false">
|
||||
<ripple android:color="@color/accent_ripple_color">
|
||||
<item>
|
||||
<selector android:enterFadeDuration="100">
|
||||
<item android:drawable="@drawable/all_apps_tabs_background_unselected" />
|
||||
</selector>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/selected"
|
||||
android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
|
||||
<solid android:color="?attr/materialColorPrimary" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
</ripple>
|
||||
</item>
|
||||
|
||||
</ripple>
|
||||
<item android:id="@+id/selected" android:state_focused="false" android:state_selected="true">
|
||||
<ripple android:color="@color/accent_ripple_color">
|
||||
<item>
|
||||
<selector android:enterFadeDuration="100">
|
||||
<item android:drawable="@drawable/all_apps_tabs_background_selected" />
|
||||
</selector>
|
||||
</item>
|
||||
</ripple>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright (C) 2024 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:bottom="@dimen/all_apps_tabs_focus_width"
|
||||
android:end="@dimen/all_apps_tabs_focus_width"
|
||||
android:start="@dimen/all_apps_tabs_focus_width"
|
||||
android:top="@dimen/all_apps_tabs_focus_width">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
|
||||
<solid android:color="?attr/materialColorPrimary" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright (C) 2024 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="16dp" />
|
||||
<solid android:color="?attr/materialColorPrimary" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:bottom="@dimen/all_apps_tabs_focus_border"
|
||||
android:end="@dimen/all_apps_tabs_focus_border"
|
||||
android:start="@dimen/all_apps_tabs_focus_border"
|
||||
android:top="@dimen/all_apps_tabs_focus_border">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="13dp" />
|
||||
<solid android:color="?attr/materialColorPrimary" />
|
||||
<stroke
|
||||
android:width="@dimen/all_apps_tabs_focus_padding"
|
||||
android:color="?attr/materialColorSurfaceDim" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright (C) 2024 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:bottom="@dimen/all_apps_tabs_focus_width"
|
||||
android:end="@dimen/all_apps_tabs_focus_width"
|
||||
android:start="@dimen/all_apps_tabs_focus_width"
|
||||
android:top="@dimen/all_apps_tabs_focus_width">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
|
||||
<solid android:color="?attr/materialColorSurfaceBright" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright (C) 2024 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="16dp" />
|
||||
<solid android:color="?attr/materialColorPrimary" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:bottom="@dimen/all_apps_tabs_focus_border"
|
||||
android:end="@dimen/all_apps_tabs_focus_border"
|
||||
android:start="@dimen/all_apps_tabs_focus_border"
|
||||
android:top="@dimen/all_apps_tabs_focus_border">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="13dp" />
|
||||
<solid android:color="?attr/materialColorSurfaceBright" />
|
||||
<stroke
|
||||
android:width="@dimen/all_apps_tabs_focus_padding"
|
||||
android:color="?attr/materialColorSurfaceDim" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -21,8 +21,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/all_apps_header_pill_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingTop="@dimen/all_apps_tabs_vertical_padding"
|
||||
android:paddingBottom="@dimen/all_apps_tabs_vertical_padding"
|
||||
android:paddingTop="@dimen/all_apps_tabs_vertical_padding_focus"
|
||||
android:paddingBottom="@dimen/all_apps_tabs_vertical_padding_focus"
|
||||
android:layout_marginTop="@dimen/all_apps_tabs_margin_top"
|
||||
android:orientation="horizontal"
|
||||
style="@style/TextHeadline"
|
||||
|
||||
@@ -126,6 +126,10 @@
|
||||
<dimen name="all_apps_work_profile_tab_footer_bottom_padding">20dp</dimen>
|
||||
<dimen name="all_apps_tabs_button_horizontal_padding">4dp</dimen>
|
||||
<dimen name="all_apps_tabs_vertical_padding">6dp</dimen>
|
||||
<dimen name="all_apps_tabs_vertical_padding_focus">1dp</dimen>
|
||||
<dimen name="all_apps_tabs_focus_width">5dp</dimen>
|
||||
<dimen name="all_apps_tabs_focus_border">3dp</dimen>
|
||||
<dimen name="all_apps_tabs_focus_padding">2dp</dimen>
|
||||
<dimen name="all_apps_tabs_margin_top">8dp</dimen>
|
||||
<dimen name="all_apps_divider_height">2dp</dimen>
|
||||
<dimen name="all_apps_divider_width">128dp</dimen>
|
||||
|
||||
Reference in New Issue
Block a user