Home | History | Annotate | Download | only in cts
      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;
     18 
     19 import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
     20 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
     21 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
     22 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
     23 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
     24 import static android.view.inputmethod.cts.util.LightNavigationBarVerifier.expectLightNavigationBarNotSupported;
     25 import static android.view.inputmethod.cts.util.LightNavigationBarVerifier.expectLightNavigationBarSupported;
     26 import static android.view.inputmethod.cts.util.NavigationBarColorVerifier.expectNavigationBarColorNotSupported;
     27 import static android.view.inputmethod.cts.util.NavigationBarColorVerifier.expectNavigationBarColorSupported;
     28 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
     29 
     30 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
     31 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
     32 import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
     33 
     34 import static org.junit.Assert.assertNotNull;
     35 import static org.junit.Assume.assumeTrue;
     36 
     37 import android.app.Activity;
     38 import android.app.AlertDialog;
     39 import android.app.UiAutomation;
     40 import android.graphics.Bitmap;
     41 import android.graphics.Color;
     42 import android.os.Process;
     43 import androidx.annotation.ColorInt;
     44 import androidx.annotation.NonNull;
     45 import android.support.test.InstrumentationRegistry;
     46 import android.support.test.filters.MediumTest;
     47 import android.support.test.runner.AndroidJUnit4;
     48 import android.text.TextUtils;
     49 import android.view.View;
     50 import android.view.ViewGroup;
     51 import android.view.inputmethod.EditorInfo;
     52 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
     53 import android.view.inputmethod.cts.util.ImeAwareEditText;
     54 import android.view.inputmethod.cts.util.NavigationBarInfo;
     55 import android.view.inputmethod.cts.util.TestActivity;
     56 import android.widget.LinearLayout;
     57 import android.widget.TextView;
     58 
     59 import com.android.cts.mockime.ImeEventStream;
     60 import com.android.cts.mockime.ImeLayoutInfo;
     61 import com.android.cts.mockime.ImeSettings;
     62 import com.android.cts.mockime.MockImeSession;
     63 
     64 import org.junit.BeforeClass;
     65 import org.junit.Before;
     66 import org.junit.Test;
     67 import org.junit.runner.RunWith;
     68 
     69 import java.util.concurrent.TimeUnit;
     70 
     71 @MediumTest
     72 @RunWith(AndroidJUnit4.class)
     73 public class NavigationBarColorTest extends EndToEndImeTestBase {
     74     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     75     private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
     76 
     77     private static final String TEST_MARKER = "android.view.inputmethod.cts.NavigationBarColorTest";
     78 
     79     private static void updateSystemUiVisibility(@NonNull View view, int flags, int mask) {
     80         final int currentFlags = view.getSystemUiVisibility();
     81         final int newFlags = (currentFlags & ~mask) | (flags & mask);
     82         if (currentFlags != newFlags) {
     83             view.setSystemUiVisibility(newFlags);
     84         }
     85     }
     86 
     87     @BeforeClass
     88     public static void initializeNavigationBarInfo() throws Exception {
     89         // Make sure that NavigationBarInfo is initialized before
     90         // EndToEndImeTestBase#showStateInitializeActivity().
     91         NavigationBarInfo.getInstance();
     92     }
     93 
     94     // TODO(b/37502066): Merge this back to initializeNavigationBarInfo() once b/37502066 is fixed.
     95     @Before
     96     public void checkNavigationBar() throws Exception {
     97         assumeTrue("This test does not make sense if there is no navigation bar",
     98                 NavigationBarInfo.getInstance().hasBottomNavigationBar());
     99 
    100         assumeTrue("This test does not make sense if custom navigation bar color is not supported"
    101                         + " even for typical Activity",
    102                 NavigationBarInfo.getInstance().supportsNavigationBarColor());
    103     }
    104 
    105     /**
    106      * Represents test scenarios regarding how a {@link android.view.Window} that has
    107      * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} interacts with a different
    108      * {@link android.view.Window} that has
    109      * {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR}.
    110      */
    111     private enum DimmingTestMode {
    112         /**
    113          * No {@link AlertDialog} is shown when testing.
    114          */
    115         NO_DIMMING_DIALOG,
    116         /**
    117          * An {@link AlertDialog} that has dimming effect is shown above the IME window.
    118          */
    119         DIMMING_DIALOG_ABOVE_IME,
    120         /**
    121          * An {@link AlertDialog} that has dimming effect is shown behind the IME window.
    122          */
    123         DIMMING_DIALOG_BEHIND_IME,
    124     }
    125 
    126     @NonNull
    127     public TestActivity launchTestActivity(@ColorInt int navigationBarColor,
    128             boolean lightNavigationBar, @NonNull DimmingTestMode dimmingTestMode) {
    129         return TestActivity.startSync(activity -> {
    130             final View contentView;
    131             switch (dimmingTestMode) {
    132                 case NO_DIMMING_DIALOG:
    133                 case DIMMING_DIALOG_ABOVE_IME: {
    134                     final LinearLayout layout = new LinearLayout(activity);
    135                     layout.setOrientation(LinearLayout.VERTICAL);
    136                     final ImeAwareEditText editText = new ImeAwareEditText(activity);
    137                     editText.setPrivateImeOptions(TEST_MARKER);
    138                     editText.setHint("editText");
    139                     editText.requestFocus();
    140                     editText.scheduleShowSoftInput();
    141                     layout.addView(editText);
    142                     contentView = layout;
    143                     break;
    144                 }
    145                 case DIMMING_DIALOG_BEHIND_IME: {
    146                     final View view = new View(activity);
    147                     view.setLayoutParams(new ViewGroup.LayoutParams(
    148                             ViewGroup.LayoutParams.MATCH_PARENT,
    149                             ViewGroup.LayoutParams.MATCH_PARENT));
    150                     contentView = view;
    151                     break;
    152                 }
    153                 default:
    154                     throw new IllegalStateException("unknown mode=" + dimmingTestMode);
    155             }
    156             activity.getWindow().setNavigationBarColor(navigationBarColor);
    157             updateSystemUiVisibility(contentView,
    158                     lightNavigationBar ? SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0,
    159                     SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
    160             return contentView;
    161         });
    162     }
    163 
    164     private AutoCloseable showDialogIfNecessary(
    165             @NonNull Activity activity, @NonNull DimmingTestMode dimmingTestMode) {
    166         switch (dimmingTestMode) {
    167             case NO_DIMMING_DIALOG:
    168                 // Dialog is not necessary.
    169                 return () -> { };
    170             case DIMMING_DIALOG_ABOVE_IME: {
    171                 final AlertDialog alertDialog = getOnMainSync(() -> {
    172                     final TextView textView = new TextView(activity);
    173                     textView.setText("Dummy");
    174                     textView.requestFocus();
    175                     final AlertDialog dialog = new AlertDialog.Builder(activity)
    176                             .setView(textView)
    177                             .create();
    178                     dialog.getWindow().setFlags(FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM,
    179                             FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
    180                     dialog.show();
    181                     return dialog;
    182                 });
    183                 // Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
    184                 // the UI thread.
    185                 return () -> alertDialog.dismiss();
    186             }
    187             case DIMMING_DIALOG_BEHIND_IME: {
    188                 final AlertDialog alertDialog = getOnMainSync(() -> {
    189                     final ImeAwareEditText editText = new ImeAwareEditText(activity);
    190                     editText.setPrivateImeOptions(TEST_MARKER);
    191                     editText.setHint("editText");
    192                     editText.requestFocus();
    193                     editText.scheduleShowSoftInput();
    194                     final AlertDialog dialog = new AlertDialog.Builder(activity)
    195                             .setView(editText)
    196                             .create();
    197                     dialog.getWindow().setFlags(FLAG_DIM_BEHIND,
    198                             FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
    199                     dialog.show();
    200                     return dialog;
    201                 });
    202                 // Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
    203                 // the UI thread.
    204                 return () -> alertDialog.dismiss();
    205             }
    206             default:
    207                 throw new IllegalStateException("unknown mode=" + dimmingTestMode);
    208         }
    209     }
    210 
    211     @NonNull
    212     private ImeSettings.Builder imeSettingForSolidNavigationBar(@ColorInt int navigationBarColor,
    213             boolean lightNavigationBar) {
    214         final ImeSettings.Builder builder = new ImeSettings.Builder();
    215         builder.setNavigationBarColor(navigationBarColor);
    216         if (lightNavigationBar) {
    217             builder.setInputViewSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
    218         }
    219         return builder;
    220     }
    221 
    222     @NonNull
    223     private ImeSettings.Builder imeSettingForFloatingIme(@ColorInt int navigationBarColor,
    224             boolean lightNavigationBar) {
    225         final ImeSettings.Builder builder = new ImeSettings.Builder();
    226         builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
    227         // As documented, Window#setNavigationBarColor() is actually ignored when the IME window
    228         // does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS.  We are calling setNavigationBarColor()
    229         // to ensure it.
    230         builder.setNavigationBarColor(navigationBarColor);
    231         if (lightNavigationBar) {
    232             // As documented, SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR is actually ignored when the IME
    233             // window does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS.  We set this flag just to
    234             // ensure it.
    235             builder.setInputViewSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
    236         }
    237         return builder;
    238     }
    239 
    240     @NonNull
    241     private Bitmap getNavigationBarBitmap(@NonNull ImeSettings.Builder builder,
    242             @ColorInt int appNavigationBarColor, boolean appLightNavigationBar,
    243             int navigationBarHeight, @NonNull DimmingTestMode dimmingTestMode)
    244             throws Exception {
    245         final UiAutomation uiAutomation =
    246                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
    247         try (MockImeSession imeSession = MockImeSession.create(
    248                 InstrumentationRegistry.getContext(), uiAutomation, builder)) {
    249             final ImeEventStream stream = imeSession.openEventStream();
    250 
    251             final TestActivity activity = launchTestActivity(
    252                     appNavigationBarColor, appLightNavigationBar, dimmingTestMode);
    253 
    254             // Show AlertDialog if necessary, based on the dimming test mode.
    255             try (AutoCloseable dialogCloser = showDialogIfNecessary(
    256                     activity, dimmingTestMode)) {
    257                 // Wait until the MockIme gets bound to the TestActivity.
    258                 expectBindInput(stream, Process.myPid(), TIMEOUT);
    259 
    260                 // Wait until "onStartInput" gets called for the EditText.
    261                 expectEvent(stream, event -> {
    262                     if (!TextUtils.equals("onStartInputView", event.getEventName())) {
    263                         return false;
    264                     }
    265                     final EditorInfo editorInfo = event.getArguments().getParcelable("editorInfo");
    266                     return TextUtils.equals(TEST_MARKER, editorInfo.privateImeOptions);
    267                 }, TIMEOUT);
    268 
    269                 // Wait until MockIme's layout becomes stable.
    270                 final ImeLayoutInfo lastLayout =
    271                         waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
    272                 assertNotNull(lastLayout);
    273 
    274                 final Bitmap bitmap = uiAutomation.takeScreenshot();
    275                 return Bitmap.createBitmap(bitmap, 0, bitmap.getHeight() - navigationBarHeight,
    276                         bitmap.getWidth(), navigationBarHeight);
    277             }
    278         }
    279     }
    280 
    281     @Test
    282     public void testSetNavigationBarColor() throws Exception {
    283         final NavigationBarInfo info = NavigationBarInfo.getInstance();
    284 
    285         // Make sure that Window#setNavigationBarColor() works for IMEs.
    286         expectNavigationBarColorSupported(color ->
    287                 getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, false),
    288                         Color.BLACK, false, info.getBottomNavigationBerHeight(),
    289                         DimmingTestMode.NO_DIMMING_DIALOG));
    290 
    291         // Make sure that IME's navigation bar can be transparent
    292         expectNavigationBarColorSupported(color ->
    293                 getNavigationBarBitmap(imeSettingForSolidNavigationBar(Color.TRANSPARENT, false),
    294                         color, false, info.getBottomNavigationBerHeight(),
    295                         DimmingTestMode.NO_DIMMING_DIALOG));
    296 
    297         // Make sure that Window#setNavigationBarColor() is ignored when
    298         // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is unset
    299         expectNavigationBarColorNotSupported(color ->
    300                 getNavigationBarBitmap(imeSettingForFloatingIme(color, false),
    301                         Color.BLACK, false, info.getBottomNavigationBerHeight(),
    302                         DimmingTestMode.NO_DIMMING_DIALOG));
    303     }
    304 
    305     @Test
    306     public void testLightNavigationBar() throws Exception {
    307         final NavigationBarInfo info = NavigationBarInfo.getInstance();
    308 
    309         assumeTrue("This test does not make sense if light navigation bar is not supported"
    310                 + " even for typical Activity", info.supportsLightNavigationBar());
    311 
    312         // Make sure that SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR works for IMEs (Bug 69002467).
    313         expectLightNavigationBarSupported((color, lightMode) ->
    314                 getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
    315                         Color.BLACK, false, info.getBottomNavigationBerHeight(),
    316                         DimmingTestMode.NO_DIMMING_DIALOG));
    317 
    318         // Make sure that IMEs can opt-out navigation bar custom rendering, including
    319         // SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, by un-setting FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag
    320         // so that it can be controlled by the target application instead (Bug 69111208).
    321         expectLightNavigationBarSupported((color, lightMode) ->
    322                 getNavigationBarBitmap(imeSettingForFloatingIme(Color.BLACK, false),
    323                         color, lightMode, info.getBottomNavigationBerHeight(),
    324                         DimmingTestMode.NO_DIMMING_DIALOG));
    325     }
    326 
    327     @Test
    328     public void testDimmingWindow() throws Exception {
    329         final NavigationBarInfo info = NavigationBarInfo.getInstance();
    330 
    331         assumeTrue("This test does not make sense if dimming windows do not affect light "
    332                 + " light navigation bar for typical Activities",
    333                 info.supportsDimmingWindowLightNavigationBarOverride());
    334 
    335         // Make sure that SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR works for IMEs, even if a dimming
    336         // window is shown behind the IME window.
    337         expectLightNavigationBarSupported((color, lightMode) ->
    338                 getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
    339                         Color.BLACK, false, info.getBottomNavigationBerHeight(),
    340                         DimmingTestMode.DIMMING_DIALOG_BEHIND_IME));
    341 
    342         // If a dimming window is shown above the IME window, IME window's
    343         // SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR should be canceled.
    344         expectLightNavigationBarNotSupported((color, lightMode) ->
    345                 getNavigationBarBitmap(imeSettingForSolidNavigationBar(color, lightMode),
    346                         Color.BLACK, false, info.getBottomNavigationBerHeight(),
    347                         DimmingTestMode.DIMMING_DIALOG_ABOVE_IME));
    348     }
    349 }
    350