1 /*
2  * Copyright (C) 2023 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.platform.helpers;
18 
19 import android.graphics.Point;
20 import android.graphics.Rect;
21 import android.platform.spectatio.utils.SpectatioConfigUtil;
22 import android.platform.spectatio.utils.SpectatioUiUtil;
23 
24 import androidx.test.uiautomator.BySelector;
25 import androidx.test.uiautomator.UiObject2;
26 
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.function.Supplier;
30 
31 /** Utility file for seek functions */
32 public class SeekUtility {
33     private static final int SEEK_BOUNDS_BUFFER = 5;
34 
35     private static SeekUtility sSeekUtilityInstance;
36 
37     public enum SeekLayout {
38         VERTICAL,
39         HORIZONTAL,
40     }
41 
42     private static class SeekBarDetails {
43         public BySelector mSeekBarSelector;
44         public SeekLayout mLayout;
45         public Supplier<Integer> mValueSupplier;
46     }
47 
48     private final SpectatioUiUtil mSpectatioUiUtil;
49     private final Map<String, SeekBarDetails> mRegisteredSeekBars;
50 
SeekUtility(SpectatioUiUtil spectatioUiUtil)51     private SeekUtility(SpectatioUiUtil spectatioUiUtil) {
52         mSpectatioUiUtil = spectatioUiUtil;
53         mRegisteredSeekBars = new HashMap<>();
54     }
55 
56     /**
57      * Get the singleton instance of SeekUtility
58      *
59      * @param spectatioUiUtil Spectatio util to initialize with
60      * @return The singleton instance of SeekUtility
61      */
getInstance(SpectatioUiUtil spectatioUiUtil)62     public static SeekUtility getInstance(SpectatioUiUtil spectatioUiUtil) {
63         if (sSeekUtilityInstance == null) {
64             sSeekUtilityInstance = new SeekUtility(spectatioUiUtil);
65         }
66         return sSeekUtilityInstance;
67     }
68 
69     /**
70      * Tell SeekUtility that a seekbar exists and how to retrieve the value it corresponds to
71      *
72      * @param id Any string you choose, which you will use to refer to this seekbar when calling
73      *     `seek`
74      * @param seekBarConfig What config element holds the seekbar's selector
75      * @param layout horizontal or vertical
76      * @param valueSupplier How to retrieve the value this seekbar sets
77      */
registerSeekBar( String id, String seekBarConfig, SeekLayout layout, Supplier<Integer> valueSupplier)78     public void registerSeekBar(
79             String id, String seekBarConfig, SeekLayout layout, Supplier<Integer> valueSupplier) {
80         if (mRegisteredSeekBars.containsKey(id)) {
81             return;
82         }
83 
84         SeekBarDetails details = new SeekBarDetails();
85         details.mSeekBarSelector =
86                 SpectatioConfigUtil.getInstance().getUiElementFromConfig(seekBarConfig);
87         details.mLayout = layout;
88         details.mValueSupplier = valueSupplier;
89 
90         mRegisteredSeekBars.put(id, details);
91     }
92 
93     /**
94      * Tap on a seekbar, then retrieve the resulting value
95      *
96      * @param id Which seekbar to tap on -- this is the string you chose when you called register
97      * @param targetPercentage Where to tap
98      * @return The resulting service value
99      */
seek(String id, float targetPercentage)100     public int seek(String id, float targetPercentage) {
101         if ((targetPercentage < 0) || (targetPercentage > 1)) {
102             throw new IllegalArgumentException(
103                     "Seekbar target percentage %f is not between 0 and 1"
104                             .formatted(targetPercentage));
105         }
106 
107         SeekBarDetails details = mRegisteredSeekBars.get(id);
108         UiObject2 seekBar = mSpectatioUiUtil.findUiObject(details.mSeekBarSelector);
109         if (seekBar == null) {
110             throw new IllegalStateException(
111                     String.format("Unable to find seekbar using %s.", details.mSeekBarSelector));
112         }
113 
114         Rect seekBounds = seekBar.getVisibleBounds();
115         Point clickLocation = new Point(seekBounds.centerX(), seekBounds.centerY());
116         switch (details.mLayout) {
117             case VERTICAL:
118                 int bottom = seekBounds.bottom - SEEK_BOUNDS_BUFFER;
119                 int top = seekBounds.top + SEEK_BOUNDS_BUFFER;
120                 clickLocation.y = (int) ((bottom - top) * (1 - targetPercentage) + top);
121                 break;
122             case HORIZONTAL:
123                 int right = seekBounds.right - SEEK_BOUNDS_BUFFER;
124                 int left = seekBounds.left + SEEK_BOUNDS_BUFFER;
125                 clickLocation.x = (int) ((right - left) * targetPercentage + left);
126                 break;
127         }
128         mSpectatioUiUtil.clickAndWait(clickLocation);
129 
130         return details.mValueSupplier.get();
131     }
132 
133     /**
134      * Get the service value associated with a seekbar
135      *
136      * @param id Which seekbar to retrieve -- this is the string you chose when you called register
137      * @return The value
138      */
getValue(String id)139     public int getValue(String id) {
140         return mRegisteredSeekBars.get(id).mValueSupplier.get();
141     }
142 }
143