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 package com.android.adservices.shared.testing;
17 
18 import com.google.common.annotations.VisibleForTesting;
19 
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.Objects;
23 
24 /** Abstraction for Android SDK levels (so it can be used both on device and host side tests). */
25 public final class AndroidSdk {
26 
27     private static final Logger sLogger = new Logger(DynamicLogger.getInstance(), AndroidSdk.class);
28 
29     /** Android version {@code RVC}. */
30     public static final int RVC = 30;
31 
32     /** Android version {@code SC}. */
33     public static final int SC = 31;
34 
35     /** Android version {@code SC_V2}. */
36     public static final int SC_V2 = 32;
37 
38     /** Android version {@code TM}. */
39     public static final int TM = 33;
40 
41     /** Android version {@code UC}. */
42     public static final int UDC = 34;
43 
44     /** Android version {@code VIC}. */
45     public static final int VIC = 35;
46 
47     /** Android version for unreleased builds}. */
48     public static final int CUR_DEVELOPMENT = 10_000; // Build.CUR_DEVELOPMENT.CUR_DEVELOPMENT
49 
50     /**
51      * Convenience for ranges that are "less than T" (for example {@code
52      * RequiresSdkRange(atMost=PRE_T)}), as S had 2 APIs (31 and 32)
53      */
54     public static final int PRE_T = SC_V2;
55 
56 
57 
58     // TODO(b/324919960): make it package-protected again or make sure it's unit tested.
59     /** Represents a specific SDK level. */
60     public enum Level {
61         ANY(Integer.MIN_VALUE),
62         DEV(CUR_DEVELOPMENT),
63         R(RVC),
64         S(SC),
65         S2(SC_V2),
66         T(TM),
67         U(UDC),
68         V(VIC);
69 
70         private final int mLevel;
71 
Level(int level)72         Level(int level) {
73             mLevel = level;
74         }
75 
76         // TODO(b/324919960): make it package-protected again or make sure it's unit tested.
77         /** Checks if SDK is at least the given level. */
isAtLeast(Level level)78         public boolean isAtLeast(Level level) {
79             return mLevel >= level.mLevel;
80         }
81 
82         // TODO(b/324919960): make it package-protected again or make sure it's unit tested.
83         /** Gets the numeric representation of the SDK level (like {@code 33}). */
getLevel()84         public int getLevel() {
85             return mLevel;
86         }
87 
88         /** Gets the level abstraction for the given level). */
forLevel(int level)89         public static Level forLevel(int level) {
90             switch (level) {
91                 case CUR_DEVELOPMENT:
92                     return DEV;
93                 case RVC:
94                     return R;
95                 case SC:
96                     return S;
97                 case SC_V2:
98                     return S2;
99                 case TM:
100                     return T;
101                 case UDC:
102                     return U;
103                 case VIC:
104                     return V;
105             }
106             if (level > VIC) {
107                 sLogger.e(
108                         "WARNING: Level.forLevel() called with unsupported / unreleased level (%d);"
109                                 + " returning DEV (%d)",
110                         level, DEV.mLevel);
111                 return DEV;
112             }
113             throw new IllegalArgumentException("Unsupported level: " + level);
114         }
115     }
116 
117     // TODO(b/324919960): make it package-protected again or make sure it's unit tested.
118     /** Represents a range of Android API levels. */
119     public static final class Range {
120         // TODO(b/324919960): make them package-protected again or make sure it's unit tested.
121         public static final int NO_MIN = Integer.MIN_VALUE;
122         public static final int NO_MAX = Integer.MAX_VALUE;
123 
124         private final int mMinLevel;
125         private final int mMaxLevel;
126 
Range(int minLevel, int maxLevel)127         private Range(int minLevel, int maxLevel) {
128             if (minLevel > maxLevel || minLevel == NO_MAX || maxLevel == NO_MIN) {
129                 throw new IllegalArgumentException(
130                         "maxLevel ("
131                                 + maxLevel
132                                 + ") must equal or higher than minLevel ("
133                                 + minLevel
134                                 + ")");
135             }
136             mMinLevel = minLevel;
137             mMaxLevel = maxLevel;
138         }
139 
140         /** Gets a range without an upper boundary. */
forAtLeast(int level)141         public static Range forAtLeast(int level) {
142             return new Range(/* minLevel= */ level, NO_MAX);
143         }
144 
145         /** Gets a range without a lower boundary. */
forAtMost(int level)146         public static Range forAtMost(int level) {
147             return new Range(NO_MIN, /* maxLevel= */ level);
148         }
149 
150         /** Gets a range for the specific levels. */
forRange(int minLevel, int maxLevel)151         public static Range forRange(int minLevel, int maxLevel) {
152             return new Range(minLevel, maxLevel);
153         }
154 
155         /** Gets a range for a specific level. */
forExactly(int level)156         public static Range forExactly(int level) {
157             return new Range(/* minLevel= */ level, /* maxLevel= */ level);
158         }
159 
160         /** Gets a range that includes any level. */
forAnyLevel()161         public static Range forAnyLevel() {
162             return new Range(NO_MIN, NO_MAX);
163         }
164 
165         /** Checks if the given level fits this range (inclusive). */
isInRange(int level)166         public boolean isInRange(int level) {
167             return level >= mMinLevel && level <= mMaxLevel;
168         }
169 
170         @VisibleForTesting
merge(Range... ranges)171         static Range merge(Range... ranges) {
172             return merge(Arrays.asList(ranges));
173         }
174 
merge(Collection<Range> ranges)175         static Range merge(Collection<Range> ranges) {
176             Objects.requireNonNull(ranges, "ranges cannot be null");
177             if (ranges.isEmpty()) {
178                 throw new IllegalArgumentException("ranges cannot be empty");
179             }
180             int minRange = NO_MIN;
181             int maxRange = NO_MAX;
182             for (Range range : ranges) {
183                 if (range == null) {
184                     throw new IllegalArgumentException("ranges cannot have null range: " + ranges);
185                 }
186                 minRange = Math.max(minRange, range.mMinLevel);
187                 maxRange = Math.min(maxRange, range.mMaxLevel);
188             }
189             return forRange(minRange, maxRange);
190         }
191 
192         @Override
hashCode()193         public int hashCode() {
194             return Objects.hash(mMaxLevel, mMinLevel);
195         }
196 
197         @Override
equals(Object obj)198         public boolean equals(Object obj) {
199             if (this == obj) return true;
200             if (obj == null) return false;
201             if (getClass() != obj.getClass()) return false;
202             Range other = (Range) obj;
203             return mMaxLevel == other.mMaxLevel && mMinLevel == other.mMinLevel;
204         }
205 
206         @Override
toString()207         public String toString() {
208             StringBuilder builder = new StringBuilder("AndroidSdkRange[minLevel=");
209             if (mMinLevel == NO_MIN) {
210                 builder.append("OPEN");
211             } else {
212                 builder.append(mMinLevel);
213             }
214             builder.append(", maxLevel=");
215             if (mMaxLevel == NO_MAX) {
216                 builder.append("OPEN");
217             } else {
218                 builder.append(mMaxLevel);
219             }
220             return builder.append(']').toString();
221         }
222     }
223 
AndroidSdk()224     private AndroidSdk() {
225         throw new UnsupportedOperationException();
226     }
227 }
228