1 /*
2  * Copyright (C) 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 com.android.testutils;
18 
19 import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
20 
21 import android.os.Build;
22 import android.os.SystemProperties;
23 import android.os.VintfRuntimeInfo;
24 import android.text.TextUtils;
25 import android.util.Pair;
26 
27 import java.util.Objects;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 
31 /**
32  * Utilities for device information.
33  */
34 public class DeviceInfoUtils {
35     /**
36      * Class for a three-part kernel version number.
37      */
38     public static class KVersion {
39         public final int major;
40         public final int minor;
41         public final int sub;
42 
KVersion(int major, int minor, int sub)43         public KVersion(int major, int minor, int sub) {
44             this.major = major;
45             this.minor = minor;
46             this.sub = sub;
47         }
48 
49         /**
50          * Compares with other version numerically.
51          *
52          * @param  other the other version to compare
53          * @return the value 0 if this == other;
54          *         a value less than 0 if this < other and
55          *         a value greater than 0 if this > other.
56          */
compareTo(final KVersion other)57         public int compareTo(final KVersion other) {
58             int res = Integer.compare(this.major, other.major);
59             if (res == 0) {
60                 res = Integer.compare(this.minor, other.minor);
61             }
62             if (res == 0) {
63                 res = Integer.compare(this.sub, other.sub);
64             }
65             return res;
66         }
67 
68         /**
69          * At least satisfied with the given version.
70          *
71          * @param  from the start version to compare
72          * @return return true if this version is at least satisfied with the given version.
73          *         otherwise, return false.
74          */
isAtLeast(final KVersion from)75         public boolean isAtLeast(final KVersion from) {
76             return compareTo(from) >= 0;
77         }
78 
79         /**
80          * Falls within the given range [from, to).
81          *
82          * @param  from the start version to compare
83          * @param  to   the end version to compare
84          * @return return true if this version falls within the given range.
85          *         otherwise, return false.
86          */
isInRange(final KVersion from, final KVersion to)87         public boolean isInRange(final KVersion from, final KVersion to) {
88             return isAtLeast(from) && !isAtLeast(to);
89         }
90 
91         @Override
equals(Object o)92         public boolean equals(Object o) {
93             if (!(o instanceof KVersion)) return false;
94             KVersion that = (KVersion) o;
95             return this.major == that.major
96                     && this.minor == that.minor
97                     && this.sub == that.sub;
98         }
99     };
100 
101     /**
102      * Get a two-part kernel version number (major and minor) from a given string.
103      *
104      * TODO: use class KVersion.
105      */
getMajorMinorVersion(String version)106     private static Pair<Integer, Integer> getMajorMinorVersion(String version) {
107         // Only gets major and minor number of the version string.
108         final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
109         final Matcher m = versionPattern.matcher(version);
110         if (m.matches()) {
111             final int major = Integer.parseInt(m.group(1));
112             final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
113             return new Pair<>(major, minor);
114         } else {
115             return new Pair<>(0, 0);
116         }
117     }
118 
119     /**
120      * Compares two version strings numerically. Compare only major and minor number of the
121      * version string. The version comparison uses #Integer.compare. Possible version
122      * 5, 5.10, 5-beta1, 4.8-RC1, 4.7.10.10 and so on.
123      *
124      * @param  s1 the first version string to compare
125      * @param  s2 the second version string to compare
126      * @return the value 0 if s1 == s2;
127      *         a value less than 0 if s1 < s2 and
128      *         a value greater than 0 if s1 > s2.
129      *
130      * TODO: use class KVersion.
131      */
compareMajorMinorVersion(final String s1, final String s2)132     public static int compareMajorMinorVersion(final String s1, final String s2) {
133         final Pair<Integer, Integer> v1 = getMajorMinorVersion(s1);
134         final Pair<Integer, Integer> v2 = getMajorMinorVersion(s2);
135 
136         if (Objects.equals(v1.first, v2.first)) {
137             return Integer.compare(v1.second, v2.second);
138         } else {
139             return Integer.compare(v1.first, v2.first);
140         }
141     }
142 
143     /**
144      * Get a three-part kernel version number (major, minor and subminor) from a given string.
145      * Any version string must at least have major and minor number. If the subminor number can't
146      * be parsed from string. Assign zero as subminor number. Invalid version is treated as
147      * version 0.0.0.
148      */
getMajorMinorSubminorVersion(final String version)149     public static KVersion getMajorMinorSubminorVersion(final String version) {
150         // The kernel version is a three-part version number (major, minor and subminor). Get
151         // the three-part version numbers and discard the remaining stuff if any.
152         // For example:
153         //   4.19.220-g500ede0aed22-ab8272303 --> 4.19.220
154         //   5.17-rc6-g52099515ca00-ab8032400 --> 5.17.0
155         final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?.*");
156         final Matcher m = versionPattern.matcher(version);
157         if (m.matches()) {
158             final int major = Integer.parseInt(m.group(1));
159             final int minor = Integer.parseInt(m.group(2));
160             final int sub = TextUtils.isEmpty(m.group(4)) ? 0 : Integer.parseInt(m.group(4));
161             return new KVersion(major, minor, sub);
162         } else {
163             return new KVersion(0, 0, 0);
164         }
165     }
166 
167     /**
168      * Check if the current kernel version is at least satisfied with the given version.
169      *
170      * @param  version the start version to compare
171      * @return return true if the current version is at least satisfied with the given version.
172      *         otherwise, return false.
173      */
isKernelVersionAtLeast(final String version)174     public static boolean isKernelVersionAtLeast(final String version) {
175         final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
176         final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
177         final KVersion from = DeviceInfoUtils.getMajorMinorSubminorVersion(version);
178         return current.isAtLeast(from);
179     }
180 
181     /**
182      * Check if the current build is a debuggable build.
183      */
isDebuggable()184     public static boolean isDebuggable() {
185         if (isAtLeastS()) {
186             return Build.isDebuggable();
187         }
188         return SystemProperties.getInt("ro.debuggable", 0) == 1;
189     }
190 }
191