3

I want to backup my internal files. These are created by My App: random number of files and random names. Like data1.xml, data7,xml, data13.xml, ....

So I do not have any fixed file list.

When MyBackupAgentHelper::onCreate is running before the onBackup(), I can easily provide the filenames by querying the files getApplicationContext().fileList();

public class MyBackupAgentHelper extends BackupAgentHelper 
{
  @Override
  public void onCreate() 
  {
    String[] files  = getApplicationContext().fileList();
    FileBackupHelper helper = new FileBackupHelper(this, files );
    addHelper(FILES_BACKUP_KEY, helper);    
  }
...

However, if the onRestore is ready to run after an uninstall/re-install, I cannot provide the filenames in the onCreate as this time the getApplicationContext().fileList() returns empty list - obviously.

So nothing is restored :(

Is there any way to restore all files which were backuped without specifying the filenames? Just saying, "do it all".

If not, how could I use the Data Backup in this scenario?

Thanks

Zoli
  • 841
  • 8
  • 31
  • DID you solve this problem??? I am experiencing the same issue. How did you retrieve all files and then add them to the FileBackupHelper? other than using getApplicationContext.fileList() – coolcool1994 Dec 11 '14 at 11:07
  • Why would you use other way? That is the way to retrieve all the files. If you don't wanna backup all, just skip the unnecessary ones. This is simple, nothing tricky here. The trick is **in the restoring**. But for that, see the answer of this thread. – Zoli Dec 11 '14 at 15:14
  • Oh I see I misread that part. Cant we just save file list as string set in sharedpreference, and after we restore the sharedpreference, just restore the files? Wouldn't this be simpler than the below answer. – coolcool1994 Dec 11 '14 at 21:36
  • Maybe could work, but what if sharedpreference is restored after the files? The you'll have nothing. The solution below is simple in fact, just looks difficult :) – Zoli Dec 15 '14 at 15:33
  • I am sorry, but I don't see how the accepted-answer lists you the files that you are restoring. He is just passing "filename" to FileBackupHelper as the list of files when restoring. Don't you have many files to restore with specific names??? – coolcool1994 Dec 17 '14 at 21:30

3 Answers3

2

I just ran into the same problem. It's frustrating because FileBackupHelper does almost exactly what we want it to do.

If you look at the code for FileBackupHelper's restoreEntity function here

https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/core/java/android/app/backup/FileBackupHelper.java

public void restoreEntity(BackupDataInputStream data) {
    if (DEBUG) Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
    String key = data.getKey();
    if (isKeyInList(key, mFiles)) {
        File f = new File(mFilesDir, key);
        writeFile(f, data);
    }
}

...you can see that the only reason the files aren't being written is because they're not in the list that you passed to the FileBackupHelper constructor.

My first solution was to override isKeyInList to always return true. And that actually worked, but then it struck me as odd because isKeyInList has default protection and my FileBackupHelper subclass is not in the same package. It turns out this is some sort dalvik vm bug that allows this so I wouldn't want to rely on it (see Android method with default (package) visibility overriding (shouldn't work, but does - why?) )

But then I realized I could just hold on to the array of files that I passed to the FileBackupHelper constructor and then change the first element to always be the name of the file that wanted to be created. That way it would always be found in the list.

class MyFileBackupHelper extends FileBackupHelper
{
    String[] mMyFiles;
    MyFileBackupHelper(Context context, String... files)
    {
        super(context,files);
        mMyFiles = files;
    }

/*  boolean isKeyInList(String key, String[] list) 
    {       
        return true;
    }   */

    public void restoreEntity(BackupDataInputStream data) 
    {
        mMyFiles[0] = data.getKey();
        super.restoreEntity(data);
    }    
}

Of course this also relies on FileBackupHelper keeping the same implementation where it doesn't make a copy of the Files list. I'm not exactly sure why they went to so much trouble to prevent restoring arbitrary files, and maybe they'll try to thwart this solution later. But for now, I'm calling it good!

Oh yeah, one extra detail to making my solution work is that you need to make sure there's always one file in the list when you're restoring. That way there will always be an array element 0 to replace. This is what I did in my BackupAgent

public class MyBackupAgent extends BackupAgentHelper 
{   
    public void AddFileHelper(String files[])
    {
        FileBackupHelper aHelper = new MyFileBackupHelper(this,files);
        addHelper("userfiles", aHelper);        
    }

    @Override
    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException
    {
        String[] anArray = GetAllUserFiles(); // I'm not including this function for brevity
        AddFileHelper(anArray);
        super.onBackup(oldState, data, newState);       
    }

    @Override
    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException
    {
        AddFileHelper(new String[] { "filename" } );
        super.onRestore(data, appVersionCode, newState);
    }
}

So you see that I don't rely on onCreate(). Instead I put the correct files in the list in onBackup and I just put one filename in the list in onRestore. Then MyFileBackupHelper replaces array element 0 in that list every time before calling the parent restoreEntity. Hopefully google will let this solution continue to work in future version of their libraries since it seems like a nice feature to have!

Community
  • 1
  • 1
Brian Rothstein
  • 1,312
  • 10
  • 20
  • Thanks Brian. I skipped the backup that time, and I forgot this thread, just found your answer. A question, are the filenames correctly restored with this solution? – Zoli Sep 08 '14 at 14:15
  • Yes. The filenames do come back correctly. The array that you pass to the FileBackupHelper in onRestore is only used by the FileBackupHelper to check that the files that are coming back are the ones that you want. So the trick is to always put the name of each file that it's about to check into the filename array (which we can do in restoreEntity). – Brian Rothstein Sep 08 '14 at 23:08
  • Sorry for later reply again. So looking at your code, you save the filename into 1st item of you private mMyFiles array. But then it is not used anywhere? – Zoli Sep 27 '14 at 11:27
  • It's used by the FileBackupHelper class (which MyFileBackupHelper dervies from). FileBackupHelper only restores files whose names are in the array of files that you pass to it. That's why the trick is to hold on to a reference to that array that you pass to the FileBackupHelper and then place the filename into the array each time before calling its restoreEntity method (the data.getKey() is the filename.) – Brian Rothstein Oct 03 '14 at 06:37
  • According to the 4.2.2 source of FileBackupHelper, there's no `mMyFiles[]`, it has `mFiles[]`. In your code, your are only assigning values to mMyFiles (in Constructor and restoreEntity()), but not using to anything. I must be missing something... – Zoli Oct 04 '14 at 14:13
  • `mMyFiles` is a member variable I have in `MyFileBackupHelper` which extends `FileBackupHelper`. You can't get at `mFiles` because it's package protected, but `mFiles` is assigned the `files` array that you pass to the constructor so you just need to keep your own reference to that array. That's what `mMyFiles` is. – Brian Rothstein Oct 22 '14 at 20:25
  • Oh yep, goddamn Java, changing mMyFiles is the same as changing mFiles, both are a reference to the same array, now it's clear, thanks! – Zoli Nov 08 '14 at 23:04
  • You are passing "filename" as the only file name to be restored to the device. But how does this allow Zoli to restore all the files like data1.xml, data7,xml, data13.xml to be restored? When onRestored is called, there is nothing saved in the app. – coolcool1994 Dec 17 '14 at 21:42
  • I'm making the file list an array with one element ("filename") so that in MyFileBackupHelper's restoreEntity method I can execute `mMyFiles[0] = data.getKey();` without worrying that the array will be empty. Note, though, that I'm overriding this name on every call to restoreEntity. The reason you have to do this is that FileBackupHelper's restoreEntity will not restore files whose names are not in the files list. – Brian Rothstein Dec 17 '14 at 22:53
  • I am still confused. So how are you getting the file names data1.xml, data7,xml, data13.xml etc.. because you don't have any String[] of those names. Isn't this Zoli's question anyways? How does overriding FileBackUpHelper and all allow you to get the Sting[] of the file names that you are restoring?. – coolcool1994 Dec 18 '14 at 01:29
  • Every file that you backup is sent back to you by Google's backup service (when you perform a restore). It sends back the file names as `data.getKey()` in restoreEntity. The only problem is that the default FileBackupHelper only actually restores the files which are in the list that you provide to it. However, if you don't know the filenames in advance then this is a problem. My solution makes sure that every file given to restoreEntity is actually written out. I do this by putting the fileName in the files list every time restoreEntity is called (which is once per file). – Brian Rothstein Dec 18 '14 at 01:40
0

EDIT: You cannot backup folders - you need to individually list files in the file helper to backup those.

coolcool1994
  • 3,704
  • 4
  • 39
  • 43
  • Is it valid to pass a folder to `FileBackupHelper` constructor?? And it is recursive then? I did not know that. – Zoli Dec 15 '14 at 15:33
  • Isn't it valid to pass a folder to FileBackupHelper constructor? Are you only allowed to pass files? Maybe this is a reason why my sharedpreference is backing up but not my files? – coolcool1994 Dec 16 '14 at 12:08
  • FileBackupHelper only backs up and restores the files that you pass to it. It won't back up entire directories. See https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/core/java/android/app/backup/FileBackupHelper.java – Brian Rothstein Dec 16 '14 at 22:32
0

I realize this question is quite old, but I was running into a similar problem (wanting to back up an arbitrary set of files from a folder), and my solution was to take all of the files, and put them into a zip file, and the have the FileBackupHelper backup the zip file. For onRestore, after the .zip file gets restored, I extract the files back. This may not be the best solution, but it seems to work for me.

vewert
  • 113
  • 2
  • 7