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