1 /*
2  * Copyright 2021 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.camera.extensions.internal;
18 
19 import androidx.annotation.VisibleForTesting;
20 import androidx.camera.core.Logger;
21 import androidx.camera.extensions.impl.ExtensionVersionImpl;
22 
23 import org.jspecify.annotations.NonNull;
24 import org.jspecify.annotations.Nullable;
25 
26 /**
27  * Provides interfaces to check the extension version.
28  */
29 public abstract class ExtensionVersion {
30     private static final String TAG = "ExtenderVersion";
31 
32     private static volatile ExtensionVersion sExtensionVersion;
33 
34     /**
35      * For testing only. Inject a fake {@link ExtensionVersion}. Set it to {@code null} to unset
36      * it.
37      */
38     @VisibleForTesting
injectInstance(@ullable ExtensionVersion extensionVersion)39     public static void injectInstance(@Nullable ExtensionVersion extensionVersion) {
40         sExtensionVersion = extensionVersion;
41     }
42 
getInstance()43     private static ExtensionVersion getInstance() {
44         if (sExtensionVersion != null) {
45             return sExtensionVersion;
46         }
47         synchronized (ExtensionVersion.class) {
48             if (sExtensionVersion == null) {
49                 try {
50                     sExtensionVersion = new VendorExtenderVersioning();
51                 } catch (NoClassDefFoundError e) {
52                     Logger.d(TAG, "No versioning extender found. Falling back to default.");
53                     sExtensionVersion = new DefaultExtenderVersioning();
54                 }
55             }
56         }
57 
58         return sExtensionVersion;
59     }
60 
61     /**
62      * Indicate the compatibility of CameraX and OEM library.
63      *
64      * @return true if OEM returned a major version is matched with the current version, false
65      * otherwise.
66      */
isExtensionVersionSupported()67     public static boolean isExtensionVersionSupported() {
68         return getInstance().getVersionObject() != null;
69     }
70 
71     /**
72      * Return the Version object of the OEM library if the version is compatible with CameraX.
73      *
74      * @return a Version object which composed of the version number string that's returned from
75      * {@link ExtensionVersionImpl#checkApiVersion(String)}.
76      * <tt>null</tt> if the OEM library didn't implement the version checking method or the
77      * version is not compatible with CameraX.
78      */
getRuntimeVersion()79     public static @Nullable Version getRuntimeVersion() {
80         return getInstance().getVersionObject();
81     }
82 
isAdvancedExtenderSupported()83     public static boolean isAdvancedExtenderSupported() {
84         return getInstance().isAdvancedExtenderSupportedInternal();
85     }
86 
87     /**
88      * Check if the Runtime Version meets the minimum compatible version requirement. This implies
89      * that the runtime version is equal to or newer than the version.
90      *
91      * <p> The compatible version is comprised of the major and minor version numbers. The patch
92      * number is ignored.
93      *
94      * @param version The minimum compatible version required
95      * @return True if the Runtime version meets the minimum version requirement and False
96      * otherwise.
97      */
isMinimumCompatibleVersion(@onNull Version version)98     public static boolean isMinimumCompatibleVersion(@NonNull Version version) {
99         return ExtensionVersion.getRuntimeVersion()
100                 .compareTo(version.getMajor(), version.getMinor()) >= 0;
101     }
102 
103     /**
104      * Check if the Runtime Version meets the maximum compatible version requirement. This implies
105      * that the runtime version is equal to or older than the version.
106      *
107      * <p> The compatible version is comprised of the major and minor version numbers. The patch
108      * number is ignored.
109      *
110      * @param version The maximum compatible version required
111      * @return True if the Runtime version meets the maximum version requirement and False
112      * otherwise.
113      */
isMaximumCompatibleVersion(@onNull Version version)114     public static boolean isMaximumCompatibleVersion(@NonNull Version version) {
115         return ExtensionVersion.getRuntimeVersion()
116                 .compareTo(version.getMajor(), version.getMinor()) <= 0;
117     }
118 
isAdvancedExtenderSupportedInternal()119     abstract boolean isAdvancedExtenderSupportedInternal();
120 
121     /**
122      * @return a Version object returned from the extension implementation.
123      */
getVersionObject()124     abstract Version getVersionObject();
125 
126     /** An implementation that calls into the vendor provided implementation. */
127     private static class VendorExtenderVersioning extends ExtensionVersion {
128         private static ExtensionVersionImpl sImpl;
129         private Version mRuntimeVersion;
130 
VendorExtenderVersioning()131         VendorExtenderVersioning() {
132             if (sImpl == null) {
133                 sImpl = new ExtensionVersionImpl();
134             }
135 
136             String vendorVersion = sImpl.checkApiVersion(
137                     ClientVersion.getCurrentVersion().toVersionString());
138             Version vendorVersionObj = Version.parse(vendorVersion);
139             if (vendorVersionObj != null
140                     && ClientVersion.getCurrentVersion().getVersion().getMajor()
141                     == vendorVersionObj.getMajor()) {
142                 mRuntimeVersion = vendorVersionObj;
143             }
144 
145             Logger.d(TAG, "Selected vendor runtime: " + mRuntimeVersion);
146         }
147 
148         @Override
getVersionObject()149         Version getVersionObject() {
150             return mRuntimeVersion;
151         }
152 
153         @Override
isAdvancedExtenderSupportedInternal()154         boolean isAdvancedExtenderSupportedInternal() {
155             try {
156                 return sImpl.isAdvancedExtenderImplemented();
157             } catch (NoSuchMethodError e) {
158                 return false;
159             }
160         }
161     }
162 
163     /** Empty implementation of ExtensionVersion which does nothing. */
164     private static class DefaultExtenderVersioning extends ExtensionVersion {
DefaultExtenderVersioning()165         DefaultExtenderVersioning() {
166         }
167 
168         @Override
getVersionObject()169         Version getVersionObject() {
170             return null;
171         }
172 
173         @Override
isAdvancedExtenderSupportedInternal()174         boolean isAdvancedExtenderSupportedInternal() {
175             return false;
176         }
177     }
178 }
179