V7
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
package com.dragontcp.client;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.text.InputType;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
@@ -17,117 +16,254 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
private static final int VPN_REQUEST = 5301;
|
||||
private static final int VPN_REQUEST = 100;
|
||||
private static final String PREFS = "dragontcp";
|
||||
|
||||
private EditText server, port, token, chunkMax, chunkMin, timeout;
|
||||
private TextView status, logs;
|
||||
private EditText server;
|
||||
private EditText port;
|
||||
private EditText token;
|
||||
private EditText chunkMax;
|
||||
private EditText chunkMin;
|
||||
private EditText reconnect;
|
||||
private EditText timeout;
|
||||
private TextView logText;
|
||||
private ScrollView logScroll;
|
||||
private Button connectButton, stopButton;
|
||||
private Intent pendingServiceIntent;
|
||||
private SharedPreferences prefs;
|
||||
private String lastLogText = "";
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private final Runnable refresher = new Runnable() {
|
||||
@Override public void run() {
|
||||
refreshStatus();
|
||||
handler.postDelayed(this, 500);
|
||||
}
|
||||
};
|
||||
private final AppLog.Listener logListener = line -> runOnUiThread(() -> appendLogLine(line));
|
||||
|
||||
@Override protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
prefs = getSharedPreferences("dragontcp", MODE_PRIVATE);
|
||||
setTitle("DragonTCP VPN");
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
buildUi();
|
||||
loadSettings();
|
||||
handler.post(refresher);
|
||||
}
|
||||
|
||||
private int dp(int v) { return (int)(v * getResources().getDisplayMetrics().density + 0.5f); }
|
||||
private TextView text(String s, float sp, boolean bold) {
|
||||
TextView v = new TextView(this); v.setText(s); v.setTextSize(sp); v.setTextColor(Color.rgb(232,236,241));
|
||||
if (bold) v.setTypeface(Typeface.DEFAULT, Typeface.BOLD); return v;
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
logText.setText(AppLog.history());
|
||||
AppLog.addListener(logListener);
|
||||
logScroll.post(() -> logScroll.fullScroll(View.FOCUS_DOWN));
|
||||
}
|
||||
private EditText field(LinearLayout root, String label, int type) {
|
||||
TextView t=text(label,13f,false);t.setPadding(0,dp(9),0,dp(4));root.addView(t);
|
||||
EditText e=new EditText(this);e.setSingleLine(true);e.setTextColor(Color.WHITE);e.setHintTextColor(Color.GRAY);e.setInputType(type);
|
||||
e.setBackgroundColor(Color.rgb(42,47,54));e.setPadding(dp(12),dp(9),dp(12),dp(9));
|
||||
root.addView(e,new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));return e;
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
AppLog.removeListener(logListener);
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void buildUi() {
|
||||
ScrollView page=new ScrollView(this);page.setFillViewport(true);page.setBackgroundColor(Color.rgb(20,23,27));
|
||||
LinearLayout root=new LinearLayout(this);root.setOrientation(LinearLayout.VERTICAL);root.setPadding(dp(18),dp(18),dp(18),dp(24));page.addView(root);
|
||||
TextView title=text("DragonTCP VPN",27f,true);title.setTextColor(Color.rgb(104,207,255));root.addView(title);
|
||||
TextView sub=text("Full IPv4 / IPv6 packet VPN over adaptive TCP/53",13f,false);sub.setTextColor(Color.rgb(170,179,188));sub.setPadding(0,dp(2),0,dp(12));root.addView(sub);
|
||||
status=text("Stopped",16f,true);status.setPadding(dp(12),dp(12),dp(12),dp(12));status.setBackgroundColor(Color.rgb(34,39,45));root.addView(status);
|
||||
int pad = dp(14);
|
||||
ScrollView outer = new ScrollView(this);
|
||||
LinearLayout root = new LinearLayout(this);
|
||||
root.setOrientation(LinearLayout.VERTICAL);
|
||||
root.setPadding(pad, pad, pad, pad);
|
||||
outer.addView(root, new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
server=field(root,"Server IP / hostname",InputType.TYPE_CLASS_TEXT);
|
||||
port=field(root,"TCP port",InputType.TYPE_CLASS_NUMBER);
|
||||
token=field(root,"Token",InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
chunkMax=field(root,"Maximum transport fragment bytes (start = max)",InputType.TYPE_CLASS_NUMBER);
|
||||
chunkMin=field(root,"Minimum transport fragment bytes",InputType.TYPE_CLASS_NUMBER);
|
||||
timeout=field(root,"Transaction timeout (example: 2s)",InputType.TYPE_CLASS_TEXT);
|
||||
TextView title = new TextView(this);
|
||||
title.setText("DragonTCP Lite VPN");
|
||||
title.setTextSize(24);
|
||||
title.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
|
||||
root.addView(title);
|
||||
|
||||
TextView note=text("Pollers are fixed at 1. Adaptive chunks always start at Max and shrink on failures. All IPv4 and IPv6 routes are captured by the VPN; DragonTCP itself is excluded to prevent a tunnel loop.",12f,false);
|
||||
note.setTextColor(Color.rgb(160,170,180));note.setPadding(0,dp(10),0,dp(8));root.addView(note);
|
||||
TextView subtitle = new TextView(this);
|
||||
subtitle.setText("Android VPN → local HTTP CONNECT proxy → adaptive XOR over TCP/53");
|
||||
subtitle.setTextSize(13);
|
||||
subtitle.setPadding(0, dp(4), 0, dp(12));
|
||||
root.addView(subtitle);
|
||||
|
||||
LinearLayout buttons=new LinearLayout(this);buttons.setOrientation(LinearLayout.HORIZONTAL);buttons.setGravity(Gravity.CENTER);buttons.setPadding(0,dp(8),0,dp(10));root.addView(buttons);
|
||||
connectButton=new Button(this);connectButton.setText("CONNECT");buttons.addView(connectButton,new LinearLayout.LayoutParams(0,ViewGroup.LayoutParams.WRAP_CONTENT,1f));
|
||||
stopButton=new Button(this);stopButton.setText("STOP");buttons.addView(stopButton,new LinearLayout.LayoutParams(0,ViewGroup.LayoutParams.WRAP_CONTENT,1f));
|
||||
connectButton.setOnClickListener(v -> startDragon()); stopButton.setOnClickListener(v -> stopDragon());
|
||||
TableLayout table = new TableLayout(this);
|
||||
table.setStretchAllColumns(false);
|
||||
table.setColumnStretchable(1, true);
|
||||
root.addView(table, new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
LinearLayout lh=new LinearLayout(this);lh.setOrientation(LinearLayout.HORIZONTAL);lh.setGravity(Gravity.CENTER_VERTICAL);root.addView(lh);
|
||||
TextView lt=text("Live log",17f,true);lh.addView(lt,new LinearLayout.LayoutParams(0,ViewGroup.LayoutParams.WRAP_CONTENT,1f));
|
||||
Button clear=new Button(this);clear.setText("CLEAR");lh.addView(clear);clear.setOnClickListener(v -> clearLog());
|
||||
logScroll=new ScrollView(this);logScroll.setFillViewport(true);logScroll.setVerticalScrollBarEnabled(true);logScroll.setBackgroundColor(Color.BLACK);
|
||||
logs=text("",11f,false);logs.setTypeface(Typeface.MONOSPACE);logs.setTextIsSelectable(true);logs.setPadding(dp(10),dp(10),dp(10),dp(10));logs.setBackgroundColor(Color.BLACK);
|
||||
logScroll.addView(logs,new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
root.addView(logScroll,new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,dp(320)));
|
||||
setContentView(page);
|
||||
server = addField(table, "Server", "", false, false);
|
||||
port = addField(table, "Port", "53", true, false);
|
||||
token = addField(table, "Token", "", false, true);
|
||||
chunkMax = addField(table, "Max chunk", "1048576", true, false);
|
||||
chunkMin = addField(table, "Min chunk", "32", true, false);
|
||||
reconnect = addField(table, "Reconnect every", "1", true, false);
|
||||
timeout = addField(table, "Timeout (s)", "2", true, false);
|
||||
|
||||
TextView fixed = new TextView(this);
|
||||
fixed.setText("Pollers: 1 (fixed) • Start chunk = Max chunk • XOR 0xAD always on");
|
||||
fixed.setTextSize(12);
|
||||
fixed.setPadding(0, dp(8), 0, dp(8));
|
||||
root.addView(fixed);
|
||||
|
||||
LinearLayout buttons = new LinearLayout(this);
|
||||
buttons.setOrientation(LinearLayout.HORIZONTAL);
|
||||
buttons.setGravity(Gravity.CENTER);
|
||||
Button connect = new Button(this);
|
||||
connect.setText("CONNECT");
|
||||
Button stop = new Button(this);
|
||||
stop.setText("STOP");
|
||||
buttons.addView(connect, new LinearLayout.LayoutParams(0, dp(52), 1f));
|
||||
buttons.addView(stop, new LinearLayout.LayoutParams(0, dp(52), 1f));
|
||||
root.addView(buttons);
|
||||
|
||||
connect.setOnClickListener(v -> requestConnect());
|
||||
stop.setOnClickListener(v -> {
|
||||
Intent i = new Intent(this, DragonService.class).setAction(DragonService.ACTION_STOP);
|
||||
startService(i);
|
||||
});
|
||||
|
||||
LinearLayout logHeader = new LinearLayout(this);
|
||||
logHeader.setOrientation(LinearLayout.HORIZONTAL);
|
||||
logHeader.setGravity(Gravity.CENTER_VERTICAL);
|
||||
TextView logLabel = new TextView(this);
|
||||
logLabel.setText("Live log");
|
||||
logLabel.setTextSize(16);
|
||||
logLabel.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
|
||||
Button clear = new Button(this);
|
||||
clear.setText("CLEAR");
|
||||
logHeader.addView(logLabel, new LinearLayout.LayoutParams(0, dp(48), 1f));
|
||||
logHeader.addView(clear, new LinearLayout.LayoutParams(dp(100), dp(48)));
|
||||
root.addView(logHeader);
|
||||
|
||||
logScroll = new ScrollView(this);
|
||||
logText = new TextView(this);
|
||||
logText.setTextSize(12);
|
||||
logText.setTypeface(Typeface.MONOSPACE);
|
||||
logText.setTextIsSelectable(true);
|
||||
logText.setPadding(dp(8), dp(8), dp(8), dp(8));
|
||||
logScroll.addView(logText, new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
root.addView(logScroll, new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
dp(280)
|
||||
));
|
||||
clear.setOnClickListener(v -> {
|
||||
AppLog.clear();
|
||||
logText.setText("");
|
||||
});
|
||||
|
||||
TextView note = new TextView(this);
|
||||
note.setText("IPv4 is tunneled. IPv6 is captured and blocked so it cannot bypass the proxy. DNS is sent to 1.1.1.1 through DragonTCP using DNS-over-TCP.");
|
||||
note.setTextSize(11);
|
||||
note.setPadding(0, dp(8), 0, dp(12));
|
||||
root.addView(note);
|
||||
|
||||
setContentView(outer);
|
||||
}
|
||||
|
||||
private void loadSettings(){server.setText(prefs.getString("server",""));port.setText(prefs.getString("port","53"));token.setText(prefs.getString("token",""));chunkMax.setText(prefs.getString("chunkMax","1048576"));chunkMin.setText(prefs.getString("chunkMin","32"));timeout.setText(prefs.getString("timeout","2s"));}
|
||||
private int intValue(EditText e,int d){try{return Integer.parseInt(e.getText().toString().trim());}catch(Exception x){return d;}}
|
||||
private boolean validateSettings(){
|
||||
if(server.getText().toString().trim().isEmpty()){toast("Enter the server IP or hostname");return false;}
|
||||
int p=intValue(port,53),min=intValue(chunkMin,32),max=intValue(chunkMax,1048576);
|
||||
if(p<1||p>65535){toast("Port must be 1-65535");return false;}
|
||||
if(min<32||max>1048576||min>max){toast("Chunks must satisfy 32 <= Min <= Max <= 1048576");return false;}
|
||||
if(timeout.getText().toString().trim().isEmpty()){toast("Enter a timeout such as 2s");return false;}
|
||||
return true;
|
||||
private EditText addField(TableLayout table, String label, String defaultValue, boolean numeric, boolean password) {
|
||||
TableRow row = new TableRow(this);
|
||||
row.setPadding(0, dp(2), 0, dp(2));
|
||||
TextView name = new TextView(this);
|
||||
name.setText(label);
|
||||
name.setGravity(Gravity.CENTER_VERTICAL);
|
||||
name.setPadding(0, 0, dp(10), 0);
|
||||
EditText value = new EditText(this);
|
||||
value.setSingleLine(true);
|
||||
value.setText(defaultValue);
|
||||
if (numeric) value.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
if (password) value.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
row.addView(name, new TableRow.LayoutParams(dp(125), dp(50)));
|
||||
row.addView(value, new TableRow.LayoutParams(0, dp(50), 1f));
|
||||
table.addView(row);
|
||||
return value;
|
||||
}
|
||||
private void saveSettings(){prefs.edit().putString("server",server.getText().toString().trim()).putString("port",port.getText().toString().trim()).putString("token",token.getText().toString()).putString("chunkMax",chunkMax.getText().toString().trim()).putString("chunkMin",chunkMin.getText().toString().trim()).putString("timeout",timeout.getText().toString().trim()).apply();}
|
||||
|
||||
private int clientHostId(){
|
||||
int id=prefs.getInt("clientHostId",0);if(id>=2&&id<=65534)return id;
|
||||
id=2+new SecureRandom().nextInt(65533);prefs.edit().putInt("clientHostId",id).apply();return id;
|
||||
private void requestConnect() {
|
||||
try {
|
||||
validateAndSave();
|
||||
} catch (Exception e) {
|
||||
AppLog.append("CONFIG: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
Intent prepare = VpnService.prepare(this);
|
||||
if (prepare != null) {
|
||||
startActivityForResult(prepare, VPN_REQUEST);
|
||||
} else {
|
||||
startDragonService();
|
||||
}
|
||||
}
|
||||
private String clientIPv4(int id){return "10.123."+((id>>8)&255)+"."+(id&255);}
|
||||
private String clientIPv6(int id){return "fd7a:4472:6167:6f6e::"+Integer.toHexString(id);}
|
||||
|
||||
private Intent buildServiceIntent(){
|
||||
int max=intValue(chunkMax,1048576),id=clientHostId();Intent i=new Intent(this,DragonService.class);i.setAction(DragonService.ACTION_CONNECT);
|
||||
i.putExtra("server",server.getText().toString().trim());i.putExtra("port",intValue(port,53));i.putExtra("token",token.getText().toString());
|
||||
i.putExtra("chunkStart",max);i.putExtra("chunkMax",max);i.putExtra("chunkMin",intValue(chunkMin,32));i.putExtra("timeout",timeout.getText().toString().trim());
|
||||
i.putExtra("vpnIPv4",clientIPv4(id));i.putExtra("vpnIPv6",clientIPv6(id));return i;
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == VPN_REQUEST) {
|
||||
if (resultCode == RESULT_OK) startDragonService();
|
||||
else AppLog.append("VPN permission was not granted");
|
||||
}
|
||||
}
|
||||
|
||||
private void startDragonService() {
|
||||
SharedPreferences p = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
Intent i = new Intent(this, DragonService.class).setAction(DragonService.ACTION_CONNECT);
|
||||
i.putExtra(DragonService.EXTRA_SERVER, p.getString("server", ""));
|
||||
i.putExtra(DragonService.EXTRA_PORT, p.getInt("port", 53));
|
||||
i.putExtra(DragonService.EXTRA_TOKEN, p.getString("token", ""));
|
||||
i.putExtra(DragonService.EXTRA_CHUNK_MAX, p.getInt("max", 1048576));
|
||||
i.putExtra(DragonService.EXTRA_CHUNK_MIN, p.getInt("min", 32));
|
||||
i.putExtra(DragonService.EXTRA_RECONNECT, p.getInt("reconnect", 1));
|
||||
i.putExtra(DragonService.EXTRA_TIMEOUT, p.getInt("timeout", 2));
|
||||
if (Build.VERSION.SDK_INT >= 26) startForegroundService(i); else startService(i);
|
||||
}
|
||||
|
||||
private void validateAndSave() {
|
||||
String h = server.getText().toString().trim();
|
||||
if (h.isEmpty()) throw new IllegalArgumentException("Server is required");
|
||||
int p = parse(port, 1, 65535, "Port");
|
||||
int max = parse(chunkMax, 32, 1048576, "Max chunk");
|
||||
int min = parse(chunkMin, 32, max, "Min chunk");
|
||||
int rec = parse(reconnect, 1, 1000000, "Reconnect every");
|
||||
int tout = parse(timeout, 1, 120, "Timeout");
|
||||
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
|
||||
.putString("server", h)
|
||||
.putInt("port", p)
|
||||
.putString("token", token.getText().toString())
|
||||
.putInt("max", max)
|
||||
.putInt("min", min)
|
||||
.putInt("reconnect", rec)
|
||||
.putInt("timeout", tout)
|
||||
.apply();
|
||||
}
|
||||
|
||||
private int parse(EditText field, int min, int max, String name) {
|
||||
int v;
|
||||
try { v = Integer.parseInt(field.getText().toString().trim()); }
|
||||
catch (Exception e) { throw new IllegalArgumentException(name + " is invalid"); }
|
||||
if (v < min || v > max) throw new IllegalArgumentException(name + " must be " + min + "-" + max);
|
||||
return v;
|
||||
}
|
||||
|
||||
private void loadSettings() {
|
||||
SharedPreferences p = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
server.setText(p.getString("server", ""));
|
||||
port.setText(Integer.toString(p.getInt("port", 53)));
|
||||
token.setText(p.getString("token", ""));
|
||||
chunkMax.setText(Integer.toString(p.getInt("max", 1048576)));
|
||||
chunkMin.setText(Integer.toString(p.getInt("min", 32)));
|
||||
reconnect.setText(Integer.toString(p.getInt("reconnect", 1)));
|
||||
timeout.setText(Integer.toString(p.getInt("timeout", 2)));
|
||||
}
|
||||
|
||||
private void appendLogLine(String line) {
|
||||
// Only auto-scroll when the user was already near the bottom.
|
||||
View child = logScroll.getChildAt(0);
|
||||
int gap = child == null ? 0 : child.getBottom() - (logScroll.getScrollY() + logScroll.getHeight());
|
||||
boolean follow = gap < dp(48);
|
||||
logText.append(line + "\n");
|
||||
if (follow) logScroll.post(() -> logScroll.fullScroll(View.FOCUS_DOWN));
|
||||
}
|
||||
|
||||
private int dp(int value) {
|
||||
return Math.round(value * getResources().getDisplayMetrics().density);
|
||||
}
|
||||
private void startDragon(){if(!validateSettings())return;saveSettings();pendingServiceIntent=buildServiceIntent();DragonService.active=true;DragonService.state="Waiting for VPN permission";refreshStatus();Intent prep=VpnService.prepare(this);if(prep!=null)startActivityForResult(prep,VPN_REQUEST);else{Intent i=pendingServiceIntent;pendingServiceIntent=null;launchService(i);}}
|
||||
private void launchService(Intent i){if(i==null)return;DragonService.active=true;DragonService.state="Starting full VPN";refreshStatus();if(Build.VERSION.SDK_INT>=26)startForegroundService(i);else startService(i);toast("Starting DragonTCP VPN...");}
|
||||
@Override protected void onActivityResult(int requestCode,int resultCode,Intent data){super.onActivityResult(requestCode,resultCode,data);if(requestCode!=VPN_REQUEST)return;if(resultCode==RESULT_OK&&pendingServiceIntent!=null){Intent i=pendingServiceIntent;pendingServiceIntent=null;launchService(i);}else{pendingServiceIntent=null;DragonService.active=false;DragonService.running=false;DragonService.state="VPN permission denied";refreshStatus();toast("VPN permission is required");}}
|
||||
private void stopDragon(){pendingServiceIntent=null;DragonService.state="Stopping...";refreshStatus();Intent s=new Intent(this,DragonService.class);s.setAction(DragonService.ACTION_STOP);try{startService(s);}catch(Exception e){stopService(new Intent(this,DragonService.class));}handler.postDelayed(()->{if(DragonService.active)stopService(new Intent(MainActivity.this,DragonService.class));refreshStatus();},1800);}
|
||||
private void clearLog(){try{File f=new File(getFilesDir(),"dragontcp.log");new java.io.FileOutputStream(f,false).close();lastLogText="";logs.setText("");}catch(Exception e){toast("Could not clear log: "+e.getMessage());}}
|
||||
private String readTail(File f,int maxBytes){if(!f.exists())return "";try(FileInputStream in=new FileInputStream(f)){long len=f.length();int n=(int)Math.min((long)maxBytes,len);byte[]buf=new byte[n];long skip=len-n;while(skip>0){long s=in.skip(skip);if(s<=0)break;skip-=s;}int off=0;while(off<n){int r=in.read(buf,off,n-off);if(r<0)break;off+=r;}return new String(buf,0,off,"UTF-8");}catch(Exception e){return "log error: "+e.getMessage();}}
|
||||
private void refreshStatus(){boolean a=DragonService.active,r=DragonService.running;status.setText((r?"● ":a?"◐ ":"○ ")+DragonService.state);status.setTextColor(r?Color.rgb(115,235,145):a?Color.rgb(255,205,95):Color.rgb(232,236,241));connectButton.setEnabled(!a);stopButton.setEnabled(a);String current=readTail(new File(getFilesDir(),"dragontcp.log"),131072);if(!current.equals(lastLogText)){lastLogText=current;logs.setText(current);logScroll.post(()->logScroll.fullScroll(View.FOCUS_DOWN));}}
|
||||
private void toast(String s){Toast.makeText(this,s,Toast.LENGTH_LONG).show();}
|
||||
@Override protected void onDestroy(){handler.removeCallbacks(refresher);super.onDestroy();}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user