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