Android fixes

This commit is contained in:
Anuken
2019-12-25 12:06:19 -05:00
parent 475794640d
commit 03342dc2f5
9 changed files with 21 additions and 19 deletions

View File

@@ -0,0 +1,224 @@
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.provider.Settings.*;
import android.telephony.*;
import arc.*;
import arc.backend.android.*;
import arc.files.*;
import arc.func.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.serialization.*;
import mindustry.*;
import mindustry.game.Saves.*;
import mindustry.io.*;
import mindustry.ui.dialogs.*;
import java.io.*;
import java.lang.System;
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;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if(doubleScaleTablets && isTablet(this.getContext())){
Scl.setAddition(0.5f);
}
initialize(new ClientLauncher(){
@Override
public void hide(){
moveTaskToBack(true);
}
@Override
public String getUUID(){
try{
String s = Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID);
int len = s.length();
byte[] data = new byte[len / 2];
for(int i = 0; i < len; i += 2){
data[i / 2] = (byte)((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
String result = new String(Base64Coder.encode(data));
if(result.equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID.");
return result;
}catch(Exception e){
return super.getUUID();
}
}
@Override
public org.mozilla.javascript.Context getScriptContext(){
return AndroidRhinoContext.enter(getContext().getCacheDir());
}
@Override
public void shareFile(Fi file){
}
@Override
public void showFileChooser(boolean open, String extension, Cons<Fi> cons){
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") ? "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(open ? "$open" : "$save", file -> file.extension().equalsIgnoreCase(extension), 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{
super.showFileChooser(open, extension, cons);
}
}
@Override
public void beginForceLandscape(){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
}
@Override
public void endForceLandscape(){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
}
}, new AndroidApplicationConfiguration(){{
useImmersiveMode = true;
depth = 0;
hideStatusBar = true;
//errorHandler = ModCrashHandler::handle;
}});
checkFiles(getIntent());
}
@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;
}
}
}
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;
}
}

View File

@@ -0,0 +1,227 @@
package mindustry.android;
import android.annotation.*;
import android.os.*;
import android.os.Build.*;
import arc.*;
import arc.backend.android.*;
import com.android.dex.*;
import com.android.dx.cf.direct.*;
import com.android.dx.command.dexer.*;
import com.android.dx.dex.*;
import com.android.dx.dex.cf.*;
import com.android.dx.dex.file.DexFile;
import com.android.dx.merge.*;
import dalvik.system.*;
import org.mozilla.javascript.*;
import java.io.*;
import java.nio.*;
/**
* Helps to prepare a Rhino Context for usage on android.
* @author F43nd1r
* @since 11.01.2016
*/
public class AndroidRhinoContext{
/**
* call this instead of {@link Context#enter()}
* @return a context prepared for android
*/
public static Context enter(File cacheDirectory){
if(!SecurityController.hasGlobal())
SecurityController.initGlobal(new SecurityController(){
@Override
public GeneratedClassLoader createClassLoader(ClassLoader classLoader, Object o){
return Context.getCurrentContext().createClassLoader(classLoader);
}
@Override
public Object getDynamicSecurityDomain(Object o){
return null;
}
});
AndroidContextFactory factory;
if(!ContextFactory.hasExplicitGlobal()){
factory = new AndroidContextFactory(cacheDirectory);
ContextFactory.getGlobalSetter().setContextFactoryGlobal(factory);
}else if(!(ContextFactory.getGlobal() instanceof AndroidContextFactory)){
throw new IllegalStateException("Cannot initialize factory for Android Rhino: There is already another factory");
}else{
factory = (AndroidContextFactory)ContextFactory.getGlobal();
}
return factory.enterContext();
}
/**
* Ensures that the classLoader used is correct
* @author F43nd1r
* @since 11.01.2016
*/
public static class AndroidContextFactory extends ContextFactory{
private final File cacheDirectory;
/**
* Create a new factory. It will cache generated code in the given directory
* @param cacheDirectory the cache directory
*/
public AndroidContextFactory(File cacheDirectory){
this.cacheDirectory = cacheDirectory;
initApplicationClassLoader(createClassLoader(AndroidContextFactory.class.getClassLoader()));
}
/**
* Create a ClassLoader which is able to deal with bytecode
* @param parent the parent of the create classloader
* @return a new ClassLoader
*/
@Override
public BaseAndroidClassLoader createClassLoader(ClassLoader parent){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
return new InMemoryAndroidClassLoader(parent);
}
return new FileAndroidClassLoader(parent, cacheDirectory);
}
@Override
protected void onContextReleased(final Context cx){
super.onContextReleased(cx);
((BaseAndroidClassLoader)cx.getApplicationClassLoader()).reset();
}
}
/**
* Compiles java bytecode to dex bytecode and loads it
* @author F43nd1r
* @since 11.01.2016
*/
abstract static class BaseAndroidClassLoader extends ClassLoader implements GeneratedClassLoader{
public BaseAndroidClassLoader(ClassLoader parent){
super(parent);
}
@Override
public Class<?> defineClass(String name, byte[] data){
try{
DexOptions dexOptions = new DexOptions();
DexFile dexFile = new DexFile(dexOptions);
DirectClassFile classFile = new DirectClassFile(data, name.replace('.', '/') + ".class", true);
classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
classFile.getMagic();
DxContext context = new DxContext();
dexFile.add(CfTranslator.translate(context, classFile, null, new CfOptions(), dexOptions, dexFile));
Dex dex = new Dex(dexFile.toDex(null, false));
Dex oldDex = getLastDex();
if(oldDex != null){
dex = new DexMerger(new Dex[]{dex, oldDex}, CollisionPolicy.KEEP_FIRST, context).merge();
}
return loadClass(dex, name);
}catch(IOException | ClassNotFoundException e){
throw new FatalLoadingException(e);
}
}
protected abstract Class<?> loadClass(Dex dex, String name) throws ClassNotFoundException;
protected abstract Dex getLastDex();
protected abstract void reset();
@Override
public void linkClass(Class<?> aClass){}
@Override
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
Class<?> loadedClass = findLoadedClass(name);
if(loadedClass == null){
Dex dex = getLastDex();
if(dex != null){
loadedClass = loadClass(dex, name);
}
if(loadedClass == null){
loadedClass = getParent().loadClass(name);
}
}
return loadedClass;
}
}
/** Might be thrown in any Rhino method that loads bytecode if the loading failed. */
public static class FatalLoadingException extends RuntimeException{
FatalLoadingException(Throwable t){
super("Failed to define class", t);
}
}
static class FileAndroidClassLoader extends BaseAndroidClassLoader{
private static int instanceCounter = 0;
private final File dexFile;
public FileAndroidClassLoader(ClassLoader parent, File cacheDir){
super(parent);
int id = instanceCounter++;
dexFile = new File(cacheDir, id + ".dex");
cacheDir.mkdirs();
reset();
}
@Override
protected Class<?> loadClass(Dex dex, String name) throws ClassNotFoundException{
try{
dex.writeTo(dexFile);
}catch(IOException e){
e.printStackTrace();
}
android.content.Context context = ((AndroidApplication) Core.app).getContext();
return new DexClassLoader(dexFile.getPath(), VERSION.SDK_INT >= 21 ? context.getCodeCacheDir().getPath() : context.getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name);
}
@Override
protected Dex getLastDex(){
if(dexFile.exists()){
try{
return new Dex(dexFile);
}catch(IOException e){
e.printStackTrace();
}
}
return null;
}
@Override
protected void reset(){
dexFile.delete();
}
}
@TargetApi(Build.VERSION_CODES.O)
static class InMemoryAndroidClassLoader extends BaseAndroidClassLoader{
private Dex last;
public InMemoryAndroidClassLoader(ClassLoader parent){
super(parent);
}
@Override
protected Class<?> loadClass(Dex dex, String name) throws ClassNotFoundException{
last = dex;
return new InMemoryDexClassLoader(ByteBuffer.wrap(dex.getBytes()), getParent()).loadClass(name);
}
@Override
protected Dex getLastDex(){
return last;
}
@Override
protected void reset(){
last = null;
}
}
}