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

App crashes when entering exploration with voiceover data without internet connectivity #1340

Closed
BenHenning opened this issue Jun 17, 2020 · 5 comments · Fixed by #1636
Closed
Assignees
Labels
Priority: Essential This work item must be completed for its milestone. Z-ibt Temporary label for Ben to keep track of issues he's triaged.

Comments

@BenHenning
Copy link
Member

BenHenning commented Jun 17, 2020

Describe the bug
The app crashes when opening an exploration that has audio voiceovers (e.g. "What is a Fraction?") when not connected to the internet.

To Reproduce
Steps to reproduce the behavior:

  1. Disable wifi & cellular connectivity on the device
  2. Open "What is a Fraction?"
  3. Experience crash

Expected behavior
App should not crash.

Screenshots
oppia_app_crash_no_internet

Device

  • Latest develop
  • Pixel XL
  • SDK 29

Additional context
Here's the observed stack trace:

2020-06-17 10:20:46.452 9808-10180/org.oppia.app W/JMediaDataSource-JNI: java.net.UnknownHostException: Unable to resolve host "storage.googleapis.com": No address associated with hostname
        at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:124)
        at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
        at java.net.InetAddress.getAllByName(InetAddress.java:1152)
        at com.android.okhttp.Dns$1.lookup(Dns.java:41)
        at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178)
        at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144)
        at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86)
        at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:176)
        at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128)
        at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
        at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289)
        at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:248)
        at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getInputStream(DelegatingHttpsURLConnection.java:211)
        at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:30)
        at java.net.URL.openStream(URL.java:1072)
        at org.oppia.util.caching.AssetRepository.openRemoteStream(AssetRepository.kt:74)
        at org.oppia.util.caching.AssetRepository.openCachingStreamToRemoteFile(AssetRepository.kt:79)
        at org.oppia.util.caching.AssetRepository.access$openCachingStreamToRemoteFile(AssetRepository.kt:23)
        at org.oppia.util.caching.AssetRepository$loadRemoteBinaryAsset$1.invoke(AssetRepository.kt:58)
        at org.oppia.util.caching.AssetRepository$loadRemoteBinaryAsset$1.invoke(AssetRepository.kt:23)
        at org.oppia.domain.audio.AudioPlayerController$prepareDataSource$mediaDataSource$1$audioFileBuffer$2.invoke(AudioPlayerController.kt:157)
        at org.oppia.domain.audio.AudioPlayerController$prepareDataSource$mediaDataSource$1$audioFileBuffer$2.invoke(AudioPlayerController.kt:153)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at org.oppia.domain.audio.AudioPlayerController$prepareDataSource$mediaDataSource$1.getAudioFileBuffer(Unknown Source:7)
        at org.oppia.domain.audio.AudioPlayerController$prepareDataSource$mediaDataSource$1.readAt(AudioPlayerController.kt:164)

It seems that although the media for audio voiceovers are being loaded on a background thread, that thread isn't resilient to exceptions being thrown. While we can probably make this more robust, this seems like a regression: we shouldn't be loading audio right when the exploration is opened. It should only be loaded when we start playing audio, and that should be guarded by an internet connectivity check.

@BenHenning BenHenning self-assigned this Jun 17, 2020
@BenHenning BenHenning added Priority: Essential This work item must be completed for its milestone. Status: Not started labels Jun 17, 2020
@BenHenning BenHenning added this to the Alpha milestone Jun 17, 2020
@BenHenning
Copy link
Member Author

Note: this issue also affects images:

2020-06-17 10:59:51.503 14180-14249/org.oppia.app W/ExifInterface: Invalid image: ExifInterface got an unsupported image format file(ExifInterface supports JPEG and some RAW image formats only) or a corrupted JPEG file to ExifInterface.
    java.io.IOException: Invalid byte order: 300
        at android.media.ExifInterface.readByteOrder(ExifInterface.java:3121)
        at android.media.ExifInterface.isOrfFormat(ExifInterface.java:2437)
        at android.media.ExifInterface.getMimeType(ExifInterface.java:2315)
        at android.media.ExifInterface.loadAttributes(ExifInterface.java:1753)
        at android.media.ExifInterface.<init>(ExifInterface.java:1447)
        at com.bumptech.glide.load.resource.bitmap.ExifInterfaceImageHeaderParser.getOrientation(ExifInterfaceImageHeaderParser.java:40)
        at com.bumptech.glide.load.ImageHeaderParserUtils.getOrientation(ImageHeaderParserUtils.java:91)
        at com.bumptech.glide.load.resource.bitmap.Downsampler.decodeFromWrappedStreams(Downsampler.java:236)
        at com.bumptech.glide.load.resource.bitmap.Downsampler.decode(Downsampler.java:206)
        at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:62)
        at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.java:18)
        at com.bumptech.glide.load.resource.bitmap.BitmapDrawableDecoder.decode(BitmapDrawableDecoder.java:58)
        at com.bumptech.glide.load.engine.DecodePath.decodeResourceWithList(DecodePath.java:72)
        at com.bumptech.glide.load.engine.DecodePath.decodeResource(DecodePath.java:55)
        at com.bumptech.glide.load.engine.DecodePath.decode(DecodePath.java:45)
        at com.bumptech.glide.load.engine.LoadPath.loadWithExceptionList(LoadPath.java:58)
        at com.bumptech.glide.load.engine.LoadPath.load(LoadPath.java:43)
        at com.bumptech.glide.load.engine.DecodeJob.runLoadPath(DecodeJob.java:515)
        at com.bumptech.glide.load.engine.DecodeJob.decodeFromFetcher(DecodeJob.java:480)
        at com.bumptech.glide.load.engine.DecodeJob.decodeFromData(DecodeJob.java:466)
        at com.bumptech.glide.load.engine.DecodeJob.decodeFromRetrievedData(DecodeJob.java:418)
        at com.bumptech.glide.load.engine.DecodeJob.onDataFetcherReady(DecodeJob.java:387)
        at com.bumptech.glide.load.engine.SourceGenerator.onDataReady(SourceGenerator.java:112)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.onDataReady(MultiModelLoader.java:145)
        at com.bumptech.glide.load.data.LocalUriFetcher.loadData(LocalUriFetcher.java:52)
        at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100)
        at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:62)
        at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:309)
        at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279)
        at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:235)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
        at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:446)

It's a bit easier to hit this when caching is enabled.

@BenHenning
Copy link
Member Author

Ah, it actually seems Glide doesn't crash. Only the audio player is triggering a crash, but images just don't seem to load (as expected).

@anandwana001
Copy link
Contributor

This crash is not having in the latest develop branch.
Are you able to recover this crash with the latest develop branch?

@BenHenning
Copy link
Member Author

I think this will only repro if you enable local file caching via

fun provideCacheAssetsLocally(): Boolean = false
.

BenHenning added a commit that referenced this issue Aug 13, 2020
This does a bunch of refactoring & hacky workarounds to keep asset
priming support fully isolated (just a few files now need to be deleted
to clean things up). This also:
1. Fixes actual asset downloading (the GCS asset path templates have
changed, so priming didn't actually work anymore).
2. Disables audio file caching since we can't play audio when offline,
anyway (there's a dialog that prevents this in-player).
3. Fixes #1340 and #1341 by accounting for error cases when trying to
play audio. It turns out that playing audio crashes if you didn't have
internet access when going into an exploration (the no connectivity
dialog only appears if you lose connectivity within a lesson). There's
also some issues with existing view model code that wrongly assumed
audio couldn't be in a failure state at that point. This has been fixed.
4. Moves the list of topics to cache to be in the same position as the
flag enabling/disabling this functionality.

The download experience isn't perfect, but it's meant to be a helper for
user study coordinators so that they know when lessons can be brought
offline.

Note that the UI aspects of this change are really hacky. This is by
design--I didn't want to overcomplicate the solution, and I wanted to
keep the priming changes fully isolated to make future cleanup easier.

Finally, no new tests were added. I clarified in StateFragmentTest that
the edge cases fixed in this PR need to have corresponding tests, but
I'm actually not sure offhand how to test that audio's playing. I think
this will require additional work. I prefer to push this off to #388,
but I will follow up with tests if anyone wants to push back on this.
@BenHenning BenHenning self-assigned this Aug 13, 2020
@BenHenning
Copy link
Member Author

It seems the issue here is that the view model assumes that there's no failure in some circumstances, and uses getOrThrow-- which triggers an actual failure. The fix is to account for the failure case and default to the same path as pending.

BenHenning added a commit that referenced this issue Aug 17, 2020
…line support (#1636)

* Show a dialog when preloading assets for offline support.

This does a bunch of refactoring & hacky workarounds to keep asset
priming support fully isolated (just a few files now need to be deleted
to clean things up). This also:
1. Fixes actual asset downloading (the GCS asset path templates have
changed, so priming didn't actually work anymore).
2. Disables audio file caching since we can't play audio when offline,
anyway (there's a dialog that prevents this in-player).
3. Fixes #1340 and #1341 by accounting for error cases when trying to
play audio. It turns out that playing audio crashes if you didn't have
internet access when going into an exploration (the no connectivity
dialog only appears if you lose connectivity within a lesson). There's
also some issues with existing view model code that wrongly assumed
audio couldn't be in a failure state at that point. This has been fixed.
4. Moves the list of topics to cache to be in the same position as the
flag enabling/disabling this functionality.

The download experience isn't perfect, but it's meant to be a helper for
user study coordinators so that they know when lessons can be brought
offline.

Note that the UI aspects of this change are really hacky. This is by
design--I didn't want to overcomplicate the solution, and I wanted to
keep the priming changes fully isolated to make future cleanup easier.

Finally, no new tests were added. I clarified in StateFragmentTest that
the edge cases fixed in this PR need to have corresponding tests, but
I'm actually not sure offhand how to test that audio's playing. I think
this will require additional work. I prefer to push this off to #388,
but I will follow up with tests if anyone wants to push back on this.

* Lint fixes.

* Introduce new caching module to simplify tests, and fix app module
tests.

* Lint fixes.
@BenHenning BenHenning added the Z-ibt Temporary label for Ben to keep track of issues he's triaged. label Sep 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: Essential This work item must be completed for its milestone. Z-ibt Temporary label for Ben to keep track of issues he's triaged.
2 participants