1 /* 2 * Copyright 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 androidx.wear.protolayout.material; 18 19 import android.annotation.SuppressLint; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.res.Configuration; 23 import android.graphics.Bitmap; 24 import android.util.DisplayMetrics; 25 import android.util.Log; 26 27 import androidx.test.core.app.ActivityScenario; 28 import androidx.test.platform.app.InstrumentationRegistry; 29 import androidx.test.screenshot.AndroidXScreenshotTestRule; 30 import androidx.test.screenshot.matchers.MSSIMMatcher; 31 import androidx.wear.protolayout.LayoutElementBuilders.Layout; 32 import androidx.wear.protolayout.material.test.GoldenTestActivity; 33 34 import org.jspecify.annotations.NonNull; 35 36 import java.util.List; 37 import java.util.Map; 38 import java.util.stream.Collectors; 39 40 public class RunnerUtils { 41 // This isn't totally ideal right now. The screenshot tests run on a phone, so emulate some 42 // watch dimensions here. 43 public static final int SCREEN_WIDTH = 390; 44 public static final int SCREEN_HEIGHT = 390; 45 RunnerUtils()46 private RunnerUtils() {} 47 runSingleScreenshotTest( @onNull AndroidXScreenshotTestRule rule, @NonNull TestCase testCase, @NonNull String expected)48 public static void runSingleScreenshotTest( 49 @NonNull AndroidXScreenshotTestRule rule, 50 @NonNull TestCase testCase, 51 @NonNull String expected) { 52 if (testCase.isForLtr) { 53 runSingleScreenshotTest(rule, testCase.mLayout, expected, /* isRtlDirection= */ false); 54 } 55 if (testCase.isForRtl) { 56 runSingleScreenshotTest( 57 rule, testCase.mLayout, expected + "_rtl", /* isRtlDirection= */ true); 58 } 59 } 60 runSingleScreenshotTest( @onNull AndroidXScreenshotTestRule rule, @NonNull Layout layout, @NonNull String expected, boolean isRtlDirection)61 public static void runSingleScreenshotTest( 62 @NonNull AndroidXScreenshotTestRule rule, 63 @NonNull Layout layout, 64 @NonNull String expected, 65 boolean isRtlDirection) { 66 byte[] layoutPayload = layout.toByteArray(); 67 68 Intent startIntent = 69 new Intent( 70 InstrumentationRegistry.getInstrumentation().getTargetContext(), 71 GoldenTestActivity.class); 72 startIntent.putExtra("layout", layoutPayload); 73 startIntent.putExtra(GoldenTestActivity.USE_RTL_DIRECTION, isRtlDirection); 74 75 try (ActivityScenario<GoldenTestActivity> scenario = ActivityScenario.launch(startIntent)) { 76 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 77 78 try { 79 // Wait 1s after launching the activity. This allows for the old white layout in the 80 // bootstrap activity to fully go away before proceeding. 81 Thread.sleep(100); 82 } catch (Exception ex) { 83 if (ex instanceof InterruptedException) { 84 Thread.currentThread().interrupt(); 85 } 86 Log.e("MaterialGoldenTest", "Error sleeping", ex); 87 } 88 89 DisplayMetrics displayMetrics = 90 InstrumentationRegistry.getInstrumentation() 91 .getTargetContext() 92 .getResources() 93 .getDisplayMetrics(); 94 95 // RTL will put the View on the right side. 96 int screenWidthStart = isRtlDirection ? displayMetrics.widthPixels - SCREEN_WIDTH : 0; 97 98 Bitmap bitmap = 99 Bitmap.createBitmap( 100 InstrumentationRegistry.getInstrumentation() 101 .getUiAutomation() 102 .takeScreenshot(), 103 screenWidthStart, 104 0, 105 SCREEN_WIDTH, 106 SCREEN_HEIGHT); 107 rule.assertBitmapAgainstGolden(bitmap, expected, new MSSIMMatcher()); 108 } 109 } 110 111 @SuppressLint("BanThreadSleep") waitForNotificationToDisappears()112 public static void waitForNotificationToDisappears() { 113 try { 114 // Wait for the initial notification to disappear. 115 Thread.sleep(5000); 116 } catch (InterruptedException e) { 117 Log.e("MaterialGoldenTest", "Error sleeping", e); 118 } 119 } 120 convertToTestParameters( Map<String, Layout> testCases, boolean isForRtr, boolean isForLtr)121 public static List<Object[]> convertToTestParameters( 122 Map<String, Layout> testCases, boolean isForRtr, boolean isForLtr) { 123 return testCases.entrySet().stream() 124 .map( 125 test -> 126 new Object[] { 127 test.getKey(), new TestCase(test.getValue(), isForRtr, isForLtr) 128 }) 129 .collect(Collectors.toList()); 130 } 131 132 /** Holds testcase parameters. */ 133 public static final class TestCase { 134 final Layout mLayout; 135 final boolean isForRtl; 136 final boolean isForLtr; 137 TestCase(Layout layout, boolean isForRtl, boolean isForLtr)138 public TestCase(Layout layout, boolean isForRtl, boolean isForLtr) { 139 mLayout = layout; 140 this.isForRtl = isForRtl; 141 this.isForLtr = isForLtr; 142 } 143 } 144 getFontScale(Context context)145 public static float getFontScale(Context context) { 146 return context.getResources().getConfiguration().fontScale; 147 } 148 149 @SuppressWarnings("deprecation") setFontScale(Context context, float fontScale)150 public static void setFontScale(Context context, float fontScale) { 151 Configuration newConfiguration = 152 new Configuration(context.getResources().getConfiguration()); 153 newConfiguration.fontScale = fontScale; 154 context.getResources() 155 .updateConfiguration(newConfiguration, context.getResources().getDisplayMetrics()); 156 } 157 } 158