Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash the debug app when accessing a non-SDK API #13354

Open
wants to merge 3 commits into
base: trunk
Choose a base branch
from

Conversation

hichamboushaba
Copy link
Member

@hichamboushaba hichamboushaba commented Jan 20, 2025

Description

As discussed here p91TBi-cHS-p2#comment-13866, this PR adds a specific violation listener that crashes the app when accessing a non-SDK API.

Steps to reproduce

  1. Apply this patch
Index: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt	(revision 7e4de2adeb58c708177098b25f7d5097df87f2f1)
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt	(date 1737391199303)
@@ -289,6 +289,10 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         installSplashScreen()
 
+        val function = Activity::class.java.getDeclaredMethod("setDisablePreviewScreenshots", Boolean::class.java)
+        function.isAccessible = true
+        function.invoke(this, true)
+
         super.onCreate(savedInstanceState)
 
         ChromeCustomTabUtils.registerForPartialTabUsage(this)
  1. Use a device with Android 12 (API 31). (I couldn't find an API to use with newer devices)
  2. Launch the app on debug.
  3. Confirm the app crashes and logs the following:
    Non-SDK API violation detected: Landroid/app/Activity;->setDisablePreviewScreenshots(Z)V

Testing information

  • Confirm the app crashes with this PR changes.

The tests that have been performed

^

  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

  • The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
  • Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
  • Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on big (tablet) and small (phone) in case of UI changes, and no regressions are added.

@hichamboushaba hichamboushaba added the type: task An internally driven task. label Jan 20, 2025
@hichamboushaba hichamboushaba marked this pull request as ready for review January 20, 2025 16:47
@hichamboushaba hichamboushaba added this to the 21.5 milestone Jan 20, 2025
@dangermattic
Copy link
Collaborator

dangermattic commented Jan 20, 2025

1 Error
🚫 This PR is tagged with status: do not merge label(s).
1 Warning
⚠️ Class NonSdkApiViolationHandler is missing tests, but unit-tests-exemption label was set to ignore this.

Generated by 🚫 Danger

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Jan 20, 2025

📲 You can test the changes from this Pull Request in WooCommerce-Wear Android by scanning the QR code below to install the corresponding build.
App Name WooCommerce-Wear Android
Platform⌚️ Wear OS
FlavorJalapeno
Build TypeDebug
Commit004c2ea
Direct Downloadwoocommerce-wear-prototype-build-pr13354-004c2ea.apk

@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Jan 20, 2025

📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App Name WooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Commit004c2ea
Direct Downloadwoocommerce-prototype-build-pr13354-004c2ea.apk

Comment on lines 16 to 18
System.err.println("Non-SDK API violation detected: ${v.message}")
Process.killProcess(Process.myPid())
exitProcess(10)
Copy link
Member Author

@hichamboushaba hichamboushaba Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is similar to the default that Android uses when we enable penaltyDeath().

@codecov-commenter
Copy link

codecov-commenter commented Jan 20, 2025

Codecov Report

Attention: Patch coverage is 0% with 8 lines in your changes missing coverage. Please review.

Project coverage is 41.13%. Comparing base (7e4de2a) to head (004c2ea).
Report is 35 commits behind head on trunk.

Files with missing lines Patch % Lines
...m/woocommerce/android/NonSdkApiViolationHandler.kt 0.00% 7 Missing ⚠️
...kotlin/com/woocommerce/android/WooCommerceDebug.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              trunk   #13354      +/-   ##
============================================
- Coverage     41.14%   41.13%   -0.01%     
  Complexity     6431     6431              
============================================
  Files          1322     1323       +1     
  Lines         77211    77219       +8     
  Branches      10658    10658              
============================================
  Hits          31765    31765              
- Misses        42621    42629       +8     
  Partials       2825     2825              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@kidinov kidinov self-assigned this Jan 21, 2025
@Suppress("MagicNumber")
class NonSdkApiViolationHandler : OnVmViolationListener {
override fun onVmViolation(v: Violation) {
if (v !is NonSdkApiUsedViolation) return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to crash on all violations in debugging or are there some that we don't want too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I won't do this, IMO not all violations are worth an urgent fix, so it's case by case, so if the app crashes, then it'll block the other developer's workflows.
The Non-SDK is the exception because Google are fighting their usage which could lead to production crashes if it goes unnoticed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the fact that you found violations now when the code/libraries were added a long time ago, and nobody noticed that, may prove the point that early detection is good?

@kidinov kidinov self-requested a review January 21, 2025 13:53
Copy link
Contributor

@kidinov kidinov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I checked with just throwing an exception and what currently in the PR and I frankly prefer the expcetion - at least there is a stack trace

now it just:

2025-01-21 14:55:00.064  5512-5512  System.err              com.woocommerce.android.dev          W  Non-SDK API violation detected: Ljava/lang/StackTraceElement;-><init>()V
2025-01-21 14:55:00.064  5512-5512  Process                 com.woocommerce.android.dev          I  Sending signal. PID: 5512 SIG: 9

I think we won't figureout for a while why it crashes

wdyt?

@hichamboushaba
Copy link
Member Author

@kidinov this is not ready for merge, after more testing of the app without my patch above, I found out that we already had some NonSdk violations, which means if we merge this no one will be able to launch the app.
We need to ensure we have no violations before implementing a change like this, but the first step now would be to check if this NonSdk usage will be blocked in Android 15.

The violation comes from Gson:

android.os.strictmode.NonSdkApiUsedViolation: Ljava/lang/StackTraceElement;-><init>()V
	at android.os.StrictMode.lambda$static$1(StrictMode.java:436)
	at android.os.StrictMode$$ExternalSyntheticLambda2.accept(D8$$SyntheticClass:0)
	at java.lang.Class.getDeclaredConstructorInternal(Native Method)
	at java.lang.Class.getConstructor0(Class.java:3392)
	at java.lang.Class.getDeclaredConstructor(Class.java:3077)
	at com.google.gson.internal.ConstructorConstructor.newDefaultConstructor(ConstructorConstructor.java:212)
	at com.google.gson.internal.ConstructorConstructor.get(ConstructorConstructor.java:120)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:129)
	at com.google.gson.Gson.getAdapter(Gson.java:556)
	at com.google.gson.internal.bind.ArrayTypeAdapter$1.create(ArrayTypeAdapter.java:45)
	at com.google.gson.Gson.getAdapter(Gson.java:556)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
	at com.google.gson.Gson.getAdapter(Gson.java:556)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
	at com.google.gson.Gson.getAdapter(Gson.java:556)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
	at com.google.gson.Gson.getAdapter(Gson.java:556)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:160)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:294)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:130)
	at com.google.gson.Gson.getAdapter(Gson.java:556)
	at com.google.gson.Gson.toJson(Gson.java:834)
	at com.google.gson.Gson.toJson(Gson.java:812)
	at com.google.gson.Gson.toJson(Gson.java:759)
	at com.google.gson.Gson.toJson(Gson.java:736)
	at com.woocommerce.android.wear.WearableConnectionRepository.sendSiteData(WearableConnectionRepository.kt:68)
	at com.woocommerce.android.SiteObserver.sendSiteDataToWearable(SiteObserver.kt:93)
	at com.woocommerce.android.SiteObserver.access$sendSiteDataToWearable(SiteObserver.kt:33)
	at com.woocommerce.android.SiteObserver$observeAndUpdateSelectedSiteData$3$1$4.invokeSuspend(SiteObserver.kt:57)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:811)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:715)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:702)

@hichamboushaba hichamboushaba added the status: do not merge Dependent on another PR, ready for review but not ready for merge. label Jan 22, 2025
@hichamboushaba
Copy link
Member Author

Another violation if we try to get around the one from gson, now from appcompat, so this could be a nightmare to get rid of all violations, I think our best bet here is to test the app manually with targetSdk set to Android 15, and keep the previous behavior of just logging the violations.

android.os.strictmode.NonSdkApiUsedViolation: Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z
	at android.os.StrictMode.lambda$static$1(StrictMode.java:436)
	at android.os.StrictMode$$ExternalSyntheticLambda2.accept(D8$$SyntheticClass:0)
	at java.lang.Class.getDeclaredMethodInternal(Native Method)
	at java.lang.Class.getMethod(Class.java:2945)
	at java.lang.Class.getDeclaredMethod(Class.java:2929)
	at androidx.appcompat.widget.ViewUtils.<clinit>(ViewUtils.java:53)
	at androidx.appcompat.widget.AppCompatTextHelper.loadFromAttributes(AppCompatTextHelper.java:251)
	at androidx.appcompat.widget.AppCompatTextView.<init>(AppCompatTextView.java:121)
	at com.google.android.material.textview.MaterialTextView.<init>(MaterialTextView.java:92)
	at com.google.android.material.textview.MaterialTextView.<init>(MaterialTextView.java:87)
	at com.google.android.material.theme.MaterialComponentsViewInflater.createTextView(MaterialComponentsViewInflater.java:61)
	at androidx.appcompat.app.AppCompatViewInflater.createView(AppCompatViewInflater.java:148)
	at androidx.appcompat.app.AppCompatDelegateImpl.createView(AppCompatDelegateImpl.java:1678)
	at androidx.appcompat.app.AppCompatDelegateImpl.onCreateView(AppCompatDelegateImpl.java:1729)
	at android.view.LayoutInflater.tryCreateView(LayoutInflater.java:949)
	at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:885)
	at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:849)
	at android.view.LayoutInflater.rInflate(LayoutInflater.java:1011)
	at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:972)
	at android.view.LayoutInflater.rInflate(LayoutInflater.java:1014)
	at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:972)
	at android.view.LayoutInflater.rInflate(LayoutInflater.java:1014)
	at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:972)
	at android.view.LayoutInflater.rInflate(LayoutInflater.java:1014)
	at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:972)
	at android.view.LayoutInflater.inflate(LayoutInflater.java:570)
	at android.view.LayoutInflater.inflate(LayoutInflater.java:462)
	at com.woocommerce.android.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:97)
	at com.woocommerce.android.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:91)
	at com.woocommerce.android.ui.main.MainActivity.onCreate(MainActivity.kt:302)
	at android.app.Activity.performCreate(Activity.java:9002)
	at android.app.Activity.performCreate(Activity.java:8980)
	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1526)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4030)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4235)
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:112)
	at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:174)
	at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:109)
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:81)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2636)
	at android.os.Handler.dispatchMessage(Handler.java:107)
	at android.os.Looper.loopOnce(Looper.java:232)
	at android.os.Looper.loop(Looper.java:317)
	at android.app.ActivityThread.main(ActivityThread.java:8705)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)

@kidinov
Copy link
Contributor

kidinov commented Jan 22, 2025

Another violation if we try to get around the one from gson, now from appcompat, so this could be a nightmare to get rid of all violations, I think our best bet here is to test the app manually with targetSdk set to Android 15, and keep the previous behavior of just logging the violations.

Isn't this just postponing the resolution of the problems? If they change the behavior of nonpublic methods, it may just break it with the next SDK version, which will go unnoticed by us and end up in the production

@hichamboushaba
Copy link
Member Author

Isn't this just postponing the resolution of the problems?

Yes maybe, but is there something else that we can do? These are usages by third party libraries, and not by our code.

The way I see this, iff the libraries are still using those APIs, it's probably because they don't find an alternative, and if the API is still allowed by Google, then maybe it's because they didn't provide this alternative yet, so IMO all we can so is wait until it happens, at which time we could have updated libraries that solve the issue.

@kidinov
Copy link
Contributor

kidinov commented Jan 22, 2025

@hichamboushaba

if the API is still allowed by Google

Why do you think that API is allowed by google? They use reflection to access not public interfaces, it's not API

Yes maybe, but is there something else that we can do? These are usages by third party libraries, and not by our code.

I'd say we may try:

  • Firstly, try to research every case
  • Try to update the library
  • If nothing works to NOT crash for those specific cases
    wdyt?

@hichamboushaba
Copy link
Member Author

hichamboushaba commented Jan 22, 2025

Why do you think that API is allowed by google?

I say allowed in the sense that they don't block it yet, but it's still a hidden API (using @hide).

I'd say we may try:

Firstly, try to research every case
Try to update the library
If nothing works to NOT crash for those specific cases
wdyt?

Personally I think it doesn't bring enough advantages to justify the effort, but that's just a personal opinion, feel free to involve the rest of the team or to proceed with your approach.

@wpmobilebot wpmobilebot modified the milestones: 21.5, 21.6 Jan 24, 2025
@wpmobilebot
Copy link
Collaborator

Version 21.5 has now entered code-freeze, so the milestone of this PR has been updated to 21.6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: do not merge Dependent on another PR, ready for review but not ready for merge. type: task An internally driven task. unit-tests-exemption
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants