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