Merge "Fix small mistakes in dumped string" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b43053b..40e1957 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -414,7 +414,7 @@
"//apex_available:platform",
"com.android.art",
"com.android.art.debug",
- "com.android.btservices",
+ "com.android.bt",
"com.android.mediaprovider",
"com.android.permission",
],
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index a949ff5..787fdee 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -1023,7 +1023,6 @@
api_levels_annotations_enabled: true,
api_levels_annotations_dirs: [
"sdk-dir",
- "api-versions-jars-dir",
],
}
diff --git a/api/api.go b/api/api.go
index b8f30a242..da75e05 100644
--- a/api/api.go
+++ b/api/api.go
@@ -102,8 +102,9 @@
}
func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- ctx.WalkDeps(func(child, parent android.Module) bool {
- if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" {
+ ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+ javaInfo, ok := android.OtherModuleProvider(ctx, child, java.JavaInfoProvider)
+ if ok && javaInfo.AndroidLibraryDependencyInfo != nil && child.Name() != "framework-res" {
// Stubs of BCP and SSCP libraries should not have any dependencies on apps
// This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true
ctx.ModuleErrorf(
diff --git a/boot/Android.bp b/boot/Android.bp
index 6eead42..6159845 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -75,8 +75,8 @@
module: "art-bootclasspath-fragment",
},
{
- apex: "com.android.btservices",
- module: "com.android.btservices-bootclasspath-fragment",
+ apex: "com.android.bt",
+ module: "com.android.bt-bootclasspath-fragment",
},
{
apex: "com.android.configinfrastructure",
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index e3e929c..a6a1d16 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -1,5 +1,4 @@
android.content.AsyncTaskLoader$LoadTask
-android.media.MediaCodecInfo$CodecCapabilities$FeatureList
android.net.ConnectivityThread$Singleton
android.os.FileObserver
android.os.NullVibrator
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 67f7bee..b5ac4e7 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -70,7 +70,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.Charset;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -1064,7 +1063,7 @@
Log.e(TAG, "Save lock exception", e);
success = false;
} finally {
- Arrays.fill(password, (byte) 0);
+ LockPatternUtils.zeroize(password);
}
return success;
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 1e45d6f..29578c9 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1129,6 +1129,10 @@
@UnsupportedAppUsage
public ClassLoader getClassLoader() {
+ ClassLoader ret = mClassLoader;
+ if (ret != null) {
+ return ret;
+ }
synchronized (mLock) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 031380d..f815df6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5431,7 +5431,7 @@
/**
* Activities that can be safely invoked from a browser must support this
* category. For example, if the user is viewing a web page or an e-mail
- * and clicks on a link in the text, the Intent generated execute that
+ * and clicks on a link in the text, the Intent generated to execute that
* link will require the BROWSABLE category, so that only activities
* supporting this category will be considered as possible actions. By
* supporting this category, you are promising that there is nothing
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 14a802a..d4905f9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4614,6 +4614,7 @@
* the Android Keystore backed by an isolated execution environment. The version indicates
* which features are implemented in the isolated execution environment:
* <ul>
+ * <li>400: Inclusion of module information (via tag MODULE_HASH) in the attestation record.
* <li>300: Ability to include a second IMEI in the ID attestation record, see
* {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
* <li>200: Hardware support for Curve 25519 (including both Ed25519 signature generation and
@@ -4647,6 +4648,7 @@
* StrongBox</a>. If this feature has a version, the version number indicates which features are
* implemented in StrongBox:
* <ul>
+ * <li>400: Inclusion of module information (via tag MODULE_HASH) in the attestation record.
* <li>300: Ability to include a second IMEI in the ID attestation record, see
* {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
* <li>200: No new features for StrongBox (the Android Keystore environment backed by an
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index ef1e6c94..51aa06b 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2741,4 +2741,12 @@
*/
public static native boolean logAllocatorStats();
+ /**
+ * Return the amount of memory (in kB) allocated by kernel drivers through CMA.
+ * @return a non-negative value or -1 on error.
+ *
+ * @hide
+ */
+ public static native long getKernelCmaUsageKb();
+
}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 0125905..2fe4871 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -70,6 +70,13 @@
private static final String TAG = "Looper";
+ private static class NoImagePreloadHolder {
+ // Enable/Disable verbose logging with a system prop. e.g.
+ // adb shell 'setprop log.looper.slow.verbose false && stop && start'
+ private static final boolean sVerboseLogging =
+ SystemProperties.getBoolean("log.looper.slow.verbose", false);
+ }
+
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@@ -246,17 +253,21 @@
}
}
if (logSlowDelivery) {
+ boolean slow = false;
+
+ if (!me.mSlowDeliveryDetected || NoImagePreloadHolder.sVerboseLogging) {
+ slow = showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart,
+ "delivery", msg);
+ }
if (me.mSlowDeliveryDetected) {
- if ((dispatchStart - msg.when) <= 10) {
+ if (!slow && (dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
me.mSlowDeliveryDetected = false;
}
- } else {
- if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
- msg)) {
- // Once we write a slow delivery log, suppress until the queue drains.
- me.mSlowDeliveryDetected = true;
- }
+ } else if (slow) {
+ // A slow delivery is detected, suppressing further logs unless verbose logging
+ // is enabled.
+ me.mSlowDeliveryDetected = true;
}
}
if (logSlowDispatch) {
@@ -322,6 +333,23 @@
@android.ravenwood.annotation.RavenwoodReplace
private static int getThresholdOverride() {
+ // Allow overriding the threshold for all processes' main looper with a system prop.
+ // e.g. adb shell 'setprop log.looper.any.main.slow 1 && stop && start'
+ if (myLooper() == getMainLooper()) {
+ final int globalOverride = SystemProperties.getInt("log.looper.any.main.slow", -1);
+ if (globalOverride >= 0) {
+ return globalOverride;
+ }
+ }
+
+ // Allow overriding the threshold for all threads within a process with a system prop.
+ // e.g. adb shell 'setprop log.looper.1000.any.slow 1 && stop && start'
+ final int processOverride = SystemProperties.getInt("log.looper."
+ + Process.myUid() + ".any.slow", -1);
+ if (processOverride >= 0) {
+ return processOverride;
+ }
+
return SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9085fe0..a58fea8 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -278,7 +278,7 @@
return service;
} else {
return Binder.allowBlocking(
- getIServiceManager().checkService(name).getServiceWithMetadata().service);
+ getIServiceManager().checkService2(name).getServiceWithMetadata().service);
}
} catch (RemoteException e) {
Log.e(TAG, "error in checkService", e);
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7ea521e..a5aa1b3 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -62,16 +62,23 @@
@UnsupportedAppUsage
public IBinder getService(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name).getServiceWithMetadata().service;
+ return checkService2(name).getServiceWithMetadata().service;
}
public Service getService2(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name);
+ return checkService2(name);
}
- public Service checkService(String name) throws RemoteException {
- return mServiceManager.checkService(name);
+ // TODO(b/355394904): This function has been deprecated, please use checkService2 instead.
+ @UnsupportedAppUsage
+ public IBinder checkService(String name) throws RemoteException {
+ // Same as checkService (old versions of servicemanager had both methods).
+ return checkService2(name).getServiceWithMetadata().service;
+ }
+
+ public Service checkService2(String name) throws RemoteException {
+ return mServiceManager.checkService2(name);
}
public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 392b42d..ceee898b 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -42,6 +42,16 @@
}
flag {
+ name: "secure_array_zeroization"
+ namespace: "platform_security"
+ description: "Enable secure array zeroization"
+ bug: "320392352"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "deprecate_fsv_sig"
namespace: "hardware_backed_security"
description: "Feature flag for deprecating .fsv_sig"
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index b21e85a..da3a817f 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -514,10 +514,14 @@
final TypedArray a = resources.obtainAttributes(
parser, com.android.internal.R.styleable.PointerIcon);
bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
- hotSpotX = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0)
- * pointerScale;
- hotSpotY = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0)
- * pointerScale;
+ // Cast the hotspot dimensions to int before scaling to match the scaling logic of
+ // the bitmap, whose intrinsic size is also an int before it is scaled.
+ final int unscaledHotSpotX =
+ (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ final int unscaledHotSpotY =
+ (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ hotSpotX = unscaledHotSpotX * pointerScale;
+ hotSpotY = unscaledHotSpotY * pointerScale;
a.recycle();
} catch (Exception ex) {
throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d91963f..6bb8b0a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4479,7 +4479,7 @@
/**
* @hide
*/
- public Transaction setDesintationFrame(SurfaceControl sc, @NonNull Rect destinationFrame) {
+ public Transaction setDestinationFrame(SurfaceControl sc, @NonNull Rect destinationFrame) {
checkPreconditions(sc);
nativeSetDestinationFrame(mNativeObject, sc.mNativeObject,
destinationFrame.left, destinationFrame.top, destinationFrame.right,
@@ -4490,7 +4490,7 @@
/**
* @hide
*/
- public Transaction setDesintationFrame(SurfaceControl sc, int width, int height) {
+ public Transaction setDestinationFrame(SurfaceControl sc, int width, int height) {
checkPreconditions(sc);
nativeSetDestinationFrame(mNativeObject, sc.mNativeObject, 0, 0, width, height);
return this;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f0d2a34..31d8556 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1092,7 +1092,7 @@
}
}
- surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+ surfaceUpdateTransaction.setDestinationFrame(mBlastSurfaceControl, mSurfaceWidth,
mSurfaceHeight);
if (isHardwareAccelerated()) {
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index ee5bd65..32eb080 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -684,14 +684,15 @@
}
private void setMiniresolverPadding() {
- Insets systemWindowInsets =
- getWindowManager().getCurrentWindowMetrics().getWindowInsets().getInsets(
- WindowInsets.Type.systemBars());
-
View buttonContainer = findViewById(R.id.button_bar_container);
- buttonContainer.setPadding(0, 0, 0,
- systemWindowInsets.bottom + getResources().getDimensionPixelOffset(
- R.dimen.resolver_button_bar_spacing));
+ if (buttonContainer != null) {
+ Insets systemWindowInsets =
+ getWindowManager().getCurrentWindowMetrics().getWindowInsets().getInsets(
+ WindowInsets.Type.systemBars());
+ buttonContainer.setPadding(0, 0, 0,
+ systemWindowInsets.bottom + getResources().getDimensionPixelOffset(
+ R.dimen.resolver_button_bar_spacing));
+ }
}
@VisibleForTesting
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index a194535..db65d31 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -929,8 +929,11 @@
if (shouldUseMiniResolver()) {
View buttonContainer = findViewById(R.id.button_bar_container);
- buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom
- + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing));
+ if (buttonContainer != null) {
+ buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+ + getResources().getDimensionPixelOffset(
+ R.dimen.resolver_button_bar_spacing));
+ }
}
// Need extra padding so the list can fully scroll up
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 1709ca7..f6de345 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -174,7 +174,9 @@
// System process is dead; ignore
} else {
try {
- Clog_e(TAG, "Error reporting crash", t2);
+ // Log original crash and then log the error reporting exception.
+ Clog_e(TAG, "Couldn't report crash. Here's the crash:", e);
+ Clog_e(TAG, "Error reporting crash. Here's the error:", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 9bd5237..d129762 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -61,6 +61,7 @@
import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.google.android.collect.Lists;
@@ -71,6 +72,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -288,6 +290,56 @@
}
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static byte[] newNonMovableByteArray(int length) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ return new byte[length];
+ }
+ return ArrayUtils.newNonMovableByteArray(length);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static char[] newNonMovableCharArray(int length) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ return new char[length];
+ }
+ return ArrayUtils.newNonMovableCharArray(length);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static void zeroize(byte[] array) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ if (array != null) {
+ Arrays.fill(array, (byte) 0);
+ }
+ return;
+ }
+ ArrayUtils.zeroize(array);
+ }
+
+ /**
+ * This exists temporarily due to trunk-stable policies.
+ * Please use ArrayUtils directly if you can.
+ */
+ public static void zeroize(char[] array) {
+ if (!android.security.Flags.secureArrayZeroization()) {
+ if (array != null) {
+ Arrays.fill(array, (char) 0);
+ }
+ return;
+ }
+ ArrayUtils.zeroize(array);
+ }
+
@UnsupportedAppUsage
public DevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) {
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 54b9a22..92ce990 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -246,7 +246,7 @@
*/
public void zeroize() {
if (mCredential != null) {
- Arrays.fill(mCredential, (byte) 0);
+ LockPatternUtils.zeroize(mCredential);
mCredential = null;
}
}
@@ -346,7 +346,7 @@
byte[] sha1 = MessageDigest.getInstance("SHA-1").digest(saltedPassword);
byte[] md5 = MessageDigest.getInstance("MD5").digest(saltedPassword);
- Arrays.fill(saltedPassword, (byte) 0);
+ LockPatternUtils.zeroize(saltedPassword);
return HexEncoding.encodeToString(ArrayUtils.concat(sha1, md5));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError("Missing digest algorithm: ", e);
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index b1221ee..68ef3d4 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -165,19 +165,25 @@
return -1;
}
- jbyte* bufferBytes = NULL;
- if (buffer) {
- bufferBytes = (jbyte*)env->GetPrimitiveArrayCritical(buffer, NULL);
+ bool is_dir_in = (requestType & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN;
+ std::unique_ptr<jbyte[]> bufferBytes(new (std::nothrow) jbyte[length]);
+ if (!bufferBytes) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return -1;
}
- jint result = usb_device_control_transfer(device, requestType, request,
- value, index, bufferBytes + start, length, timeout);
-
- if (bufferBytes) {
- env->ReleasePrimitiveArrayCritical(buffer, bufferBytes, 0);
+ if (!is_dir_in && buffer) {
+ env->GetByteArrayRegion(buffer, start, length, bufferBytes.get());
}
- return result;
+ jint bytes_transferred = usb_device_control_transfer(device, requestType, request,
+ value, index, bufferBytes.get(), length, timeout);
+
+ if (bytes_transferred > 0 && is_dir_in) {
+ env->SetByteArrayRegion(buffer, start, bytes_transferred, bufferBytes.get());
+ }
+
+ return bytes_transferred;
}
static jint
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 3c2dccd..9ef17e8 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -729,6 +729,17 @@
return gpuPrivateMem / 1024;
}
+static jlong android_os_Debug_getKernelCmaUsageKb(JNIEnv* env, jobject clazz) {
+ jlong totalKernelCmaUsageKb = -1;
+ uint64_t size;
+
+ if (meminfo::ReadKernelCmaUsageKb(&size)) {
+ totalKernelCmaUsageKb = size;
+ }
+
+ return totalKernelCmaUsageKb;
+}
+
static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) {
jlong dmabufPss = 0;
std::vector<dmabufinfo::DmaBuffer> dmabufs;
@@ -836,6 +847,7 @@
{"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb},
{"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack},
{"logAllocatorStats", "()Z", (void*)android_os_Debug_logAllocatorStats},
+ {"getKernelCmaUsageKb", "()J", (void*)android_os_Debug_getKernelCmaUsageKb},
};
int register_android_os_Debug(JNIEnv *env)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 201e0c8..45c4ea0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -180,6 +180,7 @@
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
<protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ENCRYPTION_CHANGE" />
<protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
<protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" />
@@ -8909,6 +8910,11 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.ZramMaintenance"
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.ZramWriteback"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index cccd6ba..c01a8a9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5126,10 +5126,6 @@
<!-- Whether or not swipe up gesture's opt-in setting is available on this device -->
<bool name="config_swipe_up_gesture_setting_available">true</bool>
- <!-- Applications which are disabled unless matching a particular sku -->
- <string-array name="config_disableApksUnlessMatchedSku_apk_list" translatable="false" />
- <string-array name="config_disableApkUnlessMatchedSku_skus_list" translatable="false" />
-
<!-- Whether or not we should show the option to show battery percentage -->
<bool name="config_battery_percentage_setting_available">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e827102..a3d5be7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4259,10 +4259,6 @@
<java-symbol type="integer" name="config_unfoldTransitionHalfFoldedTimeout" />
<java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />
-
- <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
- <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
-
<java-symbol type="string" name="config_misprovisionedDeviceModel" />
<java-symbol type="string" name="config_misprovisionedBrandValue" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 06cd44e..9b3a6cb 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -111,7 +111,7 @@
<shortcode country="cz" premium="90\\d{5}|90\\d{3}" free="116\\d{3}" />
<!-- Germany: 4-5 digits plus 1232xxx (premium codes from http://www.vodafone.de/infofaxe/537.pdf and http://premiumdienste.eplus.de/pdf/kodex.pdf), plus EU. To keep the premium regex from being too large, it only includes payment processors that have been used by SMS malware, with the regular pattern matching the other premium short codes. -->
- <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529|70296|83782|3011|73240|72438" />
+ <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}|81214|81215|47529|70296|83782|3011|73240|72438|70997" />
<!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
<shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index c45080fb..5fd4ffc 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -10,6 +10,9 @@
# PerformanceHintManager
per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS
+# SystemHealthManager
+per-file SystemHealthManagerUnitTest.java = file:/ADPF_OWNERS
+
# Caching
per-file IpcDataCache* = file:/PERFORMANCE_OWNERS
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 7823277..efa0b8c 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -102,7 +102,7 @@
to pre-existing users, but cannot uninstall pre-existing system packages from pre-existing users.
-->
<config>
- <!-- Bluetooth (com.android.btservices apex) - visible on the sharesheet -->
+ <!-- Bluetooth (com.android.bt apex) - visible on the sharesheet -->
<install-in-user-type package="com.android.bluetooth">
<install-in user-type="SYSTEM" />
<install-in user-type="FULL" />
diff --git a/graphics/java/android/graphics/OWNERS b/graphics/java/android/graphics/OWNERS
index ef8d26c..1ea1976 100644
--- a/graphics/java/android/graphics/OWNERS
+++ b/graphics/java/android/graphics/OWNERS
@@ -2,10 +2,10 @@
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
per-file BLASTBufferQueue.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file FontFamily.java = file:fonts/OWNERS
diff --git a/keystore/java/android/security/keystore/KeyStoreManager.java b/keystore/java/android/security/keystore/KeyStoreManager.java
index 740ccb5..13f1a72 100644
--- a/keystore/java/android/security/keystore/KeyStoreManager.java
+++ b/keystore/java/android/security/keystore/KeyStoreManager.java
@@ -312,9 +312,11 @@
* When passed into getSupplementaryAttestationInfo, getSupplementaryAttestationInfo returns the
* DER-encoded structure corresponding to the `Modules` schema described in the KeyMint HAL's
* KeyCreationResult.aidl. The SHA-256 hash of this encoded structure is what's included with
- * the tag in attestations.
+ * the tag in attestations. To ensure the returned encoded structure is the one attested to,
+ * clients should verify its SHA-256 hash matches the one in the attestation. Note that the
+ * returned structure can vary between boots.
*/
- // TODO(b/369375199): Replace with Tag.MODULE_HASH when flagging is removed.
+ // TODO(b/380020528): Replace with Tag.MODULE_HASH when KeyMint V4 is frozen.
public static final int MODULE_HASH = TagType.BYTES | 724;
/**
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index e6c652c..5e93f8d 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
@@ -732,6 +733,8 @@
}
}
+ @RequiresPermission(value = android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ conditional = true)
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
@@ -824,7 +827,13 @@
break;
}
case AttestationUtils.ID_TYPE_MEID: {
- final String meid = telephonyService.getMeid(0);
+ String meid;
+ try {
+ meid = telephonyService.getMeid(0);
+ } catch (UnsupportedOperationException e) {
+ Log.e(TAG, "Unable to retrieve MEID", e);
+ meid = null;
+ }
if (meid == null) {
throw new DeviceIdAttestationException("Unable to retrieve MEID");
}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
index b6db6d9..4043a1c 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -38,7 +38,6 @@
android_robolectric_test {
name: "WMShellRobolectricScreenshotTests",
instrumentation_for: "WMShellRobolectricScreenshotTestApp",
- upstream: true,
java_resource_dirs: [
"robolectric/config",
],
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index e74342e..1931ea9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -529,9 +529,12 @@
Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
return;
}
+ // Cache it to avoid NPE and make sure to remove it from recents history.
+ // mTaskToken can be cleared in onTaskVanished() when the task is removed.
+ final WindowContainerToken taskToken = mTaskToken;
mShellExecutor.execute(() -> {
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(mTaskToken);
+ wct.removeTask(taskToken);
mTaskViewTransitions.closeTaskView(wct, this);
});
}
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index e302393..a0d1cbc 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -674,7 +674,7 @@
VkSemaphore semaphore;
VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
ALOGE_IF(VK_SUCCESS != err,
- "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
+ "VulkanManager::finishFrame(): Failed to create semaphore");
if (err == VK_SUCCESS) {
sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
@@ -722,7 +722,7 @@
int fenceFd = -1;
VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
+ ALOGE_IF(VK_SUCCESS != err, "VulkanManager::finishFrame(): Failed to get semaphore Fd");
drawResult.presentFence.reset(fenceFd);
} else {
ALOGE("VulkanManager::finishFrame(): Semaphore submission failed");
diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java
index cdde7c6..9746dab 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -83,8 +83,11 @@
* succeed.
*/
public static boolean isPresent() {
- ILocationManager lm = Objects.requireNonNull(ILocationManager.Stub.asInterface(
- ServiceManager.getService(Context.LOCATION_SERVICE)));
+ ILocationManager lm = ILocationManager.Stub.asInterface(
+ ServiceManager.getService(Context.LOCATION_SERVICE));
+ if (lm == null) {
+ return false;
+ }
try {
return lm.isGeocodeAvailable();
} catch (RemoteException e) {
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index e52e0b1..6a21496 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -1,7 +1,10 @@
{
"presubmit": [
{
- "name": "CtsMediaBetterTogetherTestCases"
+ "name": "CtsMediaRouterTestCases"
+ },
+ {
+ "name": "CtsMediaSessionTestCases"
},
{
"name": "mediaroutertest"
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index e4f88a6..e63c59d 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -9,7 +9,7 @@
android_test {
name: "mediaroutertest",
- team: "trendy_team_android_media_solutions",
+ team: "trendy_team_android_media_better_together",
srcs: ["**/*.java"],
diff --git a/mime/Android.bp b/mime/Android.bp
index 20110f1..b609548 100644
--- a/mime/Android.bp
+++ b/mime/Android.bp
@@ -49,6 +49,17 @@
],
}
+java_library {
+ name: "mimemap-testing-alt",
+ defaults: ["mimemap-defaults"],
+ static_libs: ["mimemap-testing-alt-res.jar"],
+ jarjar_rules: "jarjar-rules-alt.txt",
+ visibility: [
+ "//cts/tests/tests/mimemap:__subpackages__",
+ "//frameworks/base:__subpackages__",
+ ],
+}
+
// The mimemap-res.jar and mimemap-testing-res.jar genrules produce a .jar that
// has the resource file in a subdirectory res/ and testres/, respectively.
// They need to be in different paths because one of them ends up in a
@@ -86,6 +97,19 @@
cmd: "mkdir $(genDir)/testres/ && cp $(in) $(genDir)/testres/ && $(location soong_zip) -C $(genDir) -o $(out) -D $(genDir)/testres/",
}
+// The same as mimemap-testing-res.jar except that the resources are placed in a different directory.
+// They get bundled with CTS so that CTS can compare a device's MimeMap implementation vs.
+// the stock Android one from when CTS was built.
+java_genrule {
+ name: "mimemap-testing-alt-res.jar",
+ tools: [
+ "soong_zip",
+ ],
+ srcs: [":mime.types.minimized-alt"],
+ out: ["mimemap-testing-alt-res.jar"],
+ cmd: "mkdir $(genDir)/testres-alt/ && cp $(in) $(genDir)/testres-alt/ && $(location soong_zip) -C $(genDir) -o $(out) -D $(genDir)/testres-alt/",
+}
+
// Combination of all *mime.types.minimized resources.
filegroup {
name: "mime.types.minimized",
@@ -99,6 +123,19 @@
],
}
+// Combination of all *mime.types.minimized resources.
+filegroup {
+ name: "mime.types.minimized-alt",
+ visibility: [
+ "//visibility:private",
+ ],
+ device_common_srcs: [
+ ":debian.mime.types.minimized-alt",
+ ":android.mime.types.minimized",
+ ":vendor.mime.types.minimized",
+ ],
+}
+
java_genrule {
name: "android.mime.types.minimized",
visibility: [
diff --git a/mime/jarjar-rules-alt.txt b/mime/jarjar-rules-alt.txt
new file mode 100644
index 0000000..9a76443
--- /dev/null
+++ b/mime/jarjar-rules-alt.txt
@@ -0,0 +1 @@
+rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidAltMimeMapFactory
diff --git a/mime/jarjar-rules.txt b/mime/jarjar-rules.txt
index 145d1db..e1ea8e1 100644
--- a/mime/jarjar-rules.txt
+++ b/mime/jarjar-rules.txt
@@ -1 +1 @@
-rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidMimeMapFactory
\ No newline at end of file
+rule android.content.type.DefaultMimeMapFactory android.content.type.cts.StockAndroidMimeMapFactory
diff --git a/native/android/tests/system_health/OWNERS b/native/android/tests/system_health/OWNERS
new file mode 100644
index 0000000..e3bbee92
--- /dev/null
+++ b/native/android/tests/system_health/OWNERS
@@ -0,0 +1 @@
+include /ADPF_OWNERS
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 962f54d..fdd44de 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -95,8 +95,13 @@
static_libs: ["libarect"],
fuzz_config: {
cc: [
+ // Alphabetical order -- assign to [email protected]
+ "[email protected]",
"[email protected]",
- "[email protected]",
+ "[email protected]",
+ "[email protected]",
+ "[email protected]",
+ "[email protected]",
],
asan_options: [
"detect_odr_violation=1",
diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
index 6b14a1e..54ded0c 100644
--- a/nfc-non-updatable/flags/flags.aconfig
+++ b/nfc-non-updatable/flags/flags.aconfig
@@ -197,3 +197,11 @@
description: "Expose constructor for ApduServiceInfo"
bug: "380892385"
}
+
+flag {
+ name: "nfc_hce_latency_events"
+ is_exported: true
+ namespace: "wallet_integration"
+ description: "Enables tracking latency for HCE"
+ bug: "379849603"
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 5dc0747..ff08403 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -408,74 +408,10 @@
Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
}
}
- // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
- if (requests == null) {
- Map<String, AconfigdFlagInfo> aconfigdFlagMap =
- AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket);
- compareFlagValueInNewStorage(
- mAconfigDefaultFlags,
- aconfigdFlagMap);
- }
}
}
}
- // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
- public int compareFlagValueInNewStorage(
- Map<String, AconfigdFlagInfo> defaultFlagMap,
- Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
-
- // Get all defaults from the default map. The mSettings may not contain
- // all flags, since it only contains updated flags.
- int diffNum = 0;
- for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) {
- String key = entry.getKey();
- AconfigdFlagInfo flag = entry.getValue();
-
- AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key);
- if (aconfigdFlag == null) {
- Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key));
- diffNum++;
- continue;
- }
- String diff = flag.dumpDiff(aconfigdFlag);
- if (!diff.isEmpty()) {
- Slog.w(
- LOG_TAG,
- String.format(
- "Flag %s is different in Settings and aconfig: %s", key, diff));
- diffNum++;
- }
- }
-
- for (String key : aconfigdFlagMap.keySet()) {
- if (defaultFlagMap.containsKey(key)) continue;
- Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key));
- diffNum++;
- }
-
- String compareMarkerName = "aconfigd_marker/compare_diff_num";
- synchronized (mLock) {
- Setting markerSetting = mSettings.get(compareMarkerName);
- if (markerSetting == null) {
- markerSetting =
- new Setting(
- compareMarkerName,
- String.valueOf(diffNum),
- false,
- "aconfig",
- "aconfig");
- mSettings.put(compareMarkerName, markerSetting);
- }
- markerSetting.value = String.valueOf(diffNum);
- }
-
- if (diffNum == 0) {
- Slog.w(LOG_TAG, "Settings and new storage have same flags.");
- }
- return diffNum;
- }
-
@GuardedBy("mLock")
public int getAllAconfigFlagsFromSettings(
@NonNull Map<String, AconfigdFlagInfo> flagInfoDefault) {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 48ce49d..f798a35 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -1302,84 +1302,4 @@
assertFalse(flag3.getHasServerOverride());
assertFalse(flag3.getHasLocalOverride());
}
-
- @Test
- public void testCompareFlagValueInNewStorage() {
- int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
- Object lock = new Object();
- SettingsState settingsState =
- new SettingsState(
- InstrumentationRegistry.getContext(),
- lock,
- mSettingsFile,
- configKey,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED,
- Looper.getMainLooper());
-
- AconfigdFlagInfo defaultFlag1 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag1")
- .setDefaultFlagValue("false")
- .setServerFlagValue("true")
- .setHasServerOverride(true)
- .setIsReadWrite(true)
- .build();
-
- AconfigdFlagInfo expectedFlag1 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag1")
- .setServerFlagValue("true")
- .setDefaultFlagValue("false")
- .setHasServerOverride(true)
- .setIsReadWrite(true)
- .build();
-
- Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>();
- Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>();
-
- defaultMap.put("com.android.flags.flag1", defaultFlag1);
- aconfigdMap.put("com.android.flags.flag1", expectedFlag1);
-
- int ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- assertEquals(0, ret);
-
- String value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("0", value);
-
- AconfigdFlagInfo defaultFlag2 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag2")
- .setDefaultFlagValue("false")
- .build();
- defaultMap.put("com.android.flags.flag2", defaultFlag2);
-
- ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- // missing from new storage
- assertEquals(1, ret);
- value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("1", value);
-
- AconfigdFlagInfo expectedFlag2 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag2")
- .setServerFlagValue("true")
- .setLocalFlagValue("true")
- .setDefaultFlagValue("false")
- .setHasServerOverride(true)
- .setHasLocalOverride(true)
- .build();
- aconfigdMap.put("com.android.flags.flag2", expectedFlag2);
- ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- // skip the server and local value comparison when the flag is read_only
- assertEquals(0, ret);
- value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("0", value);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 493afde..df3310f 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -451,6 +451,9 @@
mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener);
mGlobalSettings.unregisterContentObserverSync(mAirplaneModeObserver);
mConfigurationController.removeCallback(this);
+ if (mShowSilentToggle) {
+ mRingerModeTracker.getRingerMode().removeObservers(this);
+ }
}
protected Context getContext() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
index 208a17c..ebe603b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/OWNERS
@@ -2,6 +2,8 @@
# Bug component: 78010
+include /services/core/java/com/android/server/biometrics/OWNERS
+
[email protected]
[email protected]
[email protected]
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index 1370b06..97574e6 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -39,9 +39,7 @@
name: "connectivity-utils-service-vcn-internal",
sdk_version: "module_current",
min_sdk_version: "30",
- srcs: [
- ":framework-connectivity-shared-srcs",
- ],
+ srcs: ["service-utils/**/*.java"],
libs: [
"framework-annotations-lib",
"unsupportedappusage",
diff --git a/packages/Vcn/service-b/service-utils/android/util/LocalLog.java b/packages/Vcn/service-b/service-utils/android/util/LocalLog.java
new file mode 100644
index 0000000..5955d93
--- /dev/null
+++ b/packages/Vcn/service-b/service-utils/android/util/LocalLog.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.SystemClock;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+/**
+ * @hide
+ */
+// Exported to Mainline modules; cannot use annotations
+// @android.ravenwood.annotation.RavenwoodKeepWholeClass
+// TODO: b/374174952 This is an exact copy of frameworks/base/core/java/android/util/LocalLog.java.
+// This file is only used in "service-connectivity-b-platform" before the VCN modularization flag
+// is fully ramped. When the flag is fully ramped and the development is finalized, this file can
+// be removed.
+public final class LocalLog {
+
+ private final Deque<String> mLog;
+ private final int mMaxLines;
+
+ /**
+ * {@code true} to use log timestamps expressed in local date/time, {@code false} to use log
+ * timestamped expressed with the elapsed realtime clock and UTC system clock. {@code false} is
+ * useful when logging behavior that modifies device time zone or system clock.
+ */
+ private final boolean mUseLocalTimestamps;
+
+ @UnsupportedAppUsage
+ public LocalLog(int maxLines) {
+ this(maxLines, true /* useLocalTimestamps */);
+ }
+
+ public LocalLog(int maxLines, boolean useLocalTimestamps) {
+ mMaxLines = Math.max(0, maxLines);
+ mLog = new ArrayDeque<>(mMaxLines);
+ mUseLocalTimestamps = useLocalTimestamps;
+ }
+
+ @UnsupportedAppUsage
+ public void log(String msg) {
+ if (mMaxLines <= 0) {
+ return;
+ }
+ final String logLine;
+ if (mUseLocalTimestamps) {
+ logLine = LocalDateTime.now() + " - " + msg;
+ } else {
+ logLine = Duration.ofMillis(SystemClock.elapsedRealtime())
+ + " / " + Instant.now() + " - " + msg;
+ }
+ append(logLine);
+ }
+
+ private synchronized void append(String logLine) {
+ while (mLog.size() >= mMaxLines) {
+ mLog.remove();
+ }
+ mLog.add(logLine);
+ }
+
+ @UnsupportedAppUsage
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ dump(pw);
+ }
+
+ public synchronized void dump(PrintWriter pw) {
+ dump("", pw);
+ }
+
+ /**
+ * Dumps the content of local log to print writer with each log entry predeced with indent
+ *
+ * @param indent indent that precedes each log entry
+ * @param pw printer writer to write into
+ */
+ public synchronized void dump(String indent, PrintWriter pw) {
+ Iterator<String> itr = mLog.iterator();
+ while (itr.hasNext()) {
+ pw.printf("%s%s\n", indent, itr.next());
+ }
+ }
+
+ public synchronized void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ reverseDump(pw);
+ }
+
+ public synchronized void reverseDump(PrintWriter pw) {
+ Iterator<String> itr = mLog.descendingIterator();
+ while (itr.hasNext()) {
+ pw.println(itr.next());
+ }
+ }
+
+ // @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public synchronized void clear() {
+ mLog.clear();
+ }
+
+ public static class ReadOnlyLocalLog {
+ private final LocalLog mLog;
+ ReadOnlyLocalLog(LocalLog log) {
+ mLog = log;
+ }
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLog.dump(pw);
+ }
+ public void dump(PrintWriter pw) {
+ mLog.dump(pw);
+ }
+ public void reverseDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mLog.reverseDump(pw);
+ }
+ public void reverseDump(PrintWriter pw) {
+ mLog.reverseDump(pw);
+ }
+ }
+
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ public ReadOnlyLocalLog readOnlyLocalLog() {
+ return new ReadOnlyLocalLog(this);
+ }
+}
\ No newline at end of file
diff --git a/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java b/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java
new file mode 100644
index 0000000..7db62f8
--- /dev/null
+++ b/packages/Vcn/service-b/service-utils/com/android/internal/util/WakeupMessage.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2025 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.util;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+ /**
+ * An AlarmListener that sends the specified message to a Handler and keeps the system awake until
+ * the message is processed.
+ *
+ * This is useful when using the AlarmManager direct callback interface to wake up the system and
+ * request that an object whose API consists of messages (such as a StateMachine) perform some
+ * action.
+ *
+ * In this situation, using AlarmManager.onAlarmListener by itself will wake up the system to send
+ * the message, but does not guarantee that the system will be awake until the target object has
+ * processed it. This is because as soon as the onAlarmListener sends the message and returns, the
+ * AlarmManager releases its wakelock and the system is free to go to sleep again.
+ */
+// TODO: b/374174952 This is an exact copy of
+// frameworks/base/core/java/com/android/internal/util/WakeupMessage.java.
+// This file is only used in "service-connectivity-b-platform" before the VCN modularization flag
+// is fully ramped. When the flag is fully ramped and the development is finalized, this file can
+// be removed.
+public class WakeupMessage implements AlarmManager.OnAlarmListener {
+ private final AlarmManager mAlarmManager;
+
+ @VisibleForTesting
+ protected final Handler mHandler;
+ @VisibleForTesting
+ protected final String mCmdName;
+ @VisibleForTesting
+ protected final int mCmd, mArg1, mArg2;
+ @VisibleForTesting
+ protected final Object mObj;
+ private final Runnable mRunnable;
+ private boolean mScheduled;
+
+ public WakeupMessage(Context context, Handler handler,
+ String cmdName, int cmd, int arg1, int arg2, Object obj) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = cmd;
+ mArg1 = arg1;
+ mArg2 = arg2;
+ mObj = obj;
+ mRunnable = null;
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
+ this(context, handler, cmdName, cmd, arg1, 0, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler,
+ String cmdName, int cmd, int arg1, int arg2) {
+ this(context, handler, cmdName, cmd, arg1, arg2, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
+ this(context, handler, cmdName, cmd, 0, 0, null);
+ }
+
+ public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = 0;
+ mArg1 = 0;
+ mArg2 = 0;
+ mObj = null;
+ mRunnable = runnable;
+ }
+
+ private static AlarmManager getAlarmManager(Context context) {
+ return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ /**
+ * Schedule the message to be delivered at the time in milliseconds of the
+ * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
+ * the device when it goes off. If schedule is called multiple times without the message being
+ * dispatched then the alarm is rescheduled to the new time.
+ */
+ public synchronized void schedule(long when) {
+ mAlarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mCmdName, this, mHandler);
+ mScheduled = true;
+ }
+
+ /**
+ * Cancel all pending messages. This includes alarms that may have been fired, but have not been
+ * run on the handler yet.
+ */
+ public synchronized void cancel() {
+ if (mScheduled) {
+ mAlarmManager.cancel(this);
+ mScheduled = false;
+ }
+ }
+
+ @Override
+ public void onAlarm() {
+ // Once this method is called the alarm has already been fired and removed from
+ // AlarmManager (it is still partially tracked, but only for statistics). The alarm can now
+ // be marked as unscheduled so that it can be rescheduled in the message handler.
+ final boolean stillScheduled;
+ synchronized (this) {
+ stillScheduled = mScheduled;
+ mScheduled = false;
+ }
+ if (stillScheduled) {
+ Message msg;
+ if (mRunnable == null) {
+ msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+ } else {
+ msg = Message.obtain(mHandler, mRunnable);
+ }
+ mHandler.dispatchMessage(msg);
+ msg.recycle();
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt b/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
index 3630727..6ec39d9 100644
--- a/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
+++ b/packages/Vcn/service-b/service-vcn-platform-jarjar-rules.txt
@@ -1,5 +1,2 @@
-rule android.util.IndentingPrintWriter android.net.vcn.module.repackaged.android.util.IndentingPrintWriter
rule android.util.LocalLog android.net.vcn.module.repackaged.android.util.LocalLog
-rule com.android.internal.util.IndentingPrintWriter android.net.vcn.module.repackaged.com.android.internal.util.IndentingPrintWriter
-rule com.android.internal.util.MessageUtils android.net.vcn.module.repackaged.com.android.internal.util.MessageUtils
rule com.android.internal.util.WakeupMessage android.net.vcn.module.repackaged.com.android.internal.util.WakeupMessage
\ No newline at end of file
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
index 369ef6a..97f86b1 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
@@ -209,6 +209,8 @@
this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
}
+ // WARNING: This constructor executes on the binder thread. Thread safety MUST be ensured when
+ // accessing data within this constructor and any methods called from here.
@VisibleForTesting(visibility = Visibility.PRIVATE)
public Vcn(
@NonNull VcnContext vcnContext,
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
index 99c848f..38fcf09 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
@@ -36,7 +36,6 @@
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -46,6 +45,7 @@
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
@@ -56,12 +56,14 @@
*
* @hide
*/
+// TODO(b/388919146): Implement a more generic solution to prevent concurrent modifications on
+// mListeners and mRequests
// TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class VcnNetworkProvider extends NetworkProvider {
private static final String TAG = VcnNetworkProvider.class.getSimpleName();
- private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
+ private final Set<NetworkRequestListener> mListeners = ConcurrentHashMap.newKeySet();
private final Context mContext;
private final Handler mHandler;
@@ -72,7 +74,7 @@
*
* <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
*/
- private final Set<NetworkRequest> mRequests = new ArraySet<>();
+ private final Set<NetworkRequest> mRequests = ConcurrentHashMap.newKeySet();
public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
this(context, looper, new Dependencies());
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 6467af4..c8c645f 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -138,6 +138,8 @@
@NonNull private final Object mCancellationToken = new Object();
@NonNull private final PacketLossCalculator mPacketLossCalculator;
+ @Nullable private BroadcastReceiver mDeviceIdleReceiver;
+
@Nullable private IpSecTransformWrapper mInboundTransform;
@Nullable private IpSecTransformState mLastIpSecTransformState;
@@ -168,19 +170,21 @@
// Register for system broadcasts to monitor idle mode change
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+
+ mDeviceIdleReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+ intent.getAction())
+ && mPowerManager.isDeviceIdleMode()) {
+ mLastIpSecTransformState = null;
+ }
+ }
+ };
getVcnContext()
.getContext()
.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
- intent.getAction())
- && mPowerManager.isDeviceIdleMode()) {
- mLastIpSecTransformState = null;
- }
- }
- },
+ mDeviceIdleReceiver,
intentFilter,
null /* broadcastPermission not required */,
mHandler);
@@ -338,7 +342,12 @@
super.close();
if (mInboundTransform != null) {
- mInboundTransform.close();
+ mInboundTransform = null;
+ }
+
+ if (mDeviceIdleReceiver != null) {
+ getVcnContext().getContext().unregisterReceiver(mDeviceIdleReceiver);
+ mDeviceIdleReceiver = null;
}
}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index 1485344..55829a5 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -165,7 +165,7 @@
}
}
- /** Set the IpSecTransform that applied to the Network being monitored */
+ /** Set the IpSecTransform that is applied to the Network being monitored */
public void setInboundTransform(@NonNull IpSecTransform inTransform) {
setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 59043a83..8e99842 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -345,14 +345,41 @@
],
}
+// We define our own version of platform_compat_config's here, because:
+// - The original version (e.g. "framework-platform-compat-config) is built from
+// the output file of the device side jar, rather than the host jar, meaning
+// they're slow to build because they depend on D8/R8 output.
+// - The original services one ("services-platform-compat-config") is built from services.jar,
+// which includes service.permission, which is very slow to rebuild because of kotlin.
+//
+// Because we're re-defining the same compat-IDs that are defined elsewhere,
+// they should all have `include_in_merged_xml: false`. Otherwise, generating
+// merged_compat_config.xml would fail due to duplicate IDs.
+//
+// These module names must end with "compat-config" because these will be used as the filename,
+// and at runtime, we only loads files that match `*compat-config.xml`.
+platform_compat_config {
+ name: "ravenwood-framework-platform-compat-config",
+ src: ":framework-minus-apex-for-host",
+ include_in_merged_xml: false,
+ visibility: ["//visibility:private"],
+}
+
+platform_compat_config {
+ name: "ravenwood-services.core-platform-compat-config",
+ src: ":services.core-for-host",
+ include_in_merged_xml: false,
+ visibility: ["//visibility:private"],
+}
+
filegroup {
name: "ravenwood-data",
device_common_srcs: [
":system-build.prop",
":framework-res",
":ravenwood-empty-res",
- ":framework-platform-compat-config",
- ":services-platform-compat-config",
+ ":ravenwood-framework-platform-compat-config",
+ ":ravenwood-services.core-platform-compat-config",
"texts/ravenwood-build.prop",
],
device_first_srcs: [
@@ -616,6 +643,10 @@
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
+
+ // Note, when we include other services.* jars, we'll need to add
+ // platform_compat_config for that module too.
+ // See ravenwood-services.core-platform-compat-config above.
"services.core.ravenwood-jarjar",
"services.fakes.ravenwood-jarjar",
diff --git a/ravenwood/CleanSpec.mk b/ravenwood/CleanSpec.mk
new file mode 100644
index 0000000..50d2fab
--- /dev/null
+++ b/ravenwood/CleanSpec.mk
@@ -0,0 +1,45 @@
+# 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# *****************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
+# *****************************************************************
+
+$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime)
+
+# ******************************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
+# ******************************************************************
diff --git a/ravenwood/scripts/pta-framework.sh b/ravenwood/scripts/pta-framework.sh
new file mode 100755
index 0000000..224ab59
--- /dev/null
+++ b/ravenwood/scripts/pta-framework.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+# 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.
+
+#
+# Use "ravehleper pta" to create a shell script which:
+# - Reads the text "policy" files
+# - Convert to java annotations (using sed)
+#
+
+set -e
+
+
+# Uncomment it to always build ravenhelper (slow)
+# ${BUILD_CMD:-m} ravenhelper
+
+# Get the target directory. Default to $ANDROID_BUILD_TOP.
+TARGET_DIR="${TARGET_DIR:-${ANDROID_BUILD_TOP?\$ANDROID_BUILD_TOP must be set}}"
+
+echo "Target dir=$TARGET_DIR"
+
+cd "$TARGET_DIR"
+
+# Add -v or -d as needed.
+extra_args="$@"
+
+OUT_SCRIPT="${OUT_SCRIPT:-/tmp/pta.sh}"
+
+rm -f "$OUT_SCRIPT"
+
+# If you want to run on other files, run this script with the following
+# env vars predefined.
+
+POLICIES="${POLICIES:-
+frameworks/base/ravenwood/texts/ravenwood-common-policies.txt
+frameworks/base/ravenwood/texts/ravenwood-framework-policies.txt
+}"
+
+SOURCES="${SOURCES:-
+frameworks/base/core/java/
+frameworks/base/graphics/java/
+}"
+
+AAC="${AAC:-frameworks/base/ravenwood/texts/ravenwood-annotation-allowed-classes.txt}"
+
+with_flag() {
+ local flag="$1"
+ shift
+
+ for arg in "$@"; do
+ echo "$flag $arg"
+ done
+}
+
+run() {
+ echo "Running: $*"
+ "$@"
+}
+
+run_pta() {
+ local extra_args="$@"
+
+ run ${RAVENHELPER_CMD:-ravenhelper pta} \
+ --output-script $OUT_SCRIPT \
+ --annotation-allowed-classes-file $AAC \
+ $(with_flag --policy-override-file $POLICIES) \
+ $(with_flag --src $SOURCES) \
+ $extra_args
+
+ if ! [[ -f $OUT_SCRIPT ]] ; then
+ # no operations generated.
+ exit 0
+ fi
+
+ echo
+ echo "Created script at $OUT_SCRIPT. Run it with: sh $OUT_SCRIPT"
+ return 0
+}
+
+run_pta "$extra_args"
\ No newline at end of file
diff --git a/ravenwood/texts/ravenwood-common-policies.txt b/ravenwood/texts/ravenwood-common-policies.txt
index 83c3151..fd4ea6c 100644
--- a/ravenwood/texts/ravenwood-common-policies.txt
+++ b/ravenwood/texts/ravenwood-common-policies.txt
@@ -1,5 +1,8 @@
# Ravenwood "policy" that should apply to all code.
+# The "no-pta" marker is used to exclude the lines from "ravenhelper pta",
+# which tries to convert policies to annotations.
+
# Keep all AIDL interfaces
class :aidl keepclass
@@ -13,8 +16,8 @@
class :r keepclass
# Support APIs not available in standard JRE
-class java.io.FileDescriptor keep
+class java.io.FileDescriptor # no-pta
method getInt$ @com.android.ravenwood.RavenwoodJdkPatch.getInt$
method setInt$ @com.android.ravenwood.RavenwoodJdkPatch.setInt$
-class java.util.LinkedHashMap keep
+class java.util.LinkedHashMap # no-pta
method eldest @com.android.ravenwood.RavenwoodJdkPatch.eldest
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 80126df..4033782 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -1,62 +1,65 @@
# Ravenwood "policy" file for framework-minus-apex.
+# The "no-pta" marker is used to exclude the lines from "ravenhelper pta",
+# which tries to convert policies to annotations.
+
# To avoid VerifyError on nano proto files (b/324063814), we rename nano proto classes.
# Note: The "rename" directive must use slashes (/) as a package name separator.
rename com/.*/nano/ devicenano/
rename android/.*/nano/ devicenano/
# StatsD auto-generated
-class com.android.internal.util.FrameworkStatsLog keepclass
+class com.android.internal.util.FrameworkStatsLog keepclass # no-pta
# Exported to Mainline modules; cannot use annotations
-class com.android.internal.util.FastXmlSerializer keepclass
-class com.android.internal.util.FileRotator keepclass
-class com.android.internal.util.HexDump keepclass
-class com.android.internal.util.IndentingPrintWriter keepclass
-class com.android.internal.util.LocalLog keepclass
-class com.android.internal.util.MessageUtils keepclass
-class com.android.internal.util.TokenBucket keepclass
-class android.os.HandlerExecutor keepclass
-class android.util.BackupUtils keepclass
-class android.util.IndentingPrintWriter keepclass
-class android.util.LocalLog keepclass
-class android.util.Pair keepclass
-class android.util.Rational keepclass
+class com.android.internal.util.FastXmlSerializer keepclass # no-pta
+class com.android.internal.util.FileRotator keepclass # no-pta
+class com.android.internal.util.HexDump keepclass # no-pta
+class com.android.internal.util.IndentingPrintWriter keepclass # no-pta
+class com.android.internal.util.LocalLog keepclass # no-pta
+class com.android.internal.util.MessageUtils keepclass # no-pta
+class com.android.internal.util.TokenBucket keepclass # no-pta
+class android.os.HandlerExecutor keepclass # no-pta
+class android.util.BackupUtils keepclass # no-pta
+class android.util.IndentingPrintWriter keepclass # no-pta
+class android.util.LocalLog keepclass # no-pta
+class android.util.Pair keepclass # no-pta
+class android.util.Rational keepclass # no-pta
# From modules-utils; cannot use annotations
-class com.android.internal.util.Preconditions keepclass
-class com.android.internal.logging.InstanceId keepclass
-class com.android.internal.logging.InstanceIdSequence keepclass
-class com.android.internal.logging.UiEvent keepclass
-class com.android.internal.logging.UiEventLogger keepclass
+class com.android.internal.util.Preconditions keepclass # no-pta
+class com.android.internal.logging.InstanceId keepclass # no-pta
+class com.android.internal.logging.InstanceIdSequence keepclass # no-pta
+class com.android.internal.logging.UiEvent keepclass # no-pta
+class com.android.internal.logging.UiEventLogger keepclass # no-pta
# From modules-utils; cannot use annotations
-class com.android.modules.utils.BinaryXmlPullParser keepclass
-class com.android.modules.utils.BinaryXmlSerializer keepclass
-class com.android.modules.utils.FastDataInput keepclass
-class com.android.modules.utils.FastDataOutput keepclass
-class com.android.modules.utils.ModifiedUtf8 keepclass
-class com.android.modules.utils.TypedXmlPullParser keepclass
-class com.android.modules.utils.TypedXmlSerializer keepclass
+class com.android.modules.utils.BinaryXmlPullParser keepclass # no-pta
+class com.android.modules.utils.BinaryXmlSerializer keepclass # no-pta
+class com.android.modules.utils.FastDataInput keepclass # no-pta
+class com.android.modules.utils.FastDataOutput keepclass # no-pta
+class com.android.modules.utils.ModifiedUtf8 keepclass # no-pta
+class com.android.modules.utils.TypedXmlPullParser keepclass # no-pta
+class com.android.modules.utils.TypedXmlSerializer keepclass # no-pta
# Uri
-class android.net.Uri keepclass
-class android.net.UriCodec keepclass
+class android.net.Uri keepclass # no-pta
+class android.net.UriCodec keepclass # no-pta
# Telephony
-class android.telephony.PinResult keepclass
+class android.telephony.PinResult keepclass # no-pta
# Just enough to support mocking, no further functionality
-class android.content.BroadcastReceiver keep
+class android.content.BroadcastReceiver keep # no-pta
method <init> ()V keep
-class android.content.Context keep
+class android.content.Context keep # no-pta
method <init> ()V keep
- method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep
-class android.content.pm.PackageManager keep
+ method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep # no-pta
+class android.content.pm.PackageManager # no-pta
method <init> ()V keep
-class android.text.ClipboardManager keep
+class android.text.ClipboardManager keep # no-pta
method <init> ()V keep
# Just enough to allow ResourcesManager to run
-class android.hardware.display.DisplayManagerGlobal keep
+class android.hardware.display.DisplayManagerGlobal keep # no-pta
method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore
diff --git a/ravenwood/texts/ravenwood-services-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt
index 530e5c8..e3be9af 100644
--- a/ravenwood/texts/ravenwood-services-policies.txt
+++ b/ravenwood/texts/ravenwood-services-policies.txt
@@ -1,12 +1,15 @@
# Ravenwood "policy" file for services.core.
+# The "no-pta" marker is used to exclude the lines from "ravenhelper pta",
+# which tries to convert policies to annotations.
+
# Auto-generated from XSD
-class com.android.server.compat.config.Change keepclass
-class com.android.server.compat.config.Config keepclass
-class com.android.server.compat.config.XmlParser keepclass
-class com.android.server.compat.overrides.ChangeOverrides keepclass
-class com.android.server.compat.overrides.OverrideValue keepclass
-class com.android.server.compat.overrides.Overrides keepclass
-class com.android.server.compat.overrides.RawOverrideValue keepclass
-class com.android.server.compat.overrides.XmlParser keepclass
-class com.android.server.compat.overrides.XmlWriter keepclass
\ No newline at end of file
+class com.android.server.compat.config.Change keepclass # no-pta
+class com.android.server.compat.config.Config keepclass # no-pta
+class com.android.server.compat.config.XmlParser keepclass # no-pta
+class com.android.server.compat.overrides.ChangeOverrides keepclass # no-pta
+class com.android.server.compat.overrides.OverrideValue keepclass # no-pta
+class com.android.server.compat.overrides.Overrides keepclass # no-pta
+class com.android.server.compat.overrides.RawOverrideValue keepclass # no-pta
+class com.android.server.compat.overrides.XmlParser keepclass # no-pta
+class com.android.server.compat.overrides.XmlWriter keepclass # no-pta
\ No newline at end of file
diff --git a/ravenwood/tools/hoststubgen/scripts/dump-jar b/ravenwood/tools/hoststubgen/scripts/dump-jar
index 8765245..02a6d25 100755
--- a/ravenwood/tools/hoststubgen/scripts/dump-jar
+++ b/ravenwood/tools/hoststubgen/scripts/dump-jar
@@ -89,14 +89,33 @@
# - Some other transient lines
# - Sometimes the javap shows mysterious warnings, so remove them too.
#
- # `/PATTERN-1/,/PATTERN-1/{//!d}` is a trick to delete lines between two patterns, without
- # the start and the end lines.
+ # Most conversion are simple per-line deletion or simple inline replacement with a regex.
+ #
+ # But removing the constant pool is a bit tricky. It looks like this in the output:
+ #---------------------------
+ #Constant pool:
+ # #1 = Methodref #31.#88 // java/lang/Object."<init>":()V
+ # #2 = Class #89 // java/lang/UnsupportedOperationException
+ # :
+ #{ // Or something, I'm not sure if it always ends with a "{".
+ #---------------------------
+ # i.e. we want to delete all lines from "Constant pool:" as long as the first character
+ # is a space.
+ #
+ # If we simply use '/^Constant pool:/,/^[^ ]/d', then it'll delete the "Constant pool:"
+ # line and "{" line too, but again the last line might be important, so we don't want to
+ # delete it.
+ #
+ # So we instead, use '/^Constant pool:/,/^[^ ]/{/^ /d}', which mean:
+ # between lines matching '/^Constant pool:/' and '/^[^ ]/', delete lines that start with
+ # a space. (=='/^ /d').
+ #
sed -e 's/#[0-9][0-9]*/#x/g' \
-e 's/^\( *\)[0-9][0-9]*:/\1x:/' \
- -e '/^Constant pool:/,/^[^ ]/{//!d}' \
+ -e '/^Constant pool:/,/^[^ ]/{/^ /d}' \
-e '/^ *line *[0-9][0-9]*: *[0-9][0-9]*$/d' \
- -e '/SHA-256 checksum/d' \
- -e '/Last modified/d' \
+ -e '/^ *SHA-256 checksum/d' \
+ -e '/^ *Last modified/d' \
-e '/^Classfile jar/d' \
-e '/\[warning\]/d'
else
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 6d8d7b7..cc704b2 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -27,7 +27,7 @@
import com.android.hoststubgen.filters.KeepNativeFilter
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.filters.SanitizationFilter
-import com.android.hoststubgen.filters.TextFileFilterPolicyParser
+import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.filters.printAsTextPolicy
import com.android.hoststubgen.utils.ClassFilter
import com.android.hoststubgen.visitors.BaseAdapter
@@ -179,9 +179,9 @@
// Next, "text based" filter, which allows to override polices without touching
// the target code.
if (options.policyOverrideFiles.isNotEmpty()) {
- val parser = TextFileFilterPolicyParser(allClasses, filter)
- options.policyOverrideFiles.forEach(parser::parse)
- filter = parser.createOutputFilter()
+ val builder = TextFileFilterPolicyBuilder(allClasses, filter)
+ options.policyOverrideFiles.forEach(builder::parse)
+ filter = builder.createOutputFilter()
}
// Apply the implicit filter.
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 7462a8c..9782f3d 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -23,10 +23,12 @@
import com.android.hoststubgen.log
import com.android.hoststubgen.normalizeTextLine
import com.android.hoststubgen.whitespaceRegex
-import java.io.File
-import java.io.PrintWriter
-import java.util.regex.Pattern
import org.objectweb.asm.tree.ClassNode
+import java.io.BufferedReader
+import java.io.FileReader
+import java.io.PrintWriter
+import java.io.Reader
+import java.util.regex.Pattern
/**
* Print a class node as a "keep" policy.
@@ -48,7 +50,7 @@
private const val FILTER_REASON = "file-override"
-private enum class SpecialClass {
+enum class SpecialClass {
NotSpecial,
Aidl,
FeatureFlags,
@@ -56,10 +58,58 @@
RFile,
}
-class TextFileFilterPolicyParser(
+/**
+ * This receives [TextFileFilterPolicyBuilder] parsing result.
+ */
+interface PolicyFileProcessor {
+ /** "package" directive. */
+ fun onPackage(name: String, policy: FilterPolicyWithReason)
+
+ /** "rename" directive. */
+ fun onRename(pattern: Pattern, prefix: String)
+
+ /** "class" directive. */
+ fun onClassStart(className: String)
+ fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason)
+ fun onClassEnd(className: String)
+
+ fun onSubClassPolicy(superClassName: String, policy: FilterPolicyWithReason)
+ fun onRedirectionClass(fromClassName: String, toClassName: String)
+ fun onClassLoadHook(className: String, callback: String)
+ fun onSpecialClassPolicy(type: SpecialClass, policy: FilterPolicyWithReason)
+
+ /** "field" directive. */
+ fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason)
+
+ /** "method" directive. */
+ fun onSimpleMethodPolicy(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ )
+ fun onMethodInClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ targetName: String,
+ policy: FilterPolicyWithReason,
+ )
+ fun onMethodOutClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ policy: FilterPolicyWithReason,
+ )
+}
+
+class TextFileFilterPolicyBuilder(
private val classes: ClassNodes,
fallback: OutputFilter
) {
+ private val parser = TextFileFilterPolicyParser()
+
private val subclassFilter = SubclassFilter(classes, fallback)
private val packageFilter = PackageFilter(subclassFilter)
private val imf = InMemoryOutputFilter(classes, packageFilter)
@@ -71,30 +121,19 @@
private val methodReplaceSpec =
mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>()
- private lateinit var currentClassName: String
-
/**
- * Read a given "policy" file and return as an [OutputFilter]
+ * Parse a given policy file. This method can be called multiple times to read from
+ * multiple files. To get the resulting filter, use [createOutputFilter]
*/
fun parse(file: String) {
- log.i("Loading offloaded annotations from $file ...")
- log.withIndent {
- var lineNo = 0
- try {
- File(file).forEachLine {
- lineNo++
- val line = normalizeTextLine(it)
- if (line.isEmpty()) {
- return@forEachLine // skip empty lines.
- }
- parseLine(line)
- }
- } catch (e: ParseException) {
- throw e.withSourceInfo(file, lineNo)
- }
- }
+ // We may parse multiple files, but we reuse the same parser, because the parser
+ // will make sure there'll be no dupplicating "special class" policies.
+ parser.parse(FileReader(file), file, Processor())
}
+ /**
+ * Generate the resulting [OutputFilter].
+ */
fun createOutputFilter(): OutputFilter {
var ret: OutputFilter = imf
if (typeRenameSpec.isNotEmpty()) {
@@ -112,14 +151,204 @@
return ret
}
+ private inner class Processor : PolicyFileProcessor {
+ override fun onPackage(name: String, policy: FilterPolicyWithReason) {
+ packageFilter.addPolicy(name, policy)
+ }
+
+ override fun onRename(pattern: Pattern, prefix: String) {
+ typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
+ pattern, prefix
+ )
+ }
+
+ override fun onClassStart(className: String) {
+ }
+
+ override fun onClassEnd(className: String) {
+ }
+
+ override fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason) {
+ imf.setPolicyForClass(className, policy)
+ }
+
+ override fun onSubClassPolicy(
+ superClassName: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ log.i("class extends $superClassName")
+ subclassFilter.addPolicy( superClassName, policy)
+ }
+
+ override fun onRedirectionClass(fromClassName: String, toClassName: String) {
+ imf.setRedirectionClass(fromClassName, toClassName)
+ }
+
+ override fun onClassLoadHook(className: String, callback: String) {
+ imf.setClassLoadHook(className, callback)
+ }
+
+ override fun onSpecialClassPolicy(
+ type: SpecialClass,
+ policy: FilterPolicyWithReason,
+ ) {
+ log.i("class special $type $policy")
+ when (type) {
+ SpecialClass.NotSpecial -> {} // Shouldn't happen
+
+ SpecialClass.Aidl -> {
+ aidlPolicy = policy
+ }
+
+ SpecialClass.FeatureFlags -> {
+ featureFlagsPolicy = policy
+ }
+
+ SpecialClass.Sysprops -> {
+ syspropsPolicy = policy
+ }
+
+ SpecialClass.RFile -> {
+ rFilePolicy = policy
+ }
+ }
+ }
+
+ override fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
+ imf.setPolicyForField(className, fieldName, policy)
+ }
+
+ override fun onSimpleMethodPolicy(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ imf.setPolicyForMethod(className, methodName, methodDesc, policy)
+ }
+
+ override fun onMethodInClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ targetName: String,
+ policy: FilterPolicyWithReason,
+ ) {
+ imf.setPolicyForMethod(className, methodName, methodDesc, policy)
+
+ // Make sure to keep the target method.
+ imf.setPolicyForMethod(
+ className,
+ targetName,
+ methodDesc,
+ FilterPolicy.Keep.withReason(FILTER_REASON)
+ )
+ // Set up the rename.
+ imf.setRenameTo(className, targetName, methodDesc, methodName)
+ }
+
+ override fun onMethodOutClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ policy: FilterPolicyWithReason,
+ ) {
+ imf.setPolicyForMethod(className, methodName, methodDesc, policy)
+ methodReplaceSpec.add(replaceSpec)
+ }
+ }
+}
+
+/**
+ * Parses a filer policy text file.
+ */
+class TextFileFilterPolicyParser {
+ private lateinit var processor: PolicyFileProcessor
+ private var currentClassName: String? = null
+
+ private var aidlPolicy: FilterPolicyWithReason? = null
+ private var featureFlagsPolicy: FilterPolicyWithReason? = null
+ private var syspropsPolicy: FilterPolicyWithReason? = null
+ private var rFilePolicy: FilterPolicyWithReason? = null
+
+ /** Name of the file that's currently being processed. */
+ var filename: String = ""
+ private set
+
+ /** 1-based line number in the current file */
+ var lineNumber = -1
+ private set
+
+ /** Current line */
+ var currentLineText = ""
+ private set
+
+ /**
+ * Parse a given "policy" file.
+ */
+ fun parse(reader: Reader, inputName: String, processor: PolicyFileProcessor) {
+ filename = inputName
+
+ this.processor = processor
+ BufferedReader(reader).use { rd ->
+ lineNumber = 0
+ try {
+ while (true) {
+ var line = rd.readLine()
+ if (line == null) {
+ break
+ }
+ lineNumber++
+ currentLineText = line
+ line = normalizeTextLine(line) // Remove comment and trim.
+ if (line.isEmpty()) {
+ continue
+ }
+ parseLine(line)
+ }
+ finishLastClass()
+ } catch (e: ParseException) {
+ throw e.withSourceInfo(inputName, lineNumber)
+ }
+ }
+ }
+
+ private fun finishLastClass() {
+ currentClassName?.let { className ->
+ processor.onClassEnd(className)
+ currentClassName = null
+ }
+ }
+
+ private fun ensureInClass(directive: String): String {
+ return currentClassName ?:
+ throw ParseException("Directive '$directive' must follow a 'class' directive")
+ }
+
private fun parseLine(line: String) {
val fields = line.split(whitespaceRegex).toTypedArray()
when (fields[0].lowercase()) {
- "p", "package" -> parsePackage(fields)
- "c", "class" -> parseClass(fields)
- "f", "field" -> parseField(fields)
- "m", "method" -> parseMethod(fields)
- "r", "rename" -> parseRename(fields)
+ "p", "package" -> {
+ finishLastClass()
+ parsePackage(fields)
+ }
+ "c", "class" -> {
+ finishLastClass()
+ parseClass(fields)
+ }
+ "f", "field" -> {
+ ensureInClass("field")
+ parseField(fields)
+ }
+ "m", "method" -> {
+ ensureInClass("method")
+ parseMethod(fields)
+ }
+ "r", "rename" -> {
+ finishLastClass()
+ parseRename(fields)
+ }
else -> throw ParseException("Unknown directive \"${fields[0]}\"")
}
}
@@ -184,22 +413,24 @@
if (!policy.isUsableWithClasses) {
throw ParseException("Package can't have policy '$policy'")
}
- packageFilter.addPolicy(name, policy.withReason(FILTER_REASON))
+ processor.onPackage(name, policy.withReason(FILTER_REASON))
}
private fun parseClass(fields: Array<String>) {
- if (fields.size < 3) {
- throw ParseException("Class ('c') expects 2 fields.")
+ if (fields.size <= 1) {
+ throw ParseException("Class ('c') expects 1 or 2 fields.")
}
- currentClassName = fields[1]
+ val className = fields[1].toHumanReadableClassName()
// superClass is set when the class name starts with a "*".
- val superClass = resolveExtendingClass(currentClassName)
+ val superClass = resolveExtendingClass(className)
// :aidl, etc?
- val classType = resolveSpecialClass(currentClassName)
+ val classType = resolveSpecialClass(className)
- if (fields[2].startsWith("!")) {
+ val policyStr = if (fields.size > 2) { fields[2] } else { "" }
+
+ if (policyStr.startsWith("!")) {
if (classType != SpecialClass.NotSpecial) {
// We could support it, but not needed at least for now.
throw ParseException(
@@ -207,9 +438,12 @@
)
}
// It's a redirection class.
- val toClass = fields[2].substring(1)
- imf.setRedirectionClass(currentClassName, toClass)
- } else if (fields[2].startsWith("~")) {
+ val toClass = policyStr.substring(1)
+
+ currentClassName = className
+ processor.onClassStart(className)
+ processor.onRedirectionClass(className, toClass)
+ } else if (policyStr.startsWith("~")) {
if (classType != SpecialClass.NotSpecial) {
// We could support it, but not needed at least for now.
throw ParseException(
@@ -217,10 +451,24 @@
)
}
// It's a class-load hook
- val callback = fields[2].substring(1)
- imf.setClassLoadHook(currentClassName, callback)
+ val callback = policyStr.substring(1)
+
+ currentClassName = className
+ processor.onClassStart(className)
+ processor.onClassLoadHook(className, callback)
} else {
- val policy = parsePolicy(fields[2])
+ // Special case: if it's a class directive with no policy, then it encloses
+ // members, but we don't apply any policy to the class itself.
+ // This is only allowed in a not-special case.
+ if (policyStr == "") {
+ if (classType == SpecialClass.NotSpecial && superClass == null) {
+ currentClassName = className
+ return
+ }
+ throw ParseException("Special class or subclass directive must have a policy")
+ }
+
+ val policy = parsePolicy(policyStr)
if (!policy.isUsableWithClasses) {
throw ParseException("Class can't have policy '$policy'")
}
@@ -229,26 +477,27 @@
SpecialClass.NotSpecial -> {
// TODO: Duplicate check, etc
if (superClass == null) {
- imf.setPolicyForClass(
- currentClassName, policy.withReason(FILTER_REASON)
- )
+ currentClassName = className
+ processor.onClassStart(className)
+ processor.onSimpleClassPolicy(className, policy.withReason(FILTER_REASON))
} else {
- subclassFilter.addPolicy(
+ processor.onSubClassPolicy(
superClass,
- policy.withReason("extends $superClass")
+ policy.withReason("extends $superClass"),
)
}
}
-
SpecialClass.Aidl -> {
if (aidlPolicy != null) {
throw ParseException(
"Policy for AIDL classes already defined"
)
}
- aidlPolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class AIDL)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ aidlPolicy = p
}
SpecialClass.FeatureFlags -> {
@@ -257,9 +506,11 @@
"Policy for feature flags already defined"
)
}
- featureFlagsPolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class feature flags)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ featureFlagsPolicy = p
}
SpecialClass.Sysprops -> {
@@ -268,9 +519,11 @@
"Policy for sysprops already defined"
)
}
- syspropsPolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class sysprops)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ syspropsPolicy = p
}
SpecialClass.RFile -> {
@@ -279,9 +532,11 @@
"Policy for R file already defined"
)
}
- rFilePolicy = policy.withReason(
+ val p = policy.withReason(
"$FILTER_REASON (special-class R file)"
)
+ processor.onSpecialClassPolicy(classType, p)
+ rFilePolicy = p
}
}
}
@@ -296,17 +551,16 @@
if (!policy.isUsableWithFields) {
throw ParseException("Field can't have policy '$policy'")
}
- require(this::currentClassName.isInitialized)
// TODO: Duplicate check, etc
- imf.setPolicyForField(currentClassName, name, policy.withReason(FILTER_REASON))
+ processor.onField(currentClassName!!, name, policy.withReason(FILTER_REASON))
}
private fun parseMethod(fields: Array<String>) {
if (fields.size < 3 || fields.size > 4) {
throw ParseException("Method ('m') expects 3 or 4 fields.")
}
- val name = fields[1]
+ val methodName = fields[1]
val signature: String
val policyStr: String
if (fields.size <= 3) {
@@ -323,44 +577,48 @@
throw ParseException("Method can't have policy '$policy'")
}
- require(this::currentClassName.isInitialized)
+ val className = currentClassName!!
- imf.setPolicyForMethod(
- currentClassName, name, signature,
- policy.withReason(FILTER_REASON)
- )
- if (policy == FilterPolicy.Substitute) {
- val fromName = policyStr.substring(1)
+ val policyWithReason = policy.withReason(FILTER_REASON)
+ if (policy != FilterPolicy.Substitute) {
+ processor.onSimpleMethodPolicy(className, methodName, signature, policyWithReason)
+ } else {
+ val targetName = policyStr.substring(1)
- if (fromName == name) {
+ if (targetName == methodName) {
throw ParseException(
"Substitution must have a different name"
)
}
- // Set the policy for the "from" method.
- imf.setPolicyForMethod(
- currentClassName, fromName, signature,
- FilterPolicy.Keep.withReason(FILTER_REASON)
- )
-
- val classAndMethod = splitWithLastPeriod(fromName)
+ val classAndMethod = splitWithLastPeriod(targetName)
if (classAndMethod != null) {
// If the substitution target contains a ".", then
// it's a method call redirect.
- methodReplaceSpec.add(
- TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
- currentClassName.toJvmClassName(),
- name,
+ val spec = TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
+ currentClassName!!.toJvmClassName(),
+ methodName,
signature,
classAndMethod.first.toJvmClassName(),
classAndMethod.second,
)
+ processor.onMethodOutClassReplace(
+ className,
+ methodName,
+ signature,
+ spec,
+ policyWithReason,
)
} else {
// It's an in-class replace.
// ("@RavenwoodReplace" equivalent)
- imf.setRenameTo(currentClassName, fromName, signature, name)
+ processor.onMethodInClassReplace(
+ className,
+ methodName,
+ signature,
+ targetName,
+ policyWithReason,
+ )
}
}
}
@@ -378,7 +636,7 @@
// applied. (Which is needed for services.jar)
val prefix = fields[2].trimStart('/')
- typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
+ processor.onRename(
pattern, prefix
)
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
index b389a67..23699fd 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -34,10 +34,10 @@
SCRIPT_NAME="${0##*/}"
-GOLDEN_DIR=golden-output
+GOLDEN_DIR=${GOLDEN_DIR:-golden-output}
mkdir -p $GOLDEN_DIR
-DIFF_CMD=${DIFF:-diff -u --ignore-blank-lines --ignore-space-change}
+DIFF_CMD=${DIFF_CMD:-./tiny-framework-dump-test.py run-diff}
update=0
three_way=0
@@ -62,12 +62,10 @@
shift $(($OPTIND - 1))
# Build the dump files, which are the input of this test.
-run m dump-jar tiny-framework-dump-test
-
+run ${BUILD_CMD:-m} dump-jar tiny-framework-dump-test
# Get the path to the generate text files. (not the golden files.)
# We get them from $OUT/module-info.json
-
files=(
$(python3 -c '
import sys
@@ -77,7 +75,7 @@
with open(sys.argv[1], "r") as f:
data = json.load(f)
- # Equivalent to: jq -r '.["tiny-framework-dump-test"]["installed"][]'
+ # Equivalent to: jq -r '.["tiny-framework-dump-test"]["installed"][]'
for path in data["tiny-framework-dump-test"]["installed"]:
if "golden-output" in path:
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index 88fa492..7617482 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -28,8 +28,18 @@
# Run diff.
def run_diff(file1, file2):
- command = ['diff', '-u', '--ignore-blank-lines',
- '--ignore-space-change', file1, file2]
+ command = ['diff', '-u',
+ '--ignore-blank-lines',
+ '--ignore-space-change',
+
+ # Ignore the class file version.
+ '--ignore-matching-lines=^ *\(major\|minor\) version:$',
+
+ # We shouldn't need `--ignore-matching-lines`, but somehow
+ # the golden files were generated without these lines for b/388562869,
+ # so let's just ignore them.
+ '--ignore-matching-lines=^\(Constant.pool:\|{\)$',
+ file1, file2]
print(' '.join(command))
result = subprocess.run(command, stderr=sys.stdout)
@@ -82,4 +92,13 @@
if __name__ == "__main__":
+ args = sys.argv
+
+ # This script is used by diff-and-update-golden.sh too.
+ if len(args) > 1 and args[1] == "run-diff":
+ if run_diff(args[2], args[3]):
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
unittest.main(verbosity=2)
diff --git a/ravenwood/tools/ravenhelper/Android.bp b/ravenwood/tools/ravenhelper/Android.bp
new file mode 100644
index 0000000..a7ee468
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/Android.bp
@@ -0,0 +1,26 @@
+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"],
+}
+
+java_binary_host {
+ name: "ravenhelper",
+ main_class: "com.android.platform.test.ravenwood.ravenhelper.RavenHelperMain",
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "guava",
+ "hoststubgen-lib",
+ "junit",
+ "metalava-gradle-plugin-deps", // Get lint/PSI related classes from here.
+ "ow2-asm",
+ "ow2-asm-analysis",
+ "ow2-asm-commons",
+ "ow2-asm-tree",
+ "ow2-asm-util",
+ ],
+ visibility: ["//visibility:public"],
+}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt
new file mode 100644
index 0000000..e6efbf6
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/RavenHelperMain.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+@file:JvmName("RavenHelperMain")
+package com.android.platform.test.ravenwood.ravenhelper
+
+/*
+ * This file contains the main entry point for the "ravenhelper" command, which
+ * contains subcommands to help various tasks.
+ */
+
+import com.android.hoststubgen.GeneralUserErrorException
+import com.android.hoststubgen.LogLevel
+import com.android.hoststubgen.executableName
+import com.android.hoststubgen.log
+import com.android.hoststubgen.runMainWithBoilerplate
+import com.android.platform.test.ravenwood.ravenhelper.policytoannot.PtaProcessor
+
+interface SubcommandHandler {
+ fun handle(args: List<String>)
+}
+
+fun usage() {
+ System.out.println("""
+ Usage:
+ ravenhelper SUBCOMMAND options...
+
+ Subcommands:
+ pta: "policy-to-annotations" Convert policy file to annotations.
+ (See the pta-framework.sh script for usage.) 1
+
+ """.trimIndent())
+}
+
+fun main(args: Array<String>) {
+ executableName = "RavenHelper"
+ log.setConsoleLogLevel(LogLevel.Info)
+
+ runMainWithBoilerplate {
+ log.i("$executableName started")
+
+ if (args.size == 0) {
+ usage()
+ return
+ }
+
+ // Find the subcommand handler.
+ val subcommand = args[0]
+ val handler: SubcommandHandler = when (subcommand) {
+ "pta" -> PtaProcessor()
+ else -> {
+ usage()
+ throw GeneralUserErrorException("Unknown subcommand '$subcommand'")
+ }
+ }
+
+ // Run the subcommand.
+ handler.handle(args.copyOfRange(1, args.size).toList())
+ }
+}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt
new file mode 100644
index 0000000..4a11259
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.policytoannot
+
+import com.android.hoststubgen.filters.FilterPolicy
+
+
+/**
+ * This class knows about the Ravenwood annotations.
+ */
+class Annotations {
+ enum class Target {
+ Class,
+ Field,
+ Method,
+ }
+
+ fun get(policy: FilterPolicy, target: Target): String? {
+ return when (policy) {
+ FilterPolicy.Keep ->
+ if (target == Target.Class) {
+ "@android.ravenwood.annotation.RavenwoodKeepPartialClass"
+ } else {
+ "@android.ravenwood.annotation.RavenwoodKeep"
+ }
+ FilterPolicy.KeepClass ->
+ "@android.ravenwood.annotation.RavenwoodKeepWholeClass"
+ FilterPolicy.Substitute ->
+ "@android.ravenwood.annotation.RavenwoodReplace"
+ FilterPolicy.Redirect ->
+ "@android.ravenwood.annotation.RavenwoodRedirect"
+ FilterPolicy.Throw ->
+ "@android.ravenwood.annotation.RavenwoodThrow"
+ FilterPolicy.Ignore -> null // Ignore has no annotation. (because it's not very safe.)
+ FilterPolicy.Remove ->
+ "@android.ravenwood.annotation.RavenwoodRemove"
+ }
+ }
+
+ private fun withArg(annot: String, arg: String): String {
+ return "@$annot(\"$arg\")"
+ }
+
+ fun getClassLoadHookAnnotation(arg: String): String {
+ return withArg("android.ravenwood.annotation.RavenwoodClassLoadHook", arg)
+ }
+
+ fun getRedirectionClassAnnotation(arg: String): String {
+ return withArg("android.ravenwood.annotation.RavenwoodRedirectionClass", arg)
+ }
+}
+
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt
new file mode 100644
index 0000000..3531ba95
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Operations.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.policytoannot
+
+/*
+ * This file contains classes and functions about file edit operations, such as
+ * "insert a line", "delete a line".
+ */
+
+
+import com.android.hoststubgen.log
+import java.io.BufferedWriter
+import java.io.File
+
+enum class SourceOperationType {
+ /** Insert a line */
+ Insert,
+
+ /** delete a line */
+ Delete,
+
+ /** Insert a text at the beginning of a line */
+ Prepend,
+}
+
+data class SourceOperation(
+ /** Target file to edit. */
+ val sourceFile: String,
+
+ /** 1-based line number. Use -1 to add at the end of the file. */
+ val lineNumber: Int,
+
+ /** Operation type.*/
+ val type: SourceOperationType,
+
+ /** Operand -- text to insert or prepend. Ignored for delete. */
+ val text: String = "",
+
+ /** Human-readable description of why this operation was created */
+ val description: String,
+) {
+ override fun toString(): String {
+ return "SourceOperation(sourceFile='$sourceFile', " +
+ "lineNumber=$lineNumber, type=$type, text='$text' desc='$description')"
+ }
+}
+
+/**
+ * Stores list of [SourceOperation]s for each file.
+ */
+class SourceOperations {
+ var size: Int = 0
+ private set
+ private val fileOperations = mutableMapOf<String, MutableList<SourceOperation>>()
+
+ fun add(op: SourceOperation) {
+ log.forVerbose {
+ log.v("Adding operation: $op")
+ }
+ size++
+ fileOperations[op.sourceFile]?.let { ops ->
+ ops.add(op)
+ return
+ }
+ fileOperations[op.sourceFile] = mutableListOf(op)
+ }
+
+ /**
+ * Get the collected [SourceOperation]s for each file.
+ */
+ fun getOperations(): MutableMap<String, MutableList<SourceOperation>> {
+ return fileOperations
+ }
+}
+
+/**
+ * Create a shell script to apply all the operations (using sed).
+ */
+fun createShellScript(ops: SourceOperations, writer: BufferedWriter) {
+ // File header.
+ // Note ${'$'} is an ugly way to put a dollar sign ($) in a multi-line string.
+ writer.write(
+ """
+ #!/bin/bash
+
+ set -e # Finish when any command fails.
+
+ function apply() {
+ local file="${'$'}1"
+
+ # The script is given via stdin. Write it to file.
+ local sed="/tmp/pta-script.sed.tmp"
+ cat > "${'$'}sed"
+
+ echo "Running: sed -i -f \"${'$'}sed\" \"${'$'}file\""
+
+ if ! sed -i -f "${'$'}sed" "${'$'}file" ; then
+ echo 'Failed!' 1>&2
+ return 1
+ fi
+ }
+
+ """.trimIndent()
+ )
+
+ ops.getOperations().toSortedMap().forEach { (origFile, ops) ->
+ val file = File(origFile).absolutePath
+
+ writer.write("\n")
+
+ writer.write("#")
+ writer.write("=".repeat(78))
+ writer.write("\n")
+
+ writer.write("\n")
+
+ writer.write("apply \"$file\" <<'__END_OF_SCRIPT__'\n")
+ toSedScript(ops, writer)
+ writer.write("__END_OF_SCRIPT__\n")
+ }
+
+ writer.write("\n")
+
+ writer.write("echo \"All files updated successfully!\"\n")
+ writer.flush()
+}
+
+/**
+ * Create a sed script to apply a list of operations.
+ */
+private fun toSedScript(ops: List<SourceOperation>, writer: BufferedWriter) {
+ ops.sortedBy { it.lineNumber }.forEach { op ->
+ if (op.text.contains('\n')) {
+ throw RuntimeException("Operation $op may not contain newlines.")
+ }
+
+ // Convert each operation to a sed operation. Examples:
+ //
+ // - Insert "abc" to line 2
+ // 2i\
+ // abc
+ //
+ // - Insert "abc" to the end of the file
+ // $a\
+ // abc
+ //
+ // - Delete line 2
+ // 2d
+ //
+ // - Prepend abc to line 2
+ // 2s/^/abc/
+ //
+ // The line numbers are all the line numbers in the original file. Even though
+ // the script itself will change them because of inserts and deletes, we don't need to
+ // change the line numbers in the script.
+
+ // Write the target line number.
+ writer.write("\n")
+ writer.write("# ${op.description}\n")
+ if (op.lineNumber >= 0) {
+ writer.write(op.lineNumber.toString())
+ } else {
+ writer.write("$")
+ }
+
+ when (op.type) {
+ SourceOperationType.Insert -> {
+ if (op.lineNumber >= 0) {
+ writer.write("i\\\n") // "Insert"
+ } else {
+ // If it's the end of the file, we need to use "a" (append)
+ writer.write("a\\\n")
+ }
+ writer.write(op.text)
+ writer.write("\n")
+ }
+ SourceOperationType.Delete -> {
+ writer.write("d\n")
+ }
+ SourceOperationType.Prepend -> {
+ if (op.text.contains('/')) {
+ TODO("Operation $op contains character(s) that needs to be escaped.")
+ }
+ writer.write("s/^/${op.text}/\n")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt
new file mode 100644
index 0000000..08bd95f
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaOptions.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.policytoannot
+
+import com.android.hoststubgen.ArgIterator
+import com.android.hoststubgen.ArgumentsException
+import com.android.hoststubgen.SetOnce
+import com.android.hoststubgen.ensureFileExists
+import com.android.hoststubgen.log
+
+/**
+ * Options for the "ravenhelper pta" subcommand.
+ */
+class PtaOptions(
+ /** Text policy files */
+ var policyOverrideFiles: MutableList<String> = mutableListOf(),
+
+ /** Annotation allowed list file. */
+ var annotationAllowedClassesFile: SetOnce<String?> = SetOnce(null),
+
+ /** Source files or directories. */
+ var sourceFilesOrDirectories: MutableList<String> = mutableListOf(),
+
+ /** Output script file. */
+ var outputScriptFile: SetOnce<String?> = SetOnce(null),
+
+ /** Dump the operations (for debugging) */
+ var dumpOperations: SetOnce<Boolean> = SetOnce(false),
+) {
+ companion object {
+ fun parseArgs(args: List<String>): PtaOptions {
+ val ret = PtaOptions()
+ val ai = ArgIterator.withAtFiles(args.toTypedArray())
+
+ while (true) {
+ val arg = ai.nextArgOptional() ?: break
+
+ fun nextArg(): String = ai.nextArgRequired(arg)
+
+ if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+ continue
+ }
+ try {
+ when (arg) {
+ // TODO: Write help
+ "-h", "--help" -> TODO("Help is not implemented yet")
+
+ "-p", "--policy-override-file" ->
+ ret.policyOverrideFiles.add(nextArg().ensureFileExists())
+
+ "-a", "--annotation-allowed-classes-file" ->
+ ret.annotationAllowedClassesFile.set(nextArg().ensureFileExists())
+
+ "-s", "--src" ->
+ ret.sourceFilesOrDirectories.add(nextArg().ensureFileExists())
+
+ "--dump" ->
+ ret.dumpOperations.set(true)
+
+ "-o", "--output-script" ->
+ ret.outputScriptFile.set(nextArg())
+
+ else -> throw ArgumentsException("Unknown option: $arg")
+ }
+ } catch (e: SetOnce.SetMoreThanOnceException) {
+ throw ArgumentsException("Duplicate or conflicting argument found: $arg")
+ }
+ }
+
+ if (ret.policyOverrideFiles.size == 0) {
+ throw ArgumentsException("Must specify at least one policy file")
+ }
+
+ if (ret.sourceFilesOrDirectories.size == 0) {
+ throw ArgumentsException("Must specify at least one source path")
+ }
+
+ return ret
+ }
+ }
+
+ override fun toString(): String {
+ return """
+ PtaOptions{
+ policyOverrideFiles=$policyOverrideFiles
+ annotationAllowedClassesFile=$annotationAllowedClassesFile
+ sourceFilesOrDirectories=$sourceFilesOrDirectories
+ outputScriptFile=$outputScriptFile
+ dumpOperations=$dumpOperations
+ }
+ """.trimIndent()
+ }
+}
\ No newline at end of file
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
new file mode 100644
index 0000000..5984e4f
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/PtaProcessor.kt
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.policytoannot
+
+import com.android.hoststubgen.LogLevel
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.toJvmClassName
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.PolicyFileProcessor
+import com.android.hoststubgen.filters.SpecialClass
+import com.android.hoststubgen.filters.TextFileFilterPolicyParser
+import com.android.hoststubgen.filters.TextFilePolicyMethodReplaceFilter
+import com.android.hoststubgen.log
+import com.android.hoststubgen.utils.ClassFilter
+import com.android.platform.test.ravenwood.ravenhelper.SubcommandHandler
+import com.android.platform.test.ravenwood.ravenhelper.psi.createUastEnvironment
+import com.android.platform.test.ravenwood.ravenhelper.sourcemap.AllClassInfo
+import com.android.platform.test.ravenwood.ravenhelper.sourcemap.ClassInfo
+import com.android.platform.test.ravenwood.ravenhelper.sourcemap.MethodInfo
+import com.android.platform.test.ravenwood.ravenhelper.sourcemap.SourceLoader
+import java.io.BufferedWriter
+import java.io.FileOutputStream
+import java.io.FileReader
+import java.io.OutputStreamWriter
+import java.util.regex.Pattern
+
+/**
+ * This is the main routine of the "pta" -- policy-to-annotation -- subcommands.
+ */
+class PtaProcessor : SubcommandHandler {
+ override fun handle(args: List<String>) {
+ val options = PtaOptions.parseArgs(args)
+
+ log.v("Options: $options")
+
+ val converter = TextPolicyToAnnotationConverter(
+ options.policyOverrideFiles,
+ options.sourceFilesOrDirectories,
+ options.annotationAllowedClassesFile.get,
+ Annotations(),
+ options.dumpOperations.get || log.isEnabled(LogLevel.Debug),
+ )
+ converter.process()
+
+ val ops = converter.resultOperations
+
+ if (ops.size == 0) {
+ log.i("No files need to be updated.")
+ return
+ }
+
+ val scriptWriter = BufferedWriter(OutputStreamWriter(
+ options.outputScriptFile.get?.let { file ->
+ FileOutputStream(file)
+ } ?: System.out
+ ))
+
+ scriptWriter.use { writer ->
+ options.outputScriptFile.get?.let {
+ log.i("Creating script file at $it ...")
+ }
+ createShellScript(ops, writer)
+ }
+ }
+}
+
+/**
+ * This class implements the actual logic.
+ */
+private class TextPolicyToAnnotationConverter(
+ val policyFiles: List<String>,
+ val sourceFilesOrDirectories: List<String>,
+ val annotationAllowedClassesFile: String?,
+ val annotations: Annotations,
+ val dumpOperations: Boolean,
+) {
+ private val annotationAllowedClasses: ClassFilter = annotationAllowedClassesFile.let { file ->
+ if (file == null) {
+ ClassFilter.newNullFilter(true) // Allow all classes
+ } else {
+ ClassFilter.loadFromFile(file, false)
+ }
+ }
+
+ val resultOperations = SourceOperations()
+ private val classes = AllClassInfo()
+ private val policyParser = TextFileFilterPolicyParser()
+ private val annotationNeedingClasses = mutableSetOf<String>()
+
+ /**
+ * Entry point.
+ */
+ fun process() {
+ // First, load
+ val env = createUastEnvironment()
+ try {
+ loadSources()
+
+ processPolicies()
+
+ addToAnnotationsAllowedListFile()
+
+ if (dumpOperations) {
+ log.withIndent {
+ resultOperations.getOperations().toSortedMap().forEach { (file, ops) ->
+ log.i("ops: $file")
+ ops.forEach { op ->
+ log.i(" line: ${op.lineNumber}: ${op.type}: \"${op.text}\" " +
+ "(${op.description})")
+ }
+ }
+ }
+ }
+ } finally {
+ env.dispose()
+ }
+ }
+
+ /**
+ * Load all the java source files into [classes].
+ */
+ private fun loadSources() {
+ val env = createUastEnvironment()
+ try {
+ val loader = SourceLoader(env)
+ loader.load(sourceFilesOrDirectories, classes)
+ } finally {
+ env.dispose()
+ }
+ }
+
+ private fun addToAnnotationsAllowedListFile() {
+ log.i("Generating operations to update annotation allowlist file...")
+ log.withIndent {
+ annotationNeedingClasses.sorted().forEach { className ->
+ if (!annotationAllowedClasses.matches(className.toJvmClassName())) {
+ resultOperations.add(
+ SourceOperation(
+ annotationAllowedClassesFile!!,
+ -1, // add to the end
+ SourceOperationType.Insert,
+ className,
+ "add to annotation allowlist"
+ ))
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the policy files with [Processor].
+ */
+ private fun processPolicies() {
+ log.i("Loading the policy files and generating operations...")
+ log.withIndent {
+ policyFiles.forEach { policyFile ->
+ log.i("Parsing $policyFile ...")
+ log.withIndent {
+ policyParser.parse(FileReader(policyFile), policyFile, Processor())
+ }
+ }
+ }
+ }
+
+ private inner class Processor : PolicyFileProcessor {
+
+ var classPolicyText = ""
+ var classPolicyLine = -1
+
+ // Whether the current class has a skip marker, in which case we ignore all members.
+ // Applicable only within a "simple class"
+ var classSkipping = false
+
+ var classLineConverted = false
+ var classHasMember = false
+
+ private fun currentLineHasSkipMarker(): Boolean {
+ val ret = policyParser.currentLineText.contains("no-pta")
+
+ if (ret) {
+ log.forVerbose {
+ log.v("Current line has a skip marker: ${policyParser.currentLineText}")
+ }
+ }
+
+ return ret
+ }
+
+ private fun shouldSkipCurrentLine(): Boolean {
+ // If a line contains a special marker "no-pta", we'll skip it.
+ return classSkipping || currentLineHasSkipMarker()
+ }
+
+ /** Print a warning about an unsupported policy directive. */
+ private fun warnOnPolicy(message: String, policyLine: String, lineNumber: Int) {
+ log.w("Warning: $message")
+ log.w(" policy: \"$policyLine\"")
+ log.w(" at ${policyParser.filename}:$lineNumber")
+ }
+
+ /** Print a warning about an unsupported policy directive. */
+ private fun warnOnCurrentPolicy(message: String) {
+ warnOnPolicy(message, policyParser.currentLineText, policyParser.lineNumber)
+ }
+
+ /** Print a warning about an unsupported policy directive on the class line. */
+ private fun warnOnClassPolicy(message: String) {
+ warnOnPolicy(message, classPolicyText, classPolicyLine)
+ }
+
+ override fun onPackage(name: String, policy: FilterPolicyWithReason) {
+ warnOnCurrentPolicy("'package' directive isn't supported (yet).")
+ }
+
+ override fun onRename(pattern: Pattern, prefix: String) {
+ // Rename will never be supported, so don't show a warning.
+ }
+
+ private fun addOperation(op: SourceOperation) {
+ resultOperations.add(op)
+ }
+
+ private fun commentOutPolicy(lineNumber: Int, description: String) {
+ addOperation(
+ SourceOperation(
+ policyParser.filename,
+ lineNumber,
+ SourceOperationType.Prepend,
+ "#[PTA]: ", // comment out.
+ description,
+ )
+ )
+ }
+
+ override fun onClassStart(className: String) {
+ classSkipping = currentLineHasSkipMarker()
+ classLineConverted = false
+ classHasMember = false
+ classPolicyLine = policyParser.lineNumber
+ classPolicyText = policyParser.currentLineText
+ }
+
+ override fun onClassEnd(className: String) {
+ if (classSkipping) {
+ classSkipping = false
+ return
+ }
+ if (!classLineConverted) {
+ // Class line is still needed in the policy file.
+ // (Because the source file wasn't found.)
+ return
+ }
+ if (!classHasMember) {
+ commentOutPolicy(classPolicyLine, "remove class policy on $className")
+ } else {
+ warnOnClassPolicy(
+ "Class policy on $className can't be removed because it still has members.")
+ }
+ }
+
+ private fun findClass(className: String): ClassInfo? {
+ val ci = classes.findClass(className)
+ if (ci == null) {
+ warnOnCurrentPolicy("Class not found: $className")
+ }
+ return ci
+ }
+
+ private fun addClassAnnotation(
+ className: String,
+ annotation: String,
+ ): Boolean {
+ val ci = findClass(className) ?: return false
+
+ // Add the annotation to the source file.
+ addOperation(
+ SourceOperation(
+ ci.location.file,
+ ci.location.line,
+ SourceOperationType.Insert,
+ ci.location.getIndent() + annotation,
+ "add class annotation to $className"
+ )
+ )
+ annotationNeedingClasses.add(className)
+ return true
+ }
+
+ override fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason) {
+ if (shouldSkipCurrentLine()) {
+ return
+ }
+ log.v("Found simple class policy: $className - ${policy.policy}")
+
+ val annot = annotations.get(policy.policy, Annotations.Target.Class)!!
+ if (addClassAnnotation(className, annot)) {
+ classLineConverted = true
+ }
+ }
+
+ override fun onSubClassPolicy(superClassName: String, policy: FilterPolicyWithReason) {
+ warnOnCurrentPolicy("Subclass policies isn't supported (yet).")
+ }
+
+ override fun onRedirectionClass(fromClassName: String, toClassName: String) {
+ if (shouldSkipCurrentLine()) {
+ return
+ }
+
+ log.v("Found class redirection: $fromClassName - $toClassName")
+
+ if (addClassAnnotation(
+ fromClassName,
+ annotations.getRedirectionClassAnnotation(toClassName),
+ )) {
+ commentOutPolicy(policyParser.lineNumber,
+ "remove class redirection policy on $fromClassName")
+ }
+ }
+
+ override fun onClassLoadHook(className: String, callback: String) {
+ if (shouldSkipCurrentLine()) {
+ return
+ }
+
+ log.v("Found class load hook: $className - $callback")
+
+ if (addClassAnnotation(
+ className,
+ annotations.getClassLoadHookAnnotation(callback),
+ )) {
+ commentOutPolicy(policyParser.lineNumber,
+ "remove class load hook policy on $className")
+ }
+ }
+
+ override fun onSpecialClassPolicy(type: SpecialClass, policy: FilterPolicyWithReason) {
+ // This can't be converted to an annotation, so don't show a warning.
+ }
+
+ override fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
+ if (shouldSkipCurrentLine()) {
+ return
+ }
+
+ log.v("Found field policy: $className.$fieldName - ${policy.policy}")
+
+ val ci = findClass(className) ?: return
+
+ ci.findField(fieldName)?.let { fi ->
+ val annot = annotations.get(policy.policy, Annotations.Target.Field)!!
+
+ addOperation(
+ SourceOperation(
+ fi.location.file,
+ fi.location.line,
+ SourceOperationType.Insert,
+ fi.location.getIndent() + annot,
+ "add annotation to field $className.$fieldName",
+ )
+ )
+ commentOutPolicy(policyParser.lineNumber,
+ "remove field policy $className.$fieldName")
+
+ annotationNeedingClasses.add(className)
+ } ?: {
+ warnOnCurrentPolicy("Field not found: $className.$fieldName")
+ }
+ }
+
+ override fun onSimpleMethodPolicy(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ policy: FilterPolicyWithReason
+ ) {
+ if (shouldSkipCurrentLine()) {
+ return
+ }
+ val readableName = "$className.$methodName$methodDesc"
+ log.v("Found simple method policy: $readableName - ${policy.policy}")
+
+
+ // Inner method to get the matching methods for this policy.
+ //
+ // If this policy can't be converted for any reason, it'll return null.
+ // Otherwise, it'll return a pair of method list and the annotation string.
+ fun getMethods(): Pair<List<MethodInfo>, String>? {
+ if (methodName == CLASS_INITIALIZER_NAME) {
+ warnOnClassPolicy("Policy for class initializers not supported.")
+ return null
+ }
+ val ci = findClass(className) ?: return null
+ val methods = ci.findMethods(methodName, methodDesc)
+ if (methods == null) {
+ warnOnCurrentPolicy("Method not found: $readableName")
+ return null
+ }
+
+ // If the policy is "ignore", we can't convert it to an annotation, in which case
+ // annotations.get() will return null.
+ val annot = annotations.get(policy.policy, Annotations.Target.Method)
+ if (annot == null) {
+ warnOnCurrentPolicy("Annotation for policy '${policy.policy}' isn't available")
+ return null
+ }
+ return Pair(methods, annot)
+ }
+
+ val methodsAndAnnot = getMethods()
+
+ if (methodsAndAnnot == null) {
+ classHasMember = true
+ return // This policy can't converted.
+ }
+ val methods = methodsAndAnnot.first
+ val annot = methodsAndAnnot.second
+
+ var found = false
+ methods.forEach { mi ->
+ found = true
+ addOperation(
+ SourceOperation(
+ mi.location.file,
+ mi.location.line,
+ SourceOperationType.Insert,
+ mi.location.getIndent() + annot,
+ "add annotation to method $readableName",
+ )
+ )
+ }
+ if (found) {
+ commentOutPolicy(
+ policyParser.lineNumber,
+ "remove method policy $readableName"
+ )
+
+ annotationNeedingClasses.add(className)
+ } else {
+ warnOnCurrentPolicy("Method not found: $readableName")
+ }
+ }
+
+ override fun onMethodInClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ targetName: String,
+ policy: FilterPolicyWithReason
+ ) {
+ warnOnCurrentPolicy("Found method replace but it's not supported yet: "
+ + "$className.$methodName$methodDesc - $targetName")
+ }
+
+ override fun onMethodOutClassReplace(
+ className: String,
+ methodName: String,
+ methodDesc: String,
+ replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
+ policy: FilterPolicyWithReason
+ ) {
+ // This can't be converted to an annotation.
+ classHasMember = true
+ }
+ }
+}
\ No newline at end of file
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/psi/PsiUtil.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/psi/PsiUtil.kt
new file mode 100644
index 0000000..6775135
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/psi/PsiUtil.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.psi
+
+import com.android.tools.lint.UastEnvironment
+
+// PSI is a library to parse Java/Kotlin source files, which is part of JetBrains' IntelliJ/
+// Android Studio, and other IDEs.
+//
+// PSI is normally used by IntelliJ's plugins, and as such, there isn't really a good documentation
+// on how to use it from a standalone program. However, fortunately, Android Studio's Lint
+// and Metalava both use PSI. Metalava reuses some of the APIs exposed by Lint. We also use the
+// same Lint APIs used by Metalava here.
+//
+// Some code pointers around the relevant projects:
+//
+// - We stole code from Metalava, but the recent version of Metalava is too complicated,
+// and hard to understand. Older Metalava, such as this one:
+// https://android.git.corp.google.com/platform/tools/metalava/+/refs/heads/android13-dev
+// is easier to understand.
+//
+// - PSI is source code is available in IntelliJ's code base:
+// https://github.com/JetBrains/intellij-community.git
+//
+// - Lint is in Android studio.
+// https://android.googlesource.com/platform/tools/base/+/studio-master-dev/source.md
+
+
+/**
+ * Create [UastEnvironment] enough to parse Java source files.
+ */
+fun createUastEnvironment(): UastEnvironment {
+ val config = UastEnvironment.Configuration.create(
+ enableKotlinScripting = false,
+ useFirUast = false,
+ )
+
+ config.javaLanguageLevel = com.intellij.pom.java.LanguageLevel.JDK_21
+
+ // The following code exists in Metalava, but we don't seem to need it.
+ // We may need to when we need to support kotlin.
+// config.kotlinLanguageLevel = kotlinLanguageLevel
+// config.addSourceRoots(listOf(File(root)))
+// config.addClasspathRoots(classpath.map { it.absoluteFile })
+// options.jdkHome?.let {
+// if (options.isJdkModular(it)) {
+// config.kotlinCompilerConfig.put(JVMConfigurationKeys.JDK_HOME, it)
+// config.kotlinCompilerConfig.put(JVMConfigurationKeys.NO_JDK, false)
+// }
+// }
+
+ return UastEnvironment.create(config)
+}
diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/SourceMapGenerator.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/SourceMapGenerator.kt
new file mode 100644
index 0000000..58e4497
--- /dev/null
+++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/sourcemap/SourceMapGenerator.kt
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2025 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.platform.test.ravenwood.ravenhelper.sourcemap
+
+/*
+ * This file contains classes used to parse Java source files to build "source map" which
+ * basically tells you what classes/methods/fields are declared in what line of what file.
+ */
+
+import com.android.hoststubgen.GeneralUserErrorException
+import com.android.hoststubgen.log
+import com.android.tools.lint.UastEnvironment
+import com.intellij.openapi.editor.Document
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassOwner
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiManager
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiNameIdentifierOwner
+import com.intellij.psi.SyntheticElement
+import java.io.File
+
+
+/**
+ * Represents the location of an item. (class, field or method)
+ */
+data class Location (
+ /** Full path filename. */
+ val file: String,
+
+ /** 1-based line number */
+ val line: Int,
+
+ /** Indent of the line */
+ val indent: Int,
+) {
+
+ fun getIndent(): String {
+ return " ".repeat(indent)
+ }
+
+ fun dump() {
+ log.i("Location: $file:$line (indent: $indent)")
+ }
+}
+
+/**
+ * Represents the type of item.
+ */
+enum class ItemType {
+ Class,
+ Field,
+ Method,
+}
+
+/** Holds a field's location. */
+data class FieldInfo (
+ val name: String,
+ val location: Location,
+) {
+ fun dump() {
+ log.i("Field: $name")
+ log.withIndent {
+ location.dump()
+ }
+ }
+}
+
+/** Holds a method's location. */
+data class MethodInfo (
+ val name: String,
+ /** "Simplified" description. */
+ val simpleDesc: String,
+ val location: Location,
+) {
+ fun dump() {
+ log.i("Method: $name$simpleDesc")
+ log.withIndent {
+ location.dump()
+ }
+ }
+}
+
+/** Holds a class's location and members. */
+data class ClassInfo (
+ val fullName: String,
+ val location: Location,
+ val fields: MutableMap<String, FieldInfo> = mutableMapOf(),
+ val methods: MutableMap<String, MutableList<MethodInfo>> = mutableMapOf(),
+) {
+ fun add(fi: FieldInfo) {
+ fields.put(fi.name, fi)
+ }
+
+ fun add(mi: MethodInfo) {
+ val list = methods.get(mi.name)
+ if (list != null) {
+ list.add(mi)
+ } else {
+ methods.put(mi.name, mutableListOf(mi))
+ }
+ }
+
+ fun dump() {
+ log.i("Class: $fullName")
+ log.withIndent {
+ location.dump()
+
+ // Sort and print fields and methods.
+ methods.toSortedMap().forEach { entry ->
+ entry.value.sortedBy { method -> method.simpleDesc }.forEach {
+ it.dump()
+ }
+ }
+ }
+ }
+
+ /** Find a field by name */
+ fun findField(fieldName: String): FieldInfo? {
+ return fields[fieldName]
+ }
+
+ /**
+ * Find a field by name and descriptor.
+ *
+ * If [descriptor] is "*", then all methods with the name will be returned.
+ */
+ fun findMethods(methodName: String, methodDesc: String): List<MethodInfo>? {
+ val list = methods[methodName] ?: return null
+
+ // Wildcard method policy.
+ if (methodDesc == "*") {
+ return list
+ }
+
+ val simpleDesc = simplifyMethodDesc(methodDesc)
+ list.forEach { mi ->
+ if (simpleDesc == mi.simpleDesc) {
+ return listOf(mi)
+ }
+ }
+ log.w("Method $fullName.$methodName found, but none match description '$methodDesc'")
+ return null
+ }
+}
+
+/**
+ * Stores all classes
+ */
+data class AllClassInfo (
+ val classes: MutableMap<String, ClassInfo> = mutableMapOf(),
+) {
+ fun add(ci: ClassInfo) {
+ classes.put(ci.fullName, ci)
+ }
+
+ fun dump() {
+ classes.toSortedMap { a, b -> a.compareTo(b) }.forEach {
+ it.value.dump()
+ }
+ }
+
+ fun findClass(name: String): ClassInfo? {
+ return classes.get(name)
+ }
+}
+
+fun typeToSimpleDesc(origType: String): String {
+ var type = origType
+
+ // Detect arrays.
+ var arrayPrefix = ""
+ while (type.endsWith("[]")) {
+ arrayPrefix += "["
+ type = type.substring(0, type.length - 2)
+ }
+
+ // Delete generic parameters. (delete everything after '<')
+ type.indexOf('<').let { pos ->
+ if (pos >= 0) {
+ type = type.substring(0, pos)
+ }
+ }
+
+ // Handle builtins.
+ val builtinType = when (type) {
+ "byte" -> "B"
+ "short" -> "S"
+ "int" -> "I"
+ "long" -> "J"
+ "float" -> "F"
+ "double" -> "D"
+ "boolean" -> "Z"
+ "char" -> "C"
+ "void" -> "V"
+ else -> null
+ }
+
+ builtinType?.let {
+ return arrayPrefix + builtinType
+ }
+
+ return arrayPrefix + "L" + type + ";"
+}
+
+/**
+ * Get a "simple" description of a method.
+ *
+ * "Simple" descriptions are similar to "real" ones, except:
+ * - No return type.
+ * - No package names in type names.
+ */
+fun getSimpleDesc(method: PsiMethod): String {
+ val sb = StringBuilder()
+
+ sb.append("(")
+
+ val params = method.parameterList
+ for (i in 0..<params.parametersCount) {
+ val param = params.getParameter(i)
+
+ val type = param?.type?.presentableText
+
+ if (type == null) {
+ throw RuntimeException(
+ "Unable to decode parameter list from method from ${params.parent}")
+ }
+
+ sb.append(typeToSimpleDesc(type))
+ }
+
+ sb.append(")")
+
+ return sb.toString()
+}
+
+private val reTypeFinder = "L.*/".toRegex()
+
+private fun simplifyMethodDesc(origMethodDesc: String): String {
+ // We don't need the return type, so remove everything after the ')'.
+ val pos = origMethodDesc.indexOf(')')
+ var desc = if (pos < 0) { origMethodDesc } else { origMethodDesc.substring(0, pos + 1) }
+
+ // Then we remove the package names from all the class names.
+ // i.e. convert "Ljava/lang/String" to "LString".
+
+ return desc.replace(reTypeFinder, "L")
+}
+
+/**
+ * Class that reads and parses java source files using PSI and populate [AllClassInfo].
+ */
+class SourceLoader(
+ val environment: UastEnvironment,
+) {
+ private val fileSystem = StandardFileSystems.local()
+ private val manager = PsiManager.getInstance(environment.ideaProject)
+
+ /** Classes that were parsed */
+ private var numParsedClasses = 0
+
+ /**
+ * Main entry point.
+ */
+ fun load(filesOrDirectories: List<String>, classes: AllClassInfo) {
+ val psiFiles = mutableListOf<PsiFile>()
+ log.i("Loading source files...")
+ log.iTime("Discovering source files") {
+ load(filesOrDirectories.map { File(it) }, psiFiles)
+ }
+
+ log.i("${psiFiles.size} file(s) found.")
+
+ if (psiFiles.size == 0) {
+ throw GeneralUserErrorException("No source files found.")
+ }
+
+ log.iTime("Parsing source files") {
+ log.withIndent {
+ for (file in psiFiles.asSequence().distinct()) {
+ val classesInFile = (file as? PsiClassOwner)?.classes?.toList()
+ classesInFile?.forEach { clazz ->
+ loadClass(clazz)?.let { classes.add(it) }
+
+ clazz.innerClasses.forEach { inner ->
+ loadClass(inner)?.let { classes.add(it) }
+ }
+ }
+ }
+ }
+ }
+ log.i("$numParsedClasses class(es) found.")
+ }
+
+ private fun load(filesOrDirectories: List<File>, result: MutableList<PsiFile>) {
+ filesOrDirectories.forEach {
+ load(it, result)
+ }
+ }
+
+ private fun load(file: File, result: MutableList<PsiFile>) {
+ if (file.isDirectory) {
+ file.listFiles()?.forEach { child ->
+ load(child, result)
+ }
+ return
+ }
+
+ // It's a file
+ when (file.extension) {
+ "java" -> {
+ // Load it.
+ }
+ "kt" -> {
+ log.w("Kotlin not supported, not loading ${file.path}")
+ return
+ }
+ else -> return // Silently skip
+ }
+ fileSystem.findFileByPath(file.path)?.let { virtualFile ->
+ manager.findFile(virtualFile)?.let { psiFile ->
+ result.add(psiFile)
+ }
+ }
+ }
+
+ private fun loadClass(clazz: PsiClass): ClassInfo? {
+ if (clazz is SyntheticElement) {
+ return null
+ }
+ log.forVerbose {
+ log.v("Class found: ${clazz.qualifiedName}")
+ }
+ numParsedClasses++
+
+ log.withIndent {
+ val ci = ClassInfo(
+ clazz.qualifiedName!!,
+ getLocation(clazz) ?: return null,
+ )
+
+ // Load fields.
+ clazz.fields.filter { it !is SyntheticElement }.forEach {
+ val name = it.name
+ log.forDebug { log.d("Field found: $name") }
+ val loc = getLocation(it) ?: return@forEach
+ ci.add(FieldInfo(name, loc))
+ }
+
+ // Load methods.
+ clazz.methods.filter { it !is SyntheticElement }.forEach {
+ val name = resolveMethodName(it)
+ val simpleDesc = getSimpleDesc(it)
+ log.forDebug { log.d("Method found: $name$simpleDesc") }
+ val loc = getLocation(it) ?: return@forEach
+ ci.add(MethodInfo(name, simpleDesc, loc))
+ }
+ return ci
+ }
+ }
+
+ private fun resolveMethodName(method: PsiMethod): String {
+ val clazz = method.containingClass!!
+ if (clazz.name == method.name) {
+ return "<init>" // It's a constructor.
+ }
+ return method.name
+ }
+
+ private fun getLocation(elem: PsiElement): Location? {
+ val lineAndIndent = getLineNumberAndIndent(elem)
+ if (lineAndIndent == null) {
+ log.w("Unable to determine location of $elem")
+ return null
+ }
+ return Location(
+ elem.containingFile.originalFile.virtualFile.path,
+ lineAndIndent.first,
+ lineAndIndent.second,
+ )
+ }
+
+ private fun getLineNumberAndIndent(element: PsiElement): Pair<Int, Int>? {
+ val psiFile: PsiFile = element.containingFile ?: return null
+ val document: Document = psiFile.viewProvider.document ?: return null
+
+ // Actual elements such as PsiClass, PsiMethod and PsiField contains the leading
+ // javadoc, etc, so use the "identifier"'s element, if available.
+ // For synthesized elements, this may return null.
+ val targetRange = (
+ (element as PsiNameIdentifierOwner).nameIdentifier?.textRange ?: element.textRange
+ ) ?: return null
+ val lineNumber = document.getLineNumber(targetRange.startOffset)
+ val lineStartOffset = document.getLineStartOffset(lineNumber)
+
+ val lineLeadingText = document.getText(
+ com.intellij.openapi.util.TextRange(lineStartOffset, targetRange.startOffset))
+
+ val indent = lineLeadingText.takeWhile { it.isWhitespace() }.length
+
+ // Line numbers are 0-based, add 1 for human-readable format
+ return Pair(lineNumber + 1, indent)
+ }
+}
\ No newline at end of file
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4478232..717a2d4 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -37,6 +37,7 @@
":framework_native_aidl",
":gsiservice_aidl",
":installd_aidl",
+ ":mmd_aidl",
":storaged_aidl",
":vold_aidl",
],
@@ -242,6 +243,7 @@
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
"locksettings_flags_lib",
+ "mmd_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 6858e29..ef769cf 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -9,6 +9,7 @@
# Zram writeback
per-file ZramWriteback.java = [email protected], [email protected]
+per-file ZramMaintenance.java = [email protected]
# ServiceWatcher
per-file ServiceWatcher.java = [email protected]
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b4cdfb1..19676eb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -41,6 +41,7 @@
import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
import static android.os.storage.OnObbStateChangeListener.MOUNTED;
import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
+import static android.mmd.flags.Flags.mmdEnabled;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -944,12 +945,17 @@
});
refreshZramSettings();
- // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
- String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
- if (!zramPropValue.equals("0")
- && mContext.getResources().getBoolean(
+ if (mmdEnabled()) {
+ // TODO: b/375432472 - Start zram maintenance only when zram is enabled.
+ ZramMaintenance.startZramMaintenance(mContext);
+ } else {
+ // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
+ String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+ if (!zramPropValue.equals("0")
+ && mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
- ZramWriteback.scheduleZramWriteback(mContext);
+ ZramWriteback.scheduleZramWriteback(mContext);
+ }
}
configureTranscoding();
@@ -976,7 +982,7 @@
// sole writer.
SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
// Schedule writeback only if zram is being enabled.
- if (desiredPropertyValue.equals("1")
+ if (!mmdEnabled() && desiredPropertyValue.equals("1")
&& mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
ZramWriteback.scheduleZramWriteback(mContext);
diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java
new file mode 100644
index 0000000..cdb4812
--- /dev/null
+++ b/services/core/java/com/android/server/ZramMaintenance.java
@@ -0,0 +1,118 @@
+/*
+ * 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.server;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IMmd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.time.Duration;
+
+/**
+ * Schedules zram maintenance (e.g. zram writeback, zram recompression).
+ *
+ * <p>ZramMaintenance notifies mmd the good timing to execute zram maintenance based on:
+ *
+ * <ul>
+ * <li>Enough interval has passed.
+ * <li>The system is idle.
+ * <li>The battery is not low.
+ * </ul>
+ */
+public class ZramMaintenance extends JobService {
+ private static final String TAG = ZramMaintenance.class.getName();
+ // Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number
+ // as the job id.
+ private static final int JOB_ID = 375432472;
+ private static final ComponentName sZramMaintenance =
+ new ComponentName("android", ZramMaintenance.class.getName());
+
+ private static final String FIRST_DELAY_SECONDS_PROP =
+ "mm.zram.maintenance.first_delay_seconds";
+ // The default is 1 hour.
+ private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600;
+ private static final String PERIODIC_DELAY_SECONDS_PROP =
+ "mm.zram.maintenance.periodic_delay_seconds";
+ // The default is 1 hour.
+ private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600;
+ private static final String REQUIRE_DEVICE_IDLE_PROP =
+ "mm.zram.maintenance.require_device_idle";
+ private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE =
+ true;
+ private static final String REQUIRE_BATTERY_NOT_LOW_PROP =
+ "mm.zram.maintenance.require_battry_not_low";
+ private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW =
+ true;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ IBinder binder = ServiceManager.getService("mmd");
+ if (binder != null) {
+ IMmd mmd = IMmd.Stub.asInterface(binder);
+ try {
+ mmd.doZramMaintenance();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to doZramMaintenance", e);
+ }
+ } else {
+ Slog.w(TAG, "binder not found");
+ }
+ Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
+ DEFAULT_PERIODIC_DELAY_SECONDS));
+ scheduleZramMaintenance(this, delay);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ /**
+ * Starts periodical zram maintenance.
+ */
+ public static void startZramMaintenance(Context context) {
+ Duration delay = Duration.ofSeconds(
+ SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay);
+ }
+
+ private static void scheduleZramMaintenance(Context context, Duration delay) {
+ JobScheduler js = context.getSystemService(JobScheduler.class);
+
+ if (js != null) {
+ js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance)
+ .setMinimumLatency(delay.toMillis())
+ .setRequiresDeviceIdle(
+ SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP,
+ DEFAULT_REQUIRE_DEVICE_IDLE))
+ .setRequiresBatteryNotLow(
+ SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
+ DEFAULT_REQUIRE_BATTERY_NOT_LOW))
+ .build());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f5e1545..8de1295 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12796,6 +12796,28 @@
}
}
+ final long kernelCmaUsage = Debug.getKernelCmaUsageKb();
+ if (kernelCmaUsage >= 0) {
+ pw.print(" Kernel CMA: ");
+ pw.println(stringifyKBSize(kernelCmaUsage));
+ // CMA memory can be in one of the following four states:
+ //
+ // 1. Free, in which case it is accounted for as part of MemFree, which
+ // is already considered in the lostRAM calculation below.
+ //
+ // 2. Allocated as part of a userspace allocated, in which case it is
+ // already accounted for in the total PSS value that was computed.
+ //
+ // 3. Allocated for storing compressed memory (ZRAM) on Android kernels.
+ // This is accounted for by calculating the amount of memory ZRAM
+ // consumes and including it in the lostRAM calculuation.
+ //
+ // 4. Allocated by a kernel driver, in which case, it is currently not
+ // attributed to any term that has been derived thus far. Since the
+ // allocations come from a kernel driver, add it to kernelUsed.
+ kernelUsed += kernelCmaUsage;
+ }
+
// Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of
// memInfo.getCachedSizeKb().
final long lostRAM = memInfo.getTotalSizeKb()
@@ -13313,12 +13335,32 @@
proto.write(MemInfoDumpProto.CACHED_KERNEL_KB, memInfo.getCachedSizeKb());
proto.write(MemInfoDumpProto.FREE_KB, memInfo.getFreeSizeKb());
}
+ // CMA memory can be in one of the following four states:
+ //
+ // 1. Free, in which case it is accounted for as part of MemFree, which
+ // is already considered in the lostRAM calculation below.
+ //
+ // 2. Allocated as part of a userspace allocated, in which case it is
+ // already accounted for in the total PSS value that was computed.
+ //
+ // 3. Allocated for storing compressed memory (ZRAM) on Android Kernels.
+ // This is accounted for by calculating hte amount of memory ZRAM
+ // consumes and including it in the lostRAM calculation.
+ //
+ // 4. Allocated by a kernel driver, in which case, it is currently not
+ // attributed to any term that has been derived thus far, so subtract
+ // it from lostRAM.
+ long kernelCmaUsage = Debug.getKernelCmaUsageKb();
+ if (kernelCmaUsage < 0) {
+ kernelCmaUsage = 0;
+ }
long lostRAM = memInfo.getTotalSizeKb()
- (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS])
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
// NR_SHMEM is subtracted twice (getCachedSizeKb() and getKernelUsedSizeKb())
+ memInfo.getShmemSizeKb()
- - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
+ - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb()
+ - kernelCmaUsage;
proto.write(MemInfoDumpProto.USED_PSS_KB, ss[INDEX_TOTAL_PSS] - cachedPss);
proto.write(MemInfoDumpProto.USED_KERNEL_KB, memInfo.getKernelUsedSizeKb());
proto.write(MemInfoDumpProto.LOST_RAM_KB, lostRAM);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d82d77d..2fb58f0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2566,7 +2566,6 @@
Map<String, Ops> packages = uidState.pkgOps;
Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
- boolean uidChanged = false;
while (it.hasNext()) {
Map.Entry<String, Ops> ent = it.next();
String packageName = ent.getKey();
@@ -2599,7 +2598,6 @@
newMode,
UserHandle.getUserId(curOp.uid));
changed = true;
- uidChanged = true;
final int uid = curOp.uidState.uid;
callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
previousMode, mOpModeWatchers.get(curOp.op));
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
index daa0211..02df986 100644
--- a/services/core/java/com/android/server/crashrecovery/OWNERS
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -1,3 +1,2 @@
[email protected]
[email protected]
[email protected]
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 88a2697..27ad555 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -239,6 +239,15 @@
// requires latitude since longitudinal distances change with distance from equator.
private static double metersToDegreesLongitude(double distance, double lat) {
- return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(Math.toRadians(lat));
+ // Needed to convert from longitude distance to longitude degree.
+ // X meters near the poles is more degrees than at the equator.
+ double cosLat = Math.cos(Math.toRadians(lat));
+ // If we are right on top of the pole, the degree is always 0.
+ // We return a very small value instead to avoid divide by zero errors
+ // later on.
+ if (cosLat == 0.0) {
+ return 0.0001;
+ }
+ return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / cosLat;
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 286238e..a0e5433 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -137,6 +137,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.pm.RoSystemFeatures;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -438,9 +439,9 @@
}
LockscreenCredential credential =
LockscreenCredential.createUnifiedProfilePassword(newPassword);
- Arrays.fill(newPasswordChars, '\u0000');
- Arrays.fill(newPassword, (byte) 0);
- Arrays.fill(randomLockSeed, (byte) 0);
+ LockPatternUtils.zeroize(newPasswordChars);
+ LockPatternUtils.zeroize(newPassword);
+ LockPatternUtils.zeroize(randomLockSeed);
return credential;
}
@@ -1325,7 +1326,7 @@
mContext.enforceCallingOrSelfPermission(
Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN,
"Requires MANAGE_WEAK_ESCROW_TOKEN permission.");
- if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ if (!RoSystemFeatures.hasFeatureAutomotive(mContext)) {
throw new IllegalArgumentException(
"Weak escrow token are only for automotive devices.");
}
@@ -1537,7 +1538,7 @@
+ userId);
}
} finally {
- Arrays.fill(password, (byte) 0);
+ LockPatternUtils.zeroize(password);
}
}
@@ -1570,7 +1571,7 @@
decryptionResult = cipher.doFinal(encryptedPassword);
LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword(
decryptionResult);
- Arrays.fill(decryptionResult, (byte) 0);
+ LockPatternUtils.zeroize(decryptionResult);
try {
long parentSid = getGateKeeperService().getSecureUserId(
mUserManager.getProfileParent(userId).id);
@@ -2263,7 +2264,7 @@
} catch (RemoteException e) {
Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
} finally {
- Arrays.fill(secret, (byte) 0);
+ LockPatternUtils.zeroize(secret);
}
}
@@ -3613,7 +3614,7 @@
}
// Escrow tokens are enabled on automotive builds.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ if (RoSystemFeatures.hasFeatureAutomotive(mContext)) {
return;
}
diff --git a/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
index 21caf76..3d64f18 100644
--- a/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
@@ -26,6 +26,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import java.security.GeneralSecurityException;
@@ -154,7 +155,7 @@
}
LockscreenCredential result =
LockscreenCredential.createUnifiedProfilePassword(credential);
- Arrays.fill(credential, (byte) 0);
+ LockPatternUtils.zeroize(credential);
return result;
}
}
@@ -175,7 +176,7 @@
Slog.d(TAG, "Cannot delete key", e);
}
if (mEncryptedPasswords.contains(userId)) {
- Arrays.fill(mEncryptedPasswords.get(userId), (byte) 0);
+ LockPatternUtils.zeroize(mEncryptedPasswords.get(userId));
mEncryptedPasswords.remove(userId);
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index bf1b3c3..85dc811 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -162,7 +162,7 @@
Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
} finally {
if (mCredential != null) {
- Arrays.fill(mCredential, (byte) 0); // no longer needed.
+ LockPatternUtils.zeroize(mCredential); // no longer needed.
}
}
}
@@ -506,7 +506,7 @@
try {
byte[] hash = MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes);
- Arrays.fill(bytes, (byte) 0);
+ LockPatternUtils.zeroize(bytes);
return hash;
} catch (NoSuchAlgorithmException e) {
// Impossible, SHA-256 must be supported on Android.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 54303c0..7d8300a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -1082,7 +1082,7 @@
int keyguardCredentialsType = lockPatternUtilsToKeyguardType(savedCredentialType);
try (LockscreenCredential credential =
createLockscreenCredential(keyguardCredentialsType, decryptedCredentials)) {
- Arrays.fill(decryptedCredentials, (byte) 0);
+ LockPatternUtils.zeroize(decryptedCredentials);
decryptedCredentials = null;
VerifyCredentialResponse verifyResponse =
lockSettingsService.verifyCredential(credential, userId, 0);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
index 0e66746..f1ef333 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -19,8 +19,9 @@
import android.annotation.Nullable;
import android.util.SparseArray;
+import com.android.internal.widget.LockPatternUtils;
+
import java.util.ArrayList;
-import java.util.Arrays;
import javax.security.auth.Destroyable;
@@ -187,8 +188,8 @@
*/
@Override
public void destroy() {
- Arrays.fill(mLskfHash, (byte) 0);
- Arrays.fill(mKeyClaimant, (byte) 0);
+ LockPatternUtils.zeroize(mLskfHash);
+ LockPatternUtils.zeroize(mKeyClaimant);
}
}
}
diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING
index 43e2afd..dbf9915 100644
--- a/services/core/java/com/android/server/media/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/TEST_MAPPING
@@ -1,7 +1,10 @@
{
"presubmit": [
{
- "name": "CtsMediaBetterTogetherTestCases"
+ "name": "CtsMediaRouterTestCases"
+ },
+ {
+ "name": "CtsMediaSessionTestCases"
},
{
"name": "MediaRouterServiceTests"
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aa59258..2a03dcb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4240,8 +4240,6 @@
CarrierAppUtils.disableCarrierAppsUntilPrivileged(
mContext.getOpPackageName(), UserHandle.USER_SYSTEM, mContext);
- disableSkuSpecificApps();
-
// Read the compatibilty setting when the system is ready.
boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
mContext.getContentResolver(),
@@ -4374,29 +4372,6 @@
}
}
- //TODO: b/111402650
- private void disableSkuSpecificApps() {
- String[] apkList = mContext.getResources().getStringArray(
- R.array.config_disableApksUnlessMatchedSku_apk_list);
- String[] skuArray = mContext.getResources().getStringArray(
- R.array.config_disableApkUnlessMatchedSku_skus_list);
- if (ArrayUtils.isEmpty(apkList)) {
- return;
- }
- String sku = SystemProperties.get("ro.boot.hardware.sku");
- if (!TextUtils.isEmpty(sku) && ArrayUtils.contains(skuArray, sku)) {
- return;
- }
- final Computer snapshot = snapshotComputer();
- for (String packageName : apkList) {
- setSystemAppHiddenUntilInstalled(snapshot, packageName, true);
- final List<UserInfo> users = mInjector.getUserManagerInternal().getUsers(false);
- for (int i = 0; i < users.size(); i++) {
- setSystemAppInstallState(snapshot, packageName, false, users.get(i).id);
- }
- }
- }
-
public PackageFreezer freezePackage(String packageName, int userId, String killReason,
int exitInfoReason, InstallRequest request) {
return freezePackage(packageName, userId, killReason, exitInfoReason, request,
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index 2088e41..3831352 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -142,11 +142,8 @@
private final RateLimiter mRateLimiter;
AggregatedMobileDataStatsPuller(@NonNull NetworkStatsManager networkStatsManager) {
- if (DEBUG) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
- TAG + "-AggregatedMobileDataStatsPullerInit");
- }
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-Init");
}
mRateLimiter = new RateLimiter(/* window= */ Duration.ofSeconds(1));
@@ -173,10 +170,16 @@
public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
long unusedUptime) {
- mMobileDataStatsHandler.post(
+ if (mRateLimiter.tryAcquire()) {
+ mMobileDataStatsHandler.post(
() -> {
noteUidProcessStateImpl(uid, state);
});
+ } else {
+ synchronized (mLock) {
+ mUidPreviousState.put(uid, state);
+ }
+ }
}
public int pullDataBytesTransfer(List<StatsEvent> data) {
@@ -209,29 +212,27 @@
}
private void noteUidProcessStateImpl(int uid, int state) {
- if (mRateLimiter.tryAcquire()) {
- // noteUidProcessStateImpl can be called back to back several times while
- // the updateNetworkStats loops over several stats for multiple uids
- // and during the first call in a batch of proc state change event it can
- // contain info for uid with unknown previous state yet which can happen due to a few
- // reasons:
- // - app was just started
- // - app was started before the ActivityManagerService
- // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
- if (mNetworkStatsManager != null) {
- updateNetworkStats(mNetworkStatsManager);
- } else {
- Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
- }
+ // noteUidProcessStateImpl can be called back to back several times while
+ // the updateNetworkStats loops over several stats for multiple uids
+ // and during the first call in a batch of proc state change event it can
+ // contain info for uid with unknown previous state yet which can happen due to a few
+ // reasons:
+ // - app was just started
+ // - app was started before the ActivityManagerService
+ // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ } else {
+ Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
}
- mUidPreviousState.put(uid, state);
+ synchronized (mLock) {
+ mUidPreviousState.put(uid, state);
+ }
}
private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
- if (DEBUG) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
- }
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
}
final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
@@ -256,20 +257,25 @@
}
private void updateNetworkStatsDelta(NetworkStats delta) {
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStatsDelta");
+ }
synchronized (mLock) {
for (NetworkStats.Entry entry : delta) {
- if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
- continue;
- }
- MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
- if (stats != null) {
- stats.addTxBytes(entry.getTxBytes());
- stats.addRxBytes(entry.getRxBytes());
- stats.addTxPackets(entry.getTxPackets());
- stats.addRxPackets(entry.getRxPackets());
+ if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) {
+ MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+ if (stats != null) {
+ stats.addTxBytes(entry.getTxBytes());
+ stats.addRxBytes(entry.getRxBytes());
+ stats.addTxPackets(entry.getTxPackets());
+ stats.addRxPackets(entry.getRxPackets());
+ }
}
}
}
+ if (DEBUG) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
}
@GuardedBy("mLock")
@@ -298,18 +304,12 @@
}
private static boolean isEmpty(NetworkStats stats) {
- long totalRxPackets = 0;
- long totalTxPackets = 0;
for (NetworkStats.Entry entry : stats) {
- if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
- continue;
+ if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) {
+ // at least one non empty entry located
+ return false;
}
- totalRxPackets += entry.getRxPackets();
- totalTxPackets += entry.getTxPackets();
- // at least one non empty entry located
- break;
}
- final long totalPackets = totalRxPackets + totalTxPackets;
- return totalPackets == 0;
+ return true;
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b35a0a7..4b55f27 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -4853,7 +4853,7 @@
Slog.e(TAG, "Disconnected from keystore service. Cannot pull.", e);
return StatsManager.PULL_SKIP;
} catch (ServiceSpecificException e) {
- Slog.e(TAG, "pulling keystore metrics failed", e);
+ Slog.e(TAG, "Pulling keystore atom with tag " + atomTag + " failed", e);
return StatsManager.PULL_SKIP;
} finally {
Binder.restoreCallingIdentity(callingToken);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 9a3ad2d..cebf1be 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1655,6 +1655,12 @@
activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
true /* processPausingActivities */, null /* configuration */);
+ if (rootTask.getParent() == null) {
+ // The activities in the task may already be finishing. Then the task could be removed
+ // when performing the idle check.
+ return;
+ }
+
// Reparent all the tasks to the bottom of the display
final DisplayContent toDisplay =
mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 1c8c245..67f4e56 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -354,10 +354,8 @@
bitmap.recycle();
final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
- try {
- FileOutputStream fos = new FileOutputStream(file);
+ try (FileOutputStream fos = new FileOutputStream(file)) {
swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
- fos.close();
} catch (IOException e) {
Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
return false;
@@ -375,10 +373,8 @@
swBitmap.recycle();
final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId);
- try {
- FileOutputStream lowResFos = new FileOutputStream(lowResFile);
+ try (FileOutputStream lowResFos = new FileOutputStream(lowResFile)) {
lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos);
- lowResFos.close();
} catch (IOException e) {
Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
return false;
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index d89dc0b..91cd949 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -245,18 +245,20 @@
private static String fileToString(File file) {
final String newline = System.lineSeparator();
+ BufferedReader reader = null;
try {
- BufferedReader reader = new BufferedReader(new FileReader(file));
+ reader = new BufferedReader(new FileReader(file));
StringBuffer sb = new StringBuffer((int) file.length() * 2);
String line;
while ((line = reader.readLine()) != null) {
sb.append(line + newline);
}
- reader.close();
return sb.toString();
} catch (IOException ioe) {
Slog.e(TAG, "Couldn't read file " + file.getName());
return null;
+ } finally {
+ IoUtils.closeQuietly(reader);
}
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a8e6f68..dae481a 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1999,9 +1999,9 @@
// Create new lib file without signature info
incfs::NewFileParams libFileParams = {
.size = entry.uncompressed_length,
- .signature = {},
// Metadata of the new lib file is its relative path
.metadata = {targetLibPath.c_str(), (IncFsSize)targetLibPath.size()},
+ .signature = {},
};
incfs::FileId libFileId = idFromMetadata(targetLibPath);
if (auto res = mIncFs->makeFile(ifs->control, targetLibPathAbsolute, 0755, libFileId,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c80d6db..7972d9c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -428,7 +428,7 @@
"/apex/com.android.uwb/javalib/service-uwb.jar";
private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";
private static final String BLUETOOTH_APEX_SERVICE_JAR_PATH =
- "/apex/com.android.btservices/javalib/service-bluetooth.jar";
+ "/apex/com.android.bt/javalib/service-bluetooth.jar";
private static final String BLUETOOTH_SERVICE_CLASS =
"com.android.server.bluetooth.BluetoothService";
private static final String DEVICE_LOCK_SERVICE_CLASS =
@@ -3038,13 +3038,8 @@
&& context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH_LE))) {
t.traceBegin("RangingService");
- // TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next.
- try {
- mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS,
- RANGING_APEX_SERVICE_JAR_PATH);
- } catch (Throwable e) {
- Slog.d(TAG, "service-ranging.jar not found, not starting RangingService");
- }
+ mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS,
+ RANGING_APEX_SERVICE_JAR_PATH);
t.traceEnd();
}
}
diff --git a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
index ce63fe8..02b6391 100644
--- a/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/client/Android.bp
@@ -45,7 +45,6 @@
],
optimize: {
- proguard_compatibility: true,
proguard_flags_files: ["proguard.flags"],
},
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index 5db02e3..c11b6bb 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -284,8 +284,11 @@
// Stop the monitor
mIpSecPacketLossDetector.close();
+ mIpSecPacketLossDetector.close();
verifyStopped();
- verify(mIpSecTransform).close();
+
+ verify(mIpSecTransform, never()).close();
+ verify(mContext).unregisterReceiver(any());
}
@Test
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index ea660b0..22d364e 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -263,7 +263,7 @@
.returns(Boolean::class.java)
.addParameter(CONTEXT_CLASS, "context")
.addParameter(String::class.java, "featureName")
- .addStatement("return context.getPackageManager().hasSystemFeature(featureName, 0)")
+ .addStatement("return context.getPackageManager().hasSystemFeature(featureName)")
.build()
)
}
diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
index ee97b26..730dacb 100644
--- a/tools/systemfeatures/tests/golden/RoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
@@ -70,7 +70,7 @@
}
private static boolean hasFeatureFallback(Context context, String featureName) {
- return context.getPackageManager().hasSystemFeature(featureName, 0);
+ return context.getPackageManager().hasSystemFeature(featureName);
}
/**
diff --git a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
index 40c7db7..fe268c7 100644
--- a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
@@ -25,7 +25,7 @@
}
private static boolean hasFeatureFallback(Context context, String featureName) {
- return context.getPackageManager().hasSystemFeature(featureName, 0);
+ return context.getPackageManager().hasSystemFeature(featureName);
}
/**
diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
index 7bf8961..bcf978d 100644
--- a/tools/systemfeatures/tests/golden/RwFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
@@ -55,7 +55,7 @@
}
private static boolean hasFeatureFallback(Context context, String featureName) {
- return context.getPackageManager().hasSystemFeature(featureName, 0);
+ return context.getPackageManager().hasSystemFeature(featureName);
}
/**
diff --git a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
index eb7ec63..7bad5a2 100644
--- a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
@@ -14,7 +14,7 @@
*/
public final class RwNoFeatures {
private static boolean hasFeatureFallback(Context context, String featureName) {
- return context.getPackageManager().hasSystemFeature(featureName, 0);
+ return context.getPackageManager().hasSystemFeature(featureName);
}
/**
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
index ed3f5c9..491b55e 100644
--- a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
@@ -76,28 +76,28 @@
// Also ensure we fall back to the PackageManager for feature APIs without an accompanying
// versioned feature definition.
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
assertThat(RwFeatures.hasFeatureWatch(mContext)).isTrue();
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
assertThat(RwFeatures.hasFeatureWatch(mContext)).isFalse();
}
@Test
public void testReadonlyDisabledWithDefinedFeatures() {
// Always fall back to the PackageManager for defined, explicit features queries.
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
assertThat(RwFeatures.hasFeatureWatch(mContext)).isTrue();
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
assertThat(RwFeatures.hasFeatureWatch(mContext)).isFalse();
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI, 0)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
assertThat(RwFeatures.hasFeatureWifi(mContext)).isTrue();
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN, 0)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN)).thenReturn(false);
assertThat(RwFeatures.hasFeatureVulkan(mContext)).isFalse();
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO)).thenReturn(false);
assertThat(RwFeatures.hasFeatureAuto(mContext)).isFalse();
// For defined and undefined features, conditional queries should report null (unknown).
@@ -139,9 +139,9 @@
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 100)).isFalse();
// VERSION=
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO)).thenReturn(false);
assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO)).thenReturn(true);
assertThat(RoFeatures.hasFeatureAuto(mContext)).isTrue();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isNull();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
@@ -149,9 +149,9 @@
// For feature APIs without an associated feature definition, conditional queries should
// report null, and explicit queries should report runtime-defined versions.
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_PC, 0)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_PC)).thenReturn(true);
assertThat(RoFeatures.hasFeaturePc(mContext)).isTrue();
- when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_PC, 0)).thenReturn(false);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_PC)).thenReturn(false);
assertThat(RoFeatures.hasFeaturePc(mContext)).isFalse();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_PC, -1)).isNull();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_PC, 0)).isNull();