Merge "Add pendingAppearedIntent to TaskFragmentContainer" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3f75b0485a
@@ -530,11 +530,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
|
|
||||||
if (container == splitContainer.getPrimaryContainer()) {
|
if (container == splitContainer.getPrimaryContainer()) {
|
||||||
// The new launched can be in the primary container when it is starting a new activity
|
// The new launched can be in the primary container when it is starting a new activity
|
||||||
// onCreate, thus the secondary may still be empty.
|
// onCreate.
|
||||||
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
||||||
|
final Intent secondaryIntent = secondaryContainer.getPendingAppearedIntent();
|
||||||
|
if (secondaryIntent != null) {
|
||||||
|
// Check with the pending Intent before it is started on the server side.
|
||||||
|
// This can happen if the launched Activity start a new Intent to secondary during
|
||||||
|
// #onCreated().
|
||||||
|
return getSplitRule(launchedActivity, secondaryIntent) != null;
|
||||||
|
}
|
||||||
final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity();
|
final Activity secondaryActivity = secondaryContainer.getTopNonFinishingActivity();
|
||||||
return secondaryActivity == null
|
return secondaryActivity != null
|
||||||
|| getSplitRule(launchedActivity, secondaryActivity) != null;
|
&& getSplitRule(launchedActivity, secondaryActivity) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the new launched activity is a placeholder.
|
// Check if the new launched activity is a placeholder.
|
||||||
@@ -573,7 +580,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
Activity activityBelow = null;
|
Activity activityBelow = null;
|
||||||
final TaskFragmentContainer container = getContainerWithActivity(activity);
|
final TaskFragmentContainer container = getContainerWithActivity(activity);
|
||||||
if (container != null) {
|
if (container != null) {
|
||||||
final List<Activity> containerActivities = container.collectActivities();
|
final List<Activity> containerActivities = container.collectNonFinishingActivities();
|
||||||
final int index = containerActivities.indexOf(activity);
|
final int index = containerActivities.indexOf(activity);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
activityBelow = containerActivities.get(index - 1);
|
activityBelow = containerActivities.get(index - 1);
|
||||||
@@ -691,7 +698,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
|
|
||||||
// 1. Whether the new activity intent should always expand.
|
// 1. Whether the new activity intent should always expand.
|
||||||
if (shouldExpand(null /* activity */, intent)) {
|
if (shouldExpand(null /* activity */, intent)) {
|
||||||
return createEmptyExpandedContainer(wct, taskId, launchingActivity);
|
return createEmptyExpandedContainer(wct, intent, taskId, launchingActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Whether the launching activity (if set) should be split with the new activity intent.
|
// 2. Whether the launching activity (if set) should be split with the new activity intent.
|
||||||
@@ -742,7 +749,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private TaskFragmentContainer createEmptyExpandedContainer(
|
private TaskFragmentContainer createEmptyExpandedContainer(
|
||||||
@NonNull WindowContainerTransaction wct, int taskId,
|
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
|
||||||
@Nullable Activity launchingActivity) {
|
@Nullable Activity launchingActivity) {
|
||||||
// We need an activity in the organizer process in the same Task to use as the owner
|
// We need an activity in the organizer process in the same Task to use as the owner
|
||||||
// activity, as well as to get the Task window info.
|
// activity, as well as to get the Task window info.
|
||||||
@@ -759,8 +766,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
// Can't find any activity in the Task that we can use as the owner activity.
|
// Can't find any activity in the Task that we can use as the owner activity.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final TaskFragmentContainer expandedContainer = newContainer(null /* activity */,
|
final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
|
||||||
activityInTask, taskId);
|
taskId);
|
||||||
mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
|
mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
|
||||||
activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
|
activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
|
||||||
return expandedContainer;
|
return expandedContainer;
|
||||||
@@ -789,7 +796,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
return splitContainer.getSecondaryContainer();
|
return splitContainer.getSecondaryContainer();
|
||||||
}
|
}
|
||||||
// Create a new TaskFragment to split with the primary activity for the new activity.
|
// Create a new TaskFragment to split with the primary activity for the new activity.
|
||||||
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, splitRule);
|
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
|
||||||
|
splitRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -813,21 +821,34 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskFragmentContainer newContainer(@NonNull Activity activity, int taskId) {
|
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) {
|
||||||
return newContainer(activity, activity, taskId);
|
return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
|
||||||
|
@NonNull Activity activityInTask, int taskId) {
|
||||||
|
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
|
||||||
|
activityInTask, taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
|
||||||
|
@NonNull Activity activityInTask, int taskId) {
|
||||||
|
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
|
||||||
|
activityInTask, taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and registers a new organized container with an optional activity that will be
|
* Creates and registers a new organized container with an optional activity that will be
|
||||||
* re-parented to it in a WCT.
|
* re-parented to it in a WCT.
|
||||||
*
|
*
|
||||||
* @param activity the activity that will be reparented to the TaskFragment.
|
* @param pendingAppearedActivity the activity that will be reparented to the TaskFragment.
|
||||||
* @param activityInTask activity in the same Task so that we can get the Task bounds if
|
* @param pendingAppearedIntent the Intent that will be started in the TaskFragment.
|
||||||
* needed.
|
* @param activityInTask activity in the same Task so that we can get the Task bounds
|
||||||
|
* if needed.
|
||||||
* @param taskId parent Task of the new TaskFragment.
|
* @param taskId parent Task of the new TaskFragment.
|
||||||
*/
|
*/
|
||||||
TaskFragmentContainer newContainer(@Nullable Activity activity,
|
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
|
||||||
@NonNull Activity activityInTask, int taskId) {
|
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
|
||||||
if (activityInTask == null) {
|
if (activityInTask == null) {
|
||||||
throw new IllegalArgumentException("activityInTask must not be null,");
|
throw new IllegalArgumentException("activityInTask must not be null,");
|
||||||
}
|
}
|
||||||
@@ -835,8 +856,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
mTaskContainers.put(taskId, new TaskContainer(taskId));
|
mTaskContainers.put(taskId, new TaskContainer(taskId));
|
||||||
}
|
}
|
||||||
final TaskContainer taskContainer = mTaskContainers.get(taskId);
|
final TaskContainer taskContainer = mTaskContainers.get(taskId);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskContainer,
|
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
|
||||||
this);
|
pendingAppearedIntent, taskContainer, this);
|
||||||
if (!taskContainer.isTaskBoundsInitialized()) {
|
if (!taskContainer.isTaskBoundsInitialized()) {
|
||||||
// Get the initial bounds before the TaskFragment has appeared.
|
// Get the initial bounds before the TaskFragment has appeared.
|
||||||
final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask);
|
final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
|||||||
@NonNull
|
@NonNull
|
||||||
TaskFragmentContainer createNewSplitWithEmptySideContainer(
|
TaskFragmentContainer createNewSplitWithEmptySideContainer(
|
||||||
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
|
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
|
||||||
@NonNull SplitPairRule rule) {
|
@NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
|
||||||
final Rect parentBounds = getParentContainerBounds(primaryActivity);
|
final Rect parentBounds = getParentContainerBounds(primaryActivity);
|
||||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
||||||
isLtr(primaryActivity, rule));
|
isLtr(primaryActivity, rule));
|
||||||
@@ -111,7 +111,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
|||||||
// Create new empty task fragment
|
// Create new empty task fragment
|
||||||
final int taskId = primaryContainer.getTaskId();
|
final int taskId = primaryContainer.getTaskId();
|
||||||
final TaskFragmentContainer secondaryContainer = mController.newContainer(
|
final TaskFragmentContainer secondaryContainer = mController.newContainer(
|
||||||
null /* activity */, primaryActivity, taskId);
|
secondaryIntent, primaryActivity, taskId);
|
||||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
|
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
|
||||||
rule, isLtr(primaryActivity, rule));
|
rule, isLtr(primaryActivity, rule));
|
||||||
final int windowingMode = mController.getTaskContainer(taskId)
|
final int windowingMode = mController.getTaskContainer(taskId)
|
||||||
@@ -224,7 +224,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int taskId = primaryContainer.getTaskId();
|
final int taskId = primaryContainer.getTaskId();
|
||||||
TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */,
|
final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
|
||||||
launchingActivity, taskId);
|
launchingActivity, taskId);
|
||||||
final int windowingMode = mController.getTaskContainer(taskId)
|
final int windowingMode = mController.getTaskContainer(taskId)
|
||||||
.getWindowingModeForSplitTaskFragment(primaryRectBounds);
|
.getWindowingModeForSplitTaskFragment(primaryRectBounds);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
|
|||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.WindowConfiguration.WindowingMode;
|
import android.app.WindowConfiguration.WindowingMode;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -64,7 +65,16 @@ class TaskFragmentContainer {
|
|||||||
* Activities that are being reparented or being started to this container, but haven't been
|
* Activities that are being reparented or being started to this container, but haven't been
|
||||||
* added to {@link #mInfo} yet.
|
* added to {@link #mInfo} yet.
|
||||||
*/
|
*/
|
||||||
private final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>();
|
@VisibleForTesting
|
||||||
|
final ArrayList<Activity> mPendingAppearedActivities = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this container is created for an {@link Intent} to start within, we store that Intent
|
||||||
|
* until the container becomes non-empty on the server side, so that we can use it to check
|
||||||
|
* rules associated with this container.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private Intent mPendingAppearedIntent;
|
||||||
|
|
||||||
/** Containers that are dependent on this one and should be completely destroyed on exit. */
|
/** Containers that are dependent on this one and should be completely destroyed on exit. */
|
||||||
private final List<TaskFragmentContainer> mContainersToFinishOnExit =
|
private final List<TaskFragmentContainer> mContainersToFinishOnExit =
|
||||||
@@ -99,15 +109,22 @@ class TaskFragmentContainer {
|
|||||||
* Creates a container with an existing activity that will be re-parented to it in a window
|
* Creates a container with an existing activity that will be re-parented to it in a window
|
||||||
* container transaction.
|
* container transaction.
|
||||||
*/
|
*/
|
||||||
TaskFragmentContainer(@Nullable Activity activity, @NonNull TaskContainer taskContainer,
|
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
|
||||||
|
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
|
||||||
@NonNull SplitController controller) {
|
@NonNull SplitController controller) {
|
||||||
|
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|
||||||
|
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"One and only one of pending activity and intent must be non-null");
|
||||||
|
}
|
||||||
mController = controller;
|
mController = controller;
|
||||||
mToken = new Binder("TaskFragmentContainer");
|
mToken = new Binder("TaskFragmentContainer");
|
||||||
mTaskContainer = taskContainer;
|
mTaskContainer = taskContainer;
|
||||||
taskContainer.mContainers.add(this);
|
taskContainer.mContainers.add(this);
|
||||||
if (activity != null) {
|
if (pendingAppearedActivity != null) {
|
||||||
addPendingAppearedActivity(activity);
|
addPendingAppearedActivity(pendingAppearedActivity);
|
||||||
}
|
}
|
||||||
|
mPendingAppearedIntent = pendingAppearedIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,9 +135,9 @@ class TaskFragmentContainer {
|
|||||||
return mToken;
|
return mToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** List of activities that belong to this container and live in this process. */
|
/** List of non-finishing activities that belong to this container and live in this process. */
|
||||||
@NonNull
|
@NonNull
|
||||||
List<Activity> collectActivities() {
|
List<Activity> collectNonFinishingActivities() {
|
||||||
final List<Activity> allActivities = new ArrayList<>();
|
final List<Activity> allActivities = new ArrayList<>();
|
||||||
if (mInfo != null) {
|
if (mInfo != null) {
|
||||||
// Add activities reported from the server.
|
// Add activities reported from the server.
|
||||||
@@ -154,13 +171,14 @@ class TaskFragmentContainer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return mPendingAppearedActivities.isEmpty()
|
return mPendingAppearedActivities.isEmpty()
|
||||||
&& mInfo.getActivities().size() == collectActivities().size();
|
&& mInfo.getActivities().size() == collectNonFinishingActivities().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
ActivityStack toActivityStack() {
|
ActivityStack toActivityStack() {
|
||||||
return new ActivityStack(collectActivities(), isEmpty());
|
return new ActivityStack(collectNonFinishingActivities(), isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Adds the activity that will be reparented to this container. */
|
||||||
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
|
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
|
||||||
if (hasActivity(pendingAppearedActivity.getActivityToken())) {
|
if (hasActivity(pendingAppearedActivity.getActivityToken())) {
|
||||||
return;
|
return;
|
||||||
@@ -174,6 +192,11 @@ class TaskFragmentContainer {
|
|||||||
mPendingAppearedActivities.remove(pendingAppearedActivity);
|
mPendingAppearedActivities.remove(pendingAppearedActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Intent getPendingAppearedIntent() {
|
||||||
|
return mPendingAppearedIntent;
|
||||||
|
}
|
||||||
|
|
||||||
boolean hasActivity(@NonNull IBinder token) {
|
boolean hasActivity(@NonNull IBinder token) {
|
||||||
if (mInfo != null && mInfo.getActivities().contains(token)) {
|
if (mInfo != null && mInfo.getActivities().contains(token)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -219,7 +242,12 @@ class TaskFragmentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mInfo = info;
|
mInfo = info;
|
||||||
if (mInfo == null || mPendingAppearedActivities.isEmpty()) {
|
if (mInfo == null || mInfo.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only track the pending Intent when the container is empty.
|
||||||
|
mPendingAppearedIntent = null;
|
||||||
|
if (mPendingAppearedActivities.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Cleanup activities that were being re-parented
|
// Cleanup activities that were being re-parented
|
||||||
@@ -234,20 +262,13 @@ class TaskFragmentContainer {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Activity getTopNonFinishingActivity() {
|
Activity getTopNonFinishingActivity() {
|
||||||
List<Activity> activities = collectActivities();
|
final List<Activity> activities = collectNonFinishingActivities();
|
||||||
if (activities.isEmpty()) {
|
return activities.isEmpty() ? null : activities.get(activities.size() - 1);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int i = activities.size() - 1;
|
|
||||||
while (i >= 0 && activities.get(i).isFinishing()) {
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
return i >= 0 ? activities.get(i) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Activity getBottomMostActivity() {
|
Activity getBottomMostActivity() {
|
||||||
final List<Activity> activities = collectActivities();
|
final List<Activity> activities = collectNonFinishingActivities();
|
||||||
return activities.isEmpty() ? null : activities.get(0);
|
return activities.isEmpty() ? null : activities.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,8 +341,11 @@ class TaskFragmentContainer {
|
|||||||
private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
|
private void finishActivities(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
|
||||||
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
|
@NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
|
||||||
// Finish own activities
|
// Finish own activities
|
||||||
for (Activity activity : collectActivities()) {
|
for (Activity activity : collectNonFinishingActivities()) {
|
||||||
if (!activity.isFinishing()) {
|
if (!activity.isFinishing()
|
||||||
|
// In case we have requested to reparent the activity to another container (as
|
||||||
|
// pendingAppeared), we don't want to finish it with this container.
|
||||||
|
&& mController.getContainerWithActivity(activity) == this) {
|
||||||
activity.finish();
|
activity.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -115,7 +116,7 @@ public class JetpackTaskFragmentOrganizerTest {
|
|||||||
public void testExpandTaskFragment() {
|
public void testExpandTaskFragment() {
|
||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mSplitController);
|
new Intent(), taskContainer, mSplitController);
|
||||||
final TaskFragmentInfo info = createMockInfo(container);
|
final TaskFragmentInfo info = createMockInfo(container);
|
||||||
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
|
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
|
||||||
container.setInfo(info);
|
container.setInfo(info);
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ public class SplitControllerTest {
|
|||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
// tf1 has no running activity so is not active.
|
// tf1 has no running activity so is not active.
|
||||||
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mSplitController);
|
new Intent(), taskContainer, mSplitController);
|
||||||
// tf2 has running activity so is active.
|
// tf2 has running activity so is active.
|
||||||
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
|
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
|
||||||
doReturn(1).when(tf2).getRunningActivityCount();
|
doReturn(1).when(tf2).getRunningActivityCount();
|
||||||
@@ -205,7 +205,8 @@ public class SplitControllerTest {
|
|||||||
assertThrows(IllegalArgumentException.class, () ->
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID));
|
mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID));
|
||||||
|
|
||||||
final TaskFragmentContainer tf = mSplitController.newContainer(null, mActivity, TASK_ID);
|
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, mActivity,
|
||||||
|
TASK_ID);
|
||||||
final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
|
final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
|
||||||
|
|
||||||
assertNotNull(tf);
|
assertNotNull(tf);
|
||||||
@@ -307,7 +308,7 @@ public class SplitControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testOnActivityReparentToTask_diffProcess() {
|
public void testOnActivityReparentToTask_diffProcess() {
|
||||||
// Create an empty TaskFragment to initialize for the Task.
|
// Create an empty TaskFragment to initialize for the Task.
|
||||||
mSplitController.newContainer(null, mActivity, TASK_ID);
|
mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
|
||||||
final IBinder activityToken = new Binder();
|
final IBinder activityToken = new Binder();
|
||||||
final Intent intent = new Intent();
|
final Intent intent = new Intent();
|
||||||
|
|
||||||
@@ -417,7 +418,7 @@ public class SplitControllerTest {
|
|||||||
|
|
||||||
verify(mSplitPresenter, never()).applyTransaction(any());
|
verify(mSplitPresenter, never()).applyTransaction(any());
|
||||||
|
|
||||||
mSplitController.newContainer(null /* activity */, mActivity, TASK_ID);
|
mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
|
||||||
mSplitController.placeActivityInTopContainer(mActivity);
|
mSplitController.placeActivityInTopContainer(mActivity);
|
||||||
|
|
||||||
verify(mSplitPresenter).applyTransaction(any());
|
verify(mSplitPresenter).applyTransaction(any());
|
||||||
@@ -436,7 +437,7 @@ public class SplitControllerTest {
|
|||||||
false /* isOnReparent */);
|
false /* isOnReparent */);
|
||||||
|
|
||||||
assertFalse(result);
|
assertFalse(result);
|
||||||
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
|
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -577,7 +578,7 @@ public class SplitControllerTest {
|
|||||||
final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity,
|
final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity,
|
||||||
TASK_ID);
|
TASK_ID);
|
||||||
final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(
|
final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(
|
||||||
null /* activity */, mActivity, TASK_ID);
|
secondaryIntent, mActivity, TASK_ID);
|
||||||
mSplitController.registerSplit(
|
mSplitController.registerSplit(
|
||||||
mTransaction,
|
mTransaction,
|
||||||
primaryContainer,
|
primaryContainer,
|
||||||
@@ -589,10 +590,35 @@ public class SplitControllerTest {
|
|||||||
false /* isOnReparent */);
|
false /* isOnReparent */);
|
||||||
|
|
||||||
assertTrue(result);
|
assertTrue(result);
|
||||||
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
|
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
|
||||||
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveActivityToContainer_splitRule_inPrimarySplitWithNoRuleMatched() {
|
||||||
|
final Intent secondaryIntent = new Intent();
|
||||||
|
setupSplitRule(mActivity, secondaryIntent);
|
||||||
|
final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0);
|
||||||
|
|
||||||
|
// The new launched activity is in primary split, but there is no rule for it to split with
|
||||||
|
// the secondary, so return false.
|
||||||
|
final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity,
|
||||||
|
TASK_ID);
|
||||||
|
final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(
|
||||||
|
secondaryIntent, mActivity, TASK_ID);
|
||||||
|
mSplitController.registerSplit(
|
||||||
|
mTransaction,
|
||||||
|
primaryContainer,
|
||||||
|
mActivity,
|
||||||
|
secondaryContainer,
|
||||||
|
splitRule);
|
||||||
|
final Activity launchedActivity = createMockActivity();
|
||||||
|
primaryContainer.addPendingAppearedActivity(launchedActivity);
|
||||||
|
|
||||||
|
assertFalse(mSplitController.resolveActivityToContainer(launchedActivity,
|
||||||
|
false /* isOnReparent */));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResolveActivityToContainer_splitRule_inSecondarySplitWithRuleMatched() {
|
public void testResolveActivityToContainer_splitRule_inSecondarySplitWithRuleMatched() {
|
||||||
final Activity primaryActivity = createMockActivity();
|
final Activity primaryActivity = createMockActivity();
|
||||||
@@ -605,7 +631,7 @@ public class SplitControllerTest {
|
|||||||
false /* isOnReparent */);
|
false /* isOnReparent */);
|
||||||
|
|
||||||
assertTrue(result);
|
assertTrue(result);
|
||||||
verify(mSplitController, never()).newContainer(any(), any(), anyInt());
|
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
|
||||||
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.doReturn;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ public class TaskContainerTest {
|
|||||||
assertTrue(taskContainer.isEmpty());
|
assertTrue(taskContainer.isEmpty());
|
||||||
|
|
||||||
final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
new Intent(), taskContainer, mController);
|
||||||
|
|
||||||
assertFalse(taskContainer.isEmpty());
|
assertFalse(taskContainer.isEmpty());
|
||||||
|
|
||||||
@@ -158,11 +159,11 @@ public class TaskContainerTest {
|
|||||||
assertNull(taskContainer.getTopTaskFragmentContainer());
|
assertNull(taskContainer.getTopTaskFragmentContainer());
|
||||||
|
|
||||||
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
new Intent(), taskContainer, mController);
|
||||||
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
|
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
|
||||||
|
|
||||||
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
new Intent(), taskContainer, mController);
|
||||||
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
|
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.clearInvocations;
|
import static org.mockito.Mockito.clearInvocations;
|
||||||
@@ -29,12 +30,17 @@ import static org.mockito.Mockito.doReturn;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.platform.test.annotations.Presubmit;
|
import android.platform.test.annotations.Presubmit;
|
||||||
import android.window.TaskFragmentInfo;
|
import android.window.TaskFragmentInfo;
|
||||||
|
import android.window.WindowContainerToken;
|
||||||
import android.window.WindowContainerTransaction;
|
import android.window.WindowContainerTransaction;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@@ -49,6 +55,7 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,19 +79,35 @@ public class TaskFragmentContainerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
private Activity mActivity;
|
private Activity mActivity;
|
||||||
|
private Intent mIntent;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
doReturn(mHandler).when(mController).getHandler();
|
doReturn(mHandler).when(mController).getHandler();
|
||||||
mActivity = createMockActivity();
|
mActivity = createMockActivity();
|
||||||
|
mIntent = new Intent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNewContainer() {
|
||||||
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
|
|
||||||
|
// One of the activity and the intent must be non-null
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new TaskFragmentContainer(null, null, taskContainer, mController));
|
||||||
|
|
||||||
|
// One of the activity and the intent must be null.
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFinish() {
|
public void testFinish() {
|
||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, taskContainer,
|
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
|
||||||
mController);
|
null /* pendingAppearedIntent */, taskContainer, mController);
|
||||||
|
doReturn(container).when(mController).getContainerWithActivity(mActivity);
|
||||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||||
|
|
||||||
// Only remove the activity, but not clear the reference until appeared.
|
// Only remove the activity, but not clear the reference until appeared.
|
||||||
@@ -112,11 +135,60 @@ public class TaskFragmentContainerTest {
|
|||||||
verify(mController).removeContainer(container);
|
verify(mController).removeContainer(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFinish_notFinishActivityThatIsReparenting() {
|
||||||
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
|
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
|
||||||
|
null /* pendingAppearedIntent */, taskContainer, mController);
|
||||||
|
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
|
||||||
|
container0.setInfo(info);
|
||||||
|
// Request to reparent the activity to a new TaskFragment.
|
||||||
|
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
|
||||||
|
null /* pendingAppearedIntent */, taskContainer, mController);
|
||||||
|
doReturn(container1).when(mController).getContainerWithActivity(mActivity);
|
||||||
|
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||||
|
|
||||||
|
// The activity is requested to be reparented, so don't finish it.
|
||||||
|
container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
|
||||||
|
|
||||||
|
verify(mActivity, never()).finish();
|
||||||
|
verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken());
|
||||||
|
verify(mController).removeContainer(container0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetInfo() {
|
||||||
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
|
// Pending activity should be cleared when it has appeared on server side.
|
||||||
|
final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
|
||||||
|
null /* pendingAppearedIntent */, taskContainer, mController);
|
||||||
|
|
||||||
|
assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(mActivity));
|
||||||
|
|
||||||
|
final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
|
||||||
|
mActivity);
|
||||||
|
pendingActivityContainer.setInfo(info0);
|
||||||
|
|
||||||
|
assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty());
|
||||||
|
|
||||||
|
// Pending intent should be cleared when the container becomes non-empty.
|
||||||
|
final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer(
|
||||||
|
null /* pendingAppearedActivity */, mIntent, taskContainer, mController);
|
||||||
|
|
||||||
|
assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent());
|
||||||
|
|
||||||
|
final TaskFragmentInfo info1 = createMockTaskFragmentInfo(pendingIntentContainer,
|
||||||
|
mActivity);
|
||||||
|
pendingIntentContainer.setInfo(info1);
|
||||||
|
|
||||||
|
assertNull(pendingIntentContainer.getPendingAppearedIntent());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsWaitingActivityAppear() {
|
public void testIsWaitingActivityAppear() {
|
||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
mIntent, taskContainer, mController);
|
||||||
|
|
||||||
assertTrue(container.isWaitingActivityAppear());
|
assertTrue(container.isWaitingActivityAppear());
|
||||||
|
|
||||||
@@ -137,7 +209,7 @@ public class TaskFragmentContainerTest {
|
|||||||
public void testAppearEmptyTimeout() {
|
public void testAppearEmptyTimeout() {
|
||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
mIntent, taskContainer, mController);
|
||||||
|
|
||||||
assertNull(container.mAppearEmptyTimeout);
|
assertNull(container.mAppearEmptyTimeout);
|
||||||
|
|
||||||
@@ -173,16 +245,16 @@ public class TaskFragmentContainerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCollectActivities() {
|
public void testCollectNonFinishingActivities() {
|
||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
mIntent, taskContainer, mController);
|
||||||
List<Activity> activities = container.collectActivities();
|
List<Activity> activities = container.collectNonFinishingActivities();
|
||||||
|
|
||||||
assertTrue(activities.isEmpty());
|
assertTrue(activities.isEmpty());
|
||||||
|
|
||||||
container.addPendingAppearedActivity(mActivity);
|
container.addPendingAppearedActivity(mActivity);
|
||||||
activities = container.collectActivities();
|
activities = container.collectNonFinishingActivities();
|
||||||
|
|
||||||
assertEquals(1, activities.size());
|
assertEquals(1, activities.size());
|
||||||
|
|
||||||
@@ -192,7 +264,7 @@ public class TaskFragmentContainerTest {
|
|||||||
activity1.getActivityToken());
|
activity1.getActivityToken());
|
||||||
doReturn(runningActivities).when(mInfo).getActivities();
|
doReturn(runningActivities).when(mInfo).getActivities();
|
||||||
container.setInfo(mInfo);
|
container.setInfo(mInfo);
|
||||||
activities = container.collectActivities();
|
activities = container.collectNonFinishingActivities();
|
||||||
|
|
||||||
assertEquals(3, activities.size());
|
assertEquals(3, activities.size());
|
||||||
assertEquals(activity0, activities.get(0));
|
assertEquals(activity0, activities.get(0));
|
||||||
@@ -204,21 +276,21 @@ public class TaskFragmentContainerTest {
|
|||||||
public void testAddPendingActivity() {
|
public void testAddPendingActivity() {
|
||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
mIntent, taskContainer, mController);
|
||||||
container.addPendingAppearedActivity(mActivity);
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
|
||||||
assertEquals(1, container.collectActivities().size());
|
assertEquals(1, container.collectNonFinishingActivities().size());
|
||||||
|
|
||||||
container.addPendingAppearedActivity(mActivity);
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
|
||||||
assertEquals(1, container.collectActivities().size());
|
assertEquals(1, container.collectNonFinishingActivities().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetBottomMostActivity() {
|
public void testGetBottomMostActivity() {
|
||||||
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
|
||||||
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
|
||||||
taskContainer, mController);
|
mIntent, taskContainer, mController);
|
||||||
container.addPendingAppearedActivity(mActivity);
|
container.addPendingAppearedActivity(mActivity);
|
||||||
|
|
||||||
assertEquals(mActivity, container.getBottomMostActivity());
|
assertEquals(mActivity, container.getBottomMostActivity());
|
||||||
@@ -239,4 +311,18 @@ public class TaskFragmentContainerTest {
|
|||||||
doReturn(activity).when(mController).getActivity(activityToken);
|
doReturn(activity).when(mController).getActivity(activityToken);
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
|
||||||
|
private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
|
||||||
|
@NonNull Activity activity) {
|
||||||
|
return new TaskFragmentInfo(container.getTaskFragmentToken(),
|
||||||
|
mock(WindowContainerToken.class),
|
||||||
|
new Configuration(),
|
||||||
|
1,
|
||||||
|
true /* isVisible */,
|
||||||
|
Collections.singletonList(activity.getActivityToken()),
|
||||||
|
new Point(),
|
||||||
|
false /* isTaskClearedForReuse */,
|
||||||
|
false /* isTaskFragmentClearedForPip */);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user