-
-
Save dpesch/a3fc3b7611068dd8b27bda9380ae07ac to your computer and use it in GitHub Desktop.
DO NOT USE THIS PATCH ANY MORE (see <https://bugs.chromium.org/p/chromium/issues/detail?id=1033655#c97>) | |
/* | |
Licensed to the Apache Software Foundation (ASF) under one | |
or more contributor license agreements. See the NOTICE file | |
distributed with this work for additional information | |
regarding copyright ownership. The ASF licenses this file | |
to you 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. | |
*/ | |
/* | |
Hotfix example to mitigate the complete user data loss (websql, IndexdDB | |
and Local Storage) in Cordova Android apps if they are started after the | |
Chrome `79.0.3945.79` update. | |
This MainActivity contains a simple but working solution which copies the | |
»lost« user data to the new `Default/` directory before the cordova app | |
starts. | |
Feel free to use and/or change it, I'll hope it can help someone. | |
Thanks to si….mac…@g.xarevision.pt for the idea | |
(<https://bugs.chromium.org/p/chromium/issues/detail?id=1033655#c6>). | |
@see <https://bugs.chromium.org/p/chromium/issues/detail?id=1033655> | |
*/ | |
package de.b11com7.example; | |
import android.annotation.TargetApi; | |
import android.app.AlertDialog; | |
import android.content.pm.PackageInfo; | |
import android.content.pm.PackageManager; | |
import android.os.Build; | |
import android.os.Bundle; | |
import android.support.v4.content.ContextCompat; | |
import android.view.Window; | |
import android.view.WindowManager; | |
import android.webkit.WebView; | |
import android.widget.Toast; | |
import org.apache.cordova.CordovaActivity; | |
import org.apache.cordova.LOG; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
public class MainActivity extends CordovaActivity { | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
// enable Cordova apps to be started in the background | |
Bundle extras = getIntent().getExtras(); | |
if (extras != null && extras.getBoolean("cdvStartInBackground", false)) { | |
moveTaskToBack(true); | |
} | |
// 2019-12-14, dom: workaround for chrome v79 update disaster | |
if (_isIssue1033655()) { | |
_tryToSaveUserData(() -> { | |
loadUrl(launchUrl); | |
}); | |
} else { | |
// Set by <content src="index.html" /> in config.xml | |
loadUrl(launchUrl); | |
} | |
} | |
private boolean _isIssue1033655() { | |
String userAgent = new WebView(getApplicationContext()).getSettings().getUserAgentString(); | |
return userAgent.contains("Chrome/79.0.3945.79"); | |
} | |
private void _tryToSaveUserData(Runnable next) { | |
String appPath = getApplicationInfo().dataDir; | |
File databaseOld = new File(appPath.concat("/app_webview/databases/file__0/1")); | |
File databaseNew = new File(appPath.concat("/app_webview/Default/databases/file__0/1")); | |
// nichts tun, falls keine alte Datenbank existiert | |
if (!databaseOld.exists()) { | |
next.run(); | |
return; | |
} | |
// falls die neuen Daten noch nicht vorhanden sind, die Daten kopieren | |
if (!databaseNew.exists()) { | |
_migrateUserDataToDefault(); | |
next.run(); | |
return; | |
} | |
new AlertDialog.Builder(this) | |
.setTitle("Datenrettung?") | |
.setIcon(android.R.drawable.ic_dialog_alert) | |
.setMessage("Durch einen Fehler im Update des Google Chrome 79 werden deine eingegebenen Daten nicht mehr gefunden.\n\nEventuell kann der Finanzchecker deine Daten wiederhestellen. Falls du nach dem Datenverlust neue Daten eingegeben hast, werden diese dabei gelöscht.\n\nSollen wir es versuchen?") | |
.setPositiveButton("Ja", (dialog, which) -> { | |
_migrateUserDataToDefault(); | |
next.run(); | |
}) | |
.setNegativeButton("Nein", ((dialog, which) -> { | |
_lastQuestion(next); | |
})) | |
.setCancelable(false) | |
.show(); | |
} | |
private void _lastQuestion(Runnable next) { | |
new AlertDialog.Builder(this) | |
.setTitle("Bist du dir wirklich sicher?") | |
.setIcon(android.R.drawable.ic_dialog_alert) | |
.setMessage("Falls du dich für »Nicht mehr fragen« entscheidest, können deine vorherigen Daten nicht wiederhergestellt werden.\n\nMöchtest du beim nächsten Start nochmal gefragt werden?") | |
.setPositiveButton("Ja", (dialog, which) -> { | |
showToast( | |
"Du kannst beim nächsten Neustart erneut entscheiden, ob du deine vorherigen Daten wiederherstellen möchtest.", | |
Toast.LENGTH_LONG | |
); | |
next.run(); | |
}) | |
.setNegativeButton("Nicht mehr fragen", (dialog, which) -> { | |
_renameOldData(); | |
showToast("Du hast dich entschieden, deine vorherigen Daten nicht wiederherzustellen.", Toast.LENGTH_LONG); | |
next.run(); | |
}) | |
.show(); | |
} | |
private void _migrateUserDataToDefault() { | |
String appPath = getApplicationInfo().dataDir; | |
try { | |
copyRecursive(new File(appPath, "/app_webview/databases/"), new File(appPath, "/app_webview/Default/databases/")); | |
copyRecursive(new File(appPath, "/app_webview/Local Storage/"), new File(appPath, "/app_webview/Default/Local Storage/")); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
showToast("Sorry, die Daten konnten nicht kopiert werden."); | |
} | |
_renameOldData(); | |
showToast("Der Finanzchecker hat deine vorherigen Daten wiederhergestellt.", Toast.LENGTH_LONG); | |
} | |
private void showToast(String message) { | |
showToast(message, Toast.LENGTH_SHORT); | |
} | |
private void showToast(String message, int duration) { | |
Toast.makeText(getApplicationContext(), message, duration).show(); | |
} | |
/** | |
* @param source | |
* @param target | |
* | |
* @throws IOException | |
* | |
* @see <https://www.androidcode.ninja/copy-or-move-file-from-one-directory-to/> for the idea | |
*/ | |
private void copyRecursive(File source, File target) throws IOException { | |
if (source.isDirectory()) { | |
if (!target.exists()) { | |
target.mkdirs(); | |
} | |
String[] children = source.list(); | |
for (String child : children) { | |
copyRecursive(new File(source, child), new File(target, child)); | |
} | |
} else { | |
InputStream in = new FileInputStream(source); | |
OutputStream out = new FileOutputStream(target); | |
byte[] buffer = new byte[1024]; | |
int length; | |
while ((length = in.read(buffer)) > 0) { | |
out.write(buffer, 0, length); | |
} | |
in.close(); | |
out.close(); | |
} | |
} | |
private void _renameOldData() { | |
String appPath = getApplicationInfo().dataDir; | |
renameFile(appPath, "/app_webview/databases", "/app_webview/databases.bak"); | |
renameFile(appPath, "/app_webview/Local Storage", "/app_webview/Local Storage.bak"); | |
} | |
private void renameFile(String basePath, String nameOld, String nameNew) { | |
new File(basePath, nameOld).renameTo(new File(basePath, nameNew)); | |
} | |
} |
You can edit the MainActivity
of your app with Android Studio or every other editor.
Your MainActivity
in your existing code should have loadUrl(launchUrl);
as last line in the method onCreate
. You could replace this line with our fix (line 76 to 84). After that copy the following private methods from _isIssue1033655
(line 87) to renameFile(…)
at the end of your MainActivity
. If you don't like german questions or messages please translate them.
The questions should assure that users don't lose their data again. We've identified 3 cases:
- If we couldn't found old data (= an old database; if you only use Local Storage you have to change our code) do nothing just return.
- First start of the app after the Chrome update with old data present --> no need to ask, just copy the data from old to new location (lines 104…108)
- If the user has old and new databases present (= we've shocked the user already with data loss) we ask the user what he/she would like to do (lines 110…123):
a. Rescue the old data (lines 114…117)
b. or not, if not we ask the user again what he/she would like (lines 125…143)
1. to be asked again at the next app start (lines 130…135)
2. or to be never asked again and keep the new data (lines 137…141)
If the user would like to copy the old data, we'll copy the data from the old location to the new location (with Default/
; line 145…159). After the migration we rename the old directories so that our app knows that the migration has been done.
At the end we show a fitting message to the user with a simple toast.
OK,
I'll try and see what happend.
Thank you very much
Dominik, it works in our tests!
It is not perfect (I have translated it to Spanish although our app is in other 7 languages), but recovers the data to the user... I'm uploading now to the app store to avoid further problems...
Let's see if chromium team finally fix the problem.
deine Daten wiederhestellen.
Just a small typo there... (ignore if you aren't using this code currently) :)
thanks, good catch
I've added that you should not use this patch any more, because the next 79 update will not use the Default/
directory:
NOTE: If you have not already implemented a workaround to migrate data to the new directory, PLEASE AVOID DOING SO. The 79 update which fixes this bug will revert the migration to the old directory.
see 1033655#c97
We are not certain about what will implement finally as migration procedure... If they copy the Default/* files to root this solution is good and can be applied, if they do not copy these files all the new installations will lose their data... Don't you think?
I think all new and migrated data is »lost« if they ignore databases
and Local Storage
again. It will be possible with another patch of our app to ask the user, which version they would prefer, but it will annoy our user again.
I wouldn't ship this patch today (since #c97). I had considered two possible ways chromium could patch chrome/webview:
- Migrate the missing data to
Default/
- Let the data in the parent directory and migrate all directories back which would not exists in the parent directory.
This patch would work for both of them.
But they seem to prefer a third way (to only move the files back they've moved before) which would mess it up again – for every new user and all migrated user.
I hope the best, but I'm really not sure what to do now. I've hoped to modify this patch with a version which reverts the migrations for newer chrome versions. But without knowing what they will do and when they will ship it seems impossible. #111 sounds like they will ship in the next hours but #129 not.
One possible solution to cover most of the possibilities can be copy the Default/databases and Default/Local Sotorage back to root to have both directories populated awaiting whatever Google decides finally...
Take a look to my fork to see if it can be a solution: https://gist.github.com/biblioeteca/435045d15607eeae3bfb55696cdbbc91
We had discussed this approach today but weren't very happy with it. It could lead to a partial data loss for chrome 79 user, when the new chrome will be installed and user haven't (re-)started the app before or not often enough. If the app keeps started both databases derive and if the new chrome hits in the wrong moment data could be lost.
I'll hope that the chromium developer had understood our concerns and #c132 sounds for me like they have. If they decide to copy the data which does not exist in the new (= old) parent directory everything is fine. The renamed (old) directories would not prevent that and our user's data is saved.
I agree... Let's hope chromium people think like us.
#c139 sounds great and is one of my preferred solutions
Yes, it is for me too. I have just answered telling so.
Hello Dominik and thak you for the code.
How can we use it in a existing cordova project?