Merge "Break some dependencies on libcore internals"
diff --git a/core/java/android/util/jar/StrictJarFile.java b/core/java/android/util/jar/StrictJarFile.java
index bc4a19d..11aee2f 100644
--- a/core/java/android/util/jar/StrictJarFile.java
+++ b/core/java/android/util/jar/StrictJarFile.java
@@ -390,6 +390,7 @@
     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 @@
             }
             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 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();
+    }
+}