mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-30 08:34:16 +01:00
use ContentResolver to export and import backups
This is the kosher way now, and solves problems with accessing Downloads or other public directories on newer Android versions.
This commit is contained in:
parent
c329b1bab4
commit
a620ae4afc
5 changed files with 96 additions and 70 deletions
|
@ -32,6 +32,7 @@ import android.database.sqlite.SQLiteStatement;
|
|||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
|
||||
|
@ -52,8 +53,9 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
@ -1851,38 +1853,61 @@ public class DBUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean loadDB( Context context )
|
||||
public static boolean loadDB( Context context, Uri uri )
|
||||
{
|
||||
boolean success = copyGameDB( context, false );
|
||||
boolean success = false;
|
||||
try ( InputStream is = context
|
||||
.getContentResolver().openInputStream(uri) ) {
|
||||
String name = DBHelper.getDBName();
|
||||
File gamesDB = context.getDatabasePath( name );
|
||||
FileOutputStream fos = new FileOutputStream( gamesDB );
|
||||
success = copyStream( fos, is );
|
||||
invalGroupsCache();
|
||||
} catch ( Exception ex ) {
|
||||
Log.ex( TAG, ex );
|
||||
}
|
||||
|
||||
if ( success ) {
|
||||
PrefsDelegate.loadPrefs( context );
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static boolean saveDB( Context context )
|
||||
public static boolean saveDB( Context context, Uri uri )
|
||||
{
|
||||
PrefsDelegate.savePrefs( context );
|
||||
return copyGameDB( context, true );
|
||||
boolean success = false;
|
||||
try ( OutputStream os = context.getContentResolver().openOutputStream( uri ) ) {
|
||||
String name = DBHelper.getDBName();
|
||||
File gamesDB = context.getDatabasePath( name );
|
||||
FileInputStream fis = new FileInputStream( gamesDB );
|
||||
success = copyStream( os, fis );
|
||||
} catch ( Exception ex ) {
|
||||
Log.ex( TAG, ex );
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static boolean copyFileStream( FileOutputStream fos,
|
||||
FileInputStream fis )
|
||||
public static boolean copyStream( OutputStream fos, InputStream fis )
|
||||
{
|
||||
boolean success = false;
|
||||
FileChannel channelSrc = null;
|
||||
FileChannel channelDest = null;
|
||||
byte[] buf = new byte[1024*8];
|
||||
try {
|
||||
channelSrc = fis.getChannel();
|
||||
channelDest = fos.getChannel();
|
||||
channelSrc.transferTo( 0, channelSrc.size(), channelDest );
|
||||
for ( ; ; ) {
|
||||
int nRead = fis.read( buf );
|
||||
if ( 0 >= nRead ) {
|
||||
break;
|
||||
}
|
||||
fos.write( buf, 0, nRead );
|
||||
}
|
||||
success = true;
|
||||
Log.d( TAG, "copyFileStream(): copied %s to %s", fis, fos );
|
||||
} catch( java.io.IOException ioe ) {
|
||||
Log.ex( TAG, ioe );
|
||||
} finally {
|
||||
try {
|
||||
channelSrc.close();
|
||||
channelDest.close();
|
||||
fos.close();
|
||||
fis.close();
|
||||
} catch( java.io.IOException ioe ) {
|
||||
Log.ex( TAG, ioe );
|
||||
}
|
||||
|
@ -2448,37 +2473,6 @@ public class DBUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean copyGameDB( Context context, boolean toSDCard )
|
||||
{
|
||||
boolean success = false;
|
||||
String name = DBHelper.getDBName();
|
||||
File gamesDB = context.getDatabasePath( name );
|
||||
|
||||
// Use the variant name EXCEPT where we're copying from sdCard and
|
||||
// only the older name exists.
|
||||
File sdcardDB = new File( Environment.getExternalStorageDirectory(),
|
||||
getVariantDBName() );
|
||||
if ( !toSDCard && !sdcardDB.exists() ) {
|
||||
sdcardDB = new File( Environment.getExternalStorageDirectory(),
|
||||
name );
|
||||
}
|
||||
|
||||
try {
|
||||
File srcDB = toSDCard? gamesDB : sdcardDB;
|
||||
if ( srcDB.exists() ) {
|
||||
FileInputStream src = new FileInputStream( srcDB );
|
||||
FileOutputStream dest =
|
||||
new FileOutputStream( toSDCard? sdcardDB : gamesDB );
|
||||
copyFileStream( dest, src );
|
||||
invalGroupsCache();
|
||||
success = true;
|
||||
}
|
||||
} catch( java.io.FileNotFoundException fnfe ) {
|
||||
Log.ex( TAG, fnfe );
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Copy my .apk to the Downloads directory, from which a user could more
|
||||
// easily share it with somebody else. Should be blocked for apks
|
||||
// installed from the Play store since viral distribution isn't allowed,
|
||||
|
@ -2499,7 +2493,7 @@ public class DBUtils {
|
|||
|
||||
FileInputStream src = new FileInputStream( srcPath );
|
||||
FileOutputStream dest = new FileOutputStream( destPath );
|
||||
copyFileStream( dest, src );
|
||||
copyStream( dest, src );
|
||||
} catch ( Exception ex ) {
|
||||
Log.e( TAG, "copyApkToDownloads(): got ex: %s", ex );
|
||||
}
|
||||
|
|
|
@ -303,7 +303,7 @@ public class DictUtils {
|
|||
? context.openFileOutput( name, Context.MODE_PRIVATE )
|
||||
: new FileOutputStream( getDictFile( context, name, to ) );
|
||||
|
||||
success = DBUtils.copyFileStream( fos, fis );
|
||||
success = DBUtils.copyStream( fos, fis );
|
||||
} catch ( java.io.FileNotFoundException fnfe ) {
|
||||
Log.ex( TAG, fnfe );
|
||||
}
|
||||
|
@ -663,7 +663,7 @@ public class DictUtils {
|
|||
{
|
||||
File result = null;
|
||||
outer:
|
||||
for ( int attempt = 0; attempt < 4; ++attempt ) {
|
||||
for ( int attempt = 0; ; ++attempt ) {
|
||||
switch ( attempt ) {
|
||||
case 0:
|
||||
String myPath = XWPrefs.getMyDownloadDir( context );
|
||||
|
@ -679,7 +679,6 @@ public class DictUtils {
|
|||
result = s_dirGetter.getDownloadDir();
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if ( !haveWriteableSD() ) {
|
||||
continue;
|
||||
}
|
||||
|
@ -689,6 +688,8 @@ public class DictUtils {
|
|||
result = new File( result, "download/" );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break outer;
|
||||
}
|
||||
|
||||
// Exit test for loop
|
||||
|
|
|
@ -1522,21 +1522,6 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
rematchWithNameAndPerm( true, params );
|
||||
break;
|
||||
|
||||
case STORAGE_CONFIRMED:
|
||||
int id = (Integer)params[0];
|
||||
if ( R.id.games_menu_loaddb == id ) {
|
||||
DBUtils.loadDB( m_activity );
|
||||
storeGroupPositions( null );
|
||||
mkListAdapter();
|
||||
} else if ( R.id.games_menu_storedb == id ) {
|
||||
int msgID = DBUtils.saveDB( m_activity )
|
||||
? R.string.db_store_done : R.string.db_store_failed;
|
||||
showToast( msgID );
|
||||
} else {
|
||||
Assert.failDbg();
|
||||
}
|
||||
break;
|
||||
|
||||
case APPLY_CONFIG:
|
||||
Uri data = Uri.parse( (String)params[0] );
|
||||
CommonPrefs.loadColorPrefs( m_activity, data );
|
||||
|
@ -1561,6 +1546,43 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
return handled;
|
||||
}
|
||||
|
||||
private void startLoadOrStore( boolean isStore )
|
||||
{
|
||||
String intentAction = null;
|
||||
RequestCode rq = null;
|
||||
if ( isStore ) {
|
||||
intentAction = Intent.ACTION_CREATE_DOCUMENT;
|
||||
rq = RequestCode.STORE_DATA_FILE;
|
||||
} else {
|
||||
intentAction = Intent.ACTION_OPEN_DOCUMENT;
|
||||
rq = RequestCode.LOAD_DATA_FILE;
|
||||
}
|
||||
Intent intent = new Intent( intentAction );
|
||||
intent.addCategory( Intent.CATEGORY_OPENABLE );
|
||||
intent.setType( "application/octet-stream" );
|
||||
if ( isStore ) {
|
||||
intent.putExtra( Intent.EXTRA_TITLE, DBHelper.getDBName() );
|
||||
}
|
||||
startActivityForResult( intent, rq );
|
||||
}
|
||||
|
||||
private void handleLoadOrStoreResult( Uri uri, boolean isStore )
|
||||
{
|
||||
if ( isStore ) {
|
||||
boolean saved = DBUtils.saveDB( m_activity, uri );
|
||||
int msgID = saved ? R.string.db_store_done
|
||||
: R.string.db_store_failed;
|
||||
showToast( msgID );
|
||||
} else {
|
||||
if ( DBUtils.loadDB( m_activity, uri ) ) {
|
||||
storeGroupPositions( null );
|
||||
mkListAdapter();
|
||||
// We really want to exit the app!!! PENDING
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onNegButton( Action action, Object[] params )
|
||||
{
|
||||
|
@ -1602,6 +1624,14 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
launchGame( rowID );
|
||||
}
|
||||
break;
|
||||
case STORE_DATA_FILE:
|
||||
case LOAD_DATA_FILE:
|
||||
if ( Activity.RESULT_OK == resultCode && data != null ) {
|
||||
boolean isStore = RequestCode.STORE_DATA_FILE == requestCode;
|
||||
Uri uri = data.getData();
|
||||
handleLoadOrStoreResult( uri, isStore );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1813,8 +1843,7 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
|
||||
case R.id.games_menu_loaddb:
|
||||
case R.id.games_menu_storedb:
|
||||
Perms23.tryGetPerms( this, Perm.STORAGE, null,
|
||||
Action.STORAGE_CONFIRMED, itemID );
|
||||
startLoadOrStore( R.id.games_menu_storedb == itemID );
|
||||
break;
|
||||
|
||||
case R.id.games_menu_writegit:
|
||||
|
|
|
@ -42,6 +42,8 @@ public enum RequestCode {
|
|||
// Games list
|
||||
REQUEST_LANG_GL,
|
||||
CONFIG_GAME,
|
||||
STORE_DATA_FILE,
|
||||
LOAD_DATA_FILE,
|
||||
|
||||
// SMSInviteDelegate
|
||||
GET_CONTACT,
|
||||
|
|
|
@ -2278,8 +2278,8 @@
|
|||
<string name="mqtt_port">MQTT port</string>
|
||||
<string name="mqtt_qos">MQTT QOS</string>
|
||||
<string name="name_dict_fmt">%1$s/%2$s</string>
|
||||
<string name="gamel_menu_storedb">Write games to SD card</string>
|
||||
<string name="gamel_menu_loaddb">Load games from SD card</string>
|
||||
<string name="gamel_menu_storedb">Export app data</string>
|
||||
<string name="gamel_menu_loaddb">Import app data</string>
|
||||
<string name="gamel_menu_writegit">Copy git info to clipboard</string>
|
||||
<string name="enable_pending_count_title">Show Pending messages</string>
|
||||
<string name="enable_pending_count_summary">Show number not yet acknowledged</string>
|
||||
|
@ -2307,8 +2307,8 @@
|
|||
<string name="pref_item_update_summary">Get intermediate builds</string>
|
||||
<string name="checking_title">Checking</string>
|
||||
<string name="checking_for_fmt">Checking for wordlists in %1$s…</string>
|
||||
<string name="db_store_done">SD card write complete</string>
|
||||
<string name="db_store_failed">SD card write failed</string>
|
||||
<string name="db_store_done">Export complete</string>
|
||||
<string name="db_store_failed">Export failed</string>
|
||||
<string name="confirm_drop_mqtt">Are you sure you want to drop this
|
||||
game’s ability to communicate via the internet?</string>
|
||||
<string name="confirm_drop_relay_bt">Bluetooth only works for nearby
|
||||
|
|
Loading…
Add table
Reference in a new issue