1 /*
2  * Copyright (C) 2016 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.core.os
18 
19 import android.os.Build
20 import android.os.ext.SdkExtensions
21 import androidx.annotation.ChecksSdkIntAtLeast
22 import androidx.annotation.RequiresApi
23 import androidx.annotation.RestrictTo
24 import androidx.annotation.VisibleForTesting
25 
26 /**
27  * This class contains additional platform version checking methods for targeting pre-release
28  * versions of Android.
29  */
30 public object BuildCompat {
31 
32     /**
33      * Checks if the codename is a matching or higher version than the given build value.
34      *
35      * @param codename the requested build codename, e.g. `"O"` or `"OMR1"`
36      * @param buildCodename the value of [Build.VERSION.CODENAME]
37      * @return `true` if APIs from the requested codename are available in the build.
38      */
39     @JvmStatic
40     @RestrictTo(RestrictTo.Scope.LIBRARY)
41     @VisibleForTesting
isAtLeastPreReleaseCodenamenull42     public fun isAtLeastPreReleaseCodename(codename: String, buildCodename: String): Boolean {
43         fun codenameToInt(codename: String): Int? =
44             when (codename.uppercase()) {
45                 "BAKLAVA" -> 0
46                 else -> null
47             }
48 
49         // Special case "REL", which means the build is not a pre-release build.
50         if ("REL" == buildCodename) {
51             return false
52         }
53 
54         // Starting with Baklava, the Android dessert names wrapped around to the start of the
55         // alphabet; handle these "new" codenames explicitly; lexically compare "old" codenames.
56         // Return true if the build codename is equal to or greater than the requested codename.
57         val buildCodenameInt = codenameToInt(buildCodename)
58         val codenameInt = codenameToInt(codename)
59         if (buildCodenameInt != null && codenameInt != null) {
60             // both codenames are "new" -> use hard-coded int values
61             return buildCodenameInt >= codenameInt
62         } else if (buildCodenameInt == null && codenameInt == null) {
63             // both codenames are "old" -> use lexical comparison
64             return buildCodename.uppercase() >= codename.uppercase()
65         } else {
66             // one codename is "new", one is "old"
67             return buildCodenameInt != null
68         }
69     }
70 
71     /**
72      * Checks if the device is running on the Android N release or newer.
73      *
74      * @return `true` if N APIs are available for use
75      */
76     @JvmStatic
77     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N)
78     @Deprecated(
79         message =
80             "Android N is a finalized release and this method is no longer necessary. " +
81                 "It will be removed in a future release of this library. Instead, use " +
82                 "`Build.VERSION.SDK_INT >= 24`.",
83         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 24")
84     )
isAtLeastNnull85     public fun isAtLeastN(): Boolean = Build.VERSION.SDK_INT >= 24
86 
87     /**
88      * Checks if the device is running on the Android N MR1 release or newer.
89      *
90      * @return `true` if N MR1 APIs are available for use
91      */
92     @JvmStatic
93     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N_MR1)
94     @Deprecated(
95         message =
96             "Android N MR1 is a finalized release and this method is no longer necessary. " +
97                 "It will be removed in a future release of this library. Instead, use " +
98                 "`Build.VERSION.SDK_INT >= 25`.",
99         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 25")
100     )
101     public fun isAtLeastNMR1(): Boolean = Build.VERSION.SDK_INT >= 25
102 
103     /**
104      * Checks if the device is running on a release version of Android O or newer.
105      *
106      * @return `true` if O APIs are available for use, `false` otherwise
107      */
108     @JvmStatic
109     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
110     @Deprecated(
111         message =
112             "Android O is a finalized release and this method is no longer necessary. " +
113                 "It will be removed in a future release of this library. Instead use " +
114                 "`Build.VERSION.SDK_INT >= 26`.",
115         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 26")
116     )
117     public fun isAtLeastO(): Boolean = Build.VERSION.SDK_INT >= 26
118 
119     /**
120      * Checks if the device is running on a release version of Android O MR1 or newer.
121      *
122      * @return `true` if O MR1 APIs are available for use, `false` otherwise
123      */
124     @JvmStatic
125     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O_MR1)
126     @Deprecated(
127         message =
128             "Android O MR1 is a finalized release and this method is no longer necessary. " +
129                 "It will be removed in a future release of this library. Instead, use " +
130                 "`Build.VERSION.SDK_INT >= 27`.",
131         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 27")
132     )
133     public fun isAtLeastOMR1(): Boolean = Build.VERSION.SDK_INT >= 27
134 
135     /**
136      * Checks if the device is running on a release version of Android P or newer.
137      *
138      * @return `true` if P APIs are available for use, `false` otherwise
139      */
140     @JvmStatic
141     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
142     @Deprecated(
143         message =
144             "Android P is a finalized release and this method is no longer necessary. " +
145                 "It will be removed in a future release of this library. Instead, use " +
146                 "`Build.VERSION.SDK_INT >= 28`.",
147         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 28")
148     )
149     public fun isAtLeastP(): Boolean = Build.VERSION.SDK_INT >= 28
150 
151     /**
152      * Checks if the device is running on release version of Android Q or newer.
153      *
154      * @return `true` if Q APIs are available for use, `false` otherwise
155      */
156     @JvmStatic
157     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
158     @Deprecated(
159         message =
160             "Android Q is a finalized release and this method is no longer necessary. " +
161                 "It will be removed in a future release of this library. Instead, use " +
162                 "`Build.VERSION.SDK_INT >= 29`.",
163         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 29")
164     )
165     public fun isAtLeastQ(): Boolean = Build.VERSION.SDK_INT >= 29
166 
167     /**
168      * Checks if the device is running on release version of Android R or newer.
169      *
170      * @return `true` if R APIs are available for use, `false` otherwise
171      */
172     @JvmStatic
173     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
174     @Deprecated(
175         message =
176             "Android R is a finalized release and this method is no longer necessary. " +
177                 "It will be removed in a future release of this library. Instead, use " +
178                 "`Build.VERSION.SDK_INT >= 30`.",
179         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 30")
180     )
181     public fun isAtLeastR(): Boolean = Build.VERSION.SDK_INT >= 30
182 
183     /**
184      * Checks if the device is running on a pre-release version of Android S or a release version of
185      * Android S or newer.
186      *
187      * @return `true` if S APIs are available for use, `false` otherwise
188      */
189     @JvmStatic
190     @ChecksSdkIntAtLeast(api = 31, codename = "S")
191     @Deprecated(
192         message =
193             "Android S is a finalized release and this method is no longer necessary. " +
194                 "It will be removed in a future release of this library. Instead, use " +
195                 "`Build.VERSION.SDK_INT >= 31`.",
196         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 31")
197     )
198     public fun isAtLeastS(): Boolean =
199         Build.VERSION.SDK_INT >= 31 ||
200             (Build.VERSION.SDK_INT >= 30 &&
201                 isAtLeastPreReleaseCodename("S", Build.VERSION.CODENAME))
202 
203     /**
204      * Checks if the device is running on a pre-release version of Android Sv2 or a release version
205      * of Android Sv2 or newer.
206      *
207      * @return `true` if Sv2 APIs are available for use, `false` otherwise
208      */
209     @JvmStatic
210     @ChecksSdkIntAtLeast(api = 32, codename = "Sv2")
211     @Deprecated(
212         message =
213             "Android Sv2 is a finalized release and this method is no longer necessary. " +
214                 "It will be removed in a future release of this library. Instead, use " +
215                 "`Build.VERSION.SDK_INT >= 32`.",
216         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 32")
217     )
218     public fun isAtLeastSv2(): Boolean =
219         Build.VERSION.SDK_INT >= 32 ||
220             (Build.VERSION.SDK_INT >= 31 &&
221                 isAtLeastPreReleaseCodename("Sv2", Build.VERSION.CODENAME))
222 
223     /**
224      * Checks if the device is running on a pre-release version of Android Tiramisu or a release
225      * version of Android Tiramisu or newer.
226      *
227      * **Note:** When Android Tiramisu is finalized for release, this method will be removed and all
228      * calls must be replaced with `Build.VERSION.SDK_INT >= 33`.
229      *
230      * @return `true` if Tiramisu APIs are available for use, `false` otherwise
231      */
232     @JvmStatic
233     @ChecksSdkIntAtLeast(api = 33, codename = "Tiramisu")
234     @Deprecated(
235         message =
236             "Android Tiramisu is a finalized release and this method is no longer " +
237                 "necessary. It will be removed in a future release of this library. Instead, use " +
238                 "`Build.VERSION.SDK_INT >= 33`.",
239         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 33")
240     )
241     public fun isAtLeastT(): Boolean =
242         Build.VERSION.SDK_INT >= 33 ||
243             (Build.VERSION.SDK_INT >= 32 &&
244                 isAtLeastPreReleaseCodename("Tiramisu", Build.VERSION.CODENAME))
245 
246     /**
247      * Checks if the device is running on a pre-release version of Android UpsideDownCake or a
248      * release version of Android UpsideDownCake or newer.
249      *
250      * **Note:** When Android UpsideDownCake is finalized for release, this method will be removed
251      * and all calls must be replaced with `Build.VERSION.SDK_INT >= 34`.
252      *
253      * @return `true` if UpsideDownCake APIs are available for use, `false` otherwise
254      */
255     @JvmStatic
256     @ChecksSdkIntAtLeast(api = 34, codename = "UpsideDownCake")
257     @Deprecated(
258         message =
259             "Android UpsideDownCase is a finalized release and this method is no longer " +
260                 "necessary. It will be removed in a future release of this library. Instead, use " +
261                 "`Build.VERSION.SDK_INT >= 34`.",
262         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 34")
263     )
264     public fun isAtLeastU(): Boolean =
265         Build.VERSION.SDK_INT >= 34 ||
266             (Build.VERSION.SDK_INT >= 33 &&
267                 isAtLeastPreReleaseCodename("UpsideDownCake", Build.VERSION.CODENAME))
268 
269     /**
270      * Checks if the device is running on a pre-release version of Android VanillaIceCream or a
271      * release version of Android VanillaIceCream or newer.
272      *
273      * **Note:** When Android VanillaIceCream is finalized for release, this method will be removed
274      * and all calls must be replaced with `Build.VERSION.SDK_INT >= 35`.
275      *
276      * @return `true` if VanillaIceCream APIs are available for use, `false` otherwise
277      */
278     @JvmStatic
279     @ChecksSdkIntAtLeast(api = 35, codename = "VanillaIceCream")
280     @Deprecated(
281         message =
282             "Android VanillaIceCream is a finalized release and this method is no longer " +
283                 "necessary. It will be removed in a future release of this library. Instead, use " +
284                 "`Build.VERSION.SDK_INT >= 35`.",
285         ReplaceWith("android.os.Build.VERSION.SDK_INT >= 35")
286     )
287     public fun isAtLeastV(): Boolean =
288         Build.VERSION.SDK_INT >= 35 ||
289             (Build.VERSION.SDK_INT >= 34 &&
290                 isAtLeastPreReleaseCodename("VanillaIceCream", Build.VERSION.CODENAME))
291 
292     /**
293      * Checks if the device is running on a pre-release version of Android Baklava or a release
294      * version of Android Baklava or newer.
295      *
296      * **Note:** When Android Baklava is finalized for release, this method will be removed and all
297      * calls must be replaced with `Build.VERSION.SDK_INT >= 36`.
298      *
299      * @return `true` if Baklava APIs are available for use, `false` otherwise
300      */
301     @JvmStatic
302     @ChecksSdkIntAtLeast(api = 36, codename = "Baklava")
303     public fun isAtLeastB(): Boolean =
304         Build.VERSION.SDK_INT >= 36 ||
305             (Build.VERSION.SDK_INT >= 35 &&
306                 isAtLeastPreReleaseCodename("Baklava", Build.VERSION.CODENAME))
307 
308     /**
309      * Experimental feature set for pre-release SDK checks.
310      *
311      * Pre-release SDK checks **do not** guarantee correctness, as APIs may have been added or
312      * removed during the course of a pre-release SDK development cycle.
313      *
314      * Additionally, pre-release checks **may not** return `true` when run on a finalized version of
315      * the SDK associated with the codename.
316      */
317     @RequiresOptIn
318     @Retention(AnnotationRetention.BINARY)
319     public annotation class PrereleaseSdkCheck
320 
321     /**
322      * The value of `SdkExtensions.getExtensionVersion(R)`. This is a convenience constant which
323      * provides the extension version in a similar style to `Build.VERSION.SDK_INT`.
324      *
325      * Compared to calling `getExtensionVersion` directly, using this constant has the benefit of
326      * not having to verify the `getExtensionVersion` method is available.
327      *
328      * @return the version of the R extension, if it exists. 0 otherwise.
329      */
330     @JvmField
331     @ChecksSdkIntAtLeast(extension = Build.VERSION_CODES.R)
332     public val R_EXTENSION_INT: Int =
333         if (Build.VERSION.SDK_INT >= 30) {
334             Api30Impl.getExtensionVersion(Build.VERSION_CODES.R)
335         } else 0
336 
337     /**
338      * The value of `SdkExtensions.getExtensionVersion(S)`. This is a convenience constant which
339      * provides the extension version in a similar style to `Build.VERSION.SDK_INT`.
340      *
341      * Compared to calling `getExtensionVersion` directly, using this constant has the benefit of
342      * not having to verify the `getExtensionVersion` method is available.
343      *
344      * @return the version of the S extension, if it exists. 0 otherwise.
345      */
346     @JvmField
347     @ChecksSdkIntAtLeast(extension = Build.VERSION_CODES.S)
348     public val S_EXTENSION_INT: Int =
349         if (Build.VERSION.SDK_INT >= 30) {
350             Api30Impl.getExtensionVersion(Build.VERSION_CODES.S)
351         } else 0
352 
353     /**
354      * The value of `SdkExtensions.getExtensionVersion(TIRAMISU)`. This is a convenience constant
355      * which provides the extension version in a similar style to `Build.VERSION.SDK_INT`.
356      *
357      * Compared to calling `getExtensionVersion` directly, using this constant has the benefit of
358      * not having to verify the `getExtensionVersion` method is available.
359      *
360      * @return the version of the T extension, if it exists. 0 otherwise.
361      */
362     @JvmField
363     @ChecksSdkIntAtLeast(extension = Build.VERSION_CODES.TIRAMISU)
364     public val T_EXTENSION_INT: Int =
365         if (Build.VERSION.SDK_INT >= 30) {
366             Api30Impl.getExtensionVersion(Build.VERSION_CODES.TIRAMISU)
367         } else 0
368 
369     /**
370      * The value of `SdkExtensions.getExtensionVersion(AD_SERVICES)`. This is a convenience constant
371      * which provides the extension version in a similar style to `Build.VERSION.SDK_INT`.
372      *
373      * Compared to calling `getExtensionVersion` directly, using this constant has the benefit of
374      * not having to verify the `getExtensionVersion` method is available.
375      *
376      * @return the version of the AdServices extension, if it exists. 0 otherwise.
377      */
378     @JvmField
379     @ChecksSdkIntAtLeast(extension = SdkExtensions.AD_SERVICES)
380     public val AD_SERVICES_EXTENSION_INT: Int =
381         if (Build.VERSION.SDK_INT >= 30) {
382             Api30Impl.getExtensionVersion(SdkExtensions.AD_SERVICES)
383         } else 0
384 
385     @RequiresApi(30)
386     private object Api30Impl {
387 
getExtensionVersionnull388         fun getExtensionVersion(extension: Int): Int {
389             return SdkExtensions.getExtensionVersion(extension)
390         }
391     }
392 }
393