blob: d76f99e538e1c8f924fbc71ff735820caa4725ce [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.inputmethod.cts.util;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
import android.app.AlertDialog;
import android.app.Instrumentation;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.util.Size;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.TextView;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* A utility class to write tests that depend on some capabilities related to navigation bar.
*/
public class NavigationBarInfo {
private static final long BEFORE_SCREENSHOT_WAIT = TimeUnit.SECONDS.toMillis(1);
private final boolean mHasBottomNavigationBar;
private final int mBottomNavigationBerHeight;
private final boolean mSupportsNavigationBarColor;
private final boolean mSupportsLightNavigationBar;
private final boolean mSupportsDimmingWindowLightNavigationBarOverride;
private NavigationBarInfo(boolean hasBottomNavigationBar, int bottomNavigationBerHeight,
boolean supportsNavigationBarColor, boolean supportsLightNavigationBar,
boolean supportsDimmingWindowLightNavigationBarOverride) {
mHasBottomNavigationBar = hasBottomNavigationBar;
mBottomNavigationBerHeight = bottomNavigationBerHeight;
mSupportsNavigationBarColor = supportsNavigationBarColor;
mSupportsLightNavigationBar = supportsLightNavigationBar;
mSupportsDimmingWindowLightNavigationBarOverride =
supportsDimmingWindowLightNavigationBarOverride;
}
@Nullable
private static NavigationBarInfo sInstance;
/**
* Returns a {@link NavigationBarInfo} instance.
*
* <p>As a performance optimizations, this method internally caches the previous result and
* returns the same result if this gets called multiple times.</p>
*
* <p>Note: The caller should be aware that this method may launch {@link TestActivity}
* internally.</p>
*
* @return {@link NavigationBarInfo} obtained with {@link TestActivity}.
*/
@NonNull
public static NavigationBarInfo getInstance() throws Exception {
if (sInstance != null) {
return sInstance;
}
final int actualBottomInset;
{
final AtomicReference<View> viewRef = new AtomicReference<>();
TestActivity.startSync(activity -> {
final View view = new View(activity);
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
viewRef.set(view);
return view;
}, Intent.FLAG_ACTIVITY_NO_ANIMATION);
final View view = viewRef.get();
final WindowInsets windowInsets = getOnMainSync(() -> view.getRootWindowInsets());
if (!windowInsets.hasStableInsets() || windowInsets.getStableInsetBottom() <= 0) {
return new NavigationBarInfo(false, 0, false, false, false);
}
final Size displaySize = getOnMainSync(() -> {
final Point size = new Point();
view.getDisplay().getRealSize(size);
return new Size(size.x, size.y);
});
final Rect viewBoundsOnScreen = getOnMainSync(() -> {
final int[] xy = new int[2];
view.getLocationOnScreen(xy);
final int x = xy[0];
final int y = xy[1];
return new Rect(x, y, x + view.getWidth(), y + view.getHeight());
});
actualBottomInset = displaySize.getHeight() - viewBoundsOnScreen.bottom;
if (actualBottomInset != windowInsets.getStableInsetBottom()) {
sInstance = new NavigationBarInfo(false, 0, false, false, false);
return sInstance;
}
}
final boolean colorSupported = NavigationBarColorVerifier.verify(
color -> getBottomNavigationBarBitmapForActivity(
color, false /* lightNavigationBar */, actualBottomInset,
false /* showDimmingDialog */)).getResult()
== NavigationBarColorVerifier.ResultType.SUPPORTED;
final boolean lightModeSupported = LightNavigationBarVerifier.verify(
(color, lightNavigationBar) -> getBottomNavigationBarBitmapForActivity(
color, lightNavigationBar, actualBottomInset,
false /* showDimmingDialog */)).getResult()
== LightNavigationBarVerifier.ResultType.SUPPORTED;
final boolean dimmingSupported = lightModeSupported && LightNavigationBarVerifier.verify(
(color, lightNavigationBar) -> getBottomNavigationBarBitmapForActivity(
color, lightNavigationBar, actualBottomInset,
true /* showDimmingDialog */)).getResult()
== LightNavigationBarVerifier.ResultType.NOT_SUPPORTED;
sInstance = new NavigationBarInfo(
true, actualBottomInset, colorSupported, lightModeSupported, dimmingSupported);
return sInstance;
}
@NonNull
private static Bitmap getBottomNavigationBarBitmapForActivity(
@ColorInt int navigationBarColor, boolean lightNavigationBar,
int bottomNavigationBarHeight, boolean showDimmingDialog) throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
final TestActivity testActivity = TestActivity.startSync(activity -> {
final View view = new View(activity);
activity.getWindow().setNavigationBarColor(navigationBarColor);
// Set/unset SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR if necessary.
final int currentVis = view.getSystemUiVisibility();
final int newVis = (currentVis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
| (lightNavigationBar ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR : 0);
if (currentVis != newVis) {
view.setSystemUiVisibility(newVis);
}
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return view;
});
instrumentation.waitForIdleSync();
final AlertDialog dialog;
if (showDimmingDialog) {
dialog = getOnMainSync(() -> {
final TextView textView = new TextView(testActivity);
textView.setText("Dimming Window");
final AlertDialog alertDialog = new AlertDialog.Builder(testActivity)
.setView(textView)
.create();
alertDialog.getWindow().setFlags(FLAG_DIM_BEHIND, FLAG_DIM_BEHIND);
alertDialog.show();
return alertDialog;
});
} else {
dialog = null;
}
Thread.sleep(BEFORE_SCREENSHOT_WAIT);
final Bitmap fullBitmap = getInstrumentation().getUiAutomation().takeScreenshot();
final Bitmap bottomNavBarBitmap = Bitmap.createBitmap(fullBitmap, 0,
fullBitmap.getHeight() - bottomNavigationBarHeight, fullBitmap.getWidth(),
bottomNavigationBarHeight);
if (dialog != null) {
// Dialog#dismiss() is a thread safe method so we don't need to call this from the UI
// thread.
dialog.dismiss();
}
return bottomNavBarBitmap;
}
/**
* @return {@code true} if this device seems to have bottom navigation bar.
*/
public boolean hasBottomNavigationBar() {
return mHasBottomNavigationBar;
}
/**
* @return height of the bottom navigation bar. Valid only when
* {@link #hasBottomNavigationBar()} returns {@code true}
*/
public int getBottomNavigationBerHeight() {
return mBottomNavigationBerHeight;
}
/**
* @return {@code true} if {@link android.view.Window#setNavigationBarColor(int)} seem to take
* effect on this device. Valid only when {@link #hasBottomNavigationBar()} returns
* {@code true}
*/
public boolean supportsNavigationBarColor() {
return mSupportsNavigationBarColor;
}
/**
* @return {@code true} if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} seem to
* take effect on this device. Valid only when {@link #hasBottomNavigationBar()} returns
* {@code true}
*/
public boolean supportsLightNavigationBar() {
return mSupportsLightNavigationBar;
}
/**
* @return {@code true} if {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} will be
* canceled when a {@link android.view.Window} with
* {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} is shown.
*/
public boolean supportsDimmingWindowLightNavigationBarOverride() {
return mSupportsDimmingWindowLightNavigationBarOverride;
}
}