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