From da3a0ac4e219b7a924af3a94cf4cfc4901f0f059 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 22 May 2025 15:57:05 +0500 Subject: [PATCH] Fixed: Add explicit `serialVersionUID` to `Serializable` classes like `ReportInfo` and `TextIOInfo` Reading `ReportInfo` with `Bundle.getSerializable()` by `ReportActivity` is triggering exception when default algorithm is used for `serialVersionUID` in Termux:API plugin app when error notification created in `ResultReturner.returnData()` by `TermuxPluginUtils.sendPluginCommandErrorNotification()` is clicked. ``` java.lang.RuntimeException: Unable to start activity ComponentInfo{com.termux/com.termux.shared.activities.ReportActivity}: android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object (name = com.termux.shared.models.ReportInfo) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4280) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4467) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:222) at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:133) at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:103) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:80) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2823) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loopOnce(Looper.java:248) at android.os.Looper.loop(Looper.java:338) at android.app.ActivityThread.main(ActivityThread.java:9067) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932) Caused by: android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object (name = com.termux.shared.models.ReportInfo) at android.os.Parcel.readSerializableInternal(Parcel.java:5520) at android.os.Parcel.readValue(Parcel.java:5038) at android.os.Parcel.readValue(Parcel.java:4702) at android.os.Parcel.-$$Nest$mreadValue(Unknown Source:0) at android.os.Parcel$LazyValue.apply(Parcel.java:4811) at android.os.Parcel$LazyValue.apply(Parcel.java:4764) at android.os.BaseBundle.unwrapLazyValueFromMapLocked(BaseBundle.java:446) at android.os.BaseBundle.getValueAt(BaseBundle.java:426) at android.os.BaseBundle.getValue(BaseBundle.java:397) at android.os.BaseBundle.getValue(BaseBundle.java:380) at android.os.BaseBundle.getValue(BaseBundle.java:373) at android.os.BaseBundle.getSerializable(BaseBundle.java:1522) at android.os.Bundle.getSerializable(Bundle.java:1339) at com.termux.shared.activities.ReportActivity.updateUI(ReportActivity.java:140) at com.termux.shared.activities.ReportActivity.onCreate(ReportActivity.java:93) at android.app.Activity.performCreate(Activity.java:9155) at android.app.Activity.performCreate(Activity.java:9133) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1521) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4262) ... 13 more Caused by: java.io.InvalidClassException: com.termux.shared.models.ReportInfo; local class incompatible: stream classdesc serialVersionUID = -5165426368218339031, local class serialVersionUID = 1 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:652) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1743) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1624) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1902) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1442) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:430) at android.os.Parcel.readSerializableInternal(Parcel.java:5507) ... 31 more ``` If using release APK with obfuscation enabled, then following exception will be triggered. ``` java.lang.RuntimeException: Unable to start activity ComponentInfo{com.termux/com.termux.shared.activities.ReportActivity}: android.os.BadParcelableException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = I0.a) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3864) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4006) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:111) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2462) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:240) at android.os.Looper.loop(Looper.java:351) at android.app.ActivityThread.main(ActivityThread.java:8377) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1013) Caused by: android.os.BadParcelableException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = I0.a) at android.os.Parcel.readSerializableInternal(Parcel.java:5113) at android.os.Parcel.readValue(Parcel.java:4655) at android.os.Parcel.readValue(Parcel.java:4363) at android.os.Parcel.-$$Nest$mreadValue(Unknown Source:0) at android.os.Parcel$LazyValue.apply(Parcel.java:4461) at android.os.Parcel$LazyValue.apply(Parcel.java:4420) at android.os.BaseBundle.getValueAt(BaseBundle.java:394) at android.os.BaseBundle.getValue(BaseBundle.java:374) at android.os.BaseBundle.getValue(BaseBundle.java:357) at android.os.BaseBundle.getValue(BaseBundle.java:350) at android.os.BaseBundle.getSerializable(BaseBundle.java:1451) at android.os.Bundle.getSerializable(Bundle.java:1144) at com.termux.shared.activities.ReportActivity.updateUI(ReportActivity.java:136) at com.termux.shared.activities.ReportActivity.onCreate(ReportActivity.java:89) at android.app.Activity.performCreate(Activity.java:8397) at android.app.Activity.performCreate(Activity.java:8370) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1403) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3837) ... 12 more Caused by: java.lang.ClassNotFoundException: I0.a at java.lang.Class.classForName(Native Method) at java.lang.Class.forName(Class.java:536) at android.os.Parcel$2.resolveClass(Parcel.java:5090) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1733) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1624) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1902) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1442) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:430) at android.os.Parcel.readSerializableInternal(Parcel.java:5096) ... 29 more Caused by: java.lang.ClassNotFoundException: I0.a ... 38 more ``` Related issue https://github.com/termux/termux-api/issues/762 --- .../com/termux/shared/models/ReportInfo.java | 21 +++++++++++++++++++ .../com/termux/shared/models/TextIOInfo.java | 20 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java b/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java index 0f70fb56..3e0ad686 100644 --- a/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java +++ b/termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java @@ -1,5 +1,7 @@ package com.termux.shared.models; +import androidx.annotation.Keep; + import com.termux.shared.markdown.MarkdownUtils; import com.termux.shared.android.AndroidUtils; @@ -10,6 +12,25 @@ import java.io.Serializable; */ public class ReportInfo implements Serializable { + /** + * Explicitly define `serialVersionUID` to prevent exceptions on deserialization. + * + * Like when calling `Bundle.getSerializable()` on Android. + * `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = ) + * `java.io.InvalidClassException: ; local class incompatible` + * + * The `@Keep` annotation is necessary to prevent the field from being removed by proguard when + * app is compiled, even if its kept during library compilation. + * + * **See Also:** + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678 + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100 + */ + @Keep + private static final long serialVersionUID = 1L; + + + /** The user action that was being processed for which the report was generated. */ public final String userAction; /** The internal app component that sent the report. */ diff --git a/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java b/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java index df9993af..b688c398 100644 --- a/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java +++ b/termux-shared/src/main/java/com/termux/shared/models/TextIOInfo.java @@ -3,6 +3,7 @@ package com.termux.shared.models; import android.graphics.Color; import android.graphics.Typeface; +import androidx.annotation.Keep; import androidx.annotation.NonNull; import com.termux.shared.activities.TextIOActivity; @@ -19,6 +20,25 @@ import java.io.Serializable; */ public class TextIOInfo implements Serializable { + /** + * Explicitly define `serialVersionUID` to prevent exceptions on deserialization. + * + * Like when calling `Bundle.getSerializable()` on Android. + * `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = ) + * `java.io.InvalidClassException: ; local class incompatible` + * + * The `@Keep` annotation is necessary to prevent the field from being removed by proguard when + * app is compiled, even if its kept during library compilation. + * + * **See Also:** + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678 + * - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100 + */ + @Keep + private static final long serialVersionUID = 1L; + + + public static final int GENERAL_DATA_SIZE_LIMIT_IN_BYTES = 1000; public static final int LABEL_SIZE_LIMIT_IN_BYTES = 4000; public static final int TEXT_SIZE_LIMIT_IN_BYTES = 100000 - GENERAL_DATA_SIZE_LIMIT_IN_BYTES - LABEL_SIZE_LIMIT_IN_BYTES; // < 100KB