Loading services/core/java/com/android/server/wm/BackNavigationController.java +72 −30 Original line number Diff line number Diff line Loading @@ -395,7 +395,8 @@ class BackNavigationController { * * @return false if unable to predict what will happen */ private static boolean getAnimatablePrevActivities(@NonNull Task currentTask, @VisibleForTesting static boolean getAnimatablePrevActivities(@NonNull Task currentTask, @NonNull ActivityRecord currentActivity, @NonNull ArrayList<ActivityRecord> outPrevActivities) { if (currentActivity.mAtmService Loading @@ -413,44 +414,85 @@ class BackNavigationController { // Searching previous final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing, currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/); if (prevActivity == null) { // No previous activity in this task, can still predict if previous task exists. final TaskFragment currTF = currentActivity.getTaskFragment(); if (currTF != null && currTF.asTask() == null) { // The currentActivity is embedded, search for the candidate previous activities. if (prevActivity != null && currTF.hasChild(prevActivity)) { // PrevActivity is under the same task fragment, that's it. outPrevActivities.add(prevActivity); return true; } if (currentTask.getActivity((above) -> !above.finishing, currentActivity, false /*includeBoundary*/, false /*traverseTopToBottom*/) != null) { // another activity is above this activity, don't know what will happen if (currTF.getAdjacentTaskFragment() != null) { // The two TFs are adjacent (visually displayed side-by-side), search if any // activity below the lowest one // If companion, those two TF will be closed together. if (currTF.getCompanionTaskFragment() != null) { final WindowContainer commonParent = currTF.getParent(); final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment(); final TaskFragment lowerTF = commonParent.mChildren.indexOf(currTF) < commonParent.mChildren.indexOf(adjacentTF) ? currTF : adjacentTF; final ActivityRecord lowerActivity = lowerTF.getTopNonFinishingActivity(); // TODO (b/274997067) close currTF + companionTF, open next activities if any. // Allow to predict next task if no more activity in task. Or return previous // activities for cross-activity animation. return currentTask.getActivity((below) -> !below.finishing, lowerActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/) == null; } // Unable to predict if no companion, it can only close current activity and make // prev Activity full screened. return false; } else if (currTF.getCompanionTaskFragment() != null) { // TF is isStacked, search bottom activity from companion TF. // // Sample hierarchy: search for underPrevious if any. // Current TF // Companion TF (bottomActivityInCompanion) // Bottom Activity not inside companion TF (underPrevious) final TaskFragment companionTF = currTF.getCompanionTaskFragment(); // find bottom activity in Companion TF. final ActivityRecord bottomActivityInCompanion = companionTF.getActivity( (below) -> !below.finishing, false /* traverseTopToBottom */); final ActivityRecord underPrevious = currentTask.getActivity( (below) -> !below.finishing, bottomActivityInCompanion, false /*includeBoundary*/, true /*traverseTopToBottom*/); if (underPrevious != null) { outPrevActivities.add(underPrevious); addPreviousAdjacentActivityIfExist(underPrevious, outPrevActivities); } return true; } } final TaskFragment currTF = currentActivity.getTaskFragment(); if (prevActivity == null) { // No previous activity in this Task nor TaskFragment, it can still predict if previous // task exists. return true; } // Add possible adjacent activity if prevActivity is embedded addPreviousAdjacentActivityIfExist(prevActivity, outPrevActivities); outPrevActivities.add(prevActivity); return true; } private static void addPreviousAdjacentActivityIfExist(@NonNull ActivityRecord prevActivity, @NonNull ArrayList<ActivityRecord> outPrevActivities) { final TaskFragment prevTF = prevActivity.getTaskFragment(); if (currTF != prevTF && prevTF != null) { if (prevTF == null || prevTF.asTask() != null) { return; } final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment(); if (prevTFAdjacent != null) { if (prevTFAdjacent == currTF) { outPrevActivities.clear(); // No more activity in task, so it can predict if previous task exists. // Otherwise, unable to predict what will happen when app receive // back key, skip animation. return currentTask.getActivity((below) -> !below.finishing, prevActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/) == null; } else { if (prevTFAdjacent == null || prevTFAdjacent.asTask() != null) { return; } final ActivityRecord prevActivityAdjacent = prevTFAdjacent.getTopNonFinishingActivity(); if (prevActivityAdjacent != null) { outPrevActivities.add(prevActivityAdjacent); } else { // Don't know what will happen. outPrevActivities.clear(); return false; } } } } outPrevActivities.add(prevActivity); return true; } private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity, @NonNull ArrayList<ActivityRecord> outList) { Loading services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +104 −0 Original line number Diff line number Diff line Loading @@ -241,6 +241,110 @@ public class BackNavigationControllerTests extends WindowTestsBase { .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); } @Test public void backTypeCrossActivityInTaskFragment() { final Task task = createTask(mDefaultDisplay); final TaskFragment tf1 = createTaskFragmentWithActivity(task); final TaskFragment tf2 = createTaskFragmentWithActivity(task); final ArrayList<ActivityRecord> outPrevActivities = new ArrayList<>(); ActivityRecord prevAr = tf1.getTopMostActivity(); ActivityRecord topAr = tf2.getTopMostActivity(); boolean predictable; // Stacked + no Companion => predict for previous activity. // TF2 // TF1 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); assertTrue(predictable); outPrevActivities.clear(); // Stacked + companion => predict for previous task tf2.setCompanionTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); tf2.setCompanionTaskFragment(null); // Adjacent + no companion => unable to predict // TF1 | TF2 tf1.setAdjacentTaskFragment(tf2); tf2.setAdjacentTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertFalse(predictable); predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertFalse(predictable); // Adjacent + companion => predict for previous task tf1.setCompanionTaskFragment(tf2); tf2.setCompanionTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); // reset tf1.setAdjacentTaskFragment(null); tf2.setAdjacentTaskFragment(null); tf1.setCompanionTaskFragment(null); tf2.setCompanionTaskFragment(null); final TaskFragment tf3 = new TaskFragmentBuilder(mAtm) .createActivityCount(2) .setParentTask(task) .build(); topAr = tf3.getTopMostActivity(); prevAr = tf3.getBottomMostActivity(); // Stacked => predict for previous activity. // TF3 // TF2 // TF1 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); assertTrue(predictable); // reset outPrevActivities.clear(); // Adjacent => predict for previous activity. // TF2 | TF3 // TF1 tf2.setAdjacentTaskFragment(tf3); tf3.setAdjacentTaskFragment(tf2); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); assertTrue(predictable); // reset outPrevActivities.clear(); tf2.setAdjacentTaskFragment(null); tf3.setAdjacentTaskFragment(null); final TaskFragment tf4 = createTaskFragmentWithActivity(task); // Stacked + companion => predict for previous activity below companion. // Tf4 // TF3 // TF2 // TF1 tf4.setCompanionTaskFragment(tf3); tf3.setCompanionTaskFragment(tf4); topAr = tf4.getTopMostActivity(); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(tf2.getTopMostActivity())); assertTrue(predictable); } @Test public void backTypeDialogCloseWhenBackFromDialog() { DialogCloseTestCase testCase = createTopTaskWithActivityAndDialog(); Loading Loading
services/core/java/com/android/server/wm/BackNavigationController.java +72 −30 Original line number Diff line number Diff line Loading @@ -395,7 +395,8 @@ class BackNavigationController { * * @return false if unable to predict what will happen */ private static boolean getAnimatablePrevActivities(@NonNull Task currentTask, @VisibleForTesting static boolean getAnimatablePrevActivities(@NonNull Task currentTask, @NonNull ActivityRecord currentActivity, @NonNull ArrayList<ActivityRecord> outPrevActivities) { if (currentActivity.mAtmService Loading @@ -413,44 +414,85 @@ class BackNavigationController { // Searching previous final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing, currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/); if (prevActivity == null) { // No previous activity in this task, can still predict if previous task exists. final TaskFragment currTF = currentActivity.getTaskFragment(); if (currTF != null && currTF.asTask() == null) { // The currentActivity is embedded, search for the candidate previous activities. if (prevActivity != null && currTF.hasChild(prevActivity)) { // PrevActivity is under the same task fragment, that's it. outPrevActivities.add(prevActivity); return true; } if (currentTask.getActivity((above) -> !above.finishing, currentActivity, false /*includeBoundary*/, false /*traverseTopToBottom*/) != null) { // another activity is above this activity, don't know what will happen if (currTF.getAdjacentTaskFragment() != null) { // The two TFs are adjacent (visually displayed side-by-side), search if any // activity below the lowest one // If companion, those two TF will be closed together. if (currTF.getCompanionTaskFragment() != null) { final WindowContainer commonParent = currTF.getParent(); final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment(); final TaskFragment lowerTF = commonParent.mChildren.indexOf(currTF) < commonParent.mChildren.indexOf(adjacentTF) ? currTF : adjacentTF; final ActivityRecord lowerActivity = lowerTF.getTopNonFinishingActivity(); // TODO (b/274997067) close currTF + companionTF, open next activities if any. // Allow to predict next task if no more activity in task. Or return previous // activities for cross-activity animation. return currentTask.getActivity((below) -> !below.finishing, lowerActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/) == null; } // Unable to predict if no companion, it can only close current activity and make // prev Activity full screened. return false; } else if (currTF.getCompanionTaskFragment() != null) { // TF is isStacked, search bottom activity from companion TF. // // Sample hierarchy: search for underPrevious if any. // Current TF // Companion TF (bottomActivityInCompanion) // Bottom Activity not inside companion TF (underPrevious) final TaskFragment companionTF = currTF.getCompanionTaskFragment(); // find bottom activity in Companion TF. final ActivityRecord bottomActivityInCompanion = companionTF.getActivity( (below) -> !below.finishing, false /* traverseTopToBottom */); final ActivityRecord underPrevious = currentTask.getActivity( (below) -> !below.finishing, bottomActivityInCompanion, false /*includeBoundary*/, true /*traverseTopToBottom*/); if (underPrevious != null) { outPrevActivities.add(underPrevious); addPreviousAdjacentActivityIfExist(underPrevious, outPrevActivities); } return true; } } final TaskFragment currTF = currentActivity.getTaskFragment(); if (prevActivity == null) { // No previous activity in this Task nor TaskFragment, it can still predict if previous // task exists. return true; } // Add possible adjacent activity if prevActivity is embedded addPreviousAdjacentActivityIfExist(prevActivity, outPrevActivities); outPrevActivities.add(prevActivity); return true; } private static void addPreviousAdjacentActivityIfExist(@NonNull ActivityRecord prevActivity, @NonNull ArrayList<ActivityRecord> outPrevActivities) { final TaskFragment prevTF = prevActivity.getTaskFragment(); if (currTF != prevTF && prevTF != null) { if (prevTF == null || prevTF.asTask() != null) { return; } final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment(); if (prevTFAdjacent != null) { if (prevTFAdjacent == currTF) { outPrevActivities.clear(); // No more activity in task, so it can predict if previous task exists. // Otherwise, unable to predict what will happen when app receive // back key, skip animation. return currentTask.getActivity((below) -> !below.finishing, prevActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/) == null; } else { if (prevTFAdjacent == null || prevTFAdjacent.asTask() != null) { return; } final ActivityRecord prevActivityAdjacent = prevTFAdjacent.getTopNonFinishingActivity(); if (prevActivityAdjacent != null) { outPrevActivities.add(prevActivityAdjacent); } else { // Don't know what will happen. outPrevActivities.clear(); return false; } } } } outPrevActivities.add(prevActivity); return true; } private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity, @NonNull ArrayList<ActivityRecord> outList) { Loading
services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +104 −0 Original line number Diff line number Diff line Loading @@ -241,6 +241,110 @@ public class BackNavigationControllerTests extends WindowTestsBase { .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); } @Test public void backTypeCrossActivityInTaskFragment() { final Task task = createTask(mDefaultDisplay); final TaskFragment tf1 = createTaskFragmentWithActivity(task); final TaskFragment tf2 = createTaskFragmentWithActivity(task); final ArrayList<ActivityRecord> outPrevActivities = new ArrayList<>(); ActivityRecord prevAr = tf1.getTopMostActivity(); ActivityRecord topAr = tf2.getTopMostActivity(); boolean predictable; // Stacked + no Companion => predict for previous activity. // TF2 // TF1 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); assertTrue(predictable); outPrevActivities.clear(); // Stacked + companion => predict for previous task tf2.setCompanionTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); tf2.setCompanionTaskFragment(null); // Adjacent + no companion => unable to predict // TF1 | TF2 tf1.setAdjacentTaskFragment(tf2); tf2.setAdjacentTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertFalse(predictable); predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertFalse(predictable); // Adjacent + companion => predict for previous task tf1.setCompanionTaskFragment(tf2); tf2.setCompanionTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); // reset tf1.setAdjacentTaskFragment(null); tf2.setAdjacentTaskFragment(null); tf1.setCompanionTaskFragment(null); tf2.setCompanionTaskFragment(null); final TaskFragment tf3 = new TaskFragmentBuilder(mAtm) .createActivityCount(2) .setParentTask(task) .build(); topAr = tf3.getTopMostActivity(); prevAr = tf3.getBottomMostActivity(); // Stacked => predict for previous activity. // TF3 // TF2 // TF1 predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); assertTrue(predictable); // reset outPrevActivities.clear(); // Adjacent => predict for previous activity. // TF2 | TF3 // TF1 tf2.setAdjacentTaskFragment(tf3); tf3.setAdjacentTaskFragment(tf2); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); assertTrue(predictable); // reset outPrevActivities.clear(); tf2.setAdjacentTaskFragment(null); tf3.setAdjacentTaskFragment(null); final TaskFragment tf4 = createTaskFragmentWithActivity(task); // Stacked + companion => predict for previous activity below companion. // Tf4 // TF3 // TF2 // TF1 tf4.setCompanionTaskFragment(tf3); tf3.setCompanionTaskFragment(tf4); topAr = tf4.getTopMostActivity(); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(tf2.getTopMostActivity())); assertTrue(predictable); } @Test public void backTypeDialogCloseWhenBackFromDialog() { DialogCloseTestCase testCase = createTopTaskWithActivityAndDialog(); Loading