Merge changes from topic "deprecate-iis-closed"
* changes:
Deprecate InflaterInputStream.closed field
Correct ordering of TelephonyManager methods
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/java/android/annotation/UnsupportedAppUsage.java b/core/java/android/annotation/UnsupportedAppUsage.java
new file mode 100644
index 0000000..05de3e8
--- /dev/null
+++ b/core/java/android/annotation/UnsupportedAppUsage.java
@@ -0,0 +1,60 @@
+/*
+ * 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 android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a class member, that is not part of the SDK, is used by apps.
+ * Since the member is not part of the SDK, such use is not supported.
+ *
+ * This annotation acts as a heads up that changing a given method or field
+ * may affect apps, potentially breaking them when the next Android version is
+ * released. In some cases, for members that are heavily used, this annotation
+ * may imply restrictions on changes to the member.
+ *
+ * This annotation also results in access to the member being permitted by the
+ * runtime, with a warning being generated in debug builds.
+ *
+ * For more details, see go/UnsupportedAppUsage.
+ *
+ * {@hide}
+ */
+@Retention(CLASS)
+@Target({CONSTRUCTOR, METHOD, FIELD})
+public @interface UnsupportedAppUsage {
+
+ /**
+ * Associates a bug tracking the work to add a public alternative to this API. Optional.
+ *
+ * @return ID of the associated tracking bug
+ */
+ long trackingBug() default 0;
+
+ /**
+ * For debug use only. The expected dex signature to be generated for this API, used to verify
+ * parts of the build process.
+ *
+ * @return A dex API signature.
+ */
+ String expectedSignature() default "";
+}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index e1556b4..c0b0cee 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -24,8 +24,6 @@
import android.os.StrictMode;
import android.util.Log;
-import libcore.net.UriCodec;
-
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -1951,7 +1949,8 @@
if (s == null) {
return null;
}
- return UriCodec.decode(s, false, StandardCharsets.UTF_8, false);
+ return UriCodec.decode(
+ s, false /* convertPlus */, StandardCharsets.UTF_8, false /* throwOnFailure */);
}
/**
diff --git a/core/java/android/net/UriCodec.java b/core/java/android/net/UriCodec.java
new file mode 100644
index 0000000..e1470e0
--- /dev/null
+++ b/core/java/android/net/UriCodec.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2015 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 android.net;
+
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * Decodes “application/x-www-form-urlencoded” content.
+ *
+ * @hide
+ */
+public final class UriCodec {
+
+ private UriCodec() {}
+
+ /**
+ * Interprets a char as hex digits, returning a number from -1 (invalid char) to 15 ('f').
+ */
+ private static int hexCharToValue(char c) {
+ if ('0' <= c && c <= '9') {
+ return c - '0';
+ }
+ if ('a' <= c && c <= 'f') {
+ return 10 + c - 'a';
+ }
+ if ('A' <= c && c <= 'F') {
+ return 10 + c - 'A';
+ }
+ return -1;
+ }
+
+ private static URISyntaxException unexpectedCharacterException(
+ String uri, String name, char unexpected, int index) {
+ String nameString = (name == null) ? "" : " in [" + name + "]";
+ return new URISyntaxException(
+ uri, "Unexpected character" + nameString + ": " + unexpected, index);
+ }
+
+ private static char getNextCharacter(String uri, int index, int end, String name)
+ throws URISyntaxException {
+ if (index >= end) {
+ String nameString = (name == null) ? "" : " in [" + name + "]";
+ throw new URISyntaxException(
+ uri, "Unexpected end of string" + nameString, index);
+ }
+ return uri.charAt(index);
+ }
+
+ /**
+ * Decode a string according to the rules of this decoder.
+ *
+ * - if {@code convertPlus == true} all ‘+’ chars in the decoded output are converted to ‘ ‘
+ * (white space)
+ * - if {@code throwOnFailure == true}, an {@link IllegalArgumentException} is thrown for
+ * invalid inputs. Else, U+FFFd is emitted to the output in place of invalid input octets.
+ */
+ public static String decode(
+ String s, boolean convertPlus, Charset charset, boolean throwOnFailure) {
+ StringBuilder builder = new StringBuilder(s.length());
+ appendDecoded(builder, s, convertPlus, charset, throwOnFailure);
+ return builder.toString();
+ }
+
+ /**
+ * Character to be output when there's an error decoding an input.
+ */
+ private static final char INVALID_INPUT_CHARACTER = '\ufffd';
+
+ private static void appendDecoded(
+ StringBuilder builder,
+ String s,
+ boolean convertPlus,
+ Charset charset,
+ boolean throwOnFailure) {
+ CharsetDecoder decoder = charset.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .replaceWith("\ufffd")
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+ // Holds the bytes corresponding to the escaped chars being read (empty if the last char
+ // wasn't a escaped char).
+ ByteBuffer byteBuffer = ByteBuffer.allocate(s.length());
+ int i = 0;
+ while (i < s.length()) {
+ char c = s.charAt(i);
+ i++;
+ switch (c) {
+ case '+':
+ flushDecodingByteAccumulator(
+ builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(convertPlus ? ' ' : '+');
+ break;
+ case '%':
+ // Expect two characters representing a number in hex.
+ byte hexValue = 0;
+ for (int j = 0; j < 2; j++) {
+ try {
+ c = getNextCharacter(s, i, s.length(), null /* name */);
+ } catch (URISyntaxException e) {
+ // Unexpected end of input.
+ if (throwOnFailure) {
+ throw new IllegalArgumentException(e);
+ } else {
+ flushDecodingByteAccumulator(
+ builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(INVALID_INPUT_CHARACTER);
+ return;
+ }
+ }
+ i++;
+ int newDigit = hexCharToValue(c);
+ if (newDigit < 0) {
+ if (throwOnFailure) {
+ throw new IllegalArgumentException(
+ unexpectedCharacterException(s, null /* name */, c, i - 1));
+ } else {
+ flushDecodingByteAccumulator(
+ builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(INVALID_INPUT_CHARACTER);
+ break;
+ }
+ }
+ hexValue = (byte) (hexValue * 0x10 + newDigit);
+ }
+ byteBuffer.put(hexValue);
+ break;
+ default:
+ flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure);
+ builder.append(c);
+ }
+ }
+ flushDecodingByteAccumulator(builder, decoder, byteBuffer, throwOnFailure);
+ }
+
+ private static void flushDecodingByteAccumulator(
+ StringBuilder builder,
+ CharsetDecoder decoder,
+ ByteBuffer byteBuffer,
+ boolean throwOnFailure) {
+ if (byteBuffer.position() == 0) {
+ return;
+ }
+ byteBuffer.flip();
+ try {
+ builder.append(decoder.decode(byteBuffer));
+ } catch (CharacterCodingException e) {
+ if (throwOnFailure) {
+ throw new IllegalArgumentException(e);
+ } else {
+ builder.append(INVALID_INPUT_CHARACTER);
+ }
+ } finally {
+ // Use the byte buffer to write again.
+ byteBuffer.flip();
+ byteBuffer.limit(byteBuffer.capacity());
+ }
+ }
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index a81d16a6..36fe220 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -33,12 +33,7 @@
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
/**
* Base class for a remotable object, the core part of a lightweight
@@ -761,397 +756,3 @@
}
}
-/**
- * Java proxy for a native IBinder object.
- * Allocated and constructed by the native javaObjectforIBinder function. Never allocated
- * directly from Java code.
- */
-final class BinderProxy implements IBinder {
- // See android_util_Binder.cpp for the native half of this.
-
- // Assume the process-wide default value when created
- volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
-
- /*
- * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
- * We roll our own only because we need to lazily remove WeakReferences during accesses
- * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
- * because we want weak values, not keys.
- * Our hash table is never resized, but the number of entries is unlimited;
- * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
- * Not thread-safe. Client ensures there's a single access at a time.
- */
- private static final class ProxyMap {
- private static final int LOG_MAIN_INDEX_SIZE = 8;
- private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
- private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
- // Debuggable builds will throw an AssertionError if the number of map entries exceeds:
- private static final int CRASH_AT_SIZE = 5_000;
-
- /**
- * We next warn when we exceed this bucket size.
- */
- private int mWarnBucketSize = 20;
-
- /**
- * Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
- */
- private static final int WARN_INCREMENT = 10;
-
- /**
- * Hash function tailored to native pointers.
- * Returns a value < MAIN_INDEX_SIZE.
- */
- private static int hash(long arg) {
- return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
- }
-
- /**
- * Return the total number of pairs in the map.
- */
- private int size() {
- int size = 0;
- for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
- if (a != null) {
- size += a.size();
- }
- }
- return size;
- }
-
- /**
- * Return the total number of pairs in the map containing values that have
- * not been cleared. More expensive than the above size function.
- */
- private int unclearedSize() {
- int size = 0;
- for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
- if (a != null) {
- for (WeakReference<BinderProxy> ref : a) {
- if (ref.get() != null) {
- ++size;
- }
- }
- }
- }
- return size;
- }
-
- /**
- * Remove ith entry from the hash bucket indicated by hash.
- */
- private void remove(int hash, int index) {
- Long[] keyArray = mMainIndexKeys[hash];
- ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
- int size = valueArray.size(); // KeyArray may have extra elements.
- // Move last entry into empty slot, and truncate at end.
- if (index != size - 1) {
- keyArray[index] = keyArray[size - 1];
- valueArray.set(index, valueArray.get(size - 1));
- }
- valueArray.remove(size - 1);
- // Just leave key array entry; it's unused. We only trust the valueArray size.
- }
-
- /**
- * Look up the supplied key. If we have a non-cleared entry for it, return it.
- */
- BinderProxy get(long key) {
- int myHash = hash(key);
- Long[] keyArray = mMainIndexKeys[myHash];
- if (keyArray == null) {
- return null;
- }
- ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
- int bucketSize = valueArray.size();
- for (int i = 0; i < bucketSize; ++i) {
- long foundKey = keyArray[i];
- if (key == foundKey) {
- WeakReference<BinderProxy> wr = valueArray.get(i);
- BinderProxy bp = wr.get();
- if (bp != null) {
- return bp;
- } else {
- remove(myHash, i);
- return null;
- }
- }
- }
- return null;
- }
-
- private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG.
-
- /**
- * Add the key-value pair to the map.
- * Requires that the indicated key is not already in the map.
- */
- void set(long key, @NonNull BinderProxy value) {
- int myHash = hash(key);
- ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
- if (valueArray == null) {
- valueArray = mMainIndexValues[myHash] = new ArrayList<>();
- mMainIndexKeys[myHash] = new Long[1];
- }
- int size = valueArray.size();
- WeakReference<BinderProxy> newWr = new WeakReference<>(value);
- // First look for a cleared reference.
- // This ensures that ArrayList size is bounded by the maximum occupancy of
- // that bucket.
- for (int i = 0; i < size; ++i) {
- if (valueArray.get(i).get() == null) {
- valueArray.set(i, newWr);
- Long[] keyArray = mMainIndexKeys[myHash];
- keyArray[i] = key;
- if (i < size - 1) {
- // "Randomly" check one of the remaining entries in [i+1, size), so that
- // needlessly long buckets are eventually pruned.
- int rnd = Math.floorMod(++mRandom, size - (i + 1));
- if (valueArray.get(i + 1 + rnd).get() == null) {
- remove(myHash, i + 1 + rnd);
- }
- }
- return;
- }
- }
- valueArray.add(size, newWr);
- Long[] keyArray = mMainIndexKeys[myHash];
- if (keyArray.length == size) {
- // size >= 1, since we initially allocated one element
- Long[] newArray = new Long[size + size / 2 + 2];
- System.arraycopy(keyArray, 0, newArray, 0, size);
- newArray[size] = key;
- mMainIndexKeys[myHash] = newArray;
- } else {
- keyArray[size] = key;
- }
- if (size >= mWarnBucketSize) {
- final int totalSize = size();
- Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
- + " total = " + totalSize);
- mWarnBucketSize += WARN_INCREMENT;
- if (Build.IS_DEBUGGABLE && totalSize >= CRASH_AT_SIZE) {
- // Use the number of uncleared entries to determine whether we should
- // really report a histogram and crash. We don't want to fundamentally
- // change behavior for a debuggable process, so we GC only if we are
- // about to crash.
- final int totalUnclearedSize = unclearedSize();
- if (totalUnclearedSize >= CRASH_AT_SIZE) {
- dumpProxyInterfaceCounts();
- Runtime.getRuntime().gc();
- throw new AssertionError("Binder ProxyMap has too many entries: "
- + totalSize + " (total), " + totalUnclearedSize + " (uncleared), "
- + unclearedSize() + " (uncleared after GC). BinderProxy leak?");
- } else if (totalSize > 3 * totalUnclearedSize / 2) {
- Log.v(Binder.TAG, "BinderProxy map has many cleared entries: "
- + (totalSize - totalUnclearedSize) + " of " + totalSize
- + " are cleared");
- }
- }
- }
- }
-
- /**
- * Dump a histogram to the logcat. Used to diagnose abnormally large proxy maps.
- */
- private void dumpProxyInterfaceCounts() {
- Map<String, Integer> counts = new HashMap<>();
- for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
- if (a != null) {
- for (WeakReference<BinderProxy> weakRef : a) {
- BinderProxy bp = weakRef.get();
- String key;
- if (bp == null) {
- key = "<cleared weak-ref>";
- } else {
- try {
- key = bp.getInterfaceDescriptor();
- } catch (Throwable t) {
- key = "<exception during getDescriptor>";
- }
- }
- Integer i = counts.get(key);
- if (i == null) {
- counts.put(key, 1);
- } else {
- counts.put(key, i + 1);
- }
- }
- }
- }
- Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
- new Map.Entry[counts.size()]);
- Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b)
- -> b.getValue().compareTo(a.getValue()));
- Log.v(Binder.TAG, "BinderProxy descriptor histogram (top ten):");
- int printLength = Math.min(10, sorted.length);
- for (int i = 0; i < printLength; i++) {
- Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i].getKey() + " x"
- + sorted[i].getValue());
- }
- }
-
- // Corresponding ArrayLists in the following two arrays always have the same size.
- // They contain no empty entries. However WeakReferences in the values ArrayLists
- // may have been cleared.
-
- // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
- // The values ArrayList has the proper size(), the corresponding keys array
- // is always at least the same size, but may be larger.
- // If either a particular keys array, or the corresponding values ArrayList
- // are null, then they both are.
- private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
- private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
- new ArrayList[MAIN_INDEX_SIZE];
- }
-
- private static ProxyMap sProxyMap = new ProxyMap();
-
- /**
- * Return a BinderProxy for IBinder.
- * This method is thread-hostile! The (native) caller serializes getInstance() calls using
- * gProxyLock.
- * If we previously returned a BinderProxy bp for the same iBinder, and bp is still
- * in use, then we return the same bp.
- *
- * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
- * Takes ownership of nativeData iff <result>.mNativeData == nativeData. Caller will usually
- * delete nativeData if that's not the case.
- * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
- */
- private static BinderProxy getInstance(long nativeData, long iBinder) {
- BinderProxy result = sProxyMap.get(iBinder);
- if (result == null) {
- result = new BinderProxy(nativeData);
- sProxyMap.set(iBinder, result);
- }
- return result;
- }
-
- private BinderProxy(long nativeData) {
- mNativeData = nativeData;
- NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeData);
- }
-
- /**
- * Guestimate of native memory associated with a BinderProxy.
- * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
- * that points back to us. We guess high since it includes a GlobalRef, which
- * may be in short supply.
- */
- private static final int NATIVE_ALLOCATION_SIZE = 1000;
-
- // Use a Holder to allow static initialization of BinderProxy in the boot image, and
- // to avoid some initialization ordering issues.
- private static class NoImagePreloadHolder {
- public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- BinderProxy.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
- }
-
- public native boolean pingBinder();
- public native boolean isBinderAlive();
-
- public IInterface queryLocalInterface(String descriptor) {
- return null;
- }
-
- public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
- Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
-
- if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
- // For now, avoid spamming the log by disabling after we've logged
- // about this interface at least once
- mWarnOnBlocking = false;
- Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
- new Throwable());
- }
-
- final boolean tracingEnabled = Binder.isTracingEnabled();
- if (tracingEnabled) {
- final Throwable tr = new Throwable();
- Binder.getTransactionTracker().addTrace(tr);
- StackTraceElement stackTraceElement = tr.getStackTrace()[1];
- Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
- stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
- }
- try {
- return transactNative(code, data, reply, flags);
- } finally {
- if (tracingEnabled) {
- Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
- }
- }
- }
-
- private static native long getNativeFinalizer();
- public native String getInterfaceDescriptor() throws RemoteException;
- public native boolean transactNative(int code, Parcel data, Parcel reply,
- int flags) throws RemoteException;
- public native void linkToDeath(DeathRecipient recipient, int flags)
- throws RemoteException;
- public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
-
- public void dump(FileDescriptor fd, String[] args) throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeFileDescriptor(fd);
- data.writeStringArray(args);
- try {
- transact(DUMP_TRANSACTION, data, reply, 0);
- reply.readException();
- } finally {
- data.recycle();
- reply.recycle();
- }
- }
-
- public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeFileDescriptor(fd);
- data.writeStringArray(args);
- try {
- transact(DUMP_TRANSACTION, data, reply, FLAG_ONEWAY);
- } finally {
- data.recycle();
- reply.recycle();
- }
- }
-
- public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback,
- ResultReceiver resultReceiver) throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeFileDescriptor(in);
- data.writeFileDescriptor(out);
- data.writeFileDescriptor(err);
- data.writeStringArray(args);
- ShellCallback.writeToParcel(callback, data);
- resultReceiver.writeToParcel(data, 0);
- try {
- transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
- reply.readException();
- } finally {
- data.recycle();
- reply.recycle();
- }
- }
-
- private static final void sendDeathNotice(DeathRecipient recipient) {
- if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
- try {
- recipient.binderDied();
- }
- catch (RuntimeException exc) {
- Log.w("BinderNative", "Uncaught exception from death notification",
- exc);
- }
- }
-
- /**
- * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
- * native IBinder object, and a DeathRecipientList.
- */
- private final long mNativeData;
-}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
new file mode 100644
index 0000000..629838c
--- /dev/null
+++ b/core/java/android/os/BinderProxy.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2006 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 android.os;
+
+import android.annotation.NonNull;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BinderInternal;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.FileDescriptor;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Java proxy for a native IBinder object.
+ * Allocated and constructed by the native javaObjectforIBinder function. Never allocated
+ * directly from Java code.
+ *
+ * @hide
+ */
+public final class BinderProxy implements IBinder {
+ // See android_util_Binder.cpp for the native half of this.
+
+ // Assume the process-wide default value when created
+ volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
+
+ /*
+ * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
+ * We roll our own only because we need to lazily remove WeakReferences during accesses
+ * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
+ * because we want weak values, not keys.
+ * Our hash table is never resized, but the number of entries is unlimited;
+ * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
+ * Not thread-safe. Client ensures there's a single access at a time.
+ */
+ private static final class ProxyMap {
+ private static final int LOG_MAIN_INDEX_SIZE = 8;
+ private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
+ private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
+ // Debuggable builds will throw an AssertionError if the number of map entries exceeds:
+ private static final int CRASH_AT_SIZE = 20_000;
+
+ /**
+ * We next warn when we exceed this bucket size.
+ */
+ private int mWarnBucketSize = 20;
+
+ /**
+ * Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
+ */
+ private static final int WARN_INCREMENT = 10;
+
+ /**
+ * Hash function tailored to native pointers.
+ * Returns a value < MAIN_INDEX_SIZE.
+ */
+ private static int hash(long arg) {
+ return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
+ }
+
+ /**
+ * Return the total number of pairs in the map.
+ */
+ private int size() {
+ int size = 0;
+ for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+ if (a != null) {
+ size += a.size();
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Return the total number of pairs in the map containing values that have
+ * not been cleared. More expensive than the above size function.
+ */
+ private int unclearedSize() {
+ int size = 0;
+ for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+ if (a != null) {
+ for (WeakReference<BinderProxy> ref : a) {
+ if (ref.get() != null) {
+ ++size;
+ }
+ }
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Remove ith entry from the hash bucket indicated by hash.
+ */
+ private void remove(int hash, int index) {
+ Long[] keyArray = mMainIndexKeys[hash];
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
+ int size = valueArray.size(); // KeyArray may have extra elements.
+ // Move last entry into empty slot, and truncate at end.
+ if (index != size - 1) {
+ keyArray[index] = keyArray[size - 1];
+ valueArray.set(index, valueArray.get(size - 1));
+ }
+ valueArray.remove(size - 1);
+ // Just leave key array entry; it's unused. We only trust the valueArray size.
+ }
+
+ /**
+ * Look up the supplied key. If we have a non-cleared entry for it, return it.
+ */
+ BinderProxy get(long key) {
+ int myHash = hash(key);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ if (keyArray == null) {
+ return null;
+ }
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+ int bucketSize = valueArray.size();
+ for (int i = 0; i < bucketSize; ++i) {
+ long foundKey = keyArray[i];
+ if (key == foundKey) {
+ WeakReference<BinderProxy> wr = valueArray.get(i);
+ BinderProxy bp = wr.get();
+ if (bp != null) {
+ return bp;
+ } else {
+ remove(myHash, i);
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG.
+
+ /**
+ * Add the key-value pair to the map.
+ * Requires that the indicated key is not already in the map.
+ */
+ void set(long key, @NonNull BinderProxy value) {
+ int myHash = hash(key);
+ ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+ if (valueArray == null) {
+ valueArray = mMainIndexValues[myHash] = new ArrayList<>();
+ mMainIndexKeys[myHash] = new Long[1];
+ }
+ int size = valueArray.size();
+ WeakReference<BinderProxy> newWr = new WeakReference<>(value);
+ // First look for a cleared reference.
+ // This ensures that ArrayList size is bounded by the maximum occupancy of
+ // that bucket.
+ for (int i = 0; i < size; ++i) {
+ if (valueArray.get(i).get() == null) {
+ valueArray.set(i, newWr);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ keyArray[i] = key;
+ if (i < size - 1) {
+ // "Randomly" check one of the remaining entries in [i+1, size), so that
+ // needlessly long buckets are eventually pruned.
+ int rnd = Math.floorMod(++mRandom, size - (i + 1));
+ if (valueArray.get(i + 1 + rnd).get() == null) {
+ remove(myHash, i + 1 + rnd);
+ }
+ }
+ return;
+ }
+ }
+ valueArray.add(size, newWr);
+ Long[] keyArray = mMainIndexKeys[myHash];
+ if (keyArray.length == size) {
+ // size >= 1, since we initially allocated one element
+ Long[] newArray = new Long[size + size / 2 + 2];
+ System.arraycopy(keyArray, 0, newArray, 0, size);
+ newArray[size] = key;
+ mMainIndexKeys[myHash] = newArray;
+ } else {
+ keyArray[size] = key;
+ }
+ if (size >= mWarnBucketSize) {
+ final int totalSize = size();
+ Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
+ + " total = " + totalSize);
+ mWarnBucketSize += WARN_INCREMENT;
+ if (Build.IS_DEBUGGABLE && totalSize >= CRASH_AT_SIZE) {
+ // Use the number of uncleared entries to determine whether we should
+ // really report a histogram and crash. We don't want to fundamentally
+ // change behavior for a debuggable process, so we GC only if we are
+ // about to crash.
+ final int totalUnclearedSize = unclearedSize();
+ if (totalUnclearedSize >= CRASH_AT_SIZE) {
+ dumpProxyInterfaceCounts();
+ dumpPerUidProxyCounts();
+ Runtime.getRuntime().gc();
+ throw new AssertionError("Binder ProxyMap has too many entries: "
+ + totalSize + " (total), " + totalUnclearedSize + " (uncleared), "
+ + unclearedSize() + " (uncleared after GC). BinderProxy leak?");
+ } else if (totalSize > 3 * totalUnclearedSize / 2) {
+ Log.v(Binder.TAG, "BinderProxy map has many cleared entries: "
+ + (totalSize - totalUnclearedSize) + " of " + totalSize
+ + " are cleared");
+ }
+ }
+ }
+ }
+
+ /**
+ * Dump a histogram to the logcat. Used to diagnose abnormally large proxy maps.
+ */
+ private void dumpProxyInterfaceCounts() {
+ Map<String, Integer> counts = new HashMap<>();
+ for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+ if (a != null) {
+ for (WeakReference<BinderProxy> weakRef : a) {
+ BinderProxy bp = weakRef.get();
+ String key;
+ if (bp == null) {
+ key = "<cleared weak-ref>";
+ } else {
+ try {
+ key = bp.getInterfaceDescriptor();
+ } catch (Throwable t) {
+ key = "<exception during getDescriptor>";
+ }
+ }
+ Integer i = counts.get(key);
+ if (i == null) {
+ counts.put(key, 1);
+ } else {
+ counts.put(key, i + 1);
+ }
+ }
+ }
+ }
+ Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
+ new Map.Entry[counts.size()]);
+ Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b)
+ -> b.getValue().compareTo(a.getValue()));
+ Log.v(Binder.TAG, "BinderProxy descriptor histogram (top ten):");
+ int printLength = Math.min(10, sorted.length);
+ for (int i = 0; i < printLength; i++) {
+ Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i].getKey() + " x"
+ + sorted[i].getValue());
+ }
+ }
+
+ /**
+ * Dump per uid binder proxy counts to the logcat.
+ */
+ private void dumpPerUidProxyCounts() {
+ SparseIntArray counts = BinderInternal.nGetBinderProxyPerUidCounts();
+ if (counts.size() == 0) return;
+ Log.d(Binder.TAG, "Per Uid Binder Proxy Counts:");
+ for (int i = 0; i < counts.size(); i++) {
+ final int uid = counts.keyAt(i);
+ final int binderCount = counts.valueAt(i);
+ Log.d(Binder.TAG, "UID : " + uid + " count = " + binderCount);
+ }
+ }
+
+ // Corresponding ArrayLists in the following two arrays always have the same size.
+ // They contain no empty entries. However WeakReferences in the values ArrayLists
+ // may have been cleared.
+
+ // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
+ // The values ArrayList has the proper size(), the corresponding keys array
+ // is always at least the same size, but may be larger.
+ // If either a particular keys array, or the corresponding values ArrayList
+ // are null, then they both are.
+ private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
+ private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
+ new ArrayList[MAIN_INDEX_SIZE];
+ }
+
+ @GuardedBy("sProxyMap")
+ private static final ProxyMap sProxyMap = new ProxyMap();
+
+ /**
+ * Dump proxy debug information.
+ *
+ * @hide
+ */
+ private static void dumpProxyDebugInfo() {
+ if (Build.IS_DEBUGGABLE) {
+ synchronized (sProxyMap) {
+ sProxyMap.dumpProxyInterfaceCounts();
+ }
+ // Note that we don't call dumpPerUidProxyCounts(); this is because this
+ // method may be called as part of the uid limit being hit, and calling
+ // back into the UID tracking code would cause us to try to acquire a mutex
+ // that is held during that callback.
+ }
+ }
+
+ /**
+ * Return a BinderProxy for IBinder.
+ * If we previously returned a BinderProxy bp for the same iBinder, and bp is still
+ * in use, then we return the same bp.
+ *
+ * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
+ * Takes ownership of nativeData iff <result>.mNativeData == nativeData, or if
+ * we exit via an exception. If neither applies, it's the callers responsibility to
+ * recycle nativeData.
+ * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
+ */
+ private static BinderProxy getInstance(long nativeData, long iBinder) {
+ BinderProxy result;
+ synchronized (sProxyMap) {
+ try {
+ result = sProxyMap.get(iBinder);
+ if (result != null) {
+ return result;
+ }
+ result = new BinderProxy(nativeData);
+ } catch (Throwable e) {
+ // We're throwing an exception (probably OOME); don't drop nativeData.
+ NativeAllocationRegistry.applyFreeFunction(NoImagePreloadHolder.sNativeFinalizer,
+ nativeData);
+ throw e;
+ }
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(result, nativeData);
+ // The registry now owns nativeData, even if registration threw an exception.
+ sProxyMap.set(iBinder, result);
+ }
+ return result;
+ }
+
+ private BinderProxy(long nativeData) {
+ mNativeData = nativeData;
+ }
+
+ /**
+ * Guestimate of native memory associated with a BinderProxy.
+ * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
+ * that points back to us. We guess high since it includes a GlobalRef, which
+ * may be in short supply.
+ */
+ private static final int NATIVE_ALLOCATION_SIZE = 1000;
+
+ // Use a Holder to allow static initialization of BinderProxy in the boot image, and
+ // to avoid some initialization ordering issues.
+ private static class NoImagePreloadHolder {
+ public static final long sNativeFinalizer = getNativeFinalizer();
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ BinderProxy.class.getClassLoader(), sNativeFinalizer, NATIVE_ALLOCATION_SIZE);
+ }
+
+ /**
+ * @return false if the hosting process is gone, otherwise whatever the remote returns
+ */
+ public native boolean pingBinder();
+
+ /**
+ * @return false if the hosting process is gone
+ */
+ public native boolean isBinderAlive();
+
+ /**
+ * Retrieve a local interface - always null in case of a proxy
+ */
+ public IInterface queryLocalInterface(String descriptor) {
+ return null;
+ }
+
+ /**
+ * Perform a binder transaction on a proxy.
+ *
+ * @param code The action to perform. This should
+ * be a number between {@link #FIRST_CALL_TRANSACTION} and
+ * {@link #LAST_CALL_TRANSACTION}.
+ * @param data Marshalled data to send to the target. Must not be null.
+ * If you are not sending any data, you must create an empty Parcel
+ * that is given here.
+ * @param reply Marshalled data to be received from the target. May be
+ * null if you are not interested in the return value.
+ * @param flags Additional operation flags. Either 0 for a normal
+ * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
+ *
+ * @return
+ * @throws RemoteException
+ */
+ public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
+ Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
+
+ if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
+ // For now, avoid spamming the log by disabling after we've logged
+ // about this interface at least once
+ mWarnOnBlocking = false;
+ Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+ new Throwable());
+ }
+
+ final boolean tracingEnabled = Binder.isTracingEnabled();
+ if (tracingEnabled) {
+ final Throwable tr = new Throwable();
+ Binder.getTransactionTracker().addTrace(tr);
+ StackTraceElement stackTraceElement = tr.getStackTrace()[1];
+ Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
+ stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
+ }
+ try {
+ return transactNative(code, data, reply, flags);
+ } finally {
+ if (tracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
+ }
+ }
+ }
+
+ /* Returns the native free function */
+ private static native long getNativeFinalizer();
+ /**
+ * See {@link IBinder#getInterfaceDescriptor()}
+ */
+ public native String getInterfaceDescriptor() throws RemoteException;
+
+ /**
+ * Native implementation of transact() for proxies
+ */
+ public native boolean transactNative(int code, Parcel data, Parcel reply,
+ int flags) throws RemoteException;
+ /**
+ * See {@link IBinder#linkToDeath(DeathRecipient, int)}
+ */
+ public native void linkToDeath(DeathRecipient recipient, int flags)
+ throws RemoteException;
+ /**
+ * See {@link IBinder#unlinkToDeath}
+ */
+ public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
+
+ /**
+ * Perform a dump on the remote object
+ *
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param args additional arguments to the dump request.
+ * @throws RemoteException
+ */
+ public void dump(FileDescriptor fd, String[] args) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeFileDescriptor(fd);
+ data.writeStringArray(args);
+ try {
+ transact(DUMP_TRANSACTION, data, reply, 0);
+ reply.readException();
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ /**
+ * Perform an asynchronous dump on the remote object
+ *
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param args additional arguments to the dump request.
+ * @throws RemoteException
+ */
+ public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeFileDescriptor(fd);
+ data.writeStringArray(args);
+ try {
+ transact(DUMP_TRANSACTION, data, reply, FLAG_ONEWAY);
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ /**
+ * See {@link IBinder#shellCommand(FileDescriptor, FileDescriptor, FileDescriptor,
+ * String[], ShellCallback, ResultReceiver)}
+ *
+ * @param in The raw file descriptor that an input data stream can be read from.
+ * @param out The raw file descriptor that normal command messages should be written to.
+ * @param err The raw file descriptor that command error messages should be written to.
+ * @param args Command-line arguments.
+ * @param callback Optional callback to the caller's shell to perform operations in it.
+ * @param resultReceiver Called when the command has finished executing, with the result code.
+ * @throws RemoteException
+ */
+ public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback,
+ ResultReceiver resultReceiver) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeFileDescriptor(in);
+ data.writeFileDescriptor(out);
+ data.writeFileDescriptor(err);
+ data.writeStringArray(args);
+ ShellCallback.writeToParcel(callback, data);
+ resultReceiver.writeToParcel(data, 0);
+ try {
+ transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
+ reply.readException();
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ private static void sendDeathNotice(DeathRecipient recipient) {
+ if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+ try {
+ recipient.binderDied();
+ } catch (RuntimeException exc) {
+ Log.w("BinderNative", "Uncaught exception from death notification",
+ exc);
+ }
+ }
+
+ /**
+ * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
+ * native IBinder object, and a DeathRecipientList.
+ */
+ private final long mNativeData;
+}
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/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index a9cd5c8..e37e650 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -30,14 +30,13 @@
import android.util.Slog;
import com.android.internal.logging.AndroidConfig;
import com.android.server.NetworkManagementSocketTagger;
+import dalvik.system.RuntimeHooks;
import dalvik.system.VMRuntime;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Objects;
-import java.util.TimeZone;
import java.util.logging.LogManager;
-import org.apache.harmony.luni.internal.util.TimezoneGetter;
/**
* Main entry point for runtime initialization. Not for
@@ -195,19 +194,13 @@
* the default handler, but not the pre handler.
*/
LoggingHandler loggingHandler = new LoggingHandler();
- Thread.setUncaughtExceptionPreHandler(loggingHandler);
+ RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
/*
- * Install a TimezoneGetter subclass for ZoneInfo.db
+ * Install a time zone supplier that uses the Android persistent time zone system property.
*/
- TimezoneGetter.setInstance(new TimezoneGetter() {
- @Override
- public String getId() {
- return SystemProperties.get("persist.sys.timezone");
- }
- });
- TimeZone.setDefault(null);
+ RuntimeHooks.setTimeZoneIdSupplier(() -> SystemProperties.get("persist.sys.timezone"));
/*
* Sets handler for java.util.logging to use Android log facilities.
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index d1d0628..00ae335 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -21,9 +21,6 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.icu.impl.CacheValue;
-import android.icu.text.DecimalFormatSymbols;
-import android.icu.util.ULocale;
import android.opengl.EGL14;
import android.os.Build;
import android.os.IInstalld;
@@ -122,9 +119,9 @@
static void preload(TimingsTraceLog bootTimingsTraceLog) {
Log.d(TAG, "begin preload");
- bootTimingsTraceLog.traceBegin("BeginIcuCachePinning");
- beginIcuCachePinning();
- bootTimingsTraceLog.traceEnd(); // BeginIcuCachePinning
+ bootTimingsTraceLog.traceBegin("BeginPreload");
+ beginPreload();
+ bootTimingsTraceLog.traceEnd(); // BeginPreload
bootTimingsTraceLog.traceBegin("PreloadClasses");
preloadClasses();
bootTimingsTraceLog.traceEnd(); // PreloadClasses
@@ -142,7 +139,7 @@
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
// for memory sharing purposes.
WebViewFactory.prepareWebViewInZygote();
- endIcuCachePinning();
+ endPreload();
warmUpJcaProviders();
Log.d(TAG, "end preload");
@@ -156,27 +153,16 @@
preload(new TimingsTraceLog("ZygoteInitTiming_lazy", Trace.TRACE_TAG_DALVIK));
}
- private static void beginIcuCachePinning() {
- // Pin ICU data in memory from this point that would normally be held by soft references.
- // Without this, any references created immediately below or during class preloading
- // would be collected when the Zygote GC runs in gcAndFinalize().
- Log.i(TAG, "Installing ICU cache reference pinning...");
+ private static void beginPreload() {
+ Log.i(TAG, "Calling ZygoteHooks.beginPreload()");
- CacheValue.setStrength(CacheValue.Strength.STRONG);
-
- Log.i(TAG, "Preloading ICU data...");
- // Explicitly exercise code to cache data apps are likely to need.
- ULocale[] localesToPin = { ULocale.ROOT, ULocale.US, ULocale.getDefault() };
- for (ULocale uLocale : localesToPin) {
- new DecimalFormatSymbols(uLocale);
- }
+ ZygoteHooks.onBeginPreload();
}
- private static void endIcuCachePinning() {
- // All cache references created by ICU from this point will be soft.
- CacheValue.setStrength(CacheValue.Strength.SOFT);
+ private static void endPreload() {
+ ZygoteHooks.onEndPreload();
- Log.i(TAG, "Uninstalled ICU cache reference pinning...");
+ Log.i(TAG, "Called ZygoteHooks.endPreload()");
}
private static void preloadSharedLibraries() {
@@ -436,15 +422,8 @@
* softly- and final-reachable objects, along with any other garbage.
* This is only useful just before a fork().
*/
- /*package*/ static void gcAndFinalize() {
- final VMRuntime runtime = VMRuntime.getRuntime();
-
- /* runFinalizationSync() lets finalizers be called in Zygote,
- * which doesn't have a HeapWorker thread.
- */
- System.gc();
- runtime.runFinalizationSync();
- System.gc();
+ private static void gcAndFinalize() {
+ ZygoteHooks.gcAndFinalize();
}
/**
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/core/tests/coretests/src/android/net/UriCodecTest.java b/core/tests/coretests/src/android/net/UriCodecTest.java
new file mode 100644
index 0000000..7fe9402
--- /dev/null
+++ b/core/tests/coretests/src/android/net/UriCodecTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 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 android.net;
+
+import junit.framework.TestCase;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Tests for {@link UriCodec}
+ */
+public class UriCodecTest extends TestCase {
+
+ public void testDecode_emptyString_returnsEmptyString() {
+ assertEquals("", UriCodec.decode("",
+ false /* convertPlus */,
+ StandardCharsets.UTF_8,
+ true /* throwOnFailure */));
+ }
+
+ public void testDecode_wrongHexDigit_fails() {
+ try {
+ // %p in the end.
+ UriCodec.decode("ab%2f$%C4%82%25%e0%a1%80%p",
+ false /* convertPlus */,
+ StandardCharsets.UTF_8,
+ true /* throwOnFailure */);
+ fail("Expected URISyntaxException");
+ } catch (IllegalArgumentException expected) {
+ // Expected.
+ }
+ }
+
+ public void testDecode_secondHexDigitWrong_fails() {
+ try {
+ // %1p in the end.
+ UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%1p",
+ false /* convertPlus */,
+ StandardCharsets.UTF_8,
+ true /* throwOnFailure */);
+ fail("Expected URISyntaxException");
+ } catch (IllegalArgumentException expected) {
+ // Expected.
+ }
+ }
+
+ public void testDecode_endsWithPercent_fails() {
+ try {
+ // % in the end.
+ UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%",
+ false /* convertPlus */,
+ StandardCharsets.UTF_8,
+ true /* throwOnFailure */);
+ fail("Expected URISyntaxException");
+ } catch (IllegalArgumentException expected) {
+ // Expected.
+ }
+ }
+
+ public void testDecode_dontThrowException_appendsUnknownCharacter() {
+ assertEquals("ab/$\u0102%\u0840\ufffd",
+ UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80%",
+ false /* convertPlus */,
+ StandardCharsets.UTF_8,
+ false /* throwOnFailure */));
+ }
+
+ public void testDecode_convertPlus() {
+ assertEquals("ab/$\u0102% \u0840",
+ UriCodec.decode("ab%2f$%c4%82%25+%e0%a1%80",
+ true /* convertPlus */,
+ StandardCharsets.UTF_8,
+ false /* throwOnFailure */));
+ }
+
+ // Last character needs decoding (make sure we are flushing the buffer with chars to decode).
+ public void testDecode_lastCharacter() {
+ assertEquals("ab/$\u0102%\u0840",
+ UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80",
+ false /* convertPlus */,
+ StandardCharsets.UTF_8,
+ true /* throwOnFailure */));
+ }
+
+ // Check that a second row of encoded characters is decoded properly (internal buffers are
+ // reset properly).
+ public void testDecode_secondRowOfEncoded() {
+ assertEquals("ab/$\u0102%\u0840aa\u0840",
+ UriCodec.decode("ab%2f$%c4%82%25%e0%a1%80aa%e0%a1%80",
+ false /* convertPlus */,
+ StandardCharsets.UTF_8,
+ true /* throwOnFailure */));
+ }
+}
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();
+ }
+}