1 /*
2  * Copyright (C) 2017 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.view.inputmethod.cts.util;
18 
19 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
20 
21 import android.app.Activity;
22 import android.content.Intent;
23 import android.os.Bundle;
24 import android.view.View;
25 import android.view.Window;
26 import android.view.WindowManager;
27 
28 import androidx.annotation.AnyThread;
29 import androidx.annotation.NonNull;
30 import androidx.annotation.UiThread;
31 import androidx.test.InstrumentationRegistry;
32 
33 import java.util.concurrent.atomic.AtomicBoolean;
34 import java.util.concurrent.atomic.AtomicReference;
35 import java.util.function.Function;
36 
37 public final class TestActivity extends Activity {
38 
39     private static final AtomicReference<Function<TestActivity, View>> sInitializer =
40             new AtomicReference<>();
41 
42     private Function<TestActivity, View> mInitializer = null;
43 
44     private AtomicBoolean mIgnoreBackKey = new AtomicBoolean();
45 
46     private long mOnBackPressedCallCount;
47 
48     /**
49      * Controls how {@link #onBackPressed()} behaves.
50      *
51      * <p>TODO: Use {@link android.app.AppComponentFactory} instead to customise the behavior of
52      * {@link TestActivity}.</p>
53      *
54      * @param ignore {@code true} when {@link TestActivity} should do nothing when
55      *               {@link #onBackPressed()} is called
56      */
57     @AnyThread
setIgnoreBackKey(boolean ignore)58     public void setIgnoreBackKey(boolean ignore) {
59         mIgnoreBackKey.set(ignore);
60     }
61 
62     @UiThread
getOnBackPressedCallCount()63     public long getOnBackPressedCallCount() {
64         return mOnBackPressedCallCount;
65     }
66 
67     /**
68      * {@inheritDoc}
69      */
70     @Override
onCreate(Bundle savedInstanceState)71     protected void onCreate(Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73         if (mInitializer == null) {
74             mInitializer = sInitializer.get();
75         }
76         // Currently SOFT_INPUT_STATE_UNSPECIFIED isn't appropriate for CTS test because there is no
77         // clear spec about how it behaves.  In order to make our tests deterministic, currently we
78         // must use SOFT_INPUT_STATE_UNCHANGED.
79         // TODO(Bug 77152727): Remove the following code once we define how
80         // SOFT_INPUT_STATE_UNSPECIFIED actually behaves.
81         setSoftInputState(SOFT_INPUT_STATE_UNCHANGED);
82         setContentView(mInitializer.apply(this));
83     }
84 
85     /**
86      * {@inheritDoc}
87      */
88     @Override
onBackPressed()89     public void onBackPressed() {
90         ++mOnBackPressedCallCount;
91         if (mIgnoreBackKey.get()) {
92             return;
93         }
94         super.onBackPressed();
95     }
96 
97     /**
98      * Launches {@link TestActivity} with the given initialization logic for content view.
99      *
100      * <p>As long as you are using {@link androidx.test.runner.AndroidJUnitRunner}, the test
101      * runner automatically calls {@link Activity#finish()} for the {@link Activity} launched when
102      * the test finished.  You do not need to explicitly call {@link Activity#finish()}.</p>
103      *
104      * @param activityInitializer initializer to supply {@link View} to be passed to
105      *                           {@link Activity#setContentView(View)}
106      * @return {@link TestActivity} launched
107      */
startSync( @onNull Function<TestActivity, View> activityInitializer)108     public static TestActivity startSync(
109             @NonNull Function<TestActivity, View> activityInitializer) {
110         return startSync(activityInitializer, 0 /* noAnimation */);
111     }
112 
113     /**
114      * Launches {@link TestActivity} with the given initialization logic for content view.
115      *
116      * <p>As long as you are using {@link androidx.test.runner.AndroidJUnitRunner}, the test
117      * runner automatically calls {@link Activity#finish()} for the {@link Activity} launched when
118      * the test finished.  You do not need to explicitly call {@link Activity#finish()}.</p>
119      *
120      * @param activityInitializer initializer to supply {@link View} to be passed to
121      *                           {@link Activity#setContentView(View)}
122      * @param additionalFlags flags to be set to {@link Intent#setFlags(int)}
123      * @return {@link TestActivity} launched
124      */
startSync( @onNull Function<TestActivity, View> activityInitializer, int additionalFlags)125     public static TestActivity startSync(
126             @NonNull Function<TestActivity, View> activityInitializer,
127             int additionalFlags) {
128         sInitializer.set(activityInitializer);
129         final Intent intent = new Intent()
130                 .setAction(Intent.ACTION_MAIN)
131                 .setClass(InstrumentationRegistry.getContext(), TestActivity.class)
132                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
133                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
134                 .addFlags(additionalFlags);
135         return (TestActivity) InstrumentationRegistry
136                 .getInstrumentation().startActivitySync(intent);
137     }
138 
139     /**
140      * Updates {@link WindowManager.LayoutParams#softInputMode}.
141      *
142      * @param newState One of {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNSPECIFIED},
143      *                 {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED},
144      *                 {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_HIDDEN},
145      *                 {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN},
146      *                 {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_VISIBLE},
147      *                 {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE}
148      */
setSoftInputState(int newState)149     private void setSoftInputState(int newState) {
150         final Window window = getWindow();
151         final int currentSoftInputMode = window.getAttributes().softInputMode;
152         final int newSoftInputMode =
153                 (currentSoftInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE)
154                         | newState;
155         window.setSoftInputMode(newSoftInputMode);
156     }
157 }
158