diff options
20 files changed, 803 insertions, 48 deletions
diff --git a/core/java/android/util/jar/StrictJarFile.java b/core/java/android/util/jar/StrictJarFile.java index bc4a19d2d5e9..11aee2f540e6 100644 --- a/core/java/android/util/jar/StrictJarFile.java +++ b/core/java/android/util/jar/StrictJarFile.java @@ -390,6 +390,7 @@ public final class StrictJarFile { public static class ZipInflaterInputStream extends InflaterInputStream { private final ZipEntry entry; private long bytesRead = 0; + private boolean closed; public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) { super(is, inf, bsize); @@ -424,6 +425,12 @@ public final class StrictJarFile { } return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead); } + + @Override + public void close() throws IOException { + super.close(); + closed = true; + } } /** diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 9b9f704e1f69..1b5bbb19e6a0 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -937,7 +937,10 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( } // Assign system_server to the correct memory cgroup. - if (!WriteStringToFile(StringPrintf("%d", pid), "/dev/memcg/system/tasks")) { + // Not all devices mount /dev/memcg so check for the file first + // to avoid unnecessarily printing errors and denials in the logs. + if (!access("/dev/memcg/system/tasks", F_OK) && + !WriteStringToFile(StringPrintf("%d", pid), "/dev/memcg/system/tasks")) { ALOGE("couldn't write %d to /dev/memcg/system/tasks", pid); } } diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java index abd2244959cf..64251dcfe20e 100644 --- a/services/print/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java @@ -705,8 +705,10 @@ final class RemotePrintSpooler { @Override public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { - clearClientLocked(); - mRemoteInstance = null; + if (mRemoteInstance != null) { + clearClientLocked(); + mRemoteInstance = null; + } } } } diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java index 3aab3fc9e199..bffeb170792e 100644 --- a/telephony/java/android/telephony/CellInfo.java +++ b/telephony/java/android/telephony/CellInfo.java @@ -17,6 +17,7 @@ package android.telephony; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; @@ -124,6 +125,14 @@ public abstract class CellInfo implements Parcelable { mTimeStamp = timeStamp; } + /** @hide */ + @NonNull + public abstract CellIdentity getCellIdentity(); + + /** @hide */ + @NonNull + public abstract CellSignalStrength getCellSignalStrength(); + /** * Gets the connection status of this cell. * diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java index 6403bc5a16a1..8b8d1bbe3fc6 100644 --- a/telephony/java/android/telephony/CellInfoCdma.java +++ b/telephony/java/android/telephony/CellInfoCdma.java @@ -45,6 +45,7 @@ public final class CellInfoCdma extends CellInfo implements Parcelable { this.mCellSignalStrengthCdma = ci.mCellSignalStrengthCdma.copy(); } + @Override public CellIdentityCdma getCellIdentity() { return mCellIdentityCdma; } @@ -53,6 +54,7 @@ public final class CellInfoCdma extends CellInfo implements Parcelable { mCellIdentityCdma = cid; } + @Override public CellSignalStrengthCdma getCellSignalStrength() { return mCellSignalStrengthCdma; } diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java index a3a9b315241e..f7af1b201698 100644 --- a/telephony/java/android/telephony/CellInfoGsm.java +++ b/telephony/java/android/telephony/CellInfoGsm.java @@ -45,6 +45,7 @@ public final class CellInfoGsm extends CellInfo implements Parcelable { this.mCellSignalStrengthGsm = ci.mCellSignalStrengthGsm.copy(); } + @Override public CellIdentityGsm getCellIdentity() { return mCellIdentityGsm; } @@ -53,6 +54,7 @@ public final class CellInfoGsm extends CellInfo implements Parcelable { mCellIdentityGsm = cid; } + @Override public CellSignalStrengthGsm getCellSignalStrength() { return mCellSignalStrengthGsm; } diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java index b892e89a8517..97d856e39c80 100644 --- a/telephony/java/android/telephony/CellInfoLte.java +++ b/telephony/java/android/telephony/CellInfoLte.java @@ -45,6 +45,7 @@ public final class CellInfoLte extends CellInfo implements Parcelable { this.mCellSignalStrengthLte = ci.mCellSignalStrengthLte.copy(); } + @Override public CellIdentityLte getCellIdentity() { if (DBG) log("getCellIdentity: " + mCellIdentityLte); return mCellIdentityLte; @@ -55,6 +56,7 @@ public final class CellInfoLte extends CellInfo implements Parcelable { mCellIdentityLte = cid; } + @Override public CellSignalStrengthLte getCellSignalStrength() { if (DBG) log("getCellSignalStrength: " + mCellSignalStrengthLte); return mCellSignalStrengthLte; diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java index 7084c51f1b8a..4fb1bce1cb8f 100644 --- a/telephony/java/android/telephony/CellInfoTdscdma.java +++ b/telephony/java/android/telephony/CellInfoTdscdma.java @@ -48,6 +48,7 @@ public final class CellInfoTdscdma extends CellInfo implements Parcelable { this.mCellSignalStrengthTdscdma = ci.mCellSignalStrengthTdscdma.copy(); } + @Override public CellIdentityTdscdma getCellIdentity() { return mCellIdentityTdscdma; } @@ -56,6 +57,7 @@ public final class CellInfoTdscdma extends CellInfo implements Parcelable { mCellIdentityTdscdma = cid; } + @Override public CellSignalStrengthTdscdma getCellSignalStrength() { return mCellSignalStrengthTdscdma; } diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java index 005f3d341ec1..4f9dcb1a0637 100644 --- a/telephony/java/android/telephony/CellInfoWcdma.java +++ b/telephony/java/android/telephony/CellInfoWcdma.java @@ -47,6 +47,7 @@ public final class CellInfoWcdma extends CellInfo implements Parcelable { this.mCellSignalStrengthWcdma = ci.mCellSignalStrengthWcdma.copy(); } + @Override public CellIdentityWcdma getCellIdentity() { return mCellIdentityWcdma; } @@ -55,6 +56,7 @@ public final class CellInfoWcdma extends CellInfo implements Parcelable { mCellIdentityWcdma = cid; } + @Override public CellSignalStrengthWcdma getCellSignalStrength() { return mCellSignalStrengthWcdma; } diff --git a/telephony/java/com/android/internal/telephony/ISmsBaseImpl.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java index cc1d105ae29e..1cdf44d897b2 100644 --- a/telephony/java/com/android/internal/telephony/ISmsBaseImpl.java +++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java @@ -1,4 +1,5 @@ -/* Copyright (C) 2018 The Android Open Source Project +/* + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,17 +12,19 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package com.android.internal.telephony; import android.app.PendingIntent; import android.net.Uri; -import java.lang.UnsupportedOperationException; + import java.util.List; -public class ISmsBaseImpl extends ISms.Stub { +/** + * Base class for ISms that facilitates forward compatibility with new features. + */ +public class ISmsImplBase extends ISms.Stub { @Override public List<SmsRawData> getAllMessagesFromIccEfForSubscriber(int subId, String callingPkg) { @@ -29,45 +32,42 @@ public class ISmsBaseImpl extends ISms.Stub { } @Override - public boolean updateMessageOnIccEfForSubscriber(int subId, String callingPkg, - int messageIndex, int newStatus, byte[] pdu) throws UnsupportedOperationException { + public boolean updateMessageOnIccEfForSubscriber(int subId, String callingPkg, int messageIndex, + int newStatus, byte[] pdu) { throw new UnsupportedOperationException(); } @Override public boolean copyMessageToIccEfForSubscriber(int subId, String callingPkg, int status, - byte[] pdu, byte[] smsc) throws UnsupportedOperationException { + byte[] pdu, byte[] smsc) { throw new UnsupportedOperationException(); } @Override public void sendDataForSubscriber(int subId, String callingPkg, String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent, - PendingIntent deliveryIntent) throws UnsupportedOperationException { + PendingIntent deliveryIntent) { throw new UnsupportedOperationException(); } @Override public void sendDataForSubscriberWithSelfPermissions(int subId, String callingPkg, - String destAddr, String scAddr, int destPort, byte[] data, - PendingIntent sentIntent, PendingIntent deliveryIntent) - throws UnsupportedOperationException { + String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent, + PendingIntent deliveryIntent) { throw new UnsupportedOperationException(); } @Override public void sendTextForSubscriber(int subId, String callingPkg, String destAddr, String scAddr, String text, PendingIntent sentIntent, - PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp) - throws UnsupportedOperationException { + PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp) { throw new UnsupportedOperationException(); } @Override public void sendTextForSubscriberWithSelfPermissions(int subId, String callingPkg, String destAddr, String scAddr, String text, PendingIntent sentIntent, - PendingIntent deliveryIntent, boolean persistMessage) - throws UnsupportedOperationException { + PendingIntent deliveryIntent, boolean persistMessage) { throw new UnsupportedOperationException(); } @@ -75,15 +75,13 @@ public class ISmsBaseImpl extends ISms.Stub { public void sendTextForSubscriberWithOptions(int subId, String callingPkg, String destAddr, String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp, - int priority, boolean expectMore, int validityPeriod) - throws UnsupportedOperationException { + int priority, boolean expectMore, int validityPeriod) { throw new UnsupportedOperationException(); } @Override public void injectSmsPduForSubscriber( - int subId, byte[] pdu, String format, PendingIntent receivedIntent) - throws UnsupportedOperationException { + int subId, byte[] pdu, String format, PendingIntent receivedIntent) { throw new UnsupportedOperationException(); } @@ -91,8 +89,7 @@ public class ISmsBaseImpl extends ISms.Stub { public void sendMultipartTextForSubscriber(int subId, String callingPkg, String destinationAddress, String scAddress, List<String> parts, List<PendingIntent> sentIntents, - List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) - throws UnsupportedOperationException { + List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) { throw new UnsupportedOperationException(); } @@ -101,99 +98,94 @@ public class ISmsBaseImpl extends ISms.Stub { String destinationAddress, String scAddress, List<String> parts, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp, - int priority, boolean expectMore, int validityPeriod) - throws UnsupportedOperationException { + int priority, boolean expectMore, int validityPeriod) { throw new UnsupportedOperationException(); } @Override - public boolean enableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType) - throws UnsupportedOperationException { + public boolean enableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType) { throw new UnsupportedOperationException(); } @Override - public boolean disableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType) - throws UnsupportedOperationException { + public boolean disableCellBroadcastForSubscriber(int subId, int messageIdentifier, + int ranType) { throw new UnsupportedOperationException(); } @Override public boolean enableCellBroadcastRangeForSubscriber(int subId, int startMessageId, - int endMessageId, int ranType) throws UnsupportedOperationException { + int endMessageId, int ranType) { throw new UnsupportedOperationException(); } @Override public boolean disableCellBroadcastRangeForSubscriber(int subId, int startMessageId, - int endMessageId, int ranType) throws UnsupportedOperationException { + int endMessageId, int ranType) { throw new UnsupportedOperationException(); } @Override - public int getPremiumSmsPermission(String packageName) throws UnsupportedOperationException { + public int getPremiumSmsPermission(String packageName) { throw new UnsupportedOperationException(); } @Override - public int getPremiumSmsPermissionForSubscriber(int subId, String packageName) - throws UnsupportedOperationException { + public int getPremiumSmsPermissionForSubscriber(int subId, String packageName) { throw new UnsupportedOperationException(); } @Override - public void setPremiumSmsPermission(String packageName, int permission) throws UnsupportedOperationException { + public void setPremiumSmsPermission(String packageName, int permission) { throw new UnsupportedOperationException(); } @Override public void setPremiumSmsPermissionForSubscriber(int subId, String packageName, - int permission) throws UnsupportedOperationException { + int permission) { throw new UnsupportedOperationException(); } @Override - public boolean isImsSmsSupportedForSubscriber(int subId) throws UnsupportedOperationException { + public boolean isImsSmsSupportedForSubscriber(int subId) { throw new UnsupportedOperationException(); } @Override - public boolean isSmsSimPickActivityNeeded(int subId) throws UnsupportedOperationException { + public boolean isSmsSimPickActivityNeeded(int subId) { throw new UnsupportedOperationException(); } @Override - public int getPreferredSmsSubscription() throws UnsupportedOperationException { + public int getPreferredSmsSubscription() { throw new UnsupportedOperationException(); } @Override - public String getImsSmsFormatForSubscriber(int subId) throws UnsupportedOperationException { + public String getImsSmsFormatForSubscriber(int subId) { throw new UnsupportedOperationException(); } @Override - public boolean isSMSPromptEnabled() throws UnsupportedOperationException { + public boolean isSMSPromptEnabled() { throw new UnsupportedOperationException(); } @Override public void sendStoredText(int subId, String callingPkg, Uri messageUri, String scAddress, - PendingIntent sentIntent, PendingIntent deliveryIntent) - throws UnsupportedOperationException { + PendingIntent sentIntent, PendingIntent deliveryIntent) { throw new UnsupportedOperationException(); } @Override public void sendStoredMultipartText(int subId, String callingPkg, Uri messageUri, - String scAddress, List<PendingIntent> sentIntents, - List<PendingIntent> deliveryIntents) throws UnsupportedOperationException { + String scAddress, List<PendingIntent> sentIntents, + List<PendingIntent> deliveryIntents) { throw new UnsupportedOperationException(); } @Override - public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) - throws UnsupportedOperationException { + public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) { throw new UnsupportedOperationException(); } } diff --git a/tools/hiddenapi/class2greylist/Android.bp b/tools/hiddenapi/class2greylist/Android.bp new file mode 100644 index 000000000000..7b1233bb8535 --- /dev/null +++ b/tools/hiddenapi/class2greylist/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +java_library_host { + name: "class2greylistlib", + srcs: ["src/**/*.java"], + static_libs: [ + "commons-cli-1.2", + "apache-bcel", + ], +} + +java_binary_host { + name: "class2greylist", + manifest: "src/class2greylist.mf", + static_libs: [ + "class2greylistlib", + ], +} + diff --git a/tools/hiddenapi/class2greylist/src/class2greylist.mf b/tools/hiddenapi/class2greylist/src/class2greylist.mf new file mode 100644 index 000000000000..ea3a3d9153a3 --- /dev/null +++ b/tools/hiddenapi/class2greylist/src/class2greylist.mf @@ -0,0 +1 @@ +Main-Class: com.android.class2greylist.Class2Greylist diff --git a/tools/hiddenapi/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java new file mode 100644 index 000000000000..66857525aa0c --- /dev/null +++ b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/AnnotationVisitor.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.class2greylist; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.AnnotationEntry; +import org.apache.bcel.classfile.DescendingVisitor; +import org.apache.bcel.classfile.ElementValuePair; +import org.apache.bcel.classfile.EmptyVisitor; +import org.apache.bcel.classfile.Field; +import org.apache.bcel.classfile.FieldOrMethod; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; + +import java.util.Locale; + +/** + * Visits a JavaClass instance and pulls out all members annotated with a + * specific annotation. The signatures of such members are passed to {@link + * Status#greylistEntry(String)}. Any errors result in a call to {@link + * Status#error(String)}. + * + * If the annotation has a property "expectedSignature" the generated signature + * will be verified against the one specified there. If it differs, an error + * will be generated. + */ +public class AnnotationVisitor extends EmptyVisitor { + + private static final String EXPECTED_SIGNATURE = "expectedSignature"; + + private final JavaClass mClass; + private final String mAnnotationType; + private final Status mStatus; + private final DescendingVisitor mDescendingVisitor; + + public AnnotationVisitor(JavaClass clazz, String annotation, Status d) { + mClass = clazz; + mAnnotationType = annotation; + mStatus = d; + mDescendingVisitor = new DescendingVisitor(clazz, this); + } + + public void visit() { + mStatus.debug("Visit class %s", mClass.getClassName()); + mDescendingVisitor.visit(); + } + + private static String getClassDescriptor(JavaClass clazz) { + // JavaClass.getName() returns the Java-style name (with . not /), so we must fetch + // the original class name from the constant pool. + return clazz.getConstantPool().getConstantString( + clazz.getClassNameIndex(), Const.CONSTANT_Class); + } + + @Override + public void visitMethod(Method method) { + visitMember(method, "L%s;->%s%s"); + } + + @Override + public void visitField(Field field) { + visitMember(field, "L%s;->%s:%s"); + } + + private void visitMember(FieldOrMethod member, String signatureFormatString) { + JavaClass definingClass = (JavaClass) mDescendingVisitor.predecessor(); + mStatus.debug("Visit member %s : %s", member.getName(), member.getSignature()); + for (AnnotationEntry a : member.getAnnotationEntries()) { + if (mAnnotationType.equals(a.getAnnotationType())) { + mStatus.debug("Method has annotation %s", mAnnotationType); + String signature = String.format(Locale.US, signatureFormatString, + getClassDescriptor(definingClass), member.getName(), member.getSignature()); + for (ElementValuePair property : a.getElementValuePairs()) { + switch (property.getNameString()) { + case EXPECTED_SIGNATURE: + String expected = property.getValue().stringifyValue(); + if (!signature.equals(expected)) { + error(definingClass, member, + "Expected signature does not match generated:\n" + + "Expected: %s\n" + + "Generated: %s", expected, signature); + } + break; + } + } + mStatus.greylistEntry(signature); + } + } + } + + private void error(JavaClass clazz, FieldOrMethod member, String message, Object... args) { + StringBuilder error = new StringBuilder(); + error.append(clazz.getSourceFileName()) + .append(": ") + .append(clazz.getClassName()) + .append(".") + .append(member.getName()) + .append(": ") + .append(String.format(Locale.US, message, args)); + + mStatus.error(error.toString()); + } + +} diff --git a/tools/hiddenapi/class2greylist/src/com/android/class2greylist/Class2Greylist.java b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/Class2Greylist.java new file mode 100644 index 000000000000..bcccf4aa18a9 --- /dev/null +++ b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/Class2Greylist.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.class2greylist; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PatternOptionBuilder; + +import java.io.IOException; + +/** + * Build time tool for extracting a list of members from jar files that have the @UsedByApps + * annotation, for building the greylist. + */ +public class Class2Greylist { + + private static final String ANNOTATION_TYPE = "Landroid/annotation/UsedByApps;"; + + public static void main(String[] args) { + Options options = new Options(); + options.addOption(OptionBuilder + .withLongOpt("debug") + .hasArgs(0) + .withDescription("Enable debug") + .create("d")); + options.addOption(OptionBuilder + .withLongOpt("help") + .hasArgs(0) + .withDescription("Show this help") + .create("h")); + + CommandLineParser parser = new GnuParser(); + CommandLine cmd; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println(e.getMessage()); + help(options); + return; + } + if (cmd.hasOption('h')) { + help(options); + } + + String[] jarFiles = cmd.getArgs(); + if (jarFiles.length == 0) { + System.err.println("Error: no jar files specified."); + help(options); + } + + Status status = new Status(cmd.hasOption('d')); + + for (String jarFile : jarFiles) { + status.debug("Processing jar file %s", jarFile); + try { + JarReader reader = new JarReader(status, jarFile); + reader.stream().forEach(clazz -> new AnnotationVisitor( + clazz, ANNOTATION_TYPE, status).visit()); + reader.close(); + } catch (IOException e) { + status.error(e); + } + } + if (status.ok()) { + System.exit(0); + } else { + System.exit(1); + } + + } + + private static void help(Options options) { + new HelpFormatter().printHelp( + "class2greylist path/to/classes.jar [classes2.jar ...]", + "Extracts greylist entries from classes jar files given", + options, null, true); + System.exit(1); + } +} diff --git a/tools/hiddenapi/class2greylist/src/com/android/class2greylist/JarReader.java b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/JarReader.java new file mode 100644 index 000000000000..f3a9d0b92e6c --- /dev/null +++ b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/JarReader.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.class2greylist; + +import org.apache.bcel.classfile.ClassParser; +import org.apache.bcel.classfile.JavaClass; + +import java.io.IOException; +import java.util.Objects; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Reads {@link JavaClass} members from a zip/jar file, providing a stream of them for processing. + * Any errors are reported via {@link Status#error(Throwable)}. + */ +public class JarReader { + + private final Status mStatus; + private final String mFileName; + private final ZipFile mZipFile; + + public JarReader(Status s, String filename) throws IOException { + mStatus = s; + mFileName = filename; + mZipFile = new ZipFile(mFileName); + } + + private JavaClass openZipEntry(ZipEntry e) { + try { + mStatus.debug("Reading %s from %s", e.getName(), mFileName); + return new ClassParser(mZipFile.getInputStream(e), e.getName()).parse(); + } catch (IOException ioe) { + mStatus.error(ioe); + return null; + } + } + + + public Stream<JavaClass> stream() { + return mZipFile.stream() + .filter(zipEntry -> zipEntry.getName().endsWith(".class")) + .map(zipEntry -> openZipEntry(zipEntry)) + .filter(Objects::nonNull); + } + + public void close() throws IOException { + mZipFile.close(); + } +} diff --git a/tools/hiddenapi/class2greylist/src/com/android/class2greylist/Status.java b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/Status.java new file mode 100644 index 000000000000..d7078986d9cd --- /dev/null +++ b/tools/hiddenapi/class2greylist/src/com/android/class2greylist/Status.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.class2greylist; + +import java.util.Locale; + +public class Status { + + // Highlight "Error:" in red. + private static final String ERROR = "\u001B[31mError: \u001B[0m"; + + private final boolean mDebug; + private boolean mHasErrors; + + public Status(boolean debug) { + mDebug = debug; + } + + public void debug(String msg, Object... args) { + if (mDebug) { + System.err.println(String.format(Locale.US, msg, args)); + } + } + + public void error(Throwable t) { + System.err.print(ERROR); + t.printStackTrace(System.err); + mHasErrors = true; + } + + public void error(String message) { + System.err.print(ERROR); + System.err.println(message); + mHasErrors = true; + } + + public void greylistEntry(String signature) { + System.out.println(signature); + } + + public boolean ok() { + return !mHasErrors; + } +} diff --git a/tools/hiddenapi/class2greylist/test/Android.mk b/tools/hiddenapi/class2greylist/test/Android.mk new file mode 100644 index 000000000000..23f4156f6d03 --- /dev/null +++ b/tools/hiddenapi/class2greylist/test/Android.mk @@ -0,0 +1,32 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Only compile source java files in this apk. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_MODULE := class2greylisttest + +LOCAL_STATIC_JAVA_LIBRARIES := class2greylistlib truth-host-prebuilt mockito-host junit-host + +# tag this module as a cts test artifact +LOCAL_COMPATIBILITY_SUITE := general-tests + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build the test APKs using their own makefiles +include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file diff --git a/tools/hiddenapi/class2greylist/test/AndroidTest.xml b/tools/hiddenapi/class2greylist/test/AndroidTest.xml new file mode 100644 index 000000000000..66bb63446f71 --- /dev/null +++ b/tools/hiddenapi/class2greylist/test/AndroidTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="class2greylist tests"> + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > + <option name="jar" value="class2greylisttest.jar" /> + <option name="runtime-hint" value="1m" /> + </test> +</configuration>
\ No newline at end of file diff --git a/tools/hiddenapi/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java b/tools/hiddenapi/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java new file mode 100644 index 000000000000..2d9721803cac --- /dev/null +++ b/tools/hiddenapi/class2greylist/test/src/com/android/javac/AnnotationVisitorTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javac; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; + +import com.android.class2greylist.Status; +import com.android.class2greylist.AnnotationVisitor; + +import com.google.common.base.Joiner; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import java.io.IOException; + +public class AnnotationVisitorTest { + + private static final String ANNOTATION = "Lannotation/Anno;"; + + private Javac mJavac; + @Mock + private Status mStatus; + + @Before + public void setup() throws IOException { + initMocks(this); + mJavac = new Javac(); + mJavac.addSource("annotation.Anno", Joiner.on('\n').join( + "package annotation;", + "import static java.lang.annotation.RetentionPolicy.CLASS;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.Target;", + "@Retention(CLASS)", + "public @interface Anno {", + " String expectedSignature() default \"\";", + "}")); + } + + private void assertNoErrors() { + verify(mStatus, never()).error(any(Throwable.class)); + verify(mStatus, never()).error(any(String.class)); + } + + @Test + public void testGreylistMethod() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "import annotation.Anno;", + "public class Class {", + " @Anno", + " public void method() {}", + "}")); + assertThat(mJavac.compile()).isTrue(); + + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus) + .visit(); + + assertNoErrors(); + ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); + verify(mStatus, times(1)).greylistEntry(greylist.capture()); + assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V"); + } + + @Test + public void testGreylistConstructor() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "import annotation.Anno;", + "public class Class {", + " @Anno", + " public Class() {}", + "}")); + assertThat(mJavac.compile()).isTrue(); + + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus) + .visit(); + + assertNoErrors(); + ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); + verify(mStatus, times(1)).greylistEntry(greylist.capture()); + assertThat(greylist.getValue()).isEqualTo("La/b/Class;-><init>()V"); + } + + @Test + public void testGreylistField() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "import annotation.Anno;", + "public class Class {", + " @Anno", + " public int i;", + "}")); + assertThat(mJavac.compile()).isTrue(); + + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus) + .visit(); + + assertNoErrors(); + ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); + verify(mStatus, times(1)).greylistEntry(greylist.capture()); + assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I"); + } + + @Test + public void testGreylistMethodExpectedSignature() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "import annotation.Anno;", + "public class Class {", + " @Anno(expectedSignature=\"La/b/Class;->method()V\")", + " public void method() {}", + "}")); + assertThat(mJavac.compile()).isTrue(); + + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus) + .visit(); + + assertNoErrors(); + ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); + verify(mStatus, times(1)).greylistEntry(greylist.capture()); + assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V"); + } + + @Test + public void testGreylistMethodExpectedSignatureWrong() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "import annotation.Anno;", + "public class Class {", + " @Anno(expectedSignature=\"La/b/Class;->nomethod()V\")", + " public void method() {}", + "}")); + assertThat(mJavac.compile()).isTrue(); + + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus) + .visit(); + + verify(mStatus, times(1)).error(any(String.class)); + } + + @Test + public void testGreylistInnerClassMethod() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "import annotation.Anno;", + "public class Class {", + " public class Inner {", + " @Anno", + " public void method() {}", + " }", + "}")); + assertThat(mJavac.compile()).isTrue(); + + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), ANNOTATION, + mStatus).visit(); + + assertNoErrors(); + ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class); + verify(mStatus, times(1)).greylistEntry(greylist.capture()); + assertThat(greylist.getValue()).isEqualTo("La/b/Class$Inner;->method()V"); + } + + @Test + public void testMethodNotGreylisted() throws IOException { + mJavac.addSource("a.b.Class", Joiner.on('\n').join( + "package a.b;", + "public class Class {", + " public void method() {}", + "}")); + assertThat(mJavac.compile()).isTrue(); + + new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus) + .visit(); + + assertNoErrors(); + verify(mStatus, never()).greylistEntry(any(String.class)); + } + +} diff --git a/tools/hiddenapi/class2greylist/test/src/com/android/javac/Javac.java b/tools/hiddenapi/class2greylist/test/src/com/android/javac/Javac.java new file mode 100644 index 000000000000..202f4121fc60 --- /dev/null +++ b/tools/hiddenapi/class2greylist/test/src/com/android/javac/Javac.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javac; + +import com.google.common.io.Files; + +import org.apache.bcel.classfile.ClassParser; +import org.apache.bcel.classfile.JavaClass; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +/** + * Helper class for compiling snippets of Java source and providing access to the resulting class + * files. + */ +public class Javac { + + private final JavaCompiler mJavac; + private final StandardJavaFileManager mFileMan; + private final List<JavaFileObject> mCompilationUnits; + private final File mClassOutDir; + + public Javac() throws IOException { + mJavac = ToolProvider.getSystemJavaCompiler(); + mFileMan = mJavac.getStandardFileManager(null, Locale.US, null); + mClassOutDir = Files.createTempDir(); + mFileMan.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(mClassOutDir)); + mFileMan.setLocation(StandardLocation.CLASS_PATH, Arrays.asList(mClassOutDir)); + mCompilationUnits = new ArrayList<>(); + } + + private String classToFileName(String classname) { + return classname.replace('.', '/'); + } + + public Javac addSource(String classname, String contents) { + JavaFileObject java = new SimpleJavaFileObject(URI.create( + String.format("string:///%s.java", classToFileName(classname))), + JavaFileObject.Kind.SOURCE + ){ + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return contents; + } + }; + mCompilationUnits.add(java); + return this; + } + + public boolean compile() { + JavaCompiler.CompilationTask task = mJavac.getTask( + null, + mFileMan, + null, + null, + null, + mCompilationUnits); + return task.call(); + } + + public InputStream getClassFile(String classname) throws IOException { + Iterable<? extends JavaFileObject> objs = mFileMan.getJavaFileObjects( + new File(mClassOutDir, String.format("%s.class", classToFileName(classname)))); + if (!objs.iterator().hasNext()) { + return null; + } + return objs.iterator().next().openInputStream(); + } + + public JavaClass getCompiledClass(String classname) throws IOException { + return new ClassParser(getClassFile(classname), + String.format("%s.class", classToFileName(classname))).parse(); + } +} |