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 package android.car;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.os.Parcel;
21 import android.text.TextUtils;
22 
23 import java.util.Objects;
24 
25 /**
26  * Abstraction of Android APIs.
27  *
28  * <p>This class is used to represent a pair of major / minor API versions: the "major" version
29  * represents a "traditional" Android SDK release, while the "minor" is used to indicate incremental
30  * releases for that major.
31  *
32  * <p>This class is needed because the standard Android SDK API versioning only supports major
33  * releases, but {@code Car} APIs can now (starting on
34  * {@link android.os.Build.VERSION_CODES#TIRAMISU Android 13}) be updated on minor releases
35  * as well.
36  *
37  * @param <T> implementation type
38  */
39 public abstract class ApiVersion<T extends ApiVersion<?>> {
40 
41     /**
42      * When set, it's used on {@link #toString()} - useful for versions that are pre-defined
43      * (like {@code TIRAMISU_1}).
44      */
45     @Nullable
46     private final String mVersionName;
47 
48     private final int mMajorVersion;
49     private final int mMinorVersion;
50 
ApiVersion(int majorVersion, int minorVersion)51     ApiVersion(int majorVersion, int minorVersion) {
52         this(/* name= */ null, majorVersion, minorVersion);
53     }
54 
ApiVersion(String name, int majorVersion, int minorVersion)55     ApiVersion(String name, int majorVersion, int minorVersion) {
56         mVersionName = name;
57         mMajorVersion = majorVersion;
58         mMinorVersion = minorVersion;
59     }
60 
61     /**
62      * Checks if this API version meets the required version.
63      *
64      * @param requiredVersion required major and minor version number
65      * @return {@code true} if the {@link #getMajorVersion() major version} is newer than the
66      *         {@code requiredVersion}'s major or if the {@link #getMajorVersion() major version} is
67      *         the same as {@code requiredVersion}'s major with the {@link #getMinorVersion() minor
68      *         version} the same or newer than {@code requiredVersion}'s minor
69      * @throws IllegalArgumentException if {@code requiredVersion} is not an instance of the same
70      *         class as this object
71      */
isAtLeast(@onNull T requiredVersion)72     public final boolean isAtLeast(@NonNull T requiredVersion) {
73         Objects.requireNonNull(requiredVersion);
74 
75         if (!this.getClass().isInstance(requiredVersion)) {
76             throw new IllegalArgumentException("Cannot compare " + this.getClass().getName()
77                     + " against " + requiredVersion.getClass().getName());
78         }
79 
80         int requiredApiVersionMajor = requiredVersion.getMajorVersion();
81         int requiredApiVersionMinor = requiredVersion.getMinorVersion();
82 
83         return (mMajorVersion > requiredApiVersionMajor)
84                 || (mMajorVersion == requiredApiVersionMajor
85                         && mMinorVersion >= requiredApiVersionMinor);
86     }
87 
88     /**
89      * Gets the major version of the API represented by this object.
90      */
getMajorVersion()91     public final int getMajorVersion() {
92         return mMajorVersion;
93     }
94 
95     /**
96      * Gets the minor version change of API for the same {@link #getMajorVersion()}.
97      *
98      * <p>It will reset to {@code 0} whenever {@link #getMajorVersion()} is updated
99      * and will increase by {@code 1} if car builtin or other car platform part is changed with the
100      * same {@link #getMajorVersion()}.
101      *
102      * <p>Client should check this version to use APIs which were added in a minor-only version
103      * update.
104      */
getMinorVersion()105     public final int getMinorVersion() {
106         return mMinorVersion;
107     }
108 
109     /**
110      * @hide
111      */
112     @Override
equals(Object obj)113     public boolean equals(Object obj) {
114         if (this == obj) return true;
115         if (obj == null) return false;
116         if (getClass() != obj.getClass()) return false;
117         @SuppressWarnings("unchecked")
118         ApiVersion<T> other = (ApiVersion<T>) obj;
119         return (mMajorVersion == other.mMajorVersion) && (mMinorVersion == other.mMinorVersion);
120     }
121 
122     /**
123      * @hide
124      */
125     @Override
hashCode()126     public int hashCode() {
127         int prime = 31;
128         int result = 1;
129         result = prime * result + mMajorVersion;
130         result = prime * result + mMinorVersion;
131         return result;
132     }
133 
134     /**
135      * @hide
136      */
137     @Override
138     @NonNull
toString()139     public final String toString() {
140         StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
141         if (!TextUtils.isEmpty(mVersionName)) {
142             builder.append("name=").append(mVersionName).append(", ");
143         }
144         return builder
145                 .append("major=").append(mMajorVersion)
146                 .append(", minor=").append(mMinorVersion)
147                 .append(']').toString();
148     }
149 
150     /**
151      * @hide
152      */
writeToParcel(Parcel dest)153     protected void writeToParcel(Parcel dest) {
154         dest.writeString(mVersionName);
155         dest.writeInt(getMajorVersion());
156         dest.writeInt(getMinorVersion());
157     }
158 
159     /**
160      * @hide
161      */
readFromParcel(Parcel source, ApiVersionFactory<T> factory)162     protected static <T extends ApiVersion<?>> T readFromParcel(Parcel source,
163             ApiVersionFactory<T> factory) {
164         String name = source.readString();
165         int major = source.readInt();
166         int minor = source.readInt();
167         return factory.newInstance(name, major, minor);
168     }
169 
170     /**
171      * @hide
172      */
173     interface ApiVersionFactory<T extends ApiVersion<?>> {
newInstance(String name, int major, int minor)174         T newInstance(String name, int major, int minor);
175     }
176 }
177