diff options
author | 2024-10-18 11:03:02 -0700 | |
---|---|---|
committer | 2024-10-21 09:50:48 -0700 | |
commit | 172f2a03d8d8c8f6961de3014012e895d76b024e (patch) | |
tree | 2d639685cd2913820b8b1206fb792c8b15a7f673 /ravenwood | |
parent | eb55496c10be60e5168ad6ac378ea6ab6b30d218 (diff) |
Prepare for compat-framework support
- Create a Context for the system server
- Add the target SDK level to RavenwoodConfig
- Allow PropertyInvalidatedCache sysprops
- Copy StatsD classes, until we get a proper support
- Copy Compatibility and some annotations from libcore
- Keep FrameworkStatsLog
Flag: EXEMPT host test change only
Bug: 292141694
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh
Change-Id: Ib572d1b25c47c693b4969d12490e593dc5f48eb1
Diffstat (limited to 'ravenwood')
15 files changed, 2172 insertions, 1 deletions
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index d9182010c1cb..ff2abd275f83 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -100,6 +100,9 @@ java_library_host { srcs: [ "runtime-helper-src/libcore-fake/**/*.java", ], + libs: [ + "app-compat-annotations", + ], static_libs: [ "ravenwood-runtime-common", ], @@ -121,6 +124,7 @@ java_library { ], static_libs: [ "ravenwood-runtime-common", + "androidx.annotation_annotation", ], libs: [ "framework-minus-apex.ravenwood", diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java index 3535cb2b1b79..870a10a1f57e 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java @@ -42,6 +42,10 @@ public class RavenwoodConfigState { private final RavenwoodConfig mConfig; + // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by + // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry? + RavenwoodContext mSystemServerContext; + public RavenwoodConfigState(RavenwoodConfig config) { mConfig = config; } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index 9a145cb93811..c2806daf99a1 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -16,6 +16,8 @@ package android.platform.test.ravenwood; +import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME; + import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; @@ -267,6 +269,13 @@ public class RavenwoodRuntimeEnvironmentController { config.mInstContext = instContext; config.mTargetContext = targetContext; + final Supplier<Resources> systemResourcesLoader = () -> { + return config.mState.loadResources(null); + }; + + config.mState.mSystemServerContext = + new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader); + // Prepare other fields. config.mInstrumentation = new Instrumentation(); config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation()); @@ -314,6 +323,9 @@ public class RavenwoodRuntimeEnvironmentController { ((RavenwoodContext) config.mTargetContext).cleanUp(); config.mTargetContext = null; } + if (config.mState.mSystemServerContext != null) { + config.mState.mSystemServerContext.cleanUp(); + } Looper.getMainLooper().quit(); Looper.clearMainLooperForTest(); diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java index 3946dd8471b0..f198a08a50e3 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java @@ -33,6 +33,9 @@ import java.util.List; import java.util.Set; public class RavenwoodSystemServer { + + static final String ANDROID_PACKAGE_NAME = "android"; + /** * Set of services that we know how to provide under Ravenwood. We keep this set distinct * from {@code com.android.server.SystemServer} to give us the ability to choose either @@ -67,7 +70,7 @@ public class RavenwoodSystemServer { sStartedServices = new ArraySet<>(); sTimings = new TimingsTraceAndSlog(); - sServiceManager = new SystemServiceManager(config.mInstContext); + sServiceManager = new SystemServiceManager(config.mState.mSystemServerContext); sServiceManager.setStartInfo(false, SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index 1f6e11dd5cf2..37b0abcceede 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -67,6 +67,7 @@ public final class RavenwoodConfig { String mTargetPackageName; int mMinSdkLevel; + int mTargetSdkLevel; boolean mProvideMainThread = false; @@ -150,6 +151,14 @@ public final class RavenwoodConfig { } /** + * Configure the target SDK level of the test. + */ + public Builder setTargetSdkLevel(int sdkLevel) { + mConfig.mTargetSdkLevel = sdkLevel; + return this; + } + + /** * Configure a "main" thread to be available for the duration of the test, as defined * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. * diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java index ced151927fdc..9bc45bee1775 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java @@ -146,6 +146,9 @@ public class RavenwoodSystemProperties { if (root.startsWith("soc.")) return true; if (root.startsWith("system.")) return true; + // For PropertyInvalidatedCache + if (root.startsWith("cache_key.")) return true; + switch (key) { case "gsm.version.baseband": case "no.such.thing": @@ -170,6 +173,9 @@ public class RavenwoodSystemProperties { if (root.startsWith("debug.")) return true; + // For PropertyInvalidatedCache + if (root.startsWith("cache_key.")) return true; + return mKeyWritable.contains(key); } diff --git a/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java b/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java new file mode 100644 index 000000000000..1e3b3fcd733f --- /dev/null +++ b/ravenwood/runtime-helper-src/framework/android/util/StatsEvent.java @@ -0,0 +1,1035 @@ +/* + * Copyright (C) 2019 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.util; + +// [ravenwood] This is an exact copy from StatsD, until we make StatsD available on Ravenwood. + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Build; +import android.os.SystemClock; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * StatsEvent builds and stores the buffer sent over the statsd socket. + * This class defines and encapsulates the socket protocol. + * + * <p>Usage:</p> + * <pre> + * // Pushed event + * StatsEvent statsEvent = StatsEvent.newBuilder() + * .setAtomId(atomId) + * .writeBoolean(false) + * .writeString("annotated String field") + * .addBooleanAnnotation(annotationId, true) + * .usePooledBuffer() + * .build(); + * StatsLog.write(statsEvent); + * + * // Pulled event + * StatsEvent statsEvent = StatsEvent.newBuilder() + * .setAtomId(atomId) + * .writeBoolean(false) + * .writeString("annotated String field") + * .addBooleanAnnotation(annotationId, true) + * .build(); + * </pre> + * @hide + **/ +@SystemApi +public final class StatsEvent { + // Type Ids. + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_INT = 0x00; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_LONG = 0x01; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_STRING = 0x02; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_LIST = 0x03; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_FLOAT = 0x04; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_BOOLEAN = 0x05; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_BYTE_ARRAY = 0x06; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_OBJECT = 0x07; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_KEY_VALUE_PAIRS = 0x08; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_ATTRIBUTION_CHAIN = 0x09; + + /** + * @hide + **/ + @VisibleForTesting + public static final byte TYPE_ERRORS = 0x0F; + + // Error flags. + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_NO_TIMESTAMP = 0x1; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_NO_ATOM_ID = 0x2; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_OVERFLOW = 0x4; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_INVALID_ANNOTATION_ID = 0x40; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_TOO_MANY_FIELDS = 0x200; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000; + + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000; + + /** + * @hide + **/ + @VisibleForTesting public static final int ERROR_LIST_TOO_LONG = 0x4000; + + // Size limits. + + /** + * @hide + **/ + @VisibleForTesting + public static final int MAX_ANNOTATION_COUNT = 15; + + /** + * @hide + **/ + @VisibleForTesting + public static final int MAX_ATTRIBUTION_NODES = 127; + + /** + * @hide + **/ + @VisibleForTesting + public static final int MAX_NUM_ELEMENTS = 127; + + /** + * @hide + **/ + @VisibleForTesting + public static final int MAX_KEY_VALUE_PAIRS = 127; + + private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068; + + // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag. + // See android_util_StatsLog.cpp. + private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4; + + private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB + + private final int mAtomId; + private final byte[] mPayload; + private Buffer mBuffer; + private final int mNumBytes; + + private StatsEvent(final int atomId, @Nullable final Buffer buffer, + @NonNull final byte[] payload, final int numBytes) { + mAtomId = atomId; + mBuffer = buffer; + mPayload = payload; + mNumBytes = numBytes; + } + + /** + * Returns a new StatsEvent.Builder for building StatsEvent object. + **/ + @NonNull + public static Builder newBuilder() { + return new Builder(Buffer.obtain()); + } + + /** + * Get the atom Id of the atom encoded in this StatsEvent object. + * + * @hide + **/ + public int getAtomId() { + return mAtomId; + } + + /** + * Get the byte array that contains the encoded payload that can be sent to statsd. + * + * @hide + **/ + @NonNull + public byte[] getBytes() { + return mPayload; + } + + /** + * Get the number of bytes used to encode the StatsEvent payload. + * + * @hide + **/ + public int getNumBytes() { + return mNumBytes; + } + + /** + * Recycle resources used by this StatsEvent object. + * No actions should be taken on this StatsEvent after release() is called. + * + * @hide + **/ + public void release() { + if (mBuffer != null) { + mBuffer.release(); + mBuffer = null; + } + } + + /** + * Builder for constructing a StatsEvent object. + * + * <p>This class defines and encapsulates the socket encoding for the + *buffer. The write methods must be called in the same order as the order of + *fields in the atom definition.</p> + * + * <p>setAtomId() must be called immediately after + *StatsEvent.newBuilder().</p> + * + * <p>Example:</p> + * <pre> + * // Atom definition. + * message MyAtom { + * optional int32 field1 = 1; + * optional int64 field2 = 2; + * optional string field3 = 3 [(annotation1) = true]; + * optional repeated int32 field4 = 4; + * } + * + * // StatsEvent construction for pushed event. + * StatsEvent.newBuilder() + * StatsEvent statsEvent = StatsEvent.newBuilder() + * .setAtomId(atomId) + * .writeInt(3) // field1 + * .writeLong(8L) // field2 + * .writeString("foo") // field 3 + * .addBooleanAnnotation(annotation1Id, true) + * .writeIntArray({ 1, 2, 3 }); + * .usePooledBuffer() + * .build(); + * + * // StatsEvent construction for pulled event. + * StatsEvent.newBuilder() + * StatsEvent statsEvent = StatsEvent.newBuilder() + * .setAtomId(atomId) + * .writeInt(3) // field1 + * .writeLong(8L) // field2 + * .writeString("foo") // field 3 + * .addBooleanAnnotation(annotation1Id, true) + * .writeIntArray({ 1, 2, 3 }); + * .build(); + * </pre> + **/ + public static final class Builder { + // Fixed positions. + private static final int POS_NUM_ELEMENTS = 1; + private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES; + private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES; + + private final Buffer mBuffer; + private long mTimestampNs; + private int mAtomId; + private byte mCurrentAnnotationCount; + private int mPos; + private int mPosLastField; + private byte mLastType; + private int mNumElements; + private int mErrorMask; + private boolean mUsePooledBuffer = false; + + private Builder(final Buffer buffer) { + mBuffer = buffer; + mCurrentAnnotationCount = 0; + mAtomId = 0; + mTimestampNs = SystemClock.elapsedRealtimeNanos(); + mNumElements = 0; + + // Set mPos to 0 for writing TYPE_OBJECT at 0th position. + mPos = 0; + writeTypeId(TYPE_OBJECT); + + // Write timestamp. + mPos = POS_TIMESTAMP_NS; + writeLong(mTimestampNs); + } + + /** + * Sets the atom id for this StatsEvent. + * + * This should be called immediately after StatsEvent.newBuilder() + * and should only be called once. + * Not calling setAtomId will result in ERROR_NO_ATOM_ID. + * Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION. + **/ + @NonNull + public Builder setAtomId(final int atomId) { + if (0 == mAtomId) { + mAtomId = atomId; + + if (1 == mNumElements) { // Only timestamp is written so far. + writeInt(atomId); + } else { + // setAtomId called out of order. + mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION; + } + } + + return this; + } + + /** + * Write a boolean field to this StatsEvent. + **/ + @NonNull + public Builder writeBoolean(final boolean value) { + // Write boolean typeId byte followed by boolean byte representation. + writeTypeId(TYPE_BOOLEAN); + mPos += mBuffer.putBoolean(mPos, value); + mNumElements++; + return this; + } + + /** + * Write an integer field to this StatsEvent. + **/ + @NonNull + public Builder writeInt(final int value) { + // Write integer typeId byte followed by 4-byte representation of value. + writeTypeId(TYPE_INT); + mPos += mBuffer.putInt(mPos, value); + mNumElements++; + return this; + } + + /** + * Write a long field to this StatsEvent. + **/ + @NonNull + public Builder writeLong(final long value) { + // Write long typeId byte followed by 8-byte representation of value. + writeTypeId(TYPE_LONG); + mPos += mBuffer.putLong(mPos, value); + mNumElements++; + return this; + } + + /** + * Write a float field to this StatsEvent. + **/ + @NonNull + public Builder writeFloat(final float value) { + // Write float typeId byte followed by 4-byte representation of value. + writeTypeId(TYPE_FLOAT); + mPos += mBuffer.putFloat(mPos, value); + mNumElements++; + return this; + } + + /** + * Write a String field to this StatsEvent. + **/ + @NonNull + public Builder writeString(@NonNull final String value) { + // Write String typeId byte, followed by 4-byte representation of number of bytes + // in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value. + final byte[] valueBytes = stringToBytes(value); + writeByteArray(valueBytes, TYPE_STRING); + return this; + } + + /** + * Write a byte array field to this StatsEvent. + **/ + @NonNull + public Builder writeByteArray(@NonNull final byte[] value) { + // Write byte array typeId byte, followed by 4-byte representation of number of bytes + // in value, followed by the actual byte array. + writeByteArray(value, TYPE_BYTE_ARRAY); + return this; + } + + private void writeByteArray(@NonNull final byte[] value, final byte typeId) { + writeTypeId(typeId); + final int numBytes = value.length; + mPos += mBuffer.putInt(mPos, numBytes); + mPos += mBuffer.putByteArray(mPos, value); + mNumElements++; + } + + /** + * Write an attribution chain field to this StatsEvent. + * + * The sizes of uids and tags must be equal. The AttributionNode at position i is + * made up of uids[i] and tags[i]. + * + * @param uids array of uids in the attribution nodes. + * @param tags array of tags in the attribution nodes. + **/ + @NonNull + public Builder writeAttributionChain( + @NonNull final int[] uids, @NonNull final String[] tags) { + final byte numUids = (byte) uids.length; + final byte numTags = (byte) tags.length; + + if (numUids != numTags) { + mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL; + } else if (numUids > MAX_ATTRIBUTION_NODES) { + mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG; + } else { + // Write attribution chain typeId byte, followed by 1-byte representation of + // number of attribution nodes, followed by encoding of each attribution node. + writeTypeId(TYPE_ATTRIBUTION_CHAIN); + mPos += mBuffer.putByte(mPos, numUids); + for (int i = 0; i < numUids; i++) { + // Each uid is encoded as 4-byte representation of its int value. + mPos += mBuffer.putInt(mPos, uids[i]); + + // Each tag is encoded as 4-byte representation of number of bytes in its + // UTF-8 encoding, followed by the actual UTF-8 bytes. + final byte[] tagBytes = stringToBytes(tags[i]); + mPos += mBuffer.putInt(mPos, tagBytes.length); + mPos += mBuffer.putByteArray(mPos, tagBytes); + } + mNumElements++; + } + return this; + } + + /** + * Write KeyValuePairsAtom entries to this StatsEvent. + * + * @param intMap Integer key-value pairs. + * @param longMap Long key-value pairs. + * @param stringMap String key-value pairs. + * @param floatMap Float key-value pairs. + **/ + @NonNull + public Builder writeKeyValuePairs( + @Nullable final SparseIntArray intMap, + @Nullable final SparseLongArray longMap, + @Nullable final SparseArray<String> stringMap, + @Nullable final SparseArray<Float> floatMap) { + final int intMapSize = null == intMap ? 0 : intMap.size(); + final int longMapSize = null == longMap ? 0 : longMap.size(); + final int stringMapSize = null == stringMap ? 0 : stringMap.size(); + final int floatMapSize = null == floatMap ? 0 : floatMap.size(); + final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize; + + if (totalCount > MAX_KEY_VALUE_PAIRS) { + mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS; + } else { + writeTypeId(TYPE_KEY_VALUE_PAIRS); + mPos += mBuffer.putByte(mPos, (byte) totalCount); + + for (int i = 0; i < intMapSize; i++) { + final int key = intMap.keyAt(i); + final int value = intMap.valueAt(i); + mPos += mBuffer.putInt(mPos, key); + writeTypeId(TYPE_INT); + mPos += mBuffer.putInt(mPos, value); + } + + for (int i = 0; i < longMapSize; i++) { + final int key = longMap.keyAt(i); + final long value = longMap.valueAt(i); + mPos += mBuffer.putInt(mPos, key); + writeTypeId(TYPE_LONG); + mPos += mBuffer.putLong(mPos, value); + } + + for (int i = 0; i < stringMapSize; i++) { + final int key = stringMap.keyAt(i); + final String value = stringMap.valueAt(i); + mPos += mBuffer.putInt(mPos, key); + writeTypeId(TYPE_STRING); + final byte[] valueBytes = stringToBytes(value); + mPos += mBuffer.putInt(mPos, valueBytes.length); + mPos += mBuffer.putByteArray(mPos, valueBytes); + } + + for (int i = 0; i < floatMapSize; i++) { + final int key = floatMap.keyAt(i); + final float value = floatMap.valueAt(i); + mPos += mBuffer.putInt(mPos, key); + writeTypeId(TYPE_FLOAT); + mPos += mBuffer.putFloat(mPos, value); + } + + mNumElements++; + } + + return this; + } + + /** + * Write a repeated boolean field to this StatsEvent. + * + * The list size must not exceed 127. Otherwise, the array isn't written + * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the + * StatsEvent errors field. + * + * @param elements array of booleans. + **/ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @NonNull + public Builder writeBooleanArray(@NonNull final boolean[] elements) { + final byte numElements = (byte)elements.length; + + if (writeArrayInfo(numElements, TYPE_BOOLEAN)) { + // Write encoding of each element. + for (int i = 0; i < numElements; i++) { + mPos += mBuffer.putBoolean(mPos, elements[i]); + } + mNumElements++; + } + return this; + } + + /** + * Write a repeated int field to this StatsEvent. + * + * The list size must not exceed 127. Otherwise, the array isn't written + * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the + * StatsEvent errors field. + * + * @param elements array of ints. + **/ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @NonNull + public Builder writeIntArray(@NonNull final int[] elements) { + final byte numElements = (byte)elements.length; + + if (writeArrayInfo(numElements, TYPE_INT)) { + // Write encoding of each element. + for (int i = 0; i < numElements; i++) { + mPos += mBuffer.putInt(mPos, elements[i]); + } + mNumElements++; + } + return this; + } + + /** + * Write a repeated long field to this StatsEvent. + * + * The list size must not exceed 127. Otherwise, the array isn't written + * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the + * StatsEvent errors field. + * + * @param elements array of longs. + **/ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @NonNull + public Builder writeLongArray(@NonNull final long[] elements) { + final byte numElements = (byte)elements.length; + + if (writeArrayInfo(numElements, TYPE_LONG)) { + // Write encoding of each element. + for (int i = 0; i < numElements; i++) { + mPos += mBuffer.putLong(mPos, elements[i]); + } + mNumElements++; + } + return this; + } + + /** + * Write a repeated float field to this StatsEvent. + * + * The list size must not exceed 127. Otherwise, the array isn't written + * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the + * StatsEvent errors field. + * + * @param elements array of floats. + **/ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @NonNull + public Builder writeFloatArray(@NonNull final float[] elements) { + final byte numElements = (byte)elements.length; + + if (writeArrayInfo(numElements, TYPE_FLOAT)) { + // Write encoding of each element. + for (int i = 0; i < numElements; i++) { + mPos += mBuffer.putFloat(mPos, elements[i]); + } + mNumElements++; + } + return this; + } + + /** + * Write a repeated string field to this StatsEvent. + * + * The list size must not exceed 127. Otherwise, the array isn't written + * to the StatsEvent and ERROR_LIST_TOO_LONG is appended to the + * StatsEvent errors field. + * + * @param elements array of strings. + **/ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + @NonNull + public Builder writeStringArray(@NonNull final String[] elements) { + final byte numElements = (byte)elements.length; + + if (writeArrayInfo(numElements, TYPE_STRING)) { + // Write encoding of each element. + for (int i = 0; i < numElements; i++) { + final byte[] elementBytes = stringToBytes(elements[i]); + mPos += mBuffer.putInt(mPos, elementBytes.length); + mPos += mBuffer.putByteArray(mPos, elementBytes); + } + mNumElements++; + } + return this; + } + + /** + * Write a boolean annotation for the last field written. + **/ + @NonNull + public Builder addBooleanAnnotation( + final byte annotationId, final boolean value) { + // Ensure there's a field written to annotate. + if (mNumElements < 2) { + mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD; + } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) { + mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS; + } else { + mPos += mBuffer.putByte(mPos, annotationId); + mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN); + mPos += mBuffer.putBoolean(mPos, value); + mCurrentAnnotationCount++; + writeAnnotationCount(); + } + + return this; + } + + /** + * Write an integer annotation for the last field written. + **/ + @NonNull + public Builder addIntAnnotation(final byte annotationId, final int value) { + if (mNumElements < 2) { + mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD; + } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) { + mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS; + } else { + mPos += mBuffer.putByte(mPos, annotationId); + mPos += mBuffer.putByte(mPos, TYPE_INT); + mPos += mBuffer.putInt(mPos, value); + mCurrentAnnotationCount++; + writeAnnotationCount(); + } + + return this; + } + + /** + * Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent. + * This should be called for pushed events to reduce memory allocations and garbage + * collections. + **/ + @NonNull + public Builder usePooledBuffer() { + mUsePooledBuffer = true; + mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos); + return this; + } + + /** + * Builds a StatsEvent object with values entered in this Builder. + **/ + @NonNull + public StatsEvent build() { + if (0L == mTimestampNs) { + mErrorMask |= ERROR_NO_TIMESTAMP; + } + if (0 == mAtomId) { + mErrorMask |= ERROR_NO_ATOM_ID; + } + if (mBuffer.hasOverflowed()) { + mErrorMask |= ERROR_OVERFLOW; + } + if (mNumElements > MAX_NUM_ELEMENTS) { + mErrorMask |= ERROR_TOO_MANY_FIELDS; + } + + if (0 == mErrorMask) { + mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements); + } else { + // Write atom id and error mask. Overwrite any annotations for atom Id. + mPos = POS_ATOM_ID; + mPos += mBuffer.putByte(mPos, TYPE_INT); + mPos += mBuffer.putInt(mPos, mAtomId); + mPos += mBuffer.putByte(mPos, TYPE_ERRORS); + mPos += mBuffer.putInt(mPos, mErrorMask); + mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3); + } + + final int size = mPos; + + if (mUsePooledBuffer) { + return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size); + } else { + // Create a copy of the buffer with the required number of bytes. + final byte[] payload = new byte[size]; + System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size); + + // Return Buffer instance to the pool. + mBuffer.release(); + + return new StatsEvent(mAtomId, null, payload, size); + } + } + + private void writeTypeId(final byte typeId) { + mPosLastField = mPos; + mLastType = typeId; + mCurrentAnnotationCount = 0; + final byte encodedId = (byte) (typeId & 0x0F); + mPos += mBuffer.putByte(mPos, encodedId); + } + + private void writeAnnotationCount() { + // Use first 4 bits for annotation count and last 4 bits for typeId. + final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F)); + mBuffer.putByte(mPosLastField, encodedId); + } + + @NonNull + private static byte[] stringToBytes(@Nullable final String value) { + return (null == value ? "" : value).getBytes(UTF_8); + } + + private boolean writeArrayInfo(final byte numElements, + final byte elementTypeId) { + if (numElements > MAX_NUM_ELEMENTS) { + mErrorMask |= ERROR_LIST_TOO_LONG; + return false; + } + // Write list typeId byte, 1-byte representation of number of + // elements, and element typeId byte. + writeTypeId(TYPE_LIST); + mPos += mBuffer.putByte(mPos, numElements); + // Write element typeId byte without setting mPosLastField and mLastType (i.e. don't use + // #writeTypeId) + final byte encodedId = (byte) (elementTypeId & 0x0F); + mPos += mBuffer.putByte(mPos, encodedId); + return true; + } + } + + private static final class Buffer { + private static Object sLock = new Object(); + + @GuardedBy("sLock") + private static Buffer sPool; + + private byte[] mBytes; + private boolean mOverflow = false; + private int mMaxSize = MAX_PULL_PAYLOAD_SIZE; + + @NonNull + private static Buffer obtain() { + final Buffer buffer; + synchronized (sLock) { + buffer = null == sPool ? new Buffer() : sPool; + sPool = null; + } + buffer.reset(); + return buffer; + } + + private Buffer() { + final ByteBuffer tempBuffer = ByteBuffer.allocateDirect(MAX_PUSH_PAYLOAD_SIZE); + mBytes = tempBuffer.hasArray() ? tempBuffer.array() : new byte [MAX_PUSH_PAYLOAD_SIZE]; + } + + @NonNull + private byte[] getBytes() { + return mBytes; + } + + private void release() { + // Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under. + if (mMaxSize <= MAX_PUSH_PAYLOAD_SIZE) { + synchronized (sLock) { + if (null == sPool) { + sPool = this; + } + } + } + } + + private void reset() { + mOverflow = false; + mMaxSize = MAX_PULL_PAYLOAD_SIZE; + } + + private void setMaxSize(final int maxSize, final int numBytesWritten) { + mMaxSize = maxSize; + if (numBytesWritten > maxSize) { + mOverflow = true; + } + } + + private boolean hasOverflowed() { + return mOverflow; + } + + /** + * Checks for available space in the byte array. + * + * @param index starting position in the buffer to start the check. + * @param numBytes number of bytes to check from index. + * @return true if space is available, false otherwise. + **/ + private boolean hasEnoughSpace(final int index, final int numBytes) { + final int totalBytesNeeded = index + numBytes; + + if (totalBytesNeeded > mMaxSize) { + mOverflow = true; + return false; + } + + // Expand buffer if needed. + if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) { + int newSize = mBytes.length; + do { + newSize *= 2; + } while (newSize <= totalBytesNeeded); + + if (newSize > mMaxSize) { + newSize = mMaxSize; + } + + mBytes = Arrays.copyOf(mBytes, newSize); + } + + return true; + } + + /** + * Writes a byte into the buffer. + * + * @param index position in the buffer where the byte is written. + * @param value the byte to write. + * @return number of bytes written to buffer from this write operation. + **/ + private int putByte(final int index, final byte value) { + if (hasEnoughSpace(index, Byte.BYTES)) { + mBytes[index] = (byte) (value); + return Byte.BYTES; + } + return 0; + } + + /** + * Writes a boolean into the buffer. + * + * @param index position in the buffer where the boolean is written. + * @param value the boolean to write. + * @return number of bytes written to buffer from this write operation. + **/ + private int putBoolean(final int index, final boolean value) { + return putByte(index, (byte) (value ? 1 : 0)); + } + + /** + * Writes an integer into the buffer. + * + * @param index position in the buffer where the integer is written. + * @param value the integer to write. + * @return number of bytes written to buffer from this write operation. + **/ + private int putInt(final int index, final int value) { + if (hasEnoughSpace(index, Integer.BYTES)) { + // Use little endian byte order. + mBytes[index] = (byte) (value); + mBytes[index + 1] = (byte) (value >> 8); + mBytes[index + 2] = (byte) (value >> 16); + mBytes[index + 3] = (byte) (value >> 24); + return Integer.BYTES; + } + return 0; + } + + /** + * Writes a long into the buffer. + * + * @param index position in the buffer where the long is written. + * @param value the long to write. + * @return number of bytes written to buffer from this write operation. + **/ + private int putLong(final int index, final long value) { + if (hasEnoughSpace(index, Long.BYTES)) { + // Use little endian byte order. + mBytes[index] = (byte) (value); + mBytes[index + 1] = (byte) (value >> 8); + mBytes[index + 2] = (byte) (value >> 16); + mBytes[index + 3] = (byte) (value >> 24); + mBytes[index + 4] = (byte) (value >> 32); + mBytes[index + 5] = (byte) (value >> 40); + mBytes[index + 6] = (byte) (value >> 48); + mBytes[index + 7] = (byte) (value >> 56); + return Long.BYTES; + } + return 0; + } + + /** + * Writes a float into the buffer. + * + * @param index position in the buffer where the float is written. + * @param value the float to write. + * @return number of bytes written to buffer from this write operation. + **/ + private int putFloat(final int index, final float value) { + return putInt(index, Float.floatToIntBits(value)); + } + + /** + * Copies a byte array into the buffer. + * + * @param index position in the buffer where the byte array is copied. + * @param value the byte array to copy. + * @return number of bytes written to buffer from this write operation. + **/ + private int putByteArray(final int index, @NonNull final byte[] value) { + final int numBytes = value.length; + if (hasEnoughSpace(index, numBytes)) { + System.arraycopy(value, 0, mBytes, index, numBytes); + return numBytes; + } + return 0; + } + } +} diff --git a/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java b/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java new file mode 100644 index 000000000000..c1c20cfac9dd --- /dev/null +++ b/ravenwood/runtime-helper-src/framework/android/util/StatsLog.java @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2017 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.util; + +/* + * [Ravenwood] This is copied from StatsD, with the following changes: + * - The static {} is commented out. + * - All references to IStatsD and StatsdStatsLog are commented out. + * - The native method is no-oped. + */ + +import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.PACKAGE_USAGE_STATS; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.os.Build; +//import android.os.IStatsd; +import android.os.Process; +import android.util.proto.ProtoOutputStream; + +import androidx.annotation.RequiresApi; + +//import com.android.internal.statsd.StatsdStatsLog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * StatsLog provides an API for developers to send events to statsd. The events can be used to + * define custom metrics in side statsd. + */ +public final class StatsLog { + +// // Load JNI library +// static { +// System.loadLibrary("stats_jni"); +// } + private static final String TAG = "StatsLog"; + private static final boolean DEBUG = false; + private static final int EXPERIMENT_IDS_FIELD_ID = 1; + + /** + * Annotation ID constant for logging UID field. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_IS_UID = 1; + + /** + * Annotation ID constant to indicate logged atom event's timestamp should be truncated. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2; + + /** + * Annotation ID constant for a state atom's primary field. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_PRIMARY_FIELD = 3; + + /** + * Annotation ID constant for state atom's state field. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_EXCLUSIVE_STATE = 4; + + /** + * Annotation ID constant to indicate the first UID in the attribution chain + * is a primary field. + * Should only be used for attribution chain fields. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5; + + /** + * Annotation ID constant to indicate which state is default for the state atom. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_DEFAULT_STATE = 6; + + /** + * Annotation ID constant to signal all states should be reset to the default state. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_TRIGGER_STATE_RESET = 7; + + /** + * Annotation ID constant to indicate state changes need to account for nesting. + * This should only be used with binary state atoms. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + public static final byte ANNOTATION_ID_STATE_NESTED = 8; + + /** + * Annotation ID constant to indicate the restriction category of an atom. + * This annotation must only be attached to the atom id. This is an int annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_RESTRICTION_CATEGORY = 9; + + /** + * Annotation ID to indicate that a field of an atom contains peripheral device info. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10; + + /** + * Annotation ID to indicate that a field of an atom contains app usage information. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11; + + /** + * Annotation ID to indicate that a field of an atom contains app activity information. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12; + + /** + * Annotation ID to indicate that a field of an atom contains health connect information. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13; + + /** + * Annotation ID to indicate that a field of an atom contains accessibility information. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14; + + /** + * Annotation ID to indicate that a field of an atom contains system search information. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15; + + /** + * Annotation ID to indicate that a field of an atom contains user engagement information. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16; + + /** + * Annotation ID to indicate that a field of an atom contains ambient sensing information. + * This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17; + + /** + * Annotation ID to indicate that a field of an atom contains demographic classification + * information. This is a bool annotation. + * + * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation() + * accept byte as the type for annotation ids to save space. + * + * @hide + */ + @SuppressLint("NoByteOrShort") + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final byte ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18; + + + /** @hide */ + @IntDef(prefix = { "RESTRICTION_CATEGORY_" }, value = { + RESTRICTION_CATEGORY_DIAGNOSTIC, + RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE, + RESTRICTION_CATEGORY_AUTHENTICATION, + RESTRICTION_CATEGORY_FRAUD_AND_ABUSE}) + @Retention(RetentionPolicy.SOURCE) + public @interface RestrictionCategory {} + + /** + * Restriction category for atoms about diagnostics. + * + * @hide + */ + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final int RESTRICTION_CATEGORY_DIAGNOSTIC = 1; + + /** + * Restriction category for atoms about system intelligence. + * + * @hide + */ + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final int RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2; + + /** + * Restriction category for atoms about authentication. + * + * @hide + */ + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final int RESTRICTION_CATEGORY_AUTHENTICATION = 3; + + /** + * Restriction category for atoms about fraud and abuse. + * + * @hide + */ + @SystemApi + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final int RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4; + + private StatsLog() { + } + + /** + * Logs a start event. + * + * @param label developer-chosen label. + * @return True if the log request was sent to statsd. + */ + public static boolean logStart(int label) { + int callingUid = Process.myUid(); +// StatsdStatsLog.write( +// StatsdStatsLog.APP_BREADCRUMB_REPORTED, +// callingUid, +// label, +// StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); + return true; + } + + /** + * Logs a stop event. + * + * @param label developer-chosen label. + * @return True if the log request was sent to statsd. + */ + public static boolean logStop(int label) { + int callingUid = Process.myUid(); +// StatsdStatsLog.write( +// StatsdStatsLog.APP_BREADCRUMB_REPORTED, +// callingUid, +// label, +// StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); + return true; + } + + /** + * Logs an event that does not represent a start or stop boundary. + * + * @param label developer-chosen label. + * @return True if the log request was sent to statsd. + */ + public static boolean logEvent(int label) { + int callingUid = Process.myUid(); +// StatsdStatsLog.write( +// StatsdStatsLog.APP_BREADCRUMB_REPORTED, +// callingUid, +// label, +// StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); + return true; + } + + /** + * Logs an event for binary push for module updates. + * + * @param trainName name of install train. + * @param trainVersionCode version code of the train. + * @param options optional flags about this install. + * The last 3 bits indicate options: + * 0x01: FLAG_REQUIRE_STAGING + * 0x02: FLAG_ROLLBACK_ENABLED + * 0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR + * @param state current install state. Defined as State enums in + * BinaryPushStateChanged atom in + * frameworks/proto_logging/stats/atoms.proto + * @param experimentIds experiment ids. + * @return True if the log request was sent to statsd. + */ + @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) + public static boolean logBinaryPushStateChanged(@NonNull String trainName, + long trainVersionCode, int options, int state, + @NonNull long[] experimentIds) { + ProtoOutputStream proto = new ProtoOutputStream(); + for (long id : experimentIds) { + proto.write( + ProtoOutputStream.FIELD_TYPE_INT64 + | ProtoOutputStream.FIELD_COUNT_REPEATED + | EXPERIMENT_IDS_FIELD_ID, + id); + } +// StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED, +// trainName, +// trainVersionCode, +// (options & IStatsd.FLAG_REQUIRE_STAGING) > 0, +// (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0, +// (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0, +// state, +// proto.getBytes(), +// 0, +// 0, +// false); + return true; + } + + /** + * Write an event to stats log using the raw format. + * + * @param buffer The encoded buffer of data to write. + * @param size The number of bytes from the buffer to write. + * @hide + * @deprecated Use {@link write(final StatsEvent statsEvent)} instead. + * + */ + @Deprecated + @SystemApi + public static void writeRaw(@NonNull byte[] buffer, int size) { + writeImpl(buffer, size, 0); + } + + /** + * Write an event to stats log using the raw format. + * + * @param buffer The encoded buffer of data to write. + * @param size The number of bytes from the buffer to write. + * @param atomId The id of the atom to which the event belongs. + */ +// private static native void writeImpl(@NonNull byte[] buffer, int size, int atomId); + private static void writeImpl(@NonNull byte[] buffer, int size, int atomId) { + // no-op for now + } + + /** + * Write an event to stats log using the raw format encapsulated in StatsEvent. + * After writing to stats log, release() is called on the StatsEvent object. + * No further action should be taken on the StatsEvent object following this call. + * + * @param statsEvent The StatsEvent object containing the encoded buffer of data to write. + * @hide + */ + @SystemApi + public static void write(@NonNull final StatsEvent statsEvent) { + writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId()); + statsEvent.release(); + } +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java b/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java new file mode 100644 index 000000000000..c7376842d8f3 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/android/compat/Compatibility.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2019 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.compat; + +// [Ravenwood] Copied from libcore, with "RAVENWOOD-CHANGE" + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.SystemApi; +import android.compat.annotation.ChangeId; + +import libcore.api.IntraCoreApi; +import libcore.util.NonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Internal APIs for logging and gating compatibility changes. + * + * @see ChangeId + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) +@IntraCoreApi +public final class Compatibility { + + private Compatibility() {} + + /** + * Reports that a compatibility change is affecting the current process now. + * + * <p>Calls to this method from a non-app process are ignored. This allows code implementing + * APIs that are used by apps and by other code (e.g. the system server) to report changes + * regardless of the process it's running in. When called in a non-app process, this method is + * a no-op. + * + * <p>Note: for changes that are gated using {@link #isChangeEnabled(long)}, you do not need to + * call this API directly. The change will be reported for you in the case that + * {@link #isChangeEnabled(long)} returns {@code true}. + * + * @param changeId The ID of the compatibility change taking effect. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public static void reportUnconditionalChange(@ChangeId long changeId) { + sCallbacks.onChangeReported(changeId); + } + + /** + * Query if a given compatibility change is enabled for the current process. This method should + * only be called by code running inside a process of the affected app. + * + * <p>If this method returns {@code true}, the calling code should implement the compatibility + * change, resulting in differing behaviour compared to earlier releases. If this method returns + * {@code false}, the calling code should behave as it did in earlier releases. + * + * <p>When this method returns {@code true}, it will also report the change as + * {@link #reportUnconditionalChange(long)} would, so there is no need to call that method + * directly. + * + * @param changeId The ID of the compatibility change in question. + * @return {@code true} if the change is enabled for the current app. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public static boolean isChangeEnabled(@ChangeId long changeId) { + return sCallbacks.isChangeEnabled(changeId); + } + + private static final BehaviorChangeDelegate DEFAULT_CALLBACKS = new BehaviorChangeDelegate(){}; + + private volatile static BehaviorChangeDelegate sCallbacks = DEFAULT_CALLBACKS; + + /** + * Sets the behavior change delegate. + * + * All changes reported via the {@link Compatibility} class will be forwarded to this class. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static void setBehaviorChangeDelegate(BehaviorChangeDelegate callbacks) { + sCallbacks = Objects.requireNonNull(callbacks); + } + + /** + * Removes a behavior change delegate previously set via {@link #setBehaviorChangeDelegate}. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static void clearBehaviorChangeDelegate() { + sCallbacks = DEFAULT_CALLBACKS; + } + + /** + * Return the behavior change delegate + * + * @hide + */ + // VisibleForTesting + @NonNull + public static BehaviorChangeDelegate getBehaviorChangeDelegate() { + return sCallbacks; + } + + /** + * For use by tests only. Causes values from {@code overrides} to be returned instead of the + * real value. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static void setOverrides(ChangeConfig overrides) { + // Setting overrides twice in a row does not need to be supported because + // this method is only for enabling/disabling changes for the duration of + // a single test. + // In production, the app is restarted when changes get enabled or disabled, + // and the ChangeConfig is then set exactly once on that app process. + if (sCallbacks instanceof OverrideCallbacks) { + throw new IllegalStateException("setOverrides has already been called!"); + } + sCallbacks = new OverrideCallbacks(sCallbacks, overrides); + } + + /** + * For use by tests only. Removes overrides set by {@link #setOverrides}. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static void clearOverrides() { + if (!(sCallbacks instanceof OverrideCallbacks)) { + throw new IllegalStateException("No overrides set"); + } + sCallbacks = ((OverrideCallbacks) sCallbacks).delegate; + } + + /** + * Base class for compatibility API implementations. The default implementation logs a warning + * to logcat. + * + * This is provided as a class rather than an interface to allow new methods to be added without + * breaking @SystemApi binary compatibility. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public interface BehaviorChangeDelegate { + /** + * Called when a change is reported via {@link Compatibility#reportUnconditionalChange} + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + default void onChangeReported(long changeId) { + // Do not use String.format here (b/160912695) + + // RAVENWOOD-CHANGE + System.out.println("No Compatibility callbacks set! Reporting change " + changeId); + } + + /** + * Called when a change is queried via {@link Compatibility#isChangeEnabled} + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + default boolean isChangeEnabled(long changeId) { + // Do not use String.format here (b/160912695) + // TODO(b/289900411): Rate limit this log if it's necessary in the release build. + // System.logW("No Compatibility callbacks set! Querying change " + changeId); + return true; + } + } + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public static final class ChangeConfig { + private final Set<Long> enabled; + private final Set<Long> disabled; + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public ChangeConfig(@NonNull Set<@NonNull Long> enabled, @NonNull Set<@NonNull Long> disabled) { + this.enabled = Objects.requireNonNull(enabled); + this.disabled = Objects.requireNonNull(disabled); + if (enabled.contains(null)) { + throw new NullPointerException(); + } + if (disabled.contains(null)) { + throw new NullPointerException(); + } + Set<Long> intersection = new HashSet<>(enabled); + intersection.retainAll(disabled); + if (!intersection.isEmpty()) { + throw new IllegalArgumentException("Cannot have changes " + intersection + + " enabled and disabled!"); + } + } + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public boolean isEmpty() { + return enabled.isEmpty() && disabled.isEmpty(); + } + + private static long[] toLongArray(Set<Long> values) { + long[] result = new long[values.size()]; + int idx = 0; + for (Long value: values) { + result[idx++] = value; + } + return result; + } + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public @NonNull long[] getEnabledChangesArray() { + return toLongArray(enabled); + } + + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public @NonNull long[] getDisabledChangesArray() { + return toLongArray(disabled); + } + + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public @NonNull Set<@NonNull Long> getEnabledSet() { + return Collections.unmodifiableSet(enabled); + } + + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public @NonNull Set<@NonNull Long> getDisabledSet() { + return Collections.unmodifiableSet(disabled); + } + + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public boolean isForceEnabled(long changeId) { + return enabled.contains(changeId); + } + + + /** + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @IntraCoreApi + public boolean isForceDisabled(long changeId) { + return disabled.contains(changeId); + } + + + /** + * @hide + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ChangeConfig)) { + return false; + } + ChangeConfig that = (ChangeConfig) o; + return enabled.equals(that.enabled) && + disabled.equals(that.disabled); + } + + /** + * @hide + */ + @Override + public int hashCode() { + return Objects.hash(enabled, disabled); + } + + + /** + * @hide + */ + @Override + public String toString() { + return "ChangeConfig{enabled=" + enabled + ", disabled=" + disabled + '}'; + } + } + + private static class OverrideCallbacks implements BehaviorChangeDelegate { + private final BehaviorChangeDelegate delegate; + private final ChangeConfig changeConfig; + + private OverrideCallbacks(BehaviorChangeDelegate delegate, ChangeConfig changeConfig) { + this.delegate = Objects.requireNonNull(delegate); + this.changeConfig = Objects.requireNonNull(changeConfig); + } + @Override + public boolean isChangeEnabled(long changeId) { + if (changeConfig.isForceEnabled(changeId)) { + return true; + } + if (changeConfig.isForceDisabled(changeId)) { + return false; + } + return delegate.isChangeEnabled(changeId); + } + } +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java new file mode 100644 index 000000000000..00730efc64b3 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/CorePlatformApi.java @@ -0,0 +1,69 @@ + +/* + * 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 libcore.api; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +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.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates an API is part of a contract provided by the "core" set of + * libraries to select parts of the Android software stack. + * + * <p>This annotation should only appear on either (a) classes that are hidden by <pre>@hide</pre> + * javadoc tags or equivalent annotations, or (b) members of such classes. It is for use with + * metalava's {@code --show-single-annotation} option and so must be applied at the class level and + * applied again each member that is to be made part of the API. Members that are not part of the + * API do not have to be explicitly hidden. + * + * @hide + */ +@IntraCoreApi +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE}) +@Retention(RetentionPolicy.SOURCE) +public @interface CorePlatformApi { + + /** Enumeration of the possible statuses of the API in the core/platform API surface. */ + @IntraCoreApi + enum Status { + + /** + * This API is considered stable, and so present in both the stable and legacy version of + * the API surface. + */ + @IntraCoreApi + STABLE, + + /** + * This API is not (yet) considered stable, and so only present in the legacy version of + * the API surface. + */ + @IntraCoreApi + LEGACY_ONLY + } + + /** The status of the API in the core/platform API surface. */ + @IntraCoreApi + Status status() default Status.LEGACY_ONLY; +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java new file mode 100644 index 000000000000..f87ff11df0ab --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/Hide.java @@ -0,0 +1,47 @@ +/* + * 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 libcore.api; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +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.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that an API is hidden by default, in a similar fashion to the + * <pre>@hide</pre> javadoc tag. + * + * <p>Note that, in order for this to work, metalava has to be invoked with + * the flag {@code --hide-annotation libcore.api.Hide}. + * + * <p>This annotation should be used in {@code .annotated.java} stub files which + * contain API inclusion information about {@code libcore/ojluni} classes, to + * avoid patching the source files with <pre>@hide</pre> javadoc tags. All + * build targets which consume these stub files should also apply the above + * metalava flag. + * + * @hide + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE}) +@Retention(RetentionPolicy.SOURCE) +public @interface Hide { +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java new file mode 100644 index 000000000000..87cfcff2474b --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/api/IntraCoreApi.java @@ -0,0 +1,45 @@ +/* + * 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 libcore.api; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +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.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates an API is part of a contract within the "core" set of libraries, some of which may + * be mmodules. + * + * <p>This annotation should only appear on either (a) classes that are hidden by <pre>@hide</pre> + * javadoc tags or equivalent annotations, or (b) members of such classes. It is for use with + * metalava's {@code --show-single-annotation} option and so must be applied at the class level and + * applied again each member that is to be made part of the API. Members that are not part of the + * API do not have to be explicitly hidden. + * + * @hide + */ +@IntraCoreApi // @IntraCoreApi is itself part of the intra-core API +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE}) +@Retention(RetentionPolicy.SOURCE) +public @interface IntraCoreApi { +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java new file mode 100644 index 000000000000..db3cd8ed712f --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 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 libcore.util; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that a type use can never be null. + * <p> + * This is a marker annotation and it has no specific attributes. + * @hide + */ +@Documented +@Retention(SOURCE) +@Target({FIELD, METHOD, PARAMETER, TYPE_USE}) +@libcore.api.IntraCoreApi +public @interface NonNull { + /** + * Min Android API level (inclusive) to which this annotation is applied. + */ + int from() default Integer.MIN_VALUE; + + /** + * Max Android API level to which this annotation is applied. + */ + int to() default Integer.MAX_VALUE; +} diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java new file mode 100644 index 000000000000..3371978b0568 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 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 libcore.util; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that a type use can be a null. + * <p> + * This is a marker annotation and it has no specific attributes. + * @hide + */ +@Documented +@Retention(SOURCE) +@Target({FIELD, METHOD, PARAMETER, TYPE_USE}) +@libcore.api.IntraCoreApi +public @interface Nullable { + /** + * Min Android API level (inclusive) to which this annotation is applied. + */ + int from() default Integer.MIN_VALUE; + + /** + * Max Android API level to which this annotation is applied. + */ + int to() default Integer.MAX_VALUE; +} diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt index 3649f0e78f09..b64944ee39ed 100644 --- a/ravenwood/texts/ravenwood-framework-policies.txt +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -5,6 +5,10 @@ rename com/.*/nano/ devicenano/ rename android/.*/nano/ devicenano/ + +# StatsD autogenerated classes. Maybe add a heuristic? +class com.android.internal.util.FrameworkStatsLog keepclass + # Exported to Mainline modules; cannot use annotations class com.android.internal.util.FastXmlSerializer keepclass class com.android.internal.util.FileRotator keepclass |