Merge "Split android_non_updatable_stubs_libs" into main
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index eb5842b..45719a7 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 36137
+# Bug template url: https://b.corp.google.com/issues/new?component=36137&template=198919
+
alexbuy@google.com
patb@google.com
schfan@google.com
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5713e9e..a4b16f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8158,6 +8158,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.system.virtualmachine.SecretkeeperJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.PruneInstantAppsJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 61e6a36..7d740ef 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -88,6 +88,9 @@
<!-- Colombia: 1-6 digits (not confirmed) -->
<shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
+ <!-- Costa Rica -->
+ <shortcode country="cr" pattern="\\d{1,6}" free="466453" />
+
<!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -143,6 +146,9 @@
<!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
<shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" />
+ <!-- Guatemala -->
+ <shortcode country="gt" pattern="\\d{1,6}" free="466453" />
+
<!-- Croatia -->
<shortcode country="hr" pattern="\\d{1,5}" free="13062" />
@@ -241,10 +247,10 @@
<shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
<!-- Pakistan -->
- <shortcode country="pk" pattern="\\d{1,5}" free="2057" />
+ <shortcode country="pk" pattern="\\d{1,5}" free="2057|9092" />
<!-- Palestine: 5 digits, known premium codes listed -->
- <shortcode country="ps" pattern="\\d{1,5}" free="37477" />
+ <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" />
<!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
@@ -324,4 +330,7 @@
<!-- South Africa -->
<shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056" />
+ <!-- Zimbabwe -->
+ <shortcode country="zw" pattern="\\d{1,5}" free="33679" />
+
</shortcodes>
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 3f7f088..51100c0 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -29,11 +29,13 @@
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.NeverInline;
import libcore.util.NativeAllocationRegistry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
import java.util.Objects;
/**
@@ -83,6 +85,30 @@
return mChars;
}
+ private void rangeCheck(int start, int end) {
+ if (start < 0 || start > end || end > mChars.length) {
+ throwRangeError(start, end);
+ }
+ }
+
+ @NeverInline
+ private void throwRangeError(int start, int end) {
+ throw new IllegalArgumentException(String.format(Locale.US,
+ "start(%d) end(%d) length(%d) out of bounds", start, end, mChars.length));
+ }
+
+ private void offsetCheck(int offset) {
+ if (offset < 0 || offset >= mChars.length) {
+ throwOffsetError(offset);
+ }
+ }
+
+ @NeverInline
+ private void throwOffsetError(int offset) {
+ throw new IllegalArgumentException(String.format(Locale.US,
+ "offset (%d) length(%d) out of bounds", offset, mChars.length));
+ }
+
/**
* Returns the width of a given range.
*
@@ -91,12 +117,7 @@
*/
public @FloatRange(from = 0.0) @Px float getWidth(
@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
return nGetWidth(mNativePtr, start, end);
}
@@ -118,12 +139,7 @@
*/
public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@NonNull Rect rect) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
Preconditions.checkNotNull(rect);
nGetBounds(mNativePtr, mChars, start, end, rect);
}
@@ -137,12 +153,7 @@
*/
public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@NonNull Paint.FontMetricsInt outMetrics) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
Objects.requireNonNull(outMetrics);
long packed = nGetExtent(mNativePtr, mChars, start, end);
@@ -158,8 +169,7 @@
* @param offset an offset of the character.
*/
public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) {
- Preconditions.checkArgument(0 <= offset && offset < mChars.length,
- "offset(%d) is larger than text length %d" + offset, mChars.length);
+ offsetCheck(offset);
return nGetCharWidthAt(mNativePtr, offset);
}
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index 2795cfe..f05ea9c 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -336,8 +336,9 @@
* This method must not be called when the Visualizer is enabled.
* @param size requested capture size
* @return {@link #SUCCESS} in case of success,
- * {@link #ERROR_BAD_VALUE} in case of failure.
- * @throws IllegalStateException
+ * {@link #ERROR_INVALID_OPERATION} if Visualizer effect enginer not enabled.
+ * @throws IllegalStateException if the effect is not in proper state.
+ * @throws IllegalArgumentException if the size parameter is invalid (out of supported range).
*/
public int setCaptureSize(int size)
throws IllegalStateException {
@@ -345,7 +346,13 @@
if (mState != STATE_INITIALIZED) {
throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState));
}
- return native_setCaptureSize(size);
+
+ int ret = native_setCaptureSize(size);
+ if (ret == ERROR_BAD_VALUE) {
+ throw(new IllegalArgumentException("setCaptureSize to " + size + " failed"));
+ }
+
+ return ret;
}
}
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index f8dd756..6223acf 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -24,7 +24,10 @@
import java.util.Objects;
/**
- * An instances of this class represents a session of media playback.
+ * An instance of this class represents a session of media playback used to report playback
+ * metrics and events.
+ *
+ * Create a new instance using {@link MediaMetricsManager#createPlaybackSession}.
*/
public final class PlaybackSession implements AutoCloseable {
private final @NonNull String mId;
@@ -80,6 +83,21 @@
mManager.reportTrackChangeEvent(mId, event);
}
+ /**
+ * A session ID is used to identify a unique playback and to tie together lower-level
+ * playback components.
+ *
+ * Associate this session with a {@link MediaCodec} by passing the ID into
+ * {@link MediaFormat} through {@link MediaFormat#LOG_SESSION_ID} when
+ * creating the {@link MediaCodec}.
+ *
+ * Associate this session with an {@link AudioTrack} by calling
+ * {@link AudioTrack#setLogSessionId}.
+ *
+ * Associate this session with {@link MediaDrm} and {@link MediaCrypto} by calling
+ * {@link MediaDrm#getPlaybackComponent} and then calling
+ * {@link PlaybackComponent#setLogSessionId}.
+ */
public @NonNull LogSessionId getSessionId() {
return mLogSessionId;
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8396005..0fc80dd 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2895,6 +2895,10 @@
jint offset,
jint size,
std::shared_ptr<C2Buffer> *buffer) {
+ if ((offset + size) > context->capacity()) {
+ ALOGW("extractBufferFromContext: offset + size provided exceed capacity");
+ return;
+ }
*buffer = context->toC2Buffer(offset, size);
if (*buffer == nullptr) {
if (!context->mMemory) {
@@ -2995,18 +2999,15 @@
"MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
return;
}
- sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
- jint sampleSize = 0;
+ sp<CryptoInfosWrapper> cryptoInfos = nullptr;
+ jint sampleSize = totalSize;
if (cryptoInfoArray != nullptr) {
+ cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
extractCryptoInfosFromObjectArray(env,
&sampleSize,
&cryptoInfos->value,
cryptoInfoArray,
&errorDetailMsg);
- } else {
- sampleSize = totalSize;
- std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)};
- cryptoInfos->value.push_back(std::move(cryptoInfo));
}
if (env->ExceptionCheck()) {
// Creation of cryptoInfo failed. Let the exception bubble up.
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index 09c45ea..9ae5c99 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -25,7 +25,6 @@
#include <limits.h>
#include <audio_utils/fixedfft.h>
-#include <cutils/bitops.h>
#include <utils/Thread.h>
#include <android/content/AttributionSourceState.h>
@@ -59,8 +58,8 @@
status_t status = AudioEffect::set(
SL_IID_VISUALIZATION, nullptr, priority, cbf, user, sessionId, io, device, probe);
if (status == NO_ERROR || status == ALREADY_EXISTS) {
- initCaptureSize();
- initSampleRate();
+ status = initCaptureSize();
+ if (status == NO_ERROR) initSampleRate();
}
return status;
}
@@ -152,9 +151,8 @@
status_t Visualizer::setCaptureSize(uint32_t size)
{
- if (size > VISUALIZER_CAPTURE_SIZE_MAX ||
- size < VISUALIZER_CAPTURE_SIZE_MIN ||
- popcount(size) != 1) {
+ if (!isCaptureSizeValid(size)) {
+ ALOGE("%s with invalid capture size %u from HAL", __func__, size);
return BAD_VALUE;
}
@@ -172,7 +170,7 @@
*((int32_t *)p->data + 1)= size;
status_t status = setParameter(p);
- ALOGV("setCaptureSize size %d status %d p->status %d", size, status, p->status);
+ ALOGV("setCaptureSize size %u status %d p->status %d", size, status, p->status);
if (status == NO_ERROR) {
status = p->status;
@@ -257,8 +255,8 @@
if ((type != MEASUREMENT_MODE_PEAK_RMS)
// for peak+RMS measurement, the results are 2 int32_t values
|| (number != 2)) {
- ALOGE("Cannot retrieve int measurements, MEASUREMENT_MODE_PEAK_RMS returns 2 ints, not %d",
- number);
+ ALOGE("Cannot retrieve int measurements, MEASUREMENT_MODE_PEAK_RMS returns 2 ints, not %u",
+ number);
return BAD_VALUE;
}
@@ -390,7 +388,7 @@
}
}
-uint32_t Visualizer::initCaptureSize()
+status_t Visualizer::initCaptureSize()
{
uint32_t buf32[sizeof(effect_param_t) / sizeof(uint32_t) + 2];
effect_param_t *p = (effect_param_t *)buf32;
@@ -405,14 +403,20 @@
}
uint32_t size = 0;
- if (status == NO_ERROR) {
- size = *((int32_t *)p->data + 1);
+ if (status != NO_ERROR) {
+ ALOGE("%s getParameter failed status %d", __func__, status);
+ return status;
}
+
+ size = *((int32_t *)p->data + 1);
+ if (!isCaptureSizeValid(size)) {
+ ALOGE("%s with invalid capture size %u from HAL", __func__, size);
+ return BAD_VALUE;
+ }
+
mCaptureSize = size;
-
- ALOGV("initCaptureSize size %d status %d", mCaptureSize, status);
-
- return size;
+ ALOGV("%s size %u status %d", __func__, mCaptureSize, status);
+ return NO_ERROR;
}
void Visualizer::initSampleRate()
diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h
index b38c01f..26d58d0 100644
--- a/media/jni/audioeffect/Visualizer.h
+++ b/media/jni/audioeffect/Visualizer.h
@@ -20,6 +20,8 @@
#include <media/AudioEffect.h>
#include <system/audio_effects/effect_visualizer.h>
#include <utils/Thread.h>
+#include <cstdint>
+#include <cutils/bitops.h>
#include "android/content/AttributionSourceState.h"
/**
@@ -170,8 +172,12 @@
status_t doFft(uint8_t *fft, uint8_t *waveform);
void periodicCapture();
- uint32_t initCaptureSize();
+ status_t initCaptureSize();
void initSampleRate();
+ static constexpr bool isCaptureSizeValid(uint32_t size) {
+ return size <= VISUALIZER_CAPTURE_SIZE_MAX && size >= VISUALIZER_CAPTURE_SIZE_MIN &&
+ popcount(size) == 1;
+ }
Mutex mCaptureLock;
uint32_t mCaptureRate = CAPTURE_RATE_DEF;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index b6ba6a5..5320033 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -84,15 +84,6 @@
{
"name": "CtsVcnTestCases",
"file_patterns": ["VcnManagementService\\.java"]
- },
- {
- "name": "FrameworksNetTests",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- }
- ],
- "file_patterns": ["VpnManagerService\\.java"]
}
],
"presubmit-large": [
@@ -126,5 +117,10 @@
],
"file_patterns": ["VpnManagerService\\.java"]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksVpnTests"
+ }
]
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index c26fd5d..3ab9780 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -264,6 +264,11 @@
&& record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
+ if (mLastDeferredStates && shouldBeDeferred()
+ && (record.getDeliveryState(recordIndex)
+ == BroadcastRecord.DELIVERY_PENDING)) {
+ deferredStatesApplyConsumer.accept(record, recordIndex);
+ }
return replacedBroadcastRecord;
}
}
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index d584c99..d4e46a9 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -179,15 +179,18 @@
nextConsumer.consume(current);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
- } else {
+ } else if (mLightSensor != null) {
mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
+ } else {
+ Slog.w(TAG, "No light sensor - use current to consume");
+ nextConsumer.consume(current);
}
}
private void enableLightSensorLoggingLocked() {
- if (!mEnabled) {
+ if (!mEnabled && mLightSensor != null) {
mEnabled = true;
mLastAmbientLux = -1;
mSensorManager.registerListener(mLightSensorListener, mLightSensor,
@@ -201,7 +204,7 @@
private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
- if (mEnabled) {
+ if (mEnabled && mLightSensor != null) {
mEnabled = false;
if (!destroying) {
mLastAmbientLux = -1;
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index 687d4b0..c2b1c77 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -7,7 +7,7 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
}
],
- "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ "file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
}
],
"presubmit-large": [
@@ -26,5 +26,10 @@
],
"file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
}
+ ],
+ "postsubmit":[
+ {
+ "name":"FrameworksVpnTests"
+ }
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 44c4b57..2adbaaa 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1251,7 +1251,7 @@
if (isUidStateChangeRelevant(callbackInfo, procState, procStateSeq, capability)) {
callbackInfo.update(uid, procState, procStateSeq, capability);
if (!callbackInfo.isPending) {
- mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo)
+ mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, uid, 0)
.sendToTarget();
callbackInfo.isPending = true;
}
@@ -1263,7 +1263,6 @@
synchronized (mUidStateCallbackInfos) {
mUidStateCallbackInfos.remove(uid);
}
- // TODO: b/327058756 - Remove any pending UID_MSG_STATE_CHANGED on the handler.
mUidEventHandler.obtainMessage(UID_MSG_GONE, uid, 0).sendToTarget();
}
};
@@ -5851,13 +5850,13 @@
private final Handler.Callback mUidEventHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
+ final int uid = msg.arg1;
switch (msg.what) {
case UID_MSG_STATE_CHANGED: {
- handleUidChanged((UidStateCallbackInfo) msg.obj);
+ handleUidChanged(uid);
return true;
}
case UID_MSG_GONE: {
- final int uid = msg.arg1;
handleUidGone(uid);
return true;
}
@@ -5868,23 +5867,27 @@
}
};
- void handleUidChanged(@NonNull UidStateCallbackInfo uidStateCallbackInfo) {
+ void handleUidChanged(int uid) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
try {
- boolean updated;
- final int uid;
final int procState;
final long procStateSeq;
final int capability;
- synchronized (mUidRulesFirstLock) {
- synchronized (mUidStateCallbackInfos) {
- uid = uidStateCallbackInfo.uid;
- procState = uidStateCallbackInfo.procState;
- procStateSeq = uidStateCallbackInfo.procStateSeq;
- capability = uidStateCallbackInfo.capability;
- uidStateCallbackInfo.isPending = false;
+ synchronized (mUidStateCallbackInfos) {
+ final UidStateCallbackInfo uidStateCallbackInfo = mUidStateCallbackInfos.get(uid);
+ if (uidStateCallbackInfo == null) {
+ // This can happen if UidObserver#onUidGone gets called before we reach
+ // here. In this case, there is no point in processing this change as this
+ // will immediately be followed by a call to handleUidGone anyway.
+ return;
}
-
+ procState = uidStateCallbackInfo.procState;
+ procStateSeq = uidStateCallbackInfo.procStateSeq;
+ capability = uidStateCallbackInfo.capability;
+ uidStateCallbackInfo.isPending = false;
+ }
+ final boolean updated;
+ synchronized (mUidRulesFirstLock) {
// We received a uid state change callback, add it to the history so that it
// will be useful for debugging.
mLogger.uidStateChanged(uid, procState, procStateSeq, capability);
@@ -5907,6 +5910,14 @@
void handleUidGone(int uid) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidGone");
try {
+ synchronized (mUidStateCallbackInfos) {
+ if (mUidStateCallbackInfos.contains(uid)) {
+ // This can happen if UidObserver#onUidStateChanged gets called before we
+ // reach here. In this case, there is no point in processing this change as this
+ // will immediately be followed by a call to handleUidChanged anyway.
+ return;
+ }
+ }
final boolean updated;
synchronized (mUidRulesFirstLock) {
updated = removeUidStateUL(uid);
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index e0376ed..8db5905 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -28,5 +28,10 @@
}
]
}
+ ],
+ "postsubmit":[
+ {
+ "name":"FrameworksVpnTests"
+ }
]
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 677a5d1..e2f8c50 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3194,12 +3194,7 @@
} else if (tagName.equals("verifier")) {
final String deviceIdentity = parser.getAttributeValue(null, "device");
- try {
- mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
- } catch (IllegalArgumentException e) {
- Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
- + e.getMessage());
- }
+ mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
} else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
// No longer used.
} else if (tagName.equals("keyset-settings")) {
@@ -3228,7 +3223,7 @@
}
str.close();
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | IllegalArgumentException e) {
// Remove corrupted file and retry.
atomicFile.failRead(str, e);
diff --git a/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
new file mode 100644
index 0000000..180f54e
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
@@ -0,0 +1,563 @@
+/*
+ * 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.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
+import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.net.module.util.ProxyUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class Ikev2VpnProfileTest {
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final String USERNAME_STRING = "username";
+ private static final String PASSWORD_STRING = "pa55w0rd";
+ private static final String EXCL_LIST = "exclList";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+ private static final int TEST_MTU = 1300;
+
+ private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
+ SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
+
+ private X509Certificate mUserCert;
+ private X509Certificate mServerRootCa;
+ private PrivateKey mPrivateKey;
+
+ @Before
+ public void setUp() throws Exception {
+ mServerRootCa = generateRandomCertAndKeyPair().cert;
+
+ final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
+ mUserCert = userCertKey.cert;
+ mPrivateKey = userCertKey.key;
+ }
+
+ private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
+
+ builder.setBypassable(true);
+ builder.setProxy(mProxy);
+ builder.setMaxMtu(TEST_MTU);
+ builder.setMetered(true);
+
+ return builder;
+ }
+
+ @Test
+ public void testBuildValidProfileWithOptions() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ // Check non-auth parameters correctly stored
+ assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
+ assertEquals(IDENTITY_STRING, profile.getUserIdentity());
+ assertEquals(mProxy, profile.getProxyInfo());
+ assertTrue(profile.isBypassable());
+ assertTrue(profile.isMetered());
+ assertEquals(TEST_MTU, profile.getMaxMtu());
+ assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildUsernamePasswordProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(USERNAME_STRING, profile.getUsername());
+ assertEquals(PASSWORD_STRING, profile.getPassword());
+ assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildDigitalSignatureProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(profile.getUserCert(), mUserCert);
+ assertEquals(mPrivateKey, profile.getRsaPrivateKey());
+ assertEquals(profile.getServerRootCaCert(), mServerRootCa);
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ }
+
+ @Test
+ public void testBuildPresharedKeyProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
+
+ assertNull(profile.getServerRootCaCert());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsAead() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.AUTH_AES_XCBC,
+ IpSecAlgorithm.AUTH_AES_CMAC,
+ IpSecAlgorithm.CRYPT_AES_CBC,
+ IpSecAlgorithm.CRYPT_AES_CTR);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsEmptyList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(new ArrayList<>());
+ fail("Expected exception due to no valid algorithm set");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInvalidList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
+ fail("Expected exception due to missing encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
+ fail("Expected exception due to missing authentication");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildNoAuthMethodSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.build();
+ fail("Expected exception due to lack of auth method");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildExcludeLocalRoutesSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+ builder.setLocalRoutesExcluded(true);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+ assertTrue(profile.areLocalRoutesExcluded());
+
+ builder.setBypassable(false);
+ try {
+ builder.build();
+ fail("Expected exception because excludeLocalRoutes should be set only"
+ + " on the bypassable VPN");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildInvalidMtu() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setMaxMtu(500);
+ fail("Expected exception due to too-small MTU");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private void verifyVpnProfileCommon(VpnProfile profile) {
+ assertEquals(SERVER_ADDR_STRING, profile.server);
+ assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
+ assertEquals(mProxy, profile.proxy);
+ assertTrue(profile.isBypassable);
+ assertTrue(profile.isMetered);
+ assertEquals(TEST_MTU, profile.maxMtu);
+ }
+
+ @Test
+ public void testPskConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecCaCert);
+ }
+
+ @Test
+ public void testUsernamePasswordConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(USERNAME_STRING, profile.username);
+ assertEquals(PASSWORD_STRING, profile.password);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecSecret);
+ }
+
+ @Test
+ public void testRsaConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+ + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
+ assertEquals(
+ expectedSecret,
+ profile.ipsecSecret);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ }
+
+ @Test
+ public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+ profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ assertNull(result.getServerRootCaCert());
+ }
+
+ @Test
+ public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.ipsecSecret = new String(PSK_BYTES);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getPresharedKey());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ }
+
+ @Test
+ public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getPresharedKey());
+ }
+
+ @Test
+ public void testPskConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testUsernamePasswordConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testRsaConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
+ // Special keyId that contains delimiter character of VpnProfile
+ final byte[] keyId = "foo\0bar".getBytes();
+ final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
+ getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
+ CHILD_PARAMS);
+ final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+
+ assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
+
+ // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
+ // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
+ assertEquals("", vpnProfile.server);
+ assertEquals("", vpnProfile.ipsecIdentifier);
+ assertEquals("", vpnProfile.username);
+ assertEquals("", vpnProfile.password);
+ assertEquals("", vpnProfile.ipsecCaCert);
+ assertEquals("", vpnProfile.ipsecSecret);
+ assertEquals("", vpnProfile.ipsecUserCert);
+ assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
+
+ // IkeTunnelConnectionParams should stay the same.
+ assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
+
+ // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
+ final VpnProfile decodedVpnProfile =
+ VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
+ final Ikev2VpnProfile convertedIkev2VpnProfile =
+ Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
+ assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
+ }
+
+ @Test
+ public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ // Config authentication related fields is not required while building with
+ // IkeTunnelConnectionParams.
+ final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAutomaticNattKeepaliveTimerEnabled(true);
+ builder.setAutomaticIpVersionSelectionEnabled(true);
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionDefaults() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
+ assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ // Verify building without IkeTunnelConnectionParams
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ assertEquals(builder.build(), builder.build());
+
+ // Verify building with IkeTunnelConnectionParams
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ final IkeTunnelConnectionParams tunnelParams2 =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
+ new Ikev2VpnProfile.Builder(tunnelParams2).build());
+ }
+
+ @Test
+ public void testBuildProfileWithNullProxy() throws Exception {
+ final Ikev2VpnProfile ikev2VpnProfile =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
+ .build();
+
+ // ProxyInfo should be null for the profile without setting ProxyInfo.
+ assertNull(ikev2VpnProfile.getProxyInfo());
+
+ // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+ assertNull(vpnProfile.proxy);
+
+ final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
+ assertNull(convertedIkev2VpnProfile.getProxyInfo());
+ }
+
+ private static class CertificateAndKey {
+ public final X509Certificate cert;
+ public final PrivateKey key;
+
+ CertificateAndKey(X509Certificate cert, PrivateKey key) {
+ this.cert = cert;
+ this.key = key;
+ }
+ }
+
+ private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+ final Date validityBeginDate =
+ new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+ final Date validityEndDate =
+ new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+ // Generate a keypair
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(512);
+ final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ final X500Principal dnName = new X500Principal("CN=test.android.com");
+ final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+ certGen.setSubjectDN(dnName);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(validityBeginDate);
+ certGen.setNotAfter(validityEndDate);
+ certGen.setPublicKey(keyPair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+ return new CertificateAndKey(cert, keyPair.getPrivate());
+ }
+}
diff --git a/services/tests/VpnTests/java/android/net/VpnManagerTest.java b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
new file mode 100644
index 0000000..f5b83f0
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.test.mock.MockContext;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.MessageUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link VpnManager}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnManagerTest {
+
+ private static final String PKG_NAME = "fooPackage";
+
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+
+ private IVpnManager mMockService;
+ private VpnManager mVpnManager;
+ private final MockContext mMockContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return PKG_NAME;
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ assumeFalse("Skipping test because watches don't support VPN",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH));
+ mMockService = mock(IVpnManager.class);
+ mVpnManager = new VpnManager(mMockContext, mMockService);
+ }
+
+ @Test
+ public void testProvisionVpnProfilePreconsented() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(true);
+
+ // Expect there to be no intent returned, as consent has already been granted.
+ assertNull(mVpnManager.provisionVpnProfile(profile));
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testProvisionVpnProfileNeedsConsent() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(false);
+
+ // Expect intent to be returned, as consent has not already been granted.
+ final Intent intent = mVpnManager.provisionVpnProfile(profile);
+ assertNotNull(intent);
+
+ final ComponentName expectedComponentName =
+ ComponentName.unflattenFromString(
+ "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
+ assertEquals(expectedComponentName, intent.getComponent());
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testDeleteProvisionedVpnProfile() throws Exception {
+ mVpnManager.deleteProvisionedVpnProfile();
+ verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStartProvisionedVpnProfile() throws Exception {
+ mVpnManager.startProvisionedVpnProfile();
+ verify(mMockService).startVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStopProvisionedVpnProfile() throws Exception {
+ mVpnManager.stopProvisionedVpnProfile();
+ verify(mMockService).stopVpnProfile(eq(PKG_NAME));
+ }
+
+ private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
+ return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setBypassable(true)
+ .setMaxMtu(1300)
+ .setMetered(true)
+ .setAuthPsk(PSK_BYTES)
+ .build();
+ }
+
+ @Test
+ public void testVpnTypesEqual() throws Exception {
+ SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
+ new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
+ SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
+ new Class[] { NativeVpnType.class }, new String[]{ "" });
+
+ // TYPE_VPN_NONE = -1 is only defined in VpnManager.
+ assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
+ for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
+ assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
+ }
+ }
+}
diff --git a/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
new file mode 100644
index 0000000..acbe8b8
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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 com.android.internal.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link VpnProfile}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnProfileTest {
+ private static final String DUMMY_PROFILE_KEY = "Test";
+
+ private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
+ private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+ private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
+ private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
+ private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
+ private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
+ private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
+
+ @Test
+ public void testDefaults() throws Exception {
+ final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
+
+ assertEquals(DUMMY_PROFILE_KEY, p.key);
+ assertEquals("", p.name);
+ assertEquals(VpnProfile.TYPE_PPTP, p.type);
+ assertEquals("", p.server);
+ assertEquals("", p.username);
+ assertEquals("", p.password);
+ assertEquals("", p.dnsServers);
+ assertEquals("", p.searchDomains);
+ assertEquals("", p.routes);
+ assertTrue(p.mppe);
+ assertEquals("", p.l2tpSecret);
+ assertEquals("", p.ipsecIdentifier);
+ assertEquals("", p.ipsecSecret);
+ assertEquals("", p.ipsecUserCert);
+ assertEquals("", p.ipsecCaCert);
+ assertEquals("", p.ipsecServerCert);
+ assertEquals(null, p.proxy);
+ assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
+ assertFalse(p.isBypassable);
+ assertFalse(p.isMetered);
+ assertEquals(1360, p.maxMtu);
+ assertFalse(p.areAuthParamsInline);
+ assertFalse(p.isRestrictedToTestNetworks);
+ assertFalse(p.excludeLocalRoutes);
+ assertFalse(p.requiresInternetValidation);
+ assertFalse(p.automaticNattKeepaliveTimerEnabled);
+ assertFalse(p.automaticIpVersionSelectionEnabled);
+ }
+
+ private VpnProfile getSampleIkev2Profile(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+ p.server = "bar";
+ p.username = "baz";
+ p.password = "qux";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.l2tpSecret = "";
+ p.ipsecIdentifier = "quux";
+ p.ipsecSecret = "quuz";
+ p.ipsecUserCert = "corge";
+ p.ipsecCaCert = "grault";
+ p.ipsecServerCert = "garply";
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
+ true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.server = "bar";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(
+ getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
+
+ final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ modified.maxMtu--;
+ assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
+ assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
+ }
+
+ @Test
+ public void testEncodeDecodeWithIkeTunConnParams() {
+ final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecode() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecodeTooManyValues() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final byte[] tooManyValues =
+ (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
+ }
+
+ private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
+ // Sort to ensure when we remove, we can do it from greatest first.
+ Arrays.sort(missingIndices);
+
+ final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
+ final List<String> parts =
+ new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
+
+ // Remove from back first to ensure indexing is consistent.
+ for (int i = missingIndices.length - 1; i >= 0; i--) {
+ parts.remove(missingIndices[i]);
+ }
+
+ return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
+ }
+
+ @Test
+ public void testEncodeDecodeInvalidNumberOfValues() {
+ final String tooFewValues =
+ getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_AUTH_PARAMS_INLINE,
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
+ /* missingIndices */);
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
+ }
+
+ private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
+ return getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without isRestrictedToTestNetworks defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.isRestrictedToTestNetworks);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingExcludeLocalRoutes() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without excludeLocalRoutes defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.excludeLocalRoutes);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingRequiresValidation() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without requiresValidation defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.requiresInternetValidation);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticIpVersionSelectionEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeLoginsNotSaved() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ profile.saveLogin = false;
+
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertNotEquals(profile, decoded);
+
+ // Add the username/password back, everything else must be equal.
+ decoded.username = profile.username;
+ decoded.password = profile.password;
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testClone() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile clone = profile.clone();
+ assertEquals(profile, clone);
+ assertNotSame(profile, clone);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
rename to services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e914726..28a5864 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1802,6 +1802,59 @@
}
@Test
+ public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
+ // Legacy stack doesn't support deferral
+ Assume.assumeTrue(mImpl == Impl.MODERN);
+
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ setProcessFreezable(receiverGreenApp, true, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ final BroadcastOptions opts = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 10);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 5);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp, 0);
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ // Enqueue the broadcast again to replace the earlier one
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ waitForIdle();
+ // Green should still be in the cached state and shouldn't receive the broadcast
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+
+ final IApplicationThread blueThread = receiverBlueApp.getThread();
+ final IApplicationThread yellowThread = receiverYellowApp.getThread();
+ final InOrder inOrder = inOrder(blueThread, yellowThread);
+ inOrder.verify(blueThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+ inOrder.verify(yellowThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+
+ setProcessFreezable(receiverGreenApp, false, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ // Confirm that green receives the broadcast once it comes out of the cached state
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
new file mode 100644
index 0000000..e94b8ad
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "RollbackPackageHealthObserverTests",
+
+ srcs: [
+ "*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "services.core",
+ "truth",
+ "flag-junit",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
new file mode 100644
index 0000000..c52dbde
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.rollback">
+
+ <uses-sdk android:targetSdkVersion="35" />
+
+ <application android:testOnly="true"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.rollback"
+ android:label="Frameworks Rollback Package Health Observer test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
new file mode 100644
index 0000000..635183c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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="Runs Rollback Package Health Observer Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="RollbackPackageHealthObserverTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="RollbackPackageHealthObserverTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.rollback" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index e42bdad..6ac56bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"postsubmit": [
{
- "name": "FrameworksMockingServicesTests",
+ "name": "RollbackPackageHealthObserverTests",
"options": [
{
"include-filter": "com.android.server.rollback"
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index bb00634..fa1fd90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -344,6 +344,21 @@
verifyNoMoreInteractions(mSensorManager);
}
+ @Test
+ public void testAwaitLuxWhenNoLightSensor() {
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null);
+ mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1);
+
+ AtomicInteger lux = new AtomicInteger(-5);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ // Verify that no light sensor will be registered.
+ verify(mSensorManager, times(0)).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ assertThat(lux.get()).isEqualTo(-1);
+ }
+
private void moveTimeBy(long millis) {
mLooper.moveTimeForward(millis);
mLooper.processAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 15cd511..a5f7963 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -2404,6 +2404,45 @@
}
@Test
+ public void testObsoleteHandleUidGone() throws Exception {
+ callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 51);
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+ clearInvocations(mNetworkManager);
+
+ // In the service, handleUidGone is only called from mUidEventHandler. Then a call to it may
+ // be rendered obsolete by a newer uid change posted on the handler. The latest uid state
+ // change is always reflected in the current UidStateChangeCallbackInfo for the uid, so to
+ // simulate an obsolete call for test, we directly call handleUidGone and leave the state in
+ // UidStateChangeCallbackInfo set by the previous call to onUidStateChanged(TOP). This call
+ // should then do nothing.
+ mService.handleUidGone(UID_A);
+
+ verify(mNetworkManager, times(0)).setFirewallUidRule(anyInt(), anyInt(), anyInt());
+ assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+ public void testObsoleteHandleUidChanged() throws Exception {
+ callAndWaitOnUidGone(UID_A);
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+ clearInvocations(mNetworkManager);
+
+ // In the service, handleUidChanged is only called from mUidEventHandler. Then a call to it
+ // may be rendered obsolete by an immediate uid-gone posted on the handler. The latest uid
+ // state change is always reflected in the current UidStateChangeCallbackInfo for the uid,
+ // so to simulate an obsolete call for test, we directly call handleUidChanged and leave the
+ // state in UidStateChangeCallbackInfo as null as it would get removed by the previous call
+ // to onUidGone(). This call should then do nothing.
+ mService.handleUidChanged(UID_A);
+
+ verify(mNetworkManager, times(0)).setFirewallUidRule(anyInt(), anyInt(), anyInt());
+ assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+ }
+
+ @Test
public void testLowPowerStandbyAllowlist() throws Exception {
// Chain background is also enabled but these procstates are important enough to be exempt.
callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
diff --git a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
index 407d4bf..2977c21 100644
--- a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
+++ b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
@@ -30,7 +30,7 @@
void setBuffer(AHardwareBuffer* buffer) {
ASurfaceTransaction* transaction = ASurfaceTransaction_create();
- ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer);
+ ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer, -1);
ASurfaceTransaction_setVisibility(transaction, surfaceControl,
ASURFACE_TRANSACTION_VISIBILITY_SHOW);
ASurfaceTransaction_apply(transaction);
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index b18bdff..b1b314fc 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,6 +17,7 @@
// ==========================================================
// Build the host executable: protoc-gen-javastream
// ==========================================================
+
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -41,6 +42,32 @@
static_libs: ["libprotoc"],
}
+// ==========================================================
+// Build the host static library: java_streaming_proto_lib
+// ==========================================================
+
+cc_library_host_static {
+ name: "java_streaming_proto_lib",
+ defaults: ["protoc-gen-stream-defaults"],
+ target: {
+ darwin: {
+ cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
+ },
+ },
+ cflags: [
+ "-Wno-format-y2k",
+ "-DSTATIC_ANDROIDFW_FOR_TOOLS",
+ ],
+
+ srcs: [
+ "java/java_proto_stream_code_generator.cpp",
+ ],
+}
+
+// ==========================================================
+// Build the host executable: protoc-gen-javastream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-javastream",
srcs: [
@@ -48,8 +75,13 @@
],
defaults: ["protoc-gen-stream-defaults"],
+ static_libs: ["java_streaming_proto_lib"],
}
+// ==========================================================
+// Build the host executable: protoc-gen-cppstream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-cppstream",
srcs: [
@@ -60,13 +92,31 @@
}
// ==========================================================
+// Build the host tests: StreamingProtoTest
+// ==========================================================
+
+cc_test_host {
+ name: "StreamingProtoTest",
+ defaults: ["protoc-gen-stream-defaults"],
+ srcs: [
+ "test/unit/**/*.cpp",
+ ],
+ static_libs: [
+ "java_streaming_proto_lib",
+ "libgmock",
+ "libgtest",
+ ],
+}
+
+// ==========================================================
// Build the java test
// ==========================================================
+
java_library {
- name: "StreamingProtoTest",
+ name: "StreamingProtoJavaIntegrationTest",
srcs: [
- "test/**/*.java",
- "test/**/*.proto",
+ "test/integration/**/*.java",
+ "test/integration/**/*.proto",
],
proto: {
type: "stream",
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
new file mode 100644
index 0000000..9d61111
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "java_proto_stream_code_generator.h"
+
+#include <stdio.h>
+
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += class_name;
+ result += ".java";
+
+ return result;
+}
+
+static string indent_more(const string& indent) {
+ return indent + INDENT;
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i = 0; i < N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int " << make_constant_name(value.name()) << " = "
+ << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Write a field.
+ */
+static void write_field(stringstream& text, const FieldDescriptorProto& field,
+ const string& indent) {
+ string optional_comment =
+ field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
+ string repeated_comment =
+ field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed() ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void write_message(stringstream& text, const DescriptorProto& message,
+ const string& indent) {
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i = 0; i < N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+ const string& filename, bool generate_outer,
+ const vector<EnumDescriptorProto>& enums,
+ const vector<DescriptorProto>& messages) {
+ stringstream text;
+
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API
+ text << "/** @hide */" << endl;
+ // text << "@android.annotation.TestApi" << endl;
+
+ if (generate_outer) {
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+ }
+
+ size_t N;
+ const string indented = generate_outer ? indent_more("") : string();
+
+ N = enums.size();
+ for (size_t i = 0; i < N; i++) {
+ write_enum(text, enums[i], indented);
+ }
+
+ N = messages.size();
+ for (size_t i = 0; i < N; i++) {
+ write_message(text, messages[i], indented);
+ }
+
+ if (generate_outer) {
+ text << "}" << endl;
+ }
+
+ CodeGeneratorResponse::File* file_response = response->add_file();
+ file_response->set_name(filename);
+ file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class. Put all of the enums into the "outer" class.
+ */
+static void write_multiple_files(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ // If there is anything to put in the outer class file, create one
+ if (file_descriptor.enum_type_size() > 0) {
+ vector<EnumDescriptorProto> enums;
+ int N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name =
+ file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+
+ if (messages_to_compile.empty() || !enums.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+ }
+ }
+
+ // For each of the message types, make a file
+ int N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ vector<EnumDescriptorProto> enums;
+
+ vector<DescriptorProto> messages;
+
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+ messages.push_back(file_descriptor.message_type(i));
+
+ if (messages_to_compile.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+ false, enums, messages);
+ }
+ }
+}
+
+static void write_single_file(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ int N;
+
+ vector<EnumDescriptorProto> enums;
+ N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+ N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+
+ messages.push_back(file_descriptor.message_type(i));
+ }
+
+ if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
+ enums, messages);
+ }
+}
+
+static void parse_args_string(stringstream args_string_stream,
+ set<string>* messages_to_compile_out) {
+ string line;
+ while (getline(args_string_stream, line, ';')) {
+ stringstream line_ss(line);
+ string arg_name;
+ getline(line_ss, arg_name, ':');
+ if (arg_name == "include_filter") {
+ string full_message_name;
+ while (getline(line_ss, full_message_name, ',')) {
+ messages_to_compile_out->insert(full_message_name);
+ }
+ } else {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
+ }
+ }
+}
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
+ CodeGeneratorResponse response;
+
+ set<string> messages_to_compile;
+ auto request_params = request.parameter();
+ if (!request_params.empty()) {
+ parse_args_string(stringstream(request_params), &messages_to_compile);
+ }
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i = 0; i < N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ if (file_descriptor.options().java_multiple_files()) {
+ write_multiple_files(&response, file_descriptor, messages_to_compile);
+ } else {
+ write_single_file(&response, file_descriptor, messages_to_compile);
+ }
+ }
+ }
+
+ return response;
+}
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.h b/tools/streaming_proto/java/java_proto_stream_code_generator.h
new file mode 100644
index 0000000..d2492f7
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+#define AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+
+#include "stream_proto_utils.h"
+#include "string_utils.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request);
+
+#endif // AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
\ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index c9c50a5..5b35504 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,268 +1,21 @@
-#include "Errors.h"
-#include "stream_proto_utils.h"
-#include "string_utils.h"
-
#include <stdio.h>
+
#include <iomanip>
#include <iostream>
-#include <sstream>
#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+#include "java_proto_stream_code_generator.h"
+#include "stream_proto_utils.h"
using namespace android::stream_proto;
using namespace google::protobuf::io;
using namespace std;
/**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
- string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
- }
- return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
- if (file_descriptor.options().has_java_package()) {
- return file_descriptor.options().java_package();
- } else {
- return file_descriptor.package();
- }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
- string const package = make_java_package(file_descriptor);
- string result;
- if (package.size() > 0) {
- result = replace_string(package, '.', '/');
- result += '/';
- }
-
- result += class_name;
- result += ".java";
-
- return result;
-}
-
-static string
-indent_more(const string& indent)
-{
- return indent + INDENT;
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
- const int N = enu.value_size();
- text << indent << "// enum " << enu.name() << endl;
- for (int i=0; i<N; i++) {
- const EnumValueDescriptorProto& value = enu.value(i);
- text << indent << "public static final int "
- << make_constant_name(value.name())
- << " = " << value.number() << ";" << endl;
- }
- text << endl;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
- string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
- ? "optional " : "";
- string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
- ? "repeated " : "";
- string proto_type = get_proto_type(field);
- string packed_comment = field.options().packed()
- ? " [packed=true]" : "";
- text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
- << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
- text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
- ios::fmtflags fmt(text.flags());
- text << setfill('0') << setw(16) << hex << get_field_id(field);
- text.flags(fmt);
-
- text << "L;" << endl;
-
- text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
- int N;
- const string indented = indent_more(indent);
-
- text << indent << "// message " << message.name() << endl;
- text << indent << "public final class " << message.name() << " {" << endl;
- text << endl;
-
- // Enums
- N = message.enum_type_size();
- for (int i=0; i<N; i++) {
- write_enum(text, message.enum_type(i), indented);
- }
-
- // Nested classes
- N = message.nested_type_size();
- for (int i=0; i<N; i++) {
- write_message(text, message.nested_type(i), indented);
- }
-
- // Fields
- N = message.field_size();
- for (int i=0; i<N; i++) {
- write_field(text, message.field(i), indented);
- }
-
- text << indent << "}" << endl;
- text << endl;
-}
-
-/**
- * Write the contents of a file.
*
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
- const string& filename, bool generate_outer,
- const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
- stringstream text;
-
- string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
-
- text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
- text << "// source: " << file_descriptor.name() << endl << endl;
-
- if (package_name.size() > 0) {
- if (package_name.size() > 0) {
- text << "package " << package_name << ";" << endl;
- text << endl;
- }
- }
-
- // This bit of policy is android api rules specific: Raw proto classes
- // must never be in the API
- text << "/** @hide */" << endl;
-// text << "@android.annotation.TestApi" << endl;
-
- if (generate_outer) {
- text << "public final class " << outer_class_name << " {" << endl;
- text << endl;
- }
-
- size_t N;
- const string indented = generate_outer ? indent_more("") : string();
-
- N = enums.size();
- for (size_t i=0; i<N; i++) {
- write_enum(text, enums[i], indented);
- }
-
- N = messages.size();
- for (size_t i=0; i<N; i++) {
- write_message(text, messages[i], indented);
- }
-
- if (generate_outer) {
- text << "}" << endl;
- }
-
- CodeGeneratorResponse::File* file_response = response->add_file();
- file_response->set_name(filename);
- file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class. Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- // If there is anything to put in the outer class file, create one
- if (file_descriptor.enum_type_size() > 0) {
- vector<EnumDescriptorProto> enums;
- int N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
- }
-
- // For each of the message types, make a file
- int N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- vector<EnumDescriptorProto> enums;
-
- vector<DescriptorProto> messages;
- messages.push_back(file_descriptor.message_type(i));
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
- false, enums, messages);
- }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- int N;
-
- vector<EnumDescriptorProto> enums;
- N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
- N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- messages.push_back(file_descriptor.message_type(i));
- }
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
-}
-
-/**
* Main.
*/
int
@@ -273,24 +26,11 @@
GOOGLE_PROTOBUF_VERIFY_VERSION;
- CodeGeneratorRequest request;
- CodeGeneratorResponse response;
-
// Read the request
+ CodeGeneratorRequest request;
request.ParseFromIstream(&cin);
- // Build the files we need.
- const int N = request.proto_file_size();
- for (int i=0; i<N; i++) {
- const FileDescriptorProto& file_descriptor = request.proto_file(i);
- if (should_generate_for_file(request, file_descriptor.name())) {
- if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor);
- } else {
- write_single_file(&response, file_descriptor);
- }
- }
- }
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
// If we had errors, don't write the response. Print the errors and exit.
if (ERRORS.HasErrors()) {
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/integration/imported.proto
similarity index 100%
rename from tools/streaming_proto/test/imported.proto
rename to tools/streaming_proto/test/integration/imported.proto
diff --git a/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
new file mode 100644
index 0000000..2a7001b
--- /dev/null
+++ b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.streaming_proto_test;
+
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/integration/test.proto
similarity index 97%
rename from tools/streaming_proto/test/test.proto
rename to tools/streaming_proto/test/integration/test.proto
index de80ed6..3cf81b4 100644
--- a/tools/streaming_proto/test/test.proto
+++ b/tools/streaming_proto/test/integration/test.proto
@@ -16,7 +16,7 @@
syntax = "proto2";
-import "frameworks/base/tools/streaming_proto/test/imported.proto";
+import "frameworks/base/tools/streaming_proto/test/integration/imported.proto";
package com.android.streaming_proto_test;
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
deleted file mode 100644
index 1246f53..0000000
--- a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.streaming_proto_test;
-
-public class Main {
- public void main(String[] argv) {
- System.out.println("hello world");
- }
-}
diff --git a/tools/streaming_proto/test/unit/streaming_proto_java.cpp b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
new file mode 100644
index 0000000..8df9716
--- /dev/null
+++ b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "java/java_proto_stream_code_generator.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+static void add_my_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+}
+
+static void add_my_other_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyOtherTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyOtherTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+}
+
+static CodeGeneratorRequest create_simple_two_file_request() {
+ CodeGeneratorRequest request;
+
+ add_my_test_proto_file(&request);
+ add_my_other_test_proto_file(&request);
+
+ return request;
+}
+
+static CodeGeneratorRequest create_simple_multi_file_request() {
+ CodeGeneratorRequest request;
+
+ request.add_file_to_generate("MyMultiMessageTestProtoFile");
+
+ FileDescriptorProto* file_desc = request.add_proto_file();
+ file_desc->set_name("MyMultiMessageTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(true);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+
+ message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+
+ return request;
+}
+
+TEST(StreamingProtoJavaTest, NoFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestProtoFile.java");
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestProtoFile"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithoutFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestMessage.java");
+ EXPECT_THAT(response.file(1).content(), Not(HasSubstr("class MyOtherTestProtoFile")));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}