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 void android.view.WindowManager.requestAppKeyboardShortcuts(android.view.WindowManager$KeyboardShortcutsReceiver,int)"); 70 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean javax.microedition.khronos.egl.EGL10.eglReleaseThread()"); 71 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void org.w3c.dom.ls.LSSerializer.setFilter(org.w3c.dom.ls.LSSerializerFilter)"); 72 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract org.w3c.dom.ls.LSSerializerFilter org.w3c.dom.ls.LSSerializer.getFilter()"); 73 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.graphics.Region android.view.WindowManager.getCurrentImeTouchRegion()"); 74 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract java.util.Set<android.media.AudioMetadata$Key<?>> android.media.AudioMetadataReadMap.keySet()"); 75 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract android.view.InsetsState android.view.WindowInsetsController.getState()"); 76 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.view.WindowInsetsController.isRequestedVisible(int)"); 77 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setAnimationsDisabled(boolean)"); 78 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.hideSoftInputWithToken(int,android.os.ResultReceiver,android.os.IBinder)"); 79 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.view.WindowInsetsAnimationController.hasZeroInsetsIme()"); 80 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setCaptionInsetsHeight(int)"); 81 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setSystemDrivenInsetsAnimationLoggingListener(android.view.WindowInsetsAnimationControlListener)"); 82 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentHideInputToken(android.os.IBinder)"); 83 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentShowInputToken(android.os.IBinder)"); 84 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.notifyImeHidden()"); 85 HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.removeImeSurface()"); 86 } 87 88 private final ResultObserver resultObserver; 89 90 private final Map<Class<?>, JDiffClassDescription> class2Description = 91 new TreeMap<>(Comparator.comparing(Class::getName)); 92 93 private final ClassProvider classProvider; 94 InterfaceChecker(ResultObserver resultObserver, ClassProvider classProvider)95 InterfaceChecker(ResultObserver resultObserver, ClassProvider classProvider) { 96 this.resultObserver = resultObserver; 97 this.classProvider = classProvider; 98 } 99 checkQueued()100 public void checkQueued() { 101 for (Map.Entry<Class<?>, JDiffClassDescription> entry : class2Description.entrySet()) { 102 Class<?> runtimeClass = entry.getKey(); 103 JDiffClassDescription classDescription = entry.getValue(); 104 if (classDescription.isPreviousApi()) { 105 // Skip the interface method check as it provides no value. If the runtime interface 106 // contains additional methods that are not present in a previous API then either 107 // the methods have been added in a later API (in which case it is ok), or it will 108 // be caught when comparing against the current API. 109 continue; 110 } 111 112 List<Method> methods = checkInterfaceMethodCompliance(classDescription, runtimeClass); 113 if (methods.size() > 0) { 114 resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD, 115 classDescription.getAbsoluteClassName(), "Interfaces cannot be modified: " 116 + classDescription.getAbsoluteClassName() 117 + " has the following methods that are not present in the API specification:\n\t" 118 + methods.stream().map(Method::toGenericString).collect(Collectors.joining("\n\t"))); 119 } 120 } 121 } 122 not(Predicate<T> predicate)123 private static <T> Predicate<T> not(Predicate<T> predicate) { 124 return predicate.negate(); 125 } 126 127 /** 128 * Validate that an interfaces method count is as expected. 129 * 130 * @param classDescription the class's API description. 131 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 132 */ checkInterfaceMethodCompliance( JDiffClassDescription classDescription, Class<?> runtimeClass)133 private List<Method> checkInterfaceMethodCompliance( 134 JDiffClassDescription classDescription, Class<?> runtimeClass) { 135 136 return Stream.of(runtimeClass.getDeclaredMethods()) 137 .filter(not(Method::isDefault)) 138 .filter(not(Method::isSynthetic)) 139 .filter(not(Method::isBridge)) 140 .filter(m -> !Modifier.isStatic(m.getModifiers())) 141 .filter(m -> !HIDDEN_INTERFACE_METHOD_ALLOW_LIST.contains(m.toGenericString())) 142 .filter(m -> !findMethod(classDescription, m)) 143 .collect(Collectors.toCollection(ArrayList::new)); 144 } 145 findMethod(JDiffClassDescription classDescription, Method method)146 private boolean findMethod(JDiffClassDescription classDescription, Method method) { 147 for (JDiffClassDescription.JDiffMethod jdiffMethod : classDescription.getMethods()) { 148 if (ReflectionHelper.matches(jdiffMethod, method)) { 149 return true; 150 } 151 } 152 for (String interfaceName : classDescription.getImplInterfaces()) { 153 Class<?> interfaceClass = null; 154 try { 155 interfaceClass = ReflectionHelper.findMatchingClass(interfaceName, classProvider); 156 } catch (ClassNotFoundException e) { 157 LogHelper.loge("ClassNotFoundException for " + classDescription.getAbsoluteClassName(), e); 158 } 159 160 JDiffClassDescription implInterface = class2Description.get(interfaceClass); 161 if (implInterface == null) { 162 // Class definition is not in the scope of the API definitions. 163 continue; 164 } 165 166 if (findMethod(implInterface, method)) { 167 return true; 168 } 169 } 170 return false; 171 } 172 173 queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass)174 void queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass) { 175 JDiffClassDescription existingDescription = class2Description.get(runtimeClass); 176 if (existingDescription != null) { 177 for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) { 178 existingDescription.addMethod(method); 179 } 180 } else { 181 class2Description.put(runtimeClass, classDescription); 182 } 183 } 184 } 185