320 lines
12 KiB
Java
320 lines
12 KiB
Java
package mindustry.android;
|
|
|
|
import android.*;
|
|
import android.app.*;
|
|
import android.content.*;
|
|
import android.content.pm.*;
|
|
import android.net.*;
|
|
import android.os.Build.*;
|
|
import android.os.*;
|
|
import android.telephony.*;
|
|
import arc.*;
|
|
import arc.backend.android.*;
|
|
import arc.files.*;
|
|
import arc.func.*;
|
|
import arc.scene.ui.layout.*;
|
|
import arc.util.*;
|
|
import dalvik.system.*;
|
|
import mindustry.*;
|
|
import mindustry.game.Saves.*;
|
|
import mindustry.io.*;
|
|
import mindustry.net.*;
|
|
import mindustry.ui.dialogs.*;
|
|
|
|
import java.io.*;
|
|
import java.lang.Thread.*;
|
|
import java.util.*;
|
|
|
|
import static mindustry.Vars.*;
|
|
|
|
public class AndroidLauncher extends AndroidApplication{
|
|
public static final int PERMISSION_REQUEST_CODE = 1;
|
|
boolean doubleScaleTablets = true;
|
|
FileChooser chooser;
|
|
Runnable permCallback;
|
|
|
|
Object gpService;
|
|
Class<?> serviceClass;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState){
|
|
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
|
|
|
Thread.setDefaultUncaughtExceptionHandler((thread, error) -> {
|
|
CrashSender.log(error);
|
|
|
|
//try to forward exception to system handler
|
|
if(handler != null){
|
|
handler.uncaughtException(thread, error);
|
|
}else{
|
|
Log.err(error);
|
|
System.exit(1);
|
|
}
|
|
});
|
|
|
|
super.onCreate(savedInstanceState);
|
|
if(doubleScaleTablets && isTablet(this)){
|
|
Scl.setAddition(0.5f);
|
|
}
|
|
|
|
initialize(new ClientLauncher(){
|
|
|
|
@Override
|
|
public void hide(){
|
|
moveTaskToBack(true);
|
|
}
|
|
|
|
@Override
|
|
public rhino.Context getScriptContext(){
|
|
return AndroidRhinoContext.enter(getCacheDir());
|
|
}
|
|
|
|
@Override
|
|
public void shareFile(Fi file){
|
|
}
|
|
|
|
@Override
|
|
public ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{
|
|
return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent){
|
|
@Override
|
|
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
|
|
//check for loaded state
|
|
Class<?> loadedClass = findLoadedClass(name);
|
|
if(loadedClass == null){
|
|
try{
|
|
//try to load own class first
|
|
loadedClass = findClass(name);
|
|
}catch(ClassNotFoundException | NoClassDefFoundError e){
|
|
//use parent if not found
|
|
return parent.loadClass(name);
|
|
}
|
|
}
|
|
|
|
if(resolve){
|
|
resolveClass(loadedClass);
|
|
}
|
|
return loadedClass;
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public void showFileChooser(boolean open, String title, String extension, Cons<Fi> cons){
|
|
showFileChooser(open, title, cons, extension);
|
|
}
|
|
|
|
void showFileChooser(boolean open, String title, Cons<Fi> cons, String... extensions){
|
|
String extension = extensions[0];
|
|
|
|
if(VERSION.SDK_INT >= VERSION_CODES.Q){
|
|
Intent intent = new Intent(open ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT);
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
intent.setType(extension.equals("zip") && !open && extensions.length == 1 ? "application/zip" : "*/*");
|
|
|
|
addResultListener(i -> startActivityForResult(intent, i), (code, in) -> {
|
|
if(code == Activity.RESULT_OK && in != null && in.getData() != null){
|
|
Uri uri = in.getData();
|
|
|
|
if(uri.getPath().contains("(invalid)")) return;
|
|
|
|
Core.app.post(() -> Core.app.post(() -> cons.get(new Fi(uri.getPath()){
|
|
@Override
|
|
public InputStream read(){
|
|
try{
|
|
return getContentResolver().openInputStream(uri);
|
|
}catch(IOException e){
|
|
throw new ArcRuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public OutputStream write(boolean append){
|
|
try{
|
|
return getContentResolver().openOutputStream(uri);
|
|
}catch(IOException e){
|
|
throw new ArcRuntimeException(e);
|
|
}
|
|
}
|
|
})));
|
|
}
|
|
});
|
|
}else if(VERSION.SDK_INT >= VERSION_CODES.M && !(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
|
|
checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){
|
|
chooser = new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), open, file -> {
|
|
if(!open){
|
|
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
|
|
}else{
|
|
cons.get(file);
|
|
}
|
|
});
|
|
|
|
ArrayList<String> perms = new ArrayList<>();
|
|
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
|
|
perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
|
}
|
|
if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
|
|
perms.add(Manifest.permission.READ_EXTERNAL_STORAGE);
|
|
}
|
|
requestPermissions(perms.toArray(new String[0]), PERMISSION_REQUEST_CODE);
|
|
}else{
|
|
if(open){
|
|
new FileChooser(title, file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
|
|
}else{
|
|
super.showFileChooser(open, "@open", extension, cons);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void showMultiFileChooser(Cons<Fi> cons, String... extensions){
|
|
showFileChooser(true, "@open", cons, extensions);
|
|
}
|
|
|
|
@Override
|
|
public void beginForceLandscape(){
|
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
|
}
|
|
|
|
@Override
|
|
public void endForceLandscape(){
|
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
|
|
}
|
|
|
|
}, new AndroidApplicationConfiguration(){{
|
|
useImmersiveMode = true;
|
|
hideStatusBar = true;
|
|
}});
|
|
checkFiles(getIntent());
|
|
|
|
try{
|
|
//new external folder
|
|
Fi data = Core.files.absolute(((Context)this).getExternalFilesDir(null).getAbsolutePath());
|
|
Core.settings.setDataDirectory(data);
|
|
|
|
//delete unused cache folder to free up space
|
|
try{
|
|
Fi cache = Core.settings.getDataDirectory().child("cache");
|
|
if(cache.exists()){
|
|
cache.deleteDirectory();
|
|
}
|
|
}catch(Throwable t){
|
|
Log.err("Failed to delete cached folder", t);
|
|
}
|
|
|
|
|
|
//move to internal storage if there's no file indicating that it moved
|
|
if(!Core.files.local("files_moved").exists()){
|
|
Log.info("Moving files to external storage...");
|
|
|
|
try{
|
|
//current local storage folder
|
|
Fi src = Core.files.absolute(Core.files.getLocalStoragePath());
|
|
for(Fi fi : src.list()){
|
|
fi.copyTo(data);
|
|
}
|
|
//create marker
|
|
Core.files.local("files_moved").writeString("files moved to " + data);
|
|
Core.files.local("files_moved_103").writeString("files moved again");
|
|
Log.info("Files moved.");
|
|
}catch(Throwable t){
|
|
Log.err("Failed to move files!");
|
|
t.printStackTrace();
|
|
}
|
|
}
|
|
}catch(Exception e){
|
|
//print log but don't crash
|
|
Log.err(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){
|
|
if(requestCode == PERMISSION_REQUEST_CODE){
|
|
for(int i : grantResults){
|
|
if(i != PackageManager.PERMISSION_GRANTED) return;
|
|
}
|
|
if(chooser != null){
|
|
Core.app.post(chooser::show);
|
|
}
|
|
if(permCallback != null){
|
|
Core.app.post(permCallback);
|
|
permCallback = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onResume(){
|
|
super.onResume();
|
|
|
|
//TODO enable once GPGS is set up on the GP console
|
|
if(false && getPackageName().endsWith(".gp")){
|
|
try{
|
|
if(gpService == null){
|
|
serviceClass = Class.forName("mindustry.android.GPGameService");
|
|
gpService = serviceClass.getConstructor().newInstance();
|
|
}
|
|
serviceClass.getMethod("onResume", Context.class).invoke(gpService, this);
|
|
}catch(Exception e){
|
|
Log.err("Failed to update Google Play Services", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkFiles(Intent intent){
|
|
try{
|
|
Uri uri = intent.getData();
|
|
if(uri != null){
|
|
File myFile = null;
|
|
String scheme = uri.getScheme();
|
|
if(scheme.equals("file")){
|
|
String fileName = uri.getEncodedPath();
|
|
myFile = new File(fileName);
|
|
}else if(!scheme.equals("content")){
|
|
//error
|
|
return;
|
|
}
|
|
boolean save = uri.getPath().endsWith(saveExtension);
|
|
boolean map = uri.getPath().endsWith(mapExtension);
|
|
InputStream inStream;
|
|
if(myFile != null) inStream = new FileInputStream(myFile);
|
|
else inStream = getContentResolver().openInputStream(uri);
|
|
Core.app.post(() -> Core.app.post(() -> {
|
|
if(save){ //open save
|
|
System.out.println("Opening save.");
|
|
Fi file = Core.files.local("temp-save." + saveExtension);
|
|
file.write(inStream, false);
|
|
if(SaveIO.isSaveValid(file)){
|
|
try{
|
|
SaveSlot slot = control.saves.importSave(file);
|
|
ui.load.runLoadSave(slot);
|
|
}catch(IOException e){
|
|
ui.showException("@save.import.fail", e);
|
|
}
|
|
}else{
|
|
ui.showErrorMessage("@save.import.invalid");
|
|
}
|
|
}else if(map){ //open map
|
|
Fi file = Core.files.local("temp-map." + mapExtension);
|
|
file.write(inStream, false);
|
|
Core.app.post(() -> {
|
|
System.out.println("Opening map.");
|
|
if(!ui.editor.isShown()){
|
|
ui.editor.show();
|
|
}
|
|
ui.editor.beginEditMap(file);
|
|
});
|
|
}
|
|
}));
|
|
}
|
|
}catch(IOException e){
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private boolean isTablet(Context context){
|
|
TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
|
|
return manager != null && manager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE;
|
|
}
|
|
}
|