• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.signature.cts;
17 
18 import java.lang.reflect.Method;
19 import java.lang.reflect.Modifier;
20 import java.util.ArrayList;
21 import java.util.Comparator;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.TreeMap;
27 import java.util.function.Predicate;
28 import java.util.stream.Collectors;
29 import java.util.stream.Stream;
30 
31 /**
32  * Checks that the runtime representation of the interfaces match the API definition.
33  *
34  * <p>Interfaces are treated differently to other classes. Whereas other classes are checked by
35  * making sure that every member in the API is accessible through reflection. Interfaces are
36  * checked to make sure that every method visible through reflection is defined in the API. The
37  * reason for this difference is to ensure that no additional methods have been added to interfaces
38  * that are expected to be implemented by Android developers because that would break backwards
39  * compatibility.
40  *
41  * TODO(b/71886491): This also potentially applies to abstract classes that the App developers are
42  * expected to extend.
43  */
44 class InterfaceChecker {
45 
46     private static final Set<String> HIDDEN_INTERFACE_METHOD_ALLOW_LIST = new HashSet<>();
47     static {
48         // Interfaces that define @hide or @SystemApi or @TestApi methods will by definition contain
49         // methods that do not appear in current.txt but do appear at runtime. That means that those
50         // interfaces will fail compatibility checking because a developer could never implement all
51         // the methods in the interface. However, some interfaces are not intended to be implemented
52         // by a developer and so additional methods in the runtime class will not cause
53         // compatibility errors. Unfortunately, this checker has no way to determine from the
54         // interface whether an interface is intended to be implemented by a developer and for
55         // safety's sake assumes that all interfaces are.
56         //
57         // Additional methods that are provided by the runtime but are not in the API specification
58         // must be listed here to prevent them from being reported as errors.
59         //
60         // TODO(b/71886491): Avoid the need for this allow list.
61         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.companion.DeviceFilter.matches(D)");
62         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public static <D> boolean android.companion.DeviceFilter.matches(android.companion.DeviceFilter<D>,D)");
63         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract java.lang.String android.companion.DeviceFilter.getDeviceDisplayName(D)");
64         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract int android.companion.DeviceFilter.getMediumType()");
65         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.nfc.tech.TagTechnology.reconnect() throws java.io.IOException");
66         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.os.IBinder.shellCommand(java.io.FileDescriptor,java.io.FileDescriptor,java.io.FileDescriptor,java.lang.String[],android.os.ShellCallback,android.os.ResultReceiver) throws android.os.RemoteException");
67         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract int android.text.ParcelableSpan.getSpanTypeIdInternal()");
68         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.text.ParcelableSpan.writeToParcelInternal(android.os.Parcel,int)");
69         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.view.KeyboardShortcutGroup android.view.WindowManager.getApplicationLaunchKeyboardShortcuts(int)");
70         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowManager.requestAppKeyboardShortcuts(android.view.WindowManager$KeyboardShortcutsReceiver,int)");
71         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean javax.microedition.khronos.egl.EGL10.eglReleaseThread()");
72         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void org.w3c.dom.ls.LSSerializer.setFilter(org.w3c.dom.ls.LSSerializerFilter)");
73         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract org.w3c.dom.ls.LSSerializerFilter org.w3c.dom.ls.LSSerializer.getFilter()");
74         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.graphics.Region android.view.WindowManager.getCurrentImeTouchRegion()");
75         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract java.util.Set<android.media.AudioMetadata$Key<?>> android.media.AudioMetadataReadMap.keySet()");
76         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.view.InsetsState android.view.WindowInsetsController.getState()");
77         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract int android.view.WindowInsetsController.getRequestedVisibleTypes()");
78         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setAnimationsDisabled(boolean)");
79         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.hideSoftInputWithToken(int,android.os.ResultReceiver,android.os.IBinder)");
80         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.view.WindowInsetsAnimationController.hasZeroInsetsIme()");
81         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract long android.view.WindowInsetsAnimationController.getDurationMs()");
82         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.view.animation.Interpolator android.view.WindowInsetsAnimationController.getInsetsInterpolator()");
83         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setCaptionInsetsHeight(int)");
84         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setSystemDrivenInsetsAnimationLoggingListener(android.view.WindowInsetsAnimationControlListener)");
85         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setSystemBarsAppearanceFromResource(int,int)");
86         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentHideInputToken(android.os.IBinder)");
87         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentShowInputToken(android.os.IBinder)");
88         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.notifyImeHidden()");
89         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.removeImeSurface()");
90         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.bluetooth.BluetoothProfile.close()");
91         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.bluetooth.BluetoothAdapter android.bluetooth.BluetoothProfile.getAdapter()");
92         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.bluetooth.BluetoothProfile.onServiceConnected(android.os.IBinder)");
93         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.bluetooth.BluetoothProfile.onServiceDisconnected()");
94         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.telephony.satellite.SatelliteTransmissionUpdateCallback.onSendDatagramStateChanged(int,int,int,int)");
95     }
96 
97     private final ResultObserver resultObserver;
98 
99     private final Map<Class<?>, JDiffClassDescription> class2Description =
100             new TreeMap<>(Comparator.comparing(Class::getName));
101 
102     private final ClassProvider classProvider;
103 
InterfaceChecker(ResultObserver resultObserver, ClassProvider classProvider)104     InterfaceChecker(ResultObserver resultObserver, ClassProvider classProvider) {
105         this.resultObserver = resultObserver;
106         this.classProvider = classProvider;
107     }
108 
checkQueued()109     public void checkQueued() {
110         for (Map.Entry<Class<?>, JDiffClassDescription> entry : class2Description.entrySet()) {
111             Class<?> runtimeClass = entry.getKey();
112             JDiffClassDescription classDescription = entry.getValue();
113             if (classDescription.isPreviousApi()) {
114                 // Skip the interface method check as it provides no value. If the runtime interface
115                 // contains additional methods that are not present in a previous API then either
116                 // the methods have been added in a later API (in which case it is ok), or it will
117                 // be caught when comparing against the current API.
118                 continue;
119             }
120 
121             List<Method> methods = checkInterfaceMethodCompliance(classDescription, runtimeClass);
122             if (methods.size() > 0) {
123                 resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
124                         classDescription.getAbsoluteClassName(), "Interfaces cannot be modified: "
125                                 + classDescription.getAbsoluteClassName()
126                                 + " has the following methods that are not present in the API specification:\n\t"
127                                 + methods.stream().map(Method::toGenericString).collect(Collectors.joining("\n\t")));
128             }
129         }
130     }
131 
not(Predicate<T> predicate)132     private static <T> Predicate<T> not(Predicate<T> predicate) {
133         return predicate.negate();
134     }
135 
136     /**
137      * Validate that an interfaces method count is as expected.
138      *
139      * @param classDescription the class's API description.
140      * @param runtimeClass the runtime class corresponding to {@code classDescription}.
141      */
checkInterfaceMethodCompliance( JDiffClassDescription classDescription, Class<?> runtimeClass)142     private List<Method> checkInterfaceMethodCompliance(
143             JDiffClassDescription classDescription, Class<?> runtimeClass) {
144 
145         return Stream.of(runtimeClass.getDeclaredMethods())
146                 .filter(not(Method::isDefault))
147                 .filter(not(Method::isSynthetic))
148                 .filter(not(Method::isBridge))
149                 .filter(m -> !Modifier.isStatic(m.getModifiers()))
150                 .filter(m -> !HIDDEN_INTERFACE_METHOD_ALLOW_LIST.contains(m.toGenericString()))
151                 .filter(m -> !findMethod(classDescription, m))
152                 .collect(Collectors.toCollection(ArrayList::new));
153     }
154 
findMethod(JDiffClassDescription classDescription, Method method)155     private boolean findMethod(JDiffClassDescription classDescription, Method method) {
156         for (JDiffClassDescription.JDiffMethod jdiffMethod : classDescription.getMethods()) {
157             if (ReflectionHelper.matches(jdiffMethod, method)) {
158                 return true;
159             }
160         }
161         for (String interfaceName : classDescription.getImplInterfaces()) {
162             Class<?> interfaceClass = null;
163             try {
164                 interfaceClass = ReflectionHelper.findMatchingClass(interfaceName, classProvider);
165             } catch (ClassNotFoundException e) {
166                 LogHelper.loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e);
167             }
168 
169             JDiffClassDescription implInterface = class2Description.get(interfaceClass);
170             if (implInterface == null) {
171                 // Class definition is not in the scope of the API definitions.
172                 continue;
173             }
174 
175             if (findMethod(implInterface, method)) {
176                 return true;
177             }
178         }
179         return false;
180     }
181 
182 
queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass)183     void queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass) {
184         JDiffClassDescription existingDescription = class2Description.get(runtimeClass);
185         if (existingDescription != null) {
186             for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
187                 existingDescription.addMethod(method);
188             }
189         } else {
190             class2Description.put(runtimeClass, classDescription);
191         }
192     }
193 }
194