-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Android: Add function to migrate RetroArch folder from sdcard.
- Loading branch information
cwyc
committed
Mar 31, 2024
1 parent
25d5e2a
commit 58d26ce
Showing
4 changed files
with
310 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<resources> | ||
<string name="migrate_retroarch_folder_dialog_title"> | ||
Migrate RetroArch Folder | ||
</string> | ||
<string name="migrate_retroarch_folder_dialog_message"> | ||
Because RetroArch was updated, the location of the RetroArch folder has changed. \n | ||
Would you like to import data from an existing RetroArch folder? | ||
</string> | ||
<string name="migrate_retroarch_folder_dialog_positive"> | ||
Yes, select existing RetroArch folder | ||
</string> | ||
<string name="migrate_retroarch_folder_dialog_negative"> | ||
No, don\'t ask again | ||
</string> | ||
<string name="migrate_retroarch_folder_dialog_neutral"> | ||
No, ask next time | ||
</string> | ||
<string name="migrate_retroarch_folder_inprogress"> | ||
Copying RetroArch Files… | ||
</string> | ||
<string name="migrate_retroarch_folder_confirm"> | ||
Your RetroArch folder has been migrated. \n | ||
You can find it in the files app under RetroArch > User Data. | ||
</string> | ||
<string name="migrate_retroarch_folder_confirm_witherror"> | ||
Your RetroArch folder has been migrated. \n | ||
You can find it in the files app under RetroArch > User Data. \n | ||
There were errors copying some files. | ||
</string> | ||
</resources> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
265 changes: 265 additions & 0 deletions
265
pkg/android/phoenix/src/com/retroarch/browser/mainmenu/MigrateRetroarchFolderActivity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
package com.retroarch.browser.mainmenu; | ||
|
||
import android.annotation.TargetApi; | ||
import android.app.Activity; | ||
import android.app.AlertDialog; | ||
import android.app.ProgressDialog; | ||
import android.content.ContentResolver; | ||
import android.content.DialogInterface; | ||
import android.content.Intent; | ||
import android.content.SharedPreferences; | ||
import android.content.pm.PackageInfo; | ||
import android.content.pm.PackageManager; | ||
import android.database.Cursor; | ||
import android.net.Uri; | ||
import android.os.AsyncTask; | ||
import android.os.Environment; | ||
import android.os.ParcelFileDescriptor; | ||
import android.preference.PreferenceManager; | ||
import android.provider.DocumentsContract; | ||
import android.util.Log; | ||
import android.util.Pair; | ||
|
||
import com.retroarch.R; | ||
|
||
import java.io.File; | ||
import java.lang.ref.WeakReference; | ||
import java.nio.file.Files; | ||
import java.nio.file.StandardCopyOption; | ||
import java.util.ArrayList; | ||
|
||
@TargetApi(26) | ||
public class MigrateRetroarchFolderActivity extends Activity | ||
{ | ||
final int REQUEST_CODE_GET_OLD_RETROARCH_FOLDER = 125; | ||
|
||
@Override | ||
public void onStart() | ||
{ | ||
super.onStart(); | ||
|
||
// Needs v26 for some of the file handling functions below. | ||
// Remove the TargetApi annotation to see which. | ||
// If we don't have it, then just skip migration. | ||
if (android.os.Build.VERSION.SDK_INT < 26) { | ||
finish(); | ||
} | ||
if(needToMigrate()){ | ||
askToMigrate(); | ||
}else{ | ||
finish(); | ||
} | ||
} | ||
|
||
boolean needToMigrate() | ||
{ | ||
// As the RetroArch folder has been moved from shared storage to app-specific storage, | ||
// people upgrading from older versions using the old location will need to migrate their data. | ||
// We identify these users by checking that the app has been updated from an older version, | ||
// and that the older version did not use the new location. | ||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); | ||
boolean isNewInstall; | ||
try{ | ||
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0); | ||
isNewInstall = info.firstInstallTime == info.lastUpdateTime; | ||
}catch(PackageManager.NameNotFoundException ex) { | ||
isNewInstall = true; | ||
} | ||
|
||
// Avoid asking if new install | ||
if(isNewInstall && !prefs.contains("external_retroarch_folder_needs_migrate")){ | ||
SharedPreferences.Editor editor = prefs.edit(); | ||
editor.putBoolean("external_retroarch_folder_needs_migrate", false); | ||
editor.apply(); | ||
} | ||
|
||
return prefs.getBoolean("external_retroarch_folder_needs_migrate", true); | ||
} | ||
|
||
void askToMigrate() | ||
{ | ||
AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||
builder.setTitle(R.string.migrate_retroarch_folder_dialog_title); | ||
builder.setMessage(R.string.migrate_retroarch_folder_dialog_message); | ||
builder.setNegativeButton(R.string.migrate_retroarch_folder_dialog_negative, new DialogInterface.OnClickListener() { | ||
@Override public void onClick(DialogInterface dialogInterface, int i) { | ||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getParent()).edit(); | ||
editor.putBoolean("external_retroarch_folder_needs_migrate", false); | ||
editor.apply(); | ||
dialogInterface.dismiss(); | ||
finish(); | ||
} | ||
}); | ||
builder.setNeutralButton(R.string.migrate_retroarch_folder_dialog_neutral, new DialogInterface.OnClickListener() { | ||
@Override public void onClick(DialogInterface dialogInterface, int i) { | ||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getParent()).edit(); | ||
editor.putBoolean("external_retroarch_folder_needs_migrate", true); | ||
editor.apply(); | ||
finish(); | ||
} | ||
}); | ||
builder.setPositiveButton(R.string.migrate_retroarch_folder_dialog_positive, new DialogInterface.OnClickListener() { | ||
@Override public void onClick(DialogInterface dialogInterface, int i) { | ||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); | ||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.fromFile(new File( | ||
Environment.getExternalStorageDirectory().getAbsolutePath() + "/RetroArch" | ||
))); | ||
startActivityForResult(intent, REQUEST_CODE_GET_OLD_RETROARCH_FOLDER); | ||
} | ||
}); | ||
AlertDialog dialog = builder.create(); | ||
dialog.show(); | ||
} | ||
|
||
public void onActivityResult(int requestCode, int resultCode, Intent resultData) | ||
{ | ||
super.onActivityResult(requestCode, resultCode, resultData); | ||
if(requestCode == REQUEST_CODE_GET_OLD_RETROARCH_FOLDER){ | ||
if(resultCode == Activity.RESULT_OK && resultData != null){ | ||
copyFiles(resultData.getData()); | ||
}else{ | ||
//User cancelled or otherwise failed. Go back to the picker screen. | ||
askToMigrate(); | ||
} | ||
} | ||
} | ||
|
||
void copyFiles(Uri sourceDir) | ||
{ | ||
final ProgressDialog pd = new ProgressDialog(this); | ||
pd.setMax(100); | ||
pd.setTitle(R.string.migrate_retroarch_folder_inprogress); | ||
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); | ||
pd.setCancelable(false); | ||
|
||
CopyThread thread = new CopyThread() | ||
{ | ||
@Override | ||
protected void onPreExecute(){ | ||
super.onPreExecute(); | ||
pd.show(); | ||
} | ||
@Override | ||
protected void onProgressUpdate(Pair<Integer, String>... params) | ||
{ | ||
super.onProgressUpdate(params); | ||
pd.setProgress(params[0].first); | ||
pd.setMessage(params[0].second); | ||
} | ||
@Override | ||
protected void onPostExecute(Boolean ok) | ||
{ | ||
super.onPostExecute(ok); | ||
pd.dismiss(); | ||
postMigrate(ok); | ||
} | ||
}; | ||
|
||
thread.execute(sourceDir); | ||
} | ||
|
||
void postMigrate(boolean ok) | ||
{ | ||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); | ||
editor.putBoolean("external_retroarch_folder_needs_migrate", false); | ||
editor.commit(); | ||
|
||
AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||
builder.setMessage(ok ? | ||
R.string.migrate_retroarch_folder_confirm : | ||
R.string.migrate_retroarch_folder_confirm_witherror | ||
); | ||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { | ||
@Override public void onClick(DialogInterface dialogInterface, int i) { | ||
MigrateRetroarchFolderActivity.this.finish(); | ||
} | ||
}); | ||
builder.create().show(); | ||
} | ||
|
||
class CopyThread extends AsyncTask<Uri, Pair<Integer, String>, Boolean> | ||
{ | ||
String PACKAGE_NAME; | ||
ContentResolver resolver; | ||
Uri sourceRoot; | ||
boolean error; | ||
ArrayList<int[]> progress; | ||
public CopyThread() | ||
{ | ||
PACKAGE_NAME = MigrateRetroarchFolderActivity.this.getPackageName(); | ||
resolver = MigrateRetroarchFolderActivity.this.getContentResolver(); | ||
} | ||
@Override | ||
protected Boolean doInBackground(Uri... params) | ||
{ | ||
sourceRoot = params[0]; | ||
error = false; | ||
progress = new ArrayList<>(); | ||
|
||
String destination = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + PACKAGE_NAME + "/files/RetroArch"; | ||
copyFolder(sourceRoot, new File(destination)); | ||
return !error; | ||
} | ||
void copyFolder(Uri sourceUri, File dest) | ||
{ | ||
Uri sourceChildrenResolver; | ||
try{ //for subfolders | ||
sourceChildrenResolver = DocumentsContract.buildChildDocumentsUriUsingTree(sourceUri, DocumentsContract.getDocumentId(sourceUri)); | ||
}catch(IllegalArgumentException ex){ //for root selected by document picker | ||
sourceChildrenResolver = DocumentsContract.buildChildDocumentsUriUsingTree(sourceUri, DocumentsContract.getTreeDocumentId(sourceUri)); | ||
} | ||
progress.add(new int[]{0, 1}); | ||
try( | ||
Cursor c = resolver.query(sourceChildrenResolver, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null) | ||
) { | ||
if(c == null) { | ||
Log.e("MigrateRetroarchFolder", "Could not list files in source folder " + sourceUri.toString()); | ||
error = true; | ||
return; | ||
} | ||
progress.get(progress.size() - 1)[1] = c.getCount(); | ||
while(c.moveToNext()){ //loop through children returned | ||
String childFilename = c.getString(1); | ||
Uri childUri = DocumentsContract.buildDocumentUriUsingTree(sourceUri, c.getString(0)); | ||
String childDocumentId = DocumentsContract.getDocumentId(childUri); | ||
File destFile = new File(dest, childFilename); | ||
|
||
if(c.getString(2).equals(DocumentsContract.Document.MIME_TYPE_DIR)){ //is a folder, recurse | ||
if(destFile.isDirectory() || destFile.mkdirs()){ | ||
copyFolder(childUri, destFile); | ||
}else { | ||
Log.e("MigrateRetroarchFolder", "Couldn't make new destination folder " + destFile.getPath()); | ||
error = true; | ||
} | ||
}else{ //is a file, copy it | ||
try( | ||
ParcelFileDescriptor pfd = resolver.openFileDescriptor(childUri, "r"); | ||
ParcelFileDescriptor.AutoCloseInputStream sourceStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); | ||
) { | ||
Files.copy(sourceStream, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING); | ||
}catch(Exception ex){ | ||
Log.e("MigrateRetroarchFolder", "Error copying file " + childDocumentId, ex); | ||
error = true; | ||
} | ||
} | ||
progress.get(progress.size() - 1)[0]++; | ||
publishProgress(new Pair<Integer, String>(getProgressPercentage(), destFile.getPath())); | ||
} | ||
}catch(Exception ex){ | ||
Log.e("MigrateRetroarchFolder", "Error while copying", ex); | ||
error = true; | ||
} | ||
progress.remove(progress.size() - 1); | ||
} | ||
int getProgressPercentage() | ||
{ | ||
float sum = 0; | ||
int lastDenominator = 1; | ||
for(int[] frac : progress){ | ||
sum += ((float) frac[0]) / frac[1] / lastDenominator; | ||
lastDenominator *= frac[1]; | ||
} | ||
return (int) (sum * 100); | ||
} | ||
} | ||
} |