Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce7b8182 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Remove SystemUI ScreenshotTestRule (1/2)

This CL removes SystemUI ScreenshotTestRule now that most of what it
does was contributed back to the platform screenshot library in
ag/19135707.

Bug: 230832101
Test: atest SystemUIGoogleScreenshotTests
Change-Id: I38d01bcd35e6263752c69539dd65950d898f53de
parent 7736d682
Loading
Loading
Loading
Loading
+23 −8
Original line number Diff line number Diff line
@@ -24,22 +24,37 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onRoot
import com.android.systemui.compose.theme.SystemUITheme
import com.android.systemui.testing.screenshot.ScreenshotActivity
import com.android.systemui.testing.screenshot.ScreenshotTestRule
import com.android.systemui.testing.screenshot.ScreenshotTestSpec
import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager
import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher
import com.android.systemui.testing.screenshot.drawIntoBitmap
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import platform.test.screenshot.DeviceEmulationRule
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.MaterialYouColorsRule
import platform.test.screenshot.ScreenshotTestRule
import platform.test.screenshot.getEmulatedDevicePathConfig

/** A rule for Compose screenshot diff tests. */
class ComposeScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
    private val colorsRule = MaterialYouColorsRule()
    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
    private val screenshotRule =
        ScreenshotTestRule(
            SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
        )
    private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
    private val screenshotRule = ScreenshotTestRule(testSpec)

    private val delegate = RuleChain.outerRule(screenshotRule).around(composeRule)
    private val delegateRule =
        RuleChain.outerRule(colorsRule)
            .around(deviceEmulationRule)
            .around(screenshotRule)
            .around(composeRule)
    private val matcher = UnitTestBitmapMatcher

    override fun apply(base: Statement, description: Description): Statement {
        return delegate.apply(base, description)
        return delegateRule.apply(base, description)
    }

    /**
@@ -69,6 +84,6 @@ class ComposeScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
        composeRule.waitForIdle()

        val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
        screenshotRule.screenshotTest(goldenIdentifier, view)
        screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
    }
}
+0 −4
Original line number Diff line number Diff line
@@ -26,11 +26,7 @@ android_library {
    manifest: "AndroidManifest.xml",

    srcs: [
        // All files in this library should be in Kotlin besides some exceptions.
        "src/**/*.kt",

        // This file was forked from google3, so exceptionally it can be in Java.
        "src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java",
    ],

    resource_dirs: [
+0 −2
Original line number Diff line number Diff line
@@ -23,6 +23,4 @@
            android:exported="true"
            android:theme="@style/Theme.SystemUI.Screenshot" />
    </application>

    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
</manifest>
+64 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.systemui.testing.screenshot

import android.graphics.Bitmap
import android.graphics.Canvas
import android.os.Build
import android.view.View
import platform.test.screenshot.matchers.MSSIMMatcher
import platform.test.screenshot.matchers.PixelPerfectMatcher

/** Draw this [View] into a [Bitmap]. */
fun View.drawIntoBitmap(): Bitmap {
    val bitmap =
        Bitmap.createBitmap(
            measuredWidth,
            measuredHeight,
            Bitmap.Config.ARGB_8888,
        )
    val canvas = Canvas(bitmap)
    draw(canvas)
    return bitmap
}

/**
 * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
 * screenshot *unit* tests.
 */
val UnitTestBitmapMatcher =
    if (Build.CPU_ABI == "x86_64") {
        // Different CPU architectures can sometimes end up rendering differently, so we can't do
        // pixel-perfect matching on different architectures using the same golden. Given that our
        // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
        // x86_64 architecture and use the Structural Similarity Index on others.
        // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
        // do pixel perfect matching both at presubmit time and at development time with actual
        // devices.
        PixelPerfectMatcher()
    } else {
        MSSIMMatcher()
    }

/**
 * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
 * screenshot *unit* tests.
 *
 * We use the Structural Similarity Index for integration tests because they usually contain
 * additional information and noise that shouldn't break the test.
 */
val IntegrationTestBitmapMatcher = MSSIMMatcher()
+0 −193
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.systemui.testing.screenshot;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import android.app.UiAutomation;
import android.content.Context;
import android.provider.Settings;
import android.util.Log;

import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.IdlingRegistry;
import androidx.test.espresso.IdlingResource;

import org.json.JSONObject;
import org.junit.function.ThrowingRunnable;

import java.util.HashMap;
import java.util.Map;

/*
 * Note: This file was forked from
 * google3/third_party/java_src/android_libs/material_components/screenshot_tests/java/android/
 * support/design/scuba/color/DynamicColorsTestUtils.java.
 */

/** Utility that helps change the dynamic system colors for testing. */
@RequiresApi(32)
public class DynamicColorsTestUtils {

    private static final String TAG = DynamicColorsTestUtils.class.getSimpleName();

    private static final String THEME_CUSTOMIZATION_KEY = "theme_customization_overlay_packages";
    private static final String THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY =
            "android.theme.customization.system_palette";

    private static final int ORANGE_SYSTEM_SEED_COLOR = 0xA66800;
    private static final int ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR = -8235756;

    private DynamicColorsTestUtils() {
    }

    /**
     * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on an orange
     * seed color, and then wait for the change to propagate to the app by comparing
     * android.R.color.system_accent1_600 to the expected orange value.
     */
    public static void updateSystemColorsToOrange() {
        updateSystemColors(ORANGE_SYSTEM_SEED_COLOR, ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR);
    }

    /**
     * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
     * {@code seedColor}, and then wait for the change to propagate to the app by comparing
     * android.R.color.system_accent1_600 to {@code expectedSystemAccent1600}.
     */
    public static void updateSystemColors(
            @ColorInt int seedColor, @ColorInt int expectedSystemAccent1600) {
        Context context = getInstrumentation().getTargetContext();

        int actualSystemAccent1600 =
                ContextCompat.getColor(context, android.R.color.system_accent1_600);

        if (expectedSystemAccent1600 == actualSystemAccent1600) {
            String expectedColorString = Integer.toHexString(expectedSystemAccent1600);
            Log.d(
                    TAG,
                    "Skipped updating system colors since system_accent1_600 is already equal to "
                            + "expected: "
                            + expectedColorString);
            return;
        }

        updateSystemColors(seedColor);
    }

    /**
     * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
     * {@code seedColor}, and then wait for the change to propagate to the app by checking
     * android.R.color.system_accent1_600 for any change.
     */
    public static void updateSystemColors(@ColorInt int seedColor) {
        Context context = getInstrumentation().getTargetContext();

        // Initialize system color idling resource with original system_accent1_600 value.
        ColorChangeIdlingResource systemColorIdlingResource =
                new ColorChangeIdlingResource(context, android.R.color.system_accent1_600);

        // Update system theme color setting to trigger fabricated resource overlay.
        runWithShellPermissionIdentity(
                () ->
                        Settings.Secure.putString(
                                context.getContentResolver(),
                                THEME_CUSTOMIZATION_KEY,
                                buildThemeCustomizationString(seedColor)));

        // Wait for system color update to propagate to app.
        IdlingRegistry idlingRegistry = IdlingRegistry.getInstance();
        idlingRegistry.register(systemColorIdlingResource);
        Espresso.onIdle();
        idlingRegistry.unregister(systemColorIdlingResource);

        Log.d(TAG,
                Settings.Secure.getString(context.getContentResolver(), THEME_CUSTOMIZATION_KEY));
    }

    private static String buildThemeCustomizationString(@ColorInt int seedColor) {
        String seedColorHex = Integer.toHexString(seedColor);
        Map<String, String> themeCustomizationMap = new HashMap<>();
        themeCustomizationMap.put(THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY, seedColorHex);
        return new JSONObject(themeCustomizationMap).toString();
    }

    private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
        uiAutomation.adoptShellPermissionIdentity();
        try {
            runnable.run();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            uiAutomation.dropShellPermissionIdentity();
        }
    }

    private static class ColorChangeIdlingResource implements IdlingResource {

        private final Context mContext;
        private final int mColorResId;
        private final int mInitialColorInt;

        private ResourceCallback mResourceCallback;
        private boolean mIdleNow;

        ColorChangeIdlingResource(Context context, @ColorRes int colorResId) {
            this.mContext = context;
            this.mColorResId = colorResId;
            this.mInitialColorInt = ContextCompat.getColor(context, colorResId);
        }

        @Override
        public String getName() {
            return ColorChangeIdlingResource.class.getName();
        }

        @Override
        public boolean isIdleNow() {
            if (mIdleNow) {
                return true;
            }

            int currentColorInt = ContextCompat.getColor(mContext, mColorResId);

            String initialColorString = Integer.toHexString(mInitialColorInt);
            String currentColorString = Integer.toHexString(currentColorInt);
            Log.d(TAG, String.format("Initial=%s, Current=%s", initialColorString,
                    currentColorString));

            mIdleNow = currentColorInt != mInitialColorInt;
            Log.d(TAG, String.format("idleNow=%b", mIdleNow));

            if (mIdleNow) {
                mResourceCallback.onTransitionToIdle();
            }
            return mIdleNow;
        }

        @Override
        public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
            this.mResourceCallback = resourceCallback;
        }
    }
}
Loading