Merge "Keep the focus on the primary when launch ActivityEmbedding placeholder" into tm-dev
This commit is contained in:
@@ -649,6 +649,26 @@ public final class WindowContainerTransaction implements Parcelable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests focus on the top running Activity in the given TaskFragment. This will only take
|
||||||
|
* effect if there is no focus, or if the current focus is in the same Task as the requested
|
||||||
|
* TaskFragment.
|
||||||
|
* @param fragmentToken client assigned unique token to create TaskFragment with specified in
|
||||||
|
* {@link TaskFragmentCreationParams#getFragmentToken()}.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public WindowContainerTransaction requestFocusOnTaskFragment(@NonNull IBinder fragmentToken) {
|
||||||
|
final HierarchyOp hierarchyOp =
|
||||||
|
new HierarchyOp.Builder(
|
||||||
|
HierarchyOp.HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT)
|
||||||
|
.setContainer(fragmentToken)
|
||||||
|
.build();
|
||||||
|
mHierarchyOps.add(hierarchyOp);
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When this {@link WindowContainerTransaction} failed to finish on the server side, it will
|
* When this {@link WindowContainerTransaction} failed to finish on the server side, it will
|
||||||
* trigger callback with this {@param errorCallbackToken}.
|
* trigger callback with this {@param errorCallbackToken}.
|
||||||
@@ -1057,6 +1077,7 @@ public final class WindowContainerTransaction implements Parcelable {
|
|||||||
public static final int HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER = 15;
|
public static final int HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER = 15;
|
||||||
public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16;
|
public static final int HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER = 16;
|
||||||
public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
|
public static final int HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER = 17;
|
||||||
|
public static final int HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT = 18;
|
||||||
|
|
||||||
// The following key(s) are for use with mLaunchOptions:
|
// The following key(s) are for use with mLaunchOptions:
|
||||||
// When launching a task (eg. from recents), this is the taskId to be launched.
|
// When launching a task (eg. from recents), this is the taskId to be launched.
|
||||||
@@ -1368,6 +1389,8 @@ public final class WindowContainerTransaction implements Parcelable {
|
|||||||
case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
|
case HIERARCHY_OP_TYPE_REMOVE_INSETS_PROVIDER:
|
||||||
return "{removeLocalInsetsProvider: container=" + mContainer
|
return "{removeLocalInsetsProvider: container=" + mContainer
|
||||||
+ " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
|
+ " insetsType=" + Arrays.toString(mInsetsTypes) + "}";
|
||||||
|
case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
|
||||||
|
return "{requestFocusOnTaskFragment: container=" + mContainer + "}";
|
||||||
default:
|
default:
|
||||||
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
|
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
|
||||||
+ " mToTop=" + mToTop
|
+ " mToTop=" + mToTop
|
||||||
|
|||||||
@@ -112,9 +112,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
*/
|
*/
|
||||||
public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
|
public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
|
||||||
@Nullable Bundle options, @NonNull SplitRule sideRule,
|
@Nullable Bundle options, @NonNull SplitRule sideRule,
|
||||||
@Nullable Consumer<Exception> failureCallback) {
|
@Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
|
||||||
try {
|
try {
|
||||||
mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule);
|
mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
|
||||||
|
isPlaceholder);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (failureCallback != null) {
|
if (failureCallback != null) {
|
||||||
failureCallback.accept(e);
|
failureCallback.accept(e);
|
||||||
@@ -710,8 +711,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(b/190433398): Handle failed request
|
// TODO(b/190433398): Handle failed request
|
||||||
startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), null,
|
startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), null /* options */,
|
||||||
placeholderRule, null);
|
placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,12 +217,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
|||||||
* @param launchingActivity An activity that should be in the primary container. If it is not
|
* @param launchingActivity An activity that should be in the primary container. If it is not
|
||||||
* currently in an existing container, a new one will be created and
|
* currently in an existing container, a new one will be created and
|
||||||
* the activity will be re-parented to it.
|
* the activity will be re-parented to it.
|
||||||
* @param activityIntent The intent to start the new activity.
|
* @param activityIntent The intent to start the new activity.
|
||||||
* @param activityOptions The options to apply to new activity start.
|
* @param activityOptions The options to apply to new activity start.
|
||||||
* @param rule The split rule to be applied to the container.
|
* @param rule The split rule to be applied to the container.
|
||||||
|
* @param isPlaceholder Whether the launch is a placeholder.
|
||||||
*/
|
*/
|
||||||
void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
|
void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
|
||||||
@Nullable Bundle activityOptions, @NonNull SplitRule rule) {
|
@Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
|
||||||
final Rect parentBounds = getParentContainerBounds(launchingActivity);
|
final Rect parentBounds = getParentContainerBounds(launchingActivity);
|
||||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
||||||
isLtr(launchingActivity, rule));
|
isLtr(launchingActivity, rule));
|
||||||
@@ -244,6 +245,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
|||||||
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
|
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
|
||||||
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
|
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
|
||||||
activityIntent, activityOptions, rule);
|
activityIntent, activityOptions, rule);
|
||||||
|
if (isPlaceholder) {
|
||||||
|
// When placeholder is launched in split, we should keep the focus on the primary.
|
||||||
|
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
|
||||||
|
}
|
||||||
applyTransaction(wct);
|
applyTransaction(wct);
|
||||||
|
|
||||||
primaryContainer.setLastRequestedBounds(primaryRectBounds);
|
primaryContainer.setLastRequestedBounds(primaryRectBounds);
|
||||||
@@ -272,14 +277,21 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
|||||||
isLtr);
|
isLtr);
|
||||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
|
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
|
||||||
isLtr);
|
isLtr);
|
||||||
|
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
||||||
|
// Whether the placeholder is becoming side-by-side with the primary from fullscreen.
|
||||||
|
final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
|
||||||
|
&& secondaryContainer.areLastRequestedBoundsEqual(null /* bounds */)
|
||||||
|
&& !secondaryRectBounds.isEmpty();
|
||||||
|
|
||||||
// If the task fragments are not registered yet, the positions will be updated after they
|
// If the task fragments are not registered yet, the positions will be updated after they
|
||||||
// are created again.
|
// are created again.
|
||||||
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
|
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
|
||||||
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
|
||||||
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
|
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
|
||||||
|
|
||||||
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
|
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
|
||||||
|
if (isPlaceholderBecomingSplit) {
|
||||||
|
// When placeholder is shown in split, we should keep the focus on the primary.
|
||||||
|
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
|
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
|
|||||||
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
|
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
|
||||||
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
|
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
|
||||||
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN;
|
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN;
|
||||||
|
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
|
||||||
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
|
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
|
||||||
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
|
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
|
||||||
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
|
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
|
||||||
@@ -775,6 +776,29 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT: {
|
||||||
|
final TaskFragment tf = mLaunchTaskFragments.get(hop.getContainer());
|
||||||
|
if (tf == null || !tf.isAttached()) {
|
||||||
|
Slog.e(TAG, "Attempt to operate on detached container: " + tf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final ActivityRecord curFocus = tf.getDisplayContent().mFocusedApp;
|
||||||
|
if (curFocus != null && curFocus.getTaskFragment() == tf) {
|
||||||
|
Slog.d(TAG, "The requested TaskFragment already has the focus.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (curFocus != null && curFocus.getTask() != tf.getTask()) {
|
||||||
|
Slog.d(TAG, "The Task of the requested TaskFragment doesn't have focus.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final ActivityRecord targetFocus = tf.getTopResumedActivity();
|
||||||
|
if (targetFocus == null) {
|
||||||
|
Slog.d(TAG, "There is no resumed activity in the requested TaskFragment.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tf.getDisplayContent().setFocusedApp(targetFocus);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
// The other operations may change task order so they are skipped while in lock
|
// The other operations may change task order so they are skipped while in lock
|
||||||
// task mode. The above operations are still allowed because they don't move
|
// task mode. The above operations are still allowed because they don't move
|
||||||
@@ -1318,6 +1342,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
|
|||||||
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
|
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
|
||||||
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
|
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
|
||||||
case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
|
case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
|
||||||
|
case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
|
||||||
// We are allowing organizer to start/reparent activity to a TaskFragment it
|
// We are allowing organizer to start/reparent activity to a TaskFragment it
|
||||||
// created, or set two TaskFragments adjacent to each other. Nothing to check
|
// created, or set two TaskFragments adjacent to each other. Nothing to check
|
||||||
// here because the TaskFragment may not be created yet, but will be created in
|
// here because the TaskFragment may not be created yet, but will be created in
|
||||||
|
|||||||
@@ -521,6 +521,55 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
|
|||||||
verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
|
verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApplyTransaction_requestFocusOnTaskFragment() {
|
||||||
|
mOrganizer.applyTransaction(mTransaction);
|
||||||
|
mController.registerOrganizer(mIOrganizer);
|
||||||
|
final Task task = createTask(mDisplayContent);
|
||||||
|
final IBinder token0 = new Binder();
|
||||||
|
final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
|
||||||
|
.setParentTask(task)
|
||||||
|
.setFragmentToken(token0)
|
||||||
|
.setOrganizer(mOrganizer)
|
||||||
|
.createActivityCount(1)
|
||||||
|
.build();
|
||||||
|
final IBinder token1 = new Binder();
|
||||||
|
final TaskFragment tf1 = new TaskFragmentBuilder(mAtm)
|
||||||
|
.setParentTask(task)
|
||||||
|
.setFragmentToken(token1)
|
||||||
|
.setOrganizer(mOrganizer)
|
||||||
|
.createActivityCount(1)
|
||||||
|
.build();
|
||||||
|
mAtm.mWindowOrganizerController.mLaunchTaskFragments.put(token0, tf0);
|
||||||
|
mAtm.mWindowOrganizerController.mLaunchTaskFragments.put(token1, tf1);
|
||||||
|
final ActivityRecord activity0 = tf0.getTopMostActivity();
|
||||||
|
final ActivityRecord activity1 = tf1.getTopMostActivity();
|
||||||
|
|
||||||
|
// No effect if the current focus is in a different Task.
|
||||||
|
final ActivityRecord activityInOtherTask = createActivityRecord(mDefaultDisplay);
|
||||||
|
mDisplayContent.setFocusedApp(activityInOtherTask);
|
||||||
|
mTransaction.requestFocusOnTaskFragment(token0);
|
||||||
|
mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
|
||||||
|
|
||||||
|
assertEquals(activityInOtherTask, mDisplayContent.mFocusedApp);
|
||||||
|
|
||||||
|
// No effect if there is no resumed activity in the request TaskFragment.
|
||||||
|
activity0.setState(ActivityRecord.State.PAUSED, "test");
|
||||||
|
activity1.setState(ActivityRecord.State.RESUMED, "test");
|
||||||
|
mDisplayContent.setFocusedApp(activity1);
|
||||||
|
mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
|
||||||
|
|
||||||
|
assertEquals(activity1, mDisplayContent.mFocusedApp);
|
||||||
|
|
||||||
|
// Set focus to the request TaskFragment when the current focus is in the same Task, and it
|
||||||
|
// has a resumed activity.
|
||||||
|
activity0.setState(ActivityRecord.State.RESUMED, "test");
|
||||||
|
mDisplayContent.setFocusedApp(activity1);
|
||||||
|
mAtm.mWindowOrganizerController.applyTransaction(mTransaction);
|
||||||
|
|
||||||
|
assertEquals(activity0, mDisplayContent.mFocusedApp);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTaskFragmentInPip_startActivityInTaskFragment() {
|
public void testTaskFragmentInPip_startActivityInTaskFragment() {
|
||||||
setupTaskFragmentInPip();
|
setupTaskFragmentInPip();
|
||||||
|
|||||||
Reference in New Issue
Block a user