Merge "Remove dependency on InflaterInputStream.closed field."
diff --git a/Android.bp b/Android.bp
index 48757b4..c682822 100644
--- a/Android.bp
+++ b/Android.bp
@@ -609,6 +609,8 @@
"system/netd/server/binder",
"system/bt/binder",
],
+
+ generate_get_transaction_name: true
},
no_framework_libs: true,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 9b9f704..1b5bbb1 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -937,7 +937,10 @@
}
// 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 abd2244..64251dc 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 @@
@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 3aab3fc..bffeb17 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 @@
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 6403bc5..8b8d1bb 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -45,6 +45,7 @@
this.mCellSignalStrengthCdma = ci.mCellSignalStrengthCdma.copy();
}
+ @Override
public CellIdentityCdma getCellIdentity() {
return mCellIdentityCdma;
}
@@ -53,6 +54,7 @@
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 a3a9b31..f7af1b2 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -45,6 +45,7 @@
this.mCellSignalStrengthGsm = ci.mCellSignalStrengthGsm.copy();
}
+ @Override
public CellIdentityGsm getCellIdentity() {
return mCellIdentityGsm;
}
@@ -53,6 +54,7 @@
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 b892e89..97d856e 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -45,6 +45,7 @@
this.mCellSignalStrengthLte = ci.mCellSignalStrengthLte.copy();
}
+ @Override
public CellIdentityLte getCellIdentity() {
if (DBG) log("getCellIdentity: " + mCellIdentityLte);
return mCellIdentityLte;
@@ -55,6 +56,7 @@
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 7084c51..4fb1bce 100644
--- a/telephony/java/android/telephony/CellInfoTdscdma.java
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -48,6 +48,7 @@
this.mCellSignalStrengthTdscdma = ci.mCellSignalStrengthTdscdma.copy();
}
+ @Override
public CellIdentityTdscdma getCellIdentity() {
return mCellIdentityTdscdma;
}
@@ -56,6 +57,7 @@
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 005f3d3..4f9dcb1 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -47,6 +47,7 @@
this.mCellSignalStrengthWcdma = ci.mCellSignalStrengthWcdma.copy();
}
+ @Override
public CellIdentityWcdma getCellIdentity() {
return mCellIdentityWcdma;
}
@@ -55,6 +56,7 @@
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
similarity index 70%
rename from telephony/java/com/android/internal/telephony/ISmsBaseImpl.java
rename to telephony/java/com/android/internal/telephony/ISmsImplBase.java
index cc1d105..1cdf44d 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 @@
}
@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 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 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 @@
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 0000000..7b1233b
--- /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 0000000..ea3a3d9
--- /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 0000000..6685752
--- /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 0000000..bcccf4a
--- /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 0000000..f3a9d0b
--- /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 0000000..d707898
--- /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 0000000..23f4156
--- /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 0000000..66bb6344
--- /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 0000000..2d97218
--- /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 0000000..202f412
--- /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();
+ }
+}