1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.server.wm.backnavigation;
18 
19 import static android.server.wm.WindowManagerState.STATE_RESUMED;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
22 import static android.window.BackNavigationInfo.TYPE_CALLBACK;
23 import static android.window.BackNavigationInfo.TYPE_CROSS_ACTIVITY;
24 import static android.window.BackNavigationInfo.TYPE_CROSS_TASK;
25 import static android.window.BackNavigationInfo.TYPE_DIALOG_CLOSE;
26 import static android.window.BackNavigationInfo.TYPE_RETURN_TO_HOME;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertTrue;
30 import static org.junit.Assume.assumeTrue;
31 
32 import android.app.Activity;
33 import android.app.AlertDialog;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.graphics.Color;
37 import android.os.Bundle;
38 import android.platform.test.annotations.Presubmit;
39 import android.server.wm.ActivityManagerTestBase;
40 import android.server.wm.ComponentNameUtils;
41 import android.server.wm.MockImeHelper;
42 import android.view.Gravity;
43 import android.view.ViewGroup;
44 import android.view.inputmethod.InputMethodManager;
45 import android.widget.EditText;
46 import android.widget.FrameLayout;
47 import android.widget.LinearLayout;
48 import android.widget.PopupWindow;
49 
50 import androidx.annotation.Nullable;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 
55 /**
56  * Integration test for back navigation mode
57  *
58  *  <p>Build/Install/Run:
59  *      atest CtsWindowManagerDeviceBackNavigation:BackGestureInvokedTest
60  */
61 @Presubmit
62 @android.server.wm.annotation.Group1
63 public class BackGestureInvokedTest extends ActivityManagerTestBase {
64     private TestActivitySession<BackInvokedActivity> mActivitySession;
65     private BackInvokedActivity mActivity;
66 
67     @Before
setUp()68     public void setUp() throws Exception {
69         super.setUp();
70         enableAndAssumeGestureNavigationMode();
71 
72         mActivitySession = createManagedTestActivitySession();
73         mActivitySession.launchTestActivityOnDisplaySync(
74                 BackInvokedActivity.class, DEFAULT_DISPLAY);
75         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
76         mActivity = mActivitySession.getActivity();
77         mWmState.waitForActivityState(mActivity.getComponentName(), STATE_RESUMED);
78         mWmState.assertVisibility(mActivity.getComponentName(), true);
79     }
80 
81     @Test
popupWindowDismissedOnBackGesture()82     public void popupWindowDismissedOnBackGesture() {
83         mActivitySession.runOnMainSyncAndWait(mActivity::addPopupWindow);
84         final int taskId = mWmState.getTaskByActivity(mActivity.getComponentName()).getTaskId();
85         assertTrue("Popup window must show", mWmState.waitFor(state ->
86                 mWmState.getMatchingWindows(ws -> ws.getType() == TYPE_APPLICATION_PANEL
87                                 && ws.getStackId() == taskId && ws.isSurfaceShown())
88                         .findAny().isPresent(), "popup window show"));
89         triggerBackEventByGesture(DEFAULT_DISPLAY);
90 
91         assertTrue("Popup window must be removed", mWmState.waitFor(state ->
92                         mWmState.getMatchingWindows(ws -> ws.getType() == TYPE_APPLICATION_PANEL
93                                 && ws.getStackId() == taskId).findAny().isEmpty(),
94                 "popup window removed"));
95 
96         // activity remain focused
97         mWmState.assertFocusedActivity("Top activity must be focused",
98                 mActivity.getComponentName());
99         final String windowName = ComponentNameUtils.getWindowName(mActivity.getComponentName());
100         mWmState.assertFocusedWindow(
101                 "Top activity window must be focused window.", windowName);
102     }
103 
104     @Test
testBackToHome()105     public void testBackToHome() {
106         triggerBackEventByGesture(DEFAULT_DISPLAY);
107         mWmState.waitAndAssertActivityRemoved(mActivity.getComponentName());
108 
109         assertEquals(TYPE_RETURN_TO_HOME, mWmState.getBackNavigationState().getLastBackType());
110     }
111 
112     @Test
testBackToTask()113     public void testBackToTask() {
114         final ComponentName
115                 componentName = new ComponentName(mContext, NewTaskActivity.class);
116         launchActivityInNewTask(componentName);
117         mWmState.waitForActivityState(componentName, STATE_RESUMED);
118         mWmState.assertVisibility(componentName, true);
119         mWmState.waitForFocusedActivity("Wait for launched activity to be in front.",
120                 componentName);
121         triggerBackEventByGesture(DEFAULT_DISPLAY);
122         mWmState.waitForActivityState(mActivity.getComponentName(), STATE_RESUMED);
123 
124         assertEquals(TYPE_CROSS_TASK, mWmState.getBackNavigationState().getLastBackType());
125     }
126 
127     @Test
testBackToActivity()128     public void testBackToActivity() {
129         final ComponentName componentName = new ComponentName(mContext, SecondActivity.class);
130         launchActivity(componentName);
131         mWmState.waitForActivityState(componentName, STATE_RESUMED);
132         mWmState.assertVisibility(componentName, true);
133         mWmState.waitForFocusedActivity("Wait for launched activity to be in front.",
134                 componentName);
135         triggerBackEventByGesture(DEFAULT_DISPLAY);
136         mWmState.waitForActivityState(mActivity.getComponentName(), STATE_RESUMED);
137 
138         assertEquals(TYPE_CROSS_ACTIVITY, mWmState.getBackNavigationState().getLastBackType());
139     }
140 
141     @Test
testDialogDismissed()142     public void testDialogDismissed() {
143         mInstrumentation.runOnMainSync(() -> {
144             new AlertDialog.Builder(mActivity)
145                     .setTitle("Dialog")
146                     .show();
147         });
148         mInstrumentation.waitForIdleSync();
149 
150         triggerBackEventByGesture(DEFAULT_DISPLAY);
151 
152         // activity remain focused
153         mWmState.waitForFocusedActivity("top activity to be focused",
154                 mActivity.getComponentName());
155         mWmState.assertFocusedActivity("Top activity must be focused",
156                 mActivity.getComponentName());
157 
158         assertEquals(TYPE_DIALOG_CLOSE, mWmState.getBackNavigationState().getLastBackType());
159     }
160 
161     @Test
testImeDismissed()162     public void testImeDismissed() {
163         assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
164         MockImeHelper.createManagedMockImeSession(this);
165 
166         ComponentName componentName = new ComponentName(mContext, ImeTestActivity.class);
167         launchActivity(componentName);
168         mWmState.waitForActivityState(componentName, STATE_RESUMED);
169         mWmState.assertVisibility(componentName, true);
170         mWmState.waitAndAssertImeWindowShownOnDisplay(DEFAULT_DISPLAY);
171 
172         triggerBackEventByGesture(DEFAULT_DISPLAY);
173         mWmState.waitAndAssertImeWindowHiddenOnDisplay(DEFAULT_DISPLAY);
174 
175         assertEquals(TYPE_CALLBACK, mWmState.getBackNavigationState().getLastBackType());
176     }
177 
178     public static class BackInvokedActivity extends Activity {
179 
180         private FrameLayout mContentView;
181 
getContentView()182         private FrameLayout getContentView() {
183             return mContentView;
184         }
185 
186         @Override
onCreate(@ullable Bundle savedInstanceState)187         protected void onCreate(@Nullable Bundle savedInstanceState) {
188             super.onCreate(savedInstanceState);
189             mContentView = new FrameLayout(this);
190             setContentView(mContentView);
191         }
192 
addPopupWindow()193         public void addPopupWindow() {
194             FrameLayout contentView = new FrameLayout(this);
195             contentView.setBackgroundColor(Color.RED);
196             PopupWindow popup = new PopupWindow(contentView, ViewGroup.LayoutParams.MATCH_PARENT,
197                     ViewGroup.LayoutParams.MATCH_PARENT);
198 
199             // Ensure the window can get the focus by marking the views as focusable
200             popup.setFocusable(true);
201             contentView.setFocusable(true);
202             popup.showAtLocation(getContentView(), Gravity.FILL, 0, 0);
203         }
204     }
205 
206     public static class NewTaskActivity extends Activity {
207         private FrameLayout mContentView;
208 
209         @Override
onCreate(@ullable Bundle savedInstanceState)210         protected void onCreate(@Nullable Bundle savedInstanceState) {
211             super.onCreate(savedInstanceState);
212             mContentView = new FrameLayout(this);
213             mContentView.setBackgroundColor(Color.GREEN);
214             setContentView(mContentView);
215         }
216     }
217 
218     public static class SecondActivity extends Activity {
219         private FrameLayout mContentView;
220 
221         @Override
onCreate(@ullable Bundle savedInstanceState)222         protected void onCreate(@Nullable Bundle savedInstanceState) {
223             super.onCreate(savedInstanceState);
224             mContentView = new FrameLayout(this);
225             mContentView.setBackgroundColor(Color.BLUE);
226             setContentView(mContentView);
227         }
228     }
229 
230     public static class ImeTestActivity extends Activity {
231         EditText mEditText;
232 
233         @Override
onCreate(Bundle icicle)234         protected void onCreate(Bundle icicle) {
235             super.onCreate(icicle);
236             mEditText = new EditText(this);
237             final LinearLayout layout = new LinearLayout(this);
238             layout.setOrientation(LinearLayout.VERTICAL);
239             layout.addView(mEditText);
240             if (mEditText.requestFocus()) {
241                 InputMethodManager imm = (InputMethodManager)
242                         getSystemService(Context.INPUT_METHOD_SERVICE);
243                 imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
244             }
245             setContentView(layout);
246         }
247     }
248 }
249