Collect: Update targetSdkversion to 29

Created on 25 Sep 2019  ·  33Comments  ·  Source: getodk/collect

Problem description

Working on #3189 we wanted to bump targetSdkversion directly to 29 but it turned out to be not that easy.

this doc seems to be the most relevant https://developer.android.com/about/versions/10/privacy/changes

We would need fixes at least for:

in progress

Most helpful comment

For anyone who might be following this issue, the scoped storage migration is now implemented and available to beta test: https://forum.opendatakit.org/t/odk-collect-v1-26-beta-1-scoped-storage-migration/25313. There is documentation on the forum: https://forum.opendatakit.org/t/odk-collect-v1-26-storage-migration/25268.

The migration is currently opt-in and will be forced in a release right before the requirement to target SDK 29 comes into effect.

Additionally, the beta includes a new installID preference which will take the place of the IMEI as the deviceID in the first release that targets SDK 29.

We will delay targeting SDK 29 until we are required to.

All 33 comments

Hello @grzesiek2010, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 15 days.

You can reclaim this issue or claim any other issue by commenting @opendatakit-bot claim on that issue.

Thanks for your contributions, and hope to see you again soon!

The biggest problem for us is scoped storage and I'm in the 99% of people that hate it. Today starts Android Dev Summit 2019 and probably I'm naive but I hope they will say something like:

Hey, guys, it was a mistake we are going to let you use the storage, in the same way, like before.

If not we will have more info about it at least so I'm putting it on hold and will be back next week.

Makes sense! We're lucky that @seadowg will be at the Dev Summit getting information first hand. I hope you're right...

@seadowg, @grzesiek2010 and I discussed scoped storage on a call.

  • primary reason Google has given for this change is to reduce "clutter" so that by default files get cleaned up when an app is removed
  • @seadowg confirms everyone at dev summit hates scoped storage but that it's definitely coming into effect when targetSDK goes to 29
  • Google has now said they'll require targeting the SDK version before the latest release (e.g. when API 30 is released, apps will need to target 29)
  • Given prior Android version release pace, we can expect API 30 in fall 2020
  • April 2019 blog post: "Scoped Storage will be required in next year’s major platform release for all apps, independent of target SDK level"
  • Apparently it's still possible to use Context#getExternalFilesDir to get a directory that a user could access from adb but it will have a path like Android/data/org.odk.collect.android/files (will be cleared on app delete)

We agreed the next steps are:

  • [x] @lognaturel Draft up a message to users explaining the change and its implications
  • [x] @grzesiek2010 Confirm that using Context#getExternalFilesDir means files are accessible through adb
  • [x] @grzesiek2010 Confirm what happens when an app that uses Context#getExternalFilesDir is moved between SD card and internal storage. We expect files get moved over and relative paths just work
  • [x] @grzesiek2010 Migrate paths stored in the instances and forms databases to relative paths
  • [x] @grzesiek2010 Ensure all base paths are pulled out

Then, when we're ready to target API 29 and fully make the switch, all we should need to do is switch to Context#getExternalFilesDir and move /sdcard/odk there.

Confirm that using Context#getExternalFilesDir means files are accessible through adb

I can confirm that I was able to access files using eg.
adb push testfile /storage/emulated/0/Android/data/org.odk.collect.android/files

Confirm what happens when an app that uses Context#getExternalFilesDir is moved between SD card and internal storage. We expect files get moved over and relative paths just work

I had hard time trying to do such a migration and to no avail. I used various devices and every time receiving an exception like: Not enough storage space which wasn't true, on another device it's Unable to move the app...
but obviously it's how it should work so I'm 99% sure it will be ok (I'll try with more devices later).
The only thing we need is add android:installLocation="auto" to the Manifest file because by default apps are not movable.

[EDIT] Finally I managed to test moving apps to sd card. Looks as if the problem was caused by the fact I installed the sample app via USB, everything was fine using an apk file. I don't know why but it fixed the problem.

Hello @grzesiek2010, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 15 days.

You can reclaim this issue or claim any other issue by commenting @opendatakit-bot claim on that issue.

Thanks for your contributions, and hope to see you again soon!

@grzesiek2010 has done the important exploration and learning tasks that we set out to do for v1.25. We've decided to wait on code changes so I'm removing from milestone.

Hello @grzesiek2010, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 15 days.

You can reclaim this issue or claim any other issue by commenting @opendatakit-bot claim on that issue.

Thanks for your contributions, and hope to see you again soon!

There have been some exploration of approaches to getting the scoped storage migration started at https://github.com/opendatakit/collect/pull/3444, https://github.com/opendatakit/collect/pull/3460. I want to summarize at a high-level and hopefully figure out a direction.

The only thing we need is add android:installLocation="auto" to the Manifest file because by default apps are not movable.

I read more into app install location and I now think it's not relevant to us. I thought that moving the app would also move its data files but it does not. I think we can ignore this.

Some other questions that have come up for me:

  • [x] Is there really a benefit to using Context#getExternalFilesDir over Context#getFilesDir? Is it really the case that files from getFilesDir can't be accessed from adb? The docs state that the differences are that (1) getExternalFilesDir may not always be available since it could be removed by the user. This seems scary and undesirable. (2) that other apps can write to the directory if they have the Manifest.permission.WRITE_EXTERNAL_STORAGE permission. This seems useful but not a must-have, I think.

The #1 priority is that users can't lose saved and filled forms when they upgrade to the scoped storage version. This is non-negotiable. To achieve this, we need to move everything from /sdcard/odk/ to Context#getExternalFilesDir/odk. This needs to happen BEFORE we are forced to switch to scoped storage to be in the Play Store because at that point we won't be able to access /sdcard/odk/ from Collect. While we'd like to delay this change because it will be inconvenient to users, we can't delay it too much because we then risk not being able to do it at all.

"Moving everything" is tricky because it involves both form files and database files that refer to those form files. Currently the references in the database use absolute paths. If those paths were relative, then we could move the whole directory on upgrade and everything should just work. This is where the idea of changing paths in the databases to be relative came from. However, there are two problems:

  • downgrading from versions with relative paths in the db to versions with absolute paths in the db would lead to broken behavior because old code can't handle relative paths.
  • users don't have to install every version so in the version where we move the directory, we'd also need to first make sure that all paths in the databases are relative. This means that changing paths in a prior version is not very useful.

This leads me to think that for the upgrade path, we need to do the following all in the same new version:

  • change paths in the database to be relative
  • move all files currently in /sdcard/odk/ to Context#getExternalFilesDir/odk
  • use Context#getExternalFilesDir to root paths in the database

Sadly I no longer see any way to break this up. I also realize that we probably need to design a user experience around this because we will need to block the UI while the file copy happens and there could potentially be large media files involved.

@grzesiek2010's exploration has focused around what to do if a user wants to downgrade from the Collect version that requires scoped storage version. Unfortunately, we've realized that because one of the side-effects of using Context#getExternalFilesDir is that those files get deleted on uninstall (without using the -r flag), there's not much point to having a downgrade path. This makes it especially important to get the user messaging right. We've talked about pairing this change with a major version bump and visual refresh. I still think that's probably the way to go but we need to be very careful because if we make overly dramatic or controversial changes, users will want to downgrade.

@grzesiek2010 can you please review this analysis carefully and see if I've overlooked anything?

Sadly I no longer see any way to break this up.

Luckily, @seadowg was feeling a little more creative than me and has suggested that we start in v1.26 with a change that makes installs that don't yet have an /sdcard/odk folder use Context#getExternalFilesDir/odk. That means those users will never need to switch over. Collect will need to be able to look for files in one of two places depending on whether /sdcard/odk/ exists or not. In the case where /sdcard/odk is used, paths in databases will be interpreted as absolute paths. In the case where Context#getExternalFilesDir/odk is used, paths in databases will be interpreted as relative paths. If this general concept seems reasonable, I'll file an issue with a more detailed specification.

I also realize that we probably need to design a user experience around this

In discussion with @seadowg, we realized it would be better to let the user drive when the change happens. That is, we'd have some new button or something that describes the change that needs to happen and lets the user trigger a migration of their files. That would let them choose a time when they don't have data on their device or make a backup first. Then at some point in the summer, we'd force the migration on upgrade and block the UI. We could add analytics to track the migration completion rate.

One additional thing to note is that even if we time this well, we're not guaranteed that users will upgrade to one of the versions before the the one that requires scoped storage. Folks who go straight from v1.25 to that version, for example, will not get a migration (because again, it will be impossible to do in a scoped storage world). Luckily, in that case, we can tell users how to move the files via adb and the sync tasks will take care of populating the databases.

we start in v1.26 with a change that makes installs that don't yet have an /sdcard/odk folder use Context#getExternalFilesDir/odk

you mean fresh installs won't use sd card right? I'm wondering in how many cases we can avoid copying files thanks to that, you know whether the game is worth the candle :smile:
According to google play it's about 1k new installs every day compared to 280k of all active devices.
image
Assuming we for example add it in 1.26 and then force users to use Context#getExternalFilesDir in v1.27 we would have 2 months between those two releases and we could save ~60k out of ~300k users from copying files that's 20%. Of course if we have a longer break between v1.26 and scoped storage the number will increase.

The only thing we need is add android:installLocation="auto" to the Manifest file because by default apps are not movable.
I read more into app install location and I now think it's not relevant to us. I thought that moving the app would also move its data files but it does not. I think we can ignore this.

from testing a sample app I know that thing is needed to enable moving the app between internal and external storage, otherwise the Change button is not visible:
image

One additional thing to note is that even if we time this well, we're not guaranteed that users will upgrade to one of the versions before the the one that requires scoped storage. Folks who go straight from v1.25 to that version, for example, will not get a migration (because again, it will be impossible to do in a scoped storage world). Luckily, in that case, we can tell users how to move the files via adb and the sync tasks will take care of populating the databases.

That's right and that's the one of the few things we can agree on in 100% besides of the fact that we need to start using scope storage in the future.

we could save ~60k out of ~300k users from copying files that's 20%

to be honest that number 20% would be too big we have about 1.2k new installs every day but (fortunately) the number of uninstalls is lower (thanks to that our audience is growing) so in a few month the audience might be much bigger, but still.

Assuming we for example add it in 1.26 and then force users to use Context#getExternalFilesDir in v1.27 we would have 2 months between those two releases and we could save ~60k out of ~300k users from copying files

@grzesiek2010 this is a great point. I guess Collect is a little strange in having this behaviour but it will definitely benefit us here!

Thanks so much for all the continued thought that has gone into this. I hope that together we can get it right for users! I've written separate forum posts about no longer being able to access IMEIs and needing to move off /sdcard/odk to get broader feedback. I'm continuing the conversation here for now but there may be crossover and we may choose to move the rest of the decision-making on the forum depending on what others have to say and who engages in the conversation.

from testing a sample app I know that thing is needed to enable moving the app between internal and external storage, otherwise the Change button is not visible:

Yes. What I was saying is that from what I now understand, the path represented by Context#getExternalFilesDir doesn't change in that case so it's not very helpful to users (because the app is ~8mb and it's the data that's big) and doesn't require any different approach for targeting API 29. The docs are quite explicit: "all private user data, databases, optimized .dex files, and extracted native code are saved on the internal device memory." That said, could you please quickly verify this with your test app and removable media? The test app would need to change the manifest flag and write a file to Context#getExternalFilesDir. Then you could move the app and see whether the file also moved or not.

you mean fresh installs won't use sd card right? I'm wondering in how many cases we can avoid copying files thanks to that, you know whether the game is worth the candle

The big thing that I hadn't considered in @seadowg's concept is letting users manually trigger the migration rather than doing it on install. This is better because it's less likely that users will be disrupted by or interrupt the migration if they trigger it themselves. I've since realized that we'd probably need to provide a manual trigger in case something went wrong like the phone running out of batteries part way through the data migration so it's work we'd need to do anyway. That means Collect needs to be able to handle both storage locations in the same version.

If we're going to do that anyway, we might as well give new users the best possible experience by setting them up with the new location right away. Does that reasoning make sense? As we discussed above, we will need to force a migration before August while we still have access to /sdcard/odk but hopefully by then most active users will be on the new storage location already (and we'll know because of analytics).

I previously thought that we were going to have to say that other applications could no longer access form definition or submission XML files. However, I think it's too useful and that we need to provide a mechanism for this (e.g. see https://github.com/opendatakit/skunkworks-crow or https://github.com/posm/OpenMapKitAndroid). I think we can do so by reviewing the existing FileProvider declaration in the manifest and adding externally-published activities that can be used to get content URIs for form definitions and submissions. Then this should be explicitly documented as part of the app's API.

That said, could you please quickly verify this with your test app and removable media?

That option is so annoying... Now I'm getting exceptions (the same I mentioned above) and can't check it. I'll try later with other devices maybe.

but hopefully by then most active users will be on the new storage location already

If not we can still use android:requestLegacyExternalStorage=”true” and buy more time.

If not we can still use android:requestLegacyExternalStorage=”true” and buy more time.

My impression was that when Android 11 will be released, that option won't be available anymore. But maybe I'm wrong? Do you see any indication of when it might be retired? https://source.android.com/devices/storage says "Apps that target Android 10 and default to isolated storage can temporarily opt out of it" but I'm not sure what that means. @seadowg any indication from the dev summit? Do you think it's indeed something we can use beyond the release of Android 11 for devices that run Android 10 and under if we have to? Android 11 will force scoped storage regardless of targetsdk so I assume this will have no effect on it.

https://youtu.be/UnJ3amzJM94?t=1338 I thought that the flag will work until we bump tagetSdkVersion to 30 (even on Android 11) so next year, but now I'm not 100% sure... but still I think I was right they says Targeting Android 10 -> Can request legacy storage so I think as long as we use tagetSdkVersion 29 it should work.

youtu.be/UnJ3amzJM94?t=1338

Thanks, that's really helpful, @grzesiek2010. That's also what I understand from the video. We should certainly aim to have everyone migrated by fall 2020 but it's good to know that we may have more breathing room if needed.

@grzesiek2010 @lognaturel yeah my undertstanding is that flag lets us target API 29 but that it won't be available in API 30.

@lognaturel could you tell more about those paths please? We started talking about it last week.

As @yanokwa has pointed out on the forum, the issue with having new installs immediately use scoped storage is that it wouldn't be possible to use new Collect installs from the Play Store with external integrations that direction read/write to /sdcard/odk. That is likely a reason to make the change opt-in even for those new installs. We need to consult with the TSC and users of external integrations to make the final call on user experience.

could you tell more about those paths please? We started talking about it last week.

I believe what you're asking about is what the paths in the database should be relative to. Having given this more thought, I think that in an ideal world we wouldn't save paths in the db anymore. That is, in a scoped storage universe we will always be able to derive them. But for simplicity's sake, we can keep the same schema and continue storing paths. That means that there does NOT need to be a database migration. This is weird because the semantics of the path columns will have changed but given that the change is to be user-triggered I think it makes things easier (since database migrations happen automatically). Coming back to the paths, what makes sense to me is to make them relative to the ODK subfolder that they belong to. That is:

  • /storage/emulated/0/odk/instances/identify-user-audit-false_2020-01-10_22-21-42/identify-user-audit-false_2020-01-10_22-21-42.xml -> identify-user-audit-false_2020-01-10_22-21-42/identify-user-audit-false_2020-01-10_22-21-42.xml
  • /storage/emulated/0/odk/forms/identify-user-audit-media -> identify-user-audit-media
  • /storage/emulated/0/odk/forms/identify-user-audit.xml -> identify-user-audit.xml
  • /storage/emulated/0/odk/.cache/b875ff880f234d8623dc8ef96ac09690.formdef -> b875ff880f234d8623dc8ef96ac09690.formdef

As I mentioned here, I think we should provide external access to Collect files through FileProvider and some new activities that return content URIs. More concretely, we should add forms (getExternalFilesDir() + odk/forms) and instances (getExternalFilesDir() + odk/instances) to exported paths in provider_paths.xml. As I mentioned above, the consistent rule is that each database column has an associated folder that they refer to (instanceFilePath stores paths relative to the instances subdirectory, formMediaPath stores paths relative to the forms subdirectory). But as an added bonus, for form definition path, form media path and instance path, the db paths will be relative to what we expose through the FileProvider and that seems nice to me.

Going through this analysis makes me realize that originally, one of the reasons that absolute paths were stored is so that external applications could write their own paths to files outside of the odk directory. I don't know how common this is but there may be non-ODK paths in existing installations' databases. For example, an external app could have written /sdcard/cool-app/formdef.xml to the forms database and that would currently work. Once scoped storage is required, this will no longer be possible. I think perhaps that it would be possible to store content URIs and request permissions from the host apps but I'd prefer that we deprecate that (undocumented) integration. Integration apps will need to change anyway so this seems ok. Additionally, if we want to migrate those non-/sdcard/odk paths, that will require additional logic. I will give further thought to what we want to do about this but wanted to flag it for now.

I think the priority is to get a user-triggered migration working because we know this will be required no matter what. It can initially be triggered by an ugly button anywhere on the main landing screen. When the button is pressed:

  • a general settings flag representing that the migration has started is written
  • the files in getExternalStorageDirectory() + odk get copied to getExternalFilesDir() + odk
  • the databases are updated/written with relative paths
  • the files in getExternalStorageDirectory() + odk are removed
  • a general settings flag representing that the migration has successfully completed us written, and the migration start flag will be removed (or perhaps this is a single setting with two values)
  • a /sdcard/odk file will be written that says the ODK folder has moved. This will give users more of a chance of figuring out what's going on if they try to use /sdcard/odk

One decision point will be how to do the file move. From a safety perspective, it is attractive to copy all the files and then delete them from the source when done. But this may not be possible because of space constraints. Moving files one at a time makes it more likely that the move will be possible but means a messier state if things go wrong. I think that since we are letting users opt-in, we should copy all the files and then delete them from the source. We can try to do nice things like verify that there's space ahead of time and let the user know if not.

There are many things that can go wrong during the migration:

  • the device can run out of space
  • Collect can shut down because it runs out of memory, because the device runs out of power, etc
  • interruptions can happen at any time including while the database change is happening
  • a single file can fail to copy because of corruption
  • ...

We can use the migration started flag to identify that a migration has been attempted but not completed.

Currently, access to files on the device is done through static calls on constants (e.g. FORMS_PATH). Ideally, all access to paths on the devices should be mediated by a single class that can be passed in/injected as a dependency. If the "migration completed" general settings flag is written, getExternalFilesDir() is used as the root. Otherwise getExternalStorageDirectory() is used. Paths that come from the database should also go through methods of this class to be returned without modification in the current case and to be properly rooted once the migration is complete. Does this sound right, @grzesiek2010, @seadowg? Could pulling out this class out be a good first PR to submit?

I know this is a lot but I wanted to get current thinking out so @grzesiek2010 can get started. I'm going to summarize all of what we have come up with so far in a more formal spec for users and the TSC to comment on.

@lognaturel that makes sense to me. Thanks for taking the time to get that all down! The addition of the flag for migration started is a nice catch so we can handle recovery from any disaster scenarios.

I'm definitely in support of pulling out an interface that's responsible for returning file paths. That sounds like a nice first step that gets us ready withoyut any change in behaviour.

I agree that keeping file names instead of paths would be better but I'm a bit confused...
Do you think it's better to change all existed paths now (during copying files) or keep everything as is and handle everything in that new class you mentioned?

keeping file names instead of paths

In what I've described in the first bullet list here, what's stored are still paths. For example, for instance files, the instance directory and instance filename will both be included (identify-user-audit-false_2020-01-10_22-21-42/identify-user-audit-false_2020-01-10_22-21-42.xml). They're redundant but like I mentioned, that means the database schema doesn't have to change which seems simpler. We could just store the filename and derive the folder name from it but that doesn't feel like a big win to me and means some additional code changes. Does that make sense to you?

As a side note, I spent some time seeing whether we could derive that instance filename with other existing columns. It's in the structure form_name_creation_date so it seems like we should be able to. But it's not so straightforward because the database date field is converted from a unix timestamp to a formatted date using the device timezone at the time of writing the file. So that's another reason I don't think we should bother changing the schema -- we'd have to rename existing files which adds even more complexity.

Do you think it's better to change all existed paths now (during copying files) or keep everything as is and handle everything in that new class you mentioned?

Do you mean that we could keep the database files exactly as they are, move them, and then translate paths every time we read them from the databases? That is certainly possible but it feels really strange to me. I think that as part of the file moving operation, the databases should be modified to use relative paths. In the second bulleted list above, I suggested doing that as a separate operation from the file copy but it could possibly be simpler to do as files are copied, that will be up to you to discover. The important thing is that the existing database files are not modified. That way, if something goes wrong part way through the migration, everything still works with the original paths.

For paths from the database, I'm imagining that they would go through methods like getAbsoluteInstancePath or getAbsoluteCachePath. Currently, those methods would just return the same path that was passed in. Eventually, they would check whether the migration was run, and if it was, they would root them appropriately.

In what I've described in the first bullet list here, what's stored are still paths

Yeah, it will be a path in case of instances but in other cases forms/cache it will be just a file name.

As a side note, I spent some time seeing whether we could derive that instance filename with other existing columns. It's in the structure form_name_creation_date so it seems like we should be able to. But it's not so straightforward because the database date field is converted from a unix timestamp to a formatted date using the device timezone at the time of writing the file. So that's another reason I don't think we should bother changing the schema -- we'd have to rename existing files which adds even more complexity.

I agree. We could do even more then like remove formMediaPath column because it's always displayName + -media. Such improvements we can add in the future.

Do you mean that we could keep the database files exactly as they are, move them, and then translate paths every time we read them from the databases?

I'm in favor of converting existing paths so I'm happy we agree. I just misunderstood you and asked for clarification.

Ok no more questions for now. Thanks!

Hello @grzesiek2010, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 15 days.

You can reclaim this issue or claim any other issue by commenting @opendatakit-bot claim on that issue.

Thanks for your contributions, and hope to see you again soon!

For anyone who might be following this issue, the scoped storage migration is now implemented and available to beta test: https://forum.opendatakit.org/t/odk-collect-v1-26-beta-1-scoped-storage-migration/25313. There is documentation on the forum: https://forum.opendatakit.org/t/odk-collect-v1-26-storage-migration/25268.

The migration is currently opt-in and will be forced in a release right before the requirement to target SDK 29 comes into effect.

Additionally, the beta includes a new installID preference which will take the place of the IMEI as the deviceID in the first release that targets SDK 29.

We will delay targeting SDK 29 until we are required to.

We've been talking about August 2020 as the time when we need to target 29 but I now think we may have until November. From https://support.google.com/googleplay/android-developer/answer/113469#targetsdk:

  • August 3, 2020: Required for new apps
  • November 2, 2020: Required for app updates

Does that sound right? I don't necessarily thing we should delay much beyond August but it'd be good to make sure we have the right "last chance" date in mind.

Yeah that seems right. Given it looks like we'll have a September release maybe we should target that?

In v1.29 we will:

  • [x] Replace value of deviceID with existing value of installID, remove installId from settings menu
  • [x] Remove SIM serial and subscriber ID from settings menu~, remove READ_PHONE_STATE permission~ (still needed for phone number)
  • [ ] Set target API to 29 and request to keep legacy storage on
  • [ ] Force storage migration on first launch if it hasn't already been done. Try again on subsequent launch in case of failure. New installs should go straight to using scoped storage.

Those feel like different PRs to me but that'll be up to the person doing the implementation (likely @grzesiek2010). We should write acceptance criteria for the last point.

We don't know how long we will have with the requestLegacyExternalStorage flag being respected (and it's never guaranteed to be). I think we can continue targeting API 29 with the flag and forcing the migration on first launch until we need to target API 30 (likely Nov 2021). At that point we can remove the flag, remove the migration code, and only use scoped storage.

There have been further changes to the APIs and some new documentation released since I last checked. I skimmed and don't see anything that changes our plans but here are a few resources: FAQ, migration case study, use cases and best practices.

One thing I learned is that the preserveLegacyExternalStorage flag doesn't have any effect on new installs on Android 11 according to the docs. This contradicts @seadowg's experience at #4084 which is weird. Either way, I've updated the list above to reflect that new installs of v1.29+ should just go straight to using scoped storage.

This contradicts @seadowg's experience at #4084 which is weird

We've been testing with requestLegacyExternalStorage not preserveLegacyExternalStorage. The difference is obvious right? Wow.

I think the first (requestLegacyExternalStorage) is for maintaining access to legacy storage while targeting Android 10 (and is ignored in Android 11). The second (preserveLegacyExternalStorage) is for maintaining access to legacy storage when targeting Android 11. The catch for the latter is:

Note: If you set preserveLegacyExternalStorage to true, the legacy storage model remains in effect only until the user uninstalls your app. If the user installs or reinstalls your app on a device that runs Android 11, then your app cannot opt out the scoped storage model, regardless of the value of preserveLegacyExternalStorage.

So it only works for updates to apps and not fresh installs.

If that is all true it means we could continue to run forced migrations when the app targets API 30 (Android 11) on update if we still feel that would be useful. I don't think this changes you todo list but it does give us an extra step we could carry out when moving to API 30.

The difference is obvious right? Wow.

🙈🙈🙈 My brain did not notice there were two. Wow indeed.

Was this page helpful?
0 / 5 - 0 ratings