Skip to content

Instantly share code, notes, and snippets.

@dpesch
Last active December 16, 2019 21:12
Show Gist options
  • Save dpesch/a3fc3b7611068dd8b27bda9380ae07ac to your computer and use it in GitHub Desktop.
Save dpesch/a3fc3b7611068dd8b27bda9380ae07ac to your computer and use it in GitHub Desktop.
Chrome `79.0.3945.79` update hotfix to rescue user data
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));
}
}
@biblioeteca
Copy link

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

@dpesch
Copy link
Author

dpesch commented Dec 16, 2019

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.

@biblioeteca
Copy link

I agree... Let's hope chromium people think like us.

@dpesch
Copy link
Author

dpesch commented Dec 16, 2019

#c139 sounds great and is one of my preferred solutions

@biblioeteca
Copy link

biblioeteca commented Dec 16, 2019

Yes, it is for me too. I have just answered telling so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment