mirror of
https://github.com/libretro/RetroArch
synced 2025-04-01 13:20:43 +00:00
Android: Add function to migrate RetroArch folder from sdcard.
This commit is contained in:
parent
850560d1b6
commit
b7235e426f
@ -24,6 +24,9 @@
|
|||||||
android:banner="@drawable/banner"
|
android:banner="@drawable/banner"
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
<activity
|
||||||
|
android:name=".browser.mainmenu.MigrateRetroarchFolderActivity"
|
||||||
|
android:exported="false"/>
|
||||||
<activity android:name="com.retroarch.browser.mainmenu.MainMenuActivity" android:exported="true" android:launchMode="singleInstance">
|
<activity android:name="com.retroarch.browser.mainmenu.MainMenuActivity" android:exported="true" android:launchMode="singleInstance">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
31
pkg/android/phoenix/res/values/strings.xml
Normal file
31
pkg/android/phoenix/res/values/strings.xml
Normal file
@ -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>
|
@ -12,14 +12,6 @@ import android.preference.PreferenceActivity;
|
|||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link PreferenceActivity} subclass that provides all of the
|
* {@link PreferenceActivity} subclass that provides all of the
|
||||||
* functionality of the main menu screen.
|
* functionality of the main menu screen.
|
||||||
@ -27,6 +19,7 @@ import android.util.Log;
|
|||||||
public final class MainMenuActivity extends PreferenceActivity
|
public final class MainMenuActivity extends PreferenceActivity
|
||||||
{
|
{
|
||||||
public static String PACKAGE_NAME;
|
public static String PACKAGE_NAME;
|
||||||
|
final int REQUEST_CODE_START = 120;
|
||||||
|
|
||||||
public void finalStartup()
|
public void finalStartup()
|
||||||
{
|
{
|
||||||
@ -63,6 +56,14 @@ public final class MainMenuActivity extends PreferenceActivity
|
|||||||
retro.putExtra("EXTERNAL", external);
|
retro.putExtra("EXTERNAL", external);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||||
|
{
|
||||||
|
if(requestCode == REQUEST_CODE_START) {
|
||||||
|
finalStartup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState)
|
public void onCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
@ -75,6 +76,7 @@ public final class MainMenuActivity extends PreferenceActivity
|
|||||||
|
|
||||||
UserPreferences.updateConfigFile(this);
|
UserPreferences.updateConfigFile(this);
|
||||||
|
|
||||||
finalStartup();
|
Intent i = new Intent(this, MigrateRetroarchFolderActivity.class);
|
||||||
|
startActivityForResult(i, REQUEST_CODE_START);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,266 @@
|
|||||||
|
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(true || 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(MigrateRetroarchFolderActivity.this).edit();
|
||||||
|
editor.putBoolean("external_retroarch_folder_needs_migrate", false);
|
||||||
|
editor.apply();
|
||||||
|
MigrateRetroarchFolderActivity.this.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(MigrateRetroarchFolderActivity.this).edit();
|
||||||
|
editor.putBoolean("external_retroarch_folder_needs_migrate", true);
|
||||||
|
editor.apply();
|
||||||
|
MigrateRetroarchFolderActivity.this.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)
|
||||||
|
{
|
||||||
|
//create destination folder
|
||||||
|
if(!(dest.isDirectory() || dest.mkdirs())) {
|
||||||
|
Log.e("MigrateRetroarchFolder", "Couldn't make new destination folder " + dest.getPath());
|
||||||
|
error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
copyFolder(childUri, destFile);
|
||||||
|
}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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user