mirror of
https://github.com/TimeCrafters/TimeCraftersConfigurationTool.git
synced 2025-12-16 13:32:35 +00:00
Threw in TACNET and config stuff, added gson dependency
This commit is contained in:
@@ -24,6 +24,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.timecrafters.TimeCraftersConfigurationTool">
|
package="org.timecrafters.TimeCraftersConfigurationTool">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.backend;
|
||||||
|
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.tacnet.PacketHandler;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Backend {
|
||||||
|
static private Backend instance;
|
||||||
|
private TACNET tacnet;
|
||||||
|
private Config config;
|
||||||
|
private Settings settings;
|
||||||
|
private boolean configChanged, settingsChanged;
|
||||||
|
|
||||||
|
public Backend() {
|
||||||
|
instance = this;
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
if (settings.config != null) {
|
||||||
|
loadConfig(settings.config);
|
||||||
|
}
|
||||||
|
tacnet = new TACNET();
|
||||||
|
|
||||||
|
configChanged = false;
|
||||||
|
settingsChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Backend instance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TACNET tacnet() {
|
||||||
|
return tacnet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Config getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Settings getSettings() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configChanged() {
|
||||||
|
config.getConfiguration().updatedAt = new Date();
|
||||||
|
config.getConfiguration().revision += 1;
|
||||||
|
configChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConfigChanged() { return configChanged; }
|
||||||
|
|
||||||
|
public void loadConfig(String name) {
|
||||||
|
File file = new File("" + TAC.CONFIGS_PATH + File.separator + name);
|
||||||
|
|
||||||
|
if (file.exists() && file.isFile()) {
|
||||||
|
// TODO: Load configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean saveConfig(String name) {
|
||||||
|
// TODO: Implement save config
|
||||||
|
configChanged = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadConfig() {
|
||||||
|
if (config != null && tacnet.isConnected()) {
|
||||||
|
String json = "";
|
||||||
|
// tacnet.puts(PacketHandler.packetUploadConfig(json));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadConfig() {
|
||||||
|
if (config != null && tacnet.isConnected()) {
|
||||||
|
// tacnet.puts(PacketHandler.packetDownloadConfig());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeNewConfig(String name) {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
public void settingsChanged() {
|
||||||
|
settingsChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsChanged() {
|
||||||
|
return settingsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean loadSettings() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveSettings() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeDefaultSettings() {
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data":
|
||||||
|
{
|
||||||
|
"hostname":TACNET.DEFAULT_HOSTNAME,
|
||||||
|
"port":TACNET.DEFAULT_PORT,
|
||||||
|
"config":null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.backend;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Config {
|
||||||
|
private Configuration configuration;
|
||||||
|
private ArrayList<Group> groups;
|
||||||
|
private ArrayList<Preset> presets;
|
||||||
|
|
||||||
|
public Config(String name) {
|
||||||
|
// Do things
|
||||||
|
parse(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Config(Configuration configuration, ArrayList<Group> groups, ArrayList<Preset> presets) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.groups = groups;
|
||||||
|
this.presets = presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Configuration getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Preset> getPresets() {
|
||||||
|
return presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Group> getGroups() {
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse(String name) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
gson.fromJson("", Config.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Configuration {
|
||||||
|
public Date createdAt, updatedAt;
|
||||||
|
private int specVersion;
|
||||||
|
public int revision;
|
||||||
|
|
||||||
|
public Configuration(Date createdAt, Date updatedAt, int specVersion, int revision) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
this.specVersion = specVersion;
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSpecVersion() { return specVersion; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Preset {
|
||||||
|
private ArrayList<Group> groups;
|
||||||
|
private ArrayList<Action> actions;
|
||||||
|
|
||||||
|
public Preset(ArrayList<Group> groups, ArrayList<Action> actions) {
|
||||||
|
this.groups = groups;
|
||||||
|
this.actions = actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Group {
|
||||||
|
public String name;
|
||||||
|
private ArrayList<Action> actions;
|
||||||
|
|
||||||
|
public Group(String name, ArrayList<Action> actions) {
|
||||||
|
this.name = name;
|
||||||
|
this.actions = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Action> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Action {
|
||||||
|
public String name, comment;
|
||||||
|
public boolean enabled;
|
||||||
|
private ArrayList<Variable> variables;
|
||||||
|
|
||||||
|
public Action(String name, String comment, boolean enabled, ArrayList<Variable> variables) {
|
||||||
|
this.name = name;
|
||||||
|
this.comment = comment;
|
||||||
|
this.enabled = enabled;
|
||||||
|
this.variables = variables;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Variable {
|
||||||
|
public String name;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public Variable(String name, String value) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T value() {
|
||||||
|
return valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T valueOf() {
|
||||||
|
String[] split = value.split("x");
|
||||||
|
|
||||||
|
switch (split[0]) {
|
||||||
|
case "B": {
|
||||||
|
return (T) Boolean.valueOf(split[(split.length-1)]);
|
||||||
|
}
|
||||||
|
case "D": {
|
||||||
|
return (T) Double.valueOf(split[(split.length-1)]);
|
||||||
|
}
|
||||||
|
case "F": {
|
||||||
|
return (T) Float.valueOf(split[(split.length-1)]);
|
||||||
|
}
|
||||||
|
case "I": {
|
||||||
|
return (T) Integer.valueOf(split[(split.length-1)]);
|
||||||
|
}
|
||||||
|
case "L": {
|
||||||
|
return (T) Long.valueOf(split[(split.length-1)]);
|
||||||
|
}
|
||||||
|
case "S": {
|
||||||
|
return (T) String.valueOf(split[(split.length-1)]);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String typeOf(String value) {
|
||||||
|
String[] split = value.split("x");
|
||||||
|
|
||||||
|
switch (split[0]) {
|
||||||
|
case "B": {
|
||||||
|
return "Boolean";
|
||||||
|
}
|
||||||
|
case "D": {
|
||||||
|
return "Double";
|
||||||
|
}
|
||||||
|
case "F": {
|
||||||
|
return "Float";
|
||||||
|
}
|
||||||
|
case "I": {
|
||||||
|
return "Integer";
|
||||||
|
}
|
||||||
|
case "L": {
|
||||||
|
return "Long";
|
||||||
|
}
|
||||||
|
case "S": {
|
||||||
|
return "String";
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return "=!UNKNOWN!=";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.backend;
|
||||||
|
|
||||||
|
public class Settings {
|
||||||
|
public String hostname, config;
|
||||||
|
public int port;
|
||||||
|
|
||||||
|
public Settings(String hostname, int port, String config) {
|
||||||
|
this.hostname = hostname;
|
||||||
|
this.port = port;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.backend;
|
||||||
|
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class TAC {
|
||||||
|
public static final String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "TimeCrafters_Configuration_Tool",
|
||||||
|
CONFIGS_PATH = ROOT_PATH + File.separator + "/configs",
|
||||||
|
SETTINGS_PATH = ROOT_PATH + File.separator + "settings.json";
|
||||||
|
|
||||||
|
public static final int CONFIG_SPEC_VERSION = 2;
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.backend;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.tacnet.Client;
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.tacnet.Connection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class TACNET {
|
||||||
|
public static final String DEFAULT_HOSTNAME = "192.168.49.1";
|
||||||
|
public static final int DEFAULT_PORT = 8962;
|
||||||
|
|
||||||
|
public static final int SYNC_INTERVAL = 250; // ms
|
||||||
|
public static final int HEARTBEAT_INTERVAL = 1_500; // ms
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
CONNECTED,
|
||||||
|
CONNECTING,
|
||||||
|
CONNECTION_ERROR,
|
||||||
|
NOT_CONNECTED,
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection connection;
|
||||||
|
|
||||||
|
public void connect(String hostname, int port) {
|
||||||
|
if (connection != null && connection.isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection = new Connection(hostname, port);
|
||||||
|
connection.connect(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status status() {
|
||||||
|
if (isConnected()) {
|
||||||
|
return Status.CONNECTED;
|
||||||
|
} else if (connection != null && !connection.socketError()) {
|
||||||
|
return Status.CONNECTING;
|
||||||
|
} else if (connection != null && connection.socketError()) {
|
||||||
|
return Status.CONNECTION_ERROR;
|
||||||
|
} else {
|
||||||
|
return Status.NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return connection != null && connection.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (connection != null) {
|
||||||
|
try {
|
||||||
|
connection.close();
|
||||||
|
} catch (IOException e) {}
|
||||||
|
|
||||||
|
connection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
if (isConnected()) {
|
||||||
|
return connection.getClient();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void puts(String message) {
|
||||||
|
if (isConnected()) {
|
||||||
|
connection.puts(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String gets() {
|
||||||
|
if (isConnected()) {
|
||||||
|
return connection.gets();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long milliseconds() {
|
||||||
|
return SystemClock.elapsedRealtime();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.tacnet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend;
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.backend.TACNET;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class Client {
|
||||||
|
private Socket socket;
|
||||||
|
private BufferedReader bufferedReader;
|
||||||
|
private BufferedWriter bufferedWriter;
|
||||||
|
private String uuid;
|
||||||
|
|
||||||
|
private ArrayList<String> readQueue;
|
||||||
|
private ArrayList<String> writeQueue;
|
||||||
|
|
||||||
|
final private Object readQueueLock = new Object();
|
||||||
|
final private Object writeQueueLock = new Object();
|
||||||
|
|
||||||
|
private long syncInterval = TACNET.SYNC_INTERVAL;
|
||||||
|
|
||||||
|
private int packetsSent, packetsReceived = 0;
|
||||||
|
private long dataSent, dataReceived = 0;
|
||||||
|
|
||||||
|
private String TAG = "TACNET|Client";
|
||||||
|
|
||||||
|
public Client() {
|
||||||
|
this.uuid = (UUID.randomUUID()).toString();
|
||||||
|
|
||||||
|
this.readQueue = new ArrayList<>();
|
||||||
|
this.writeQueue = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSyncInterval(long milliseconds) {
|
||||||
|
syncInterval = milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSocket(Socket socket) throws IOException {
|
||||||
|
this.socket = socket;
|
||||||
|
|
||||||
|
// This socket is for a "Connection" thus set a connect timeout
|
||||||
|
if (!this.socket.isBound()) {
|
||||||
|
this.socket.connect(new InetSocketAddress(Backend.instance().getSettings().hostname, Backend.instance().getSettings().port), 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
this.bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||||
|
|
||||||
|
startReader();
|
||||||
|
startWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<String> readQueue() {
|
||||||
|
return readQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<String> writeQueue() {
|
||||||
|
return writeQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startReader() {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while(!socket.isClosed()) {
|
||||||
|
// READER
|
||||||
|
try {
|
||||||
|
String message = read();
|
||||||
|
if (!message.equals("")) {
|
||||||
|
Log.i(TAG, "Read: " + message);
|
||||||
|
|
||||||
|
synchronized (readQueueLock) {
|
||||||
|
readQueue.add(message);
|
||||||
|
|
||||||
|
packetsReceived++;
|
||||||
|
dataReceived += message.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Read error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(syncInterval);
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startWriter() {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while(!socket.isClosed()) {
|
||||||
|
// WRITER
|
||||||
|
String message;
|
||||||
|
|
||||||
|
synchronized (writeQueueLock) {
|
||||||
|
for (Iterator itr = writeQueue.iterator(); itr.hasNext(); ) {
|
||||||
|
try {
|
||||||
|
message = (String) itr.next();
|
||||||
|
|
||||||
|
write(message);
|
||||||
|
|
||||||
|
packetsSent++;
|
||||||
|
dataSent += message.length();
|
||||||
|
|
||||||
|
Log.i(TAG, "Write: " + message);
|
||||||
|
itr.remove();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Write error: " + e.getMessage());
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException k) {
|
||||||
|
Log.e(TAG, "Failed to close socket: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(syncInterval);
|
||||||
|
} catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(Runnable runner) {
|
||||||
|
runner.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleReadQueue() {
|
||||||
|
String message = this.gets();
|
||||||
|
|
||||||
|
while (message != null) {
|
||||||
|
Log.i(TAG, "Writing to Queue: " + message);
|
||||||
|
this.puts(message);
|
||||||
|
|
||||||
|
message = this.gets();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String uuid() {
|
||||||
|
return this.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return this.socket != null && !this.socket.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBound() {
|
||||||
|
return this.socket == null || this.socket.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.socket == null || this.socket.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(String message) throws IOException {
|
||||||
|
bufferedWriter.write(message + "\r\n\n");
|
||||||
|
bufferedWriter.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String read() throws IOException {
|
||||||
|
String message = "";
|
||||||
|
String readLine;
|
||||||
|
|
||||||
|
while((readLine = bufferedReader.readLine()) != null) {
|
||||||
|
message+=readLine;
|
||||||
|
if (readLine.isEmpty()) { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void puts(String message) {
|
||||||
|
synchronized (writeQueueLock) {
|
||||||
|
writeQueue.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String gets() {
|
||||||
|
String message = null;
|
||||||
|
|
||||||
|
synchronized (readQueueLock) {
|
||||||
|
if (readQueue.size() > 0) {
|
||||||
|
message = readQueue.get(0);
|
||||||
|
|
||||||
|
readQueue.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encode(String message) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String decode(String blob) {
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPacketsSent() { return packetsSent; }
|
||||||
|
public int getPacketsReceived() { return packetsReceived; }
|
||||||
|
public long getDataSent() { return dataSent; }
|
||||||
|
public long getDataReceived() { return dataReceived; }
|
||||||
|
|
||||||
|
public void flush() throws IOException {
|
||||||
|
this.bufferedWriter.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(String reason) throws IOException {
|
||||||
|
write(reason);
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (this.socket != null) {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.tacnet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.backend.TACNET;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
public class Connection {
|
||||||
|
private PacketHandler packetHandler;
|
||||||
|
private Client client;
|
||||||
|
private String hostname;
|
||||||
|
private int port;
|
||||||
|
private String lastSocketError = null;
|
||||||
|
private boolean socketError = false;
|
||||||
|
|
||||||
|
private long lastSyncTime = 0;
|
||||||
|
private long syncInterval = TACNET.SYNC_INTERVAL;
|
||||||
|
|
||||||
|
private Runnable connectionHandlingRunner;
|
||||||
|
private long lastHeartBeatSent = 0;
|
||||||
|
private long heartBeatInterval = TACNET.HEARTBEAT_INTERVAL;
|
||||||
|
|
||||||
|
private String TAG = "TACNET|Connection";
|
||||||
|
|
||||||
|
public Connection(String hostname, int port) {
|
||||||
|
this.hostname = hostname;
|
||||||
|
this.port = port;
|
||||||
|
this.packetHandler = new PacketHandler(true);
|
||||||
|
|
||||||
|
this.connectionHandlingRunner = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
handleConnection();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect(final Runnable callback) {
|
||||||
|
if (client != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = new Client();
|
||||||
|
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
client.setSocket(new Socket());
|
||||||
|
Log.i(TAG, "Connected to: " + hostname + ":" + port);
|
||||||
|
|
||||||
|
while(client != null && !client.isClosed()) {
|
||||||
|
if (System.currentTimeMillis() > lastSyncTime + syncInterval) {
|
||||||
|
lastSyncTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
client.sync(connectionHandlingRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
socketError = true;
|
||||||
|
lastSocketError = e.getMessage();
|
||||||
|
|
||||||
|
callback.run();
|
||||||
|
|
||||||
|
Log.e(TAG, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleConnection() {
|
||||||
|
if (client != null && !client.isClosed()) {
|
||||||
|
String message = client.gets();
|
||||||
|
|
||||||
|
if (message != null) {
|
||||||
|
packetHandler.handle(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() > lastHeartBeatSent + heartBeatInterval) {
|
||||||
|
lastHeartBeatSent = System.currentTimeMillis();
|
||||||
|
|
||||||
|
client.puts(PacketHandler.packetHeartBeat().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(syncInterval);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Failed to sleep I suppose.
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void puts(String message) {
|
||||||
|
this.client.puts(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String gets() {
|
||||||
|
return this.client.gets();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.client == null || this.client.isClosed();
|
||||||
|
}
|
||||||
|
public boolean isConnected() {
|
||||||
|
return this.client != null && this.client.isConnected();
|
||||||
|
}
|
||||||
|
public boolean socketError() {
|
||||||
|
return socketError;
|
||||||
|
}
|
||||||
|
public String lastError() {
|
||||||
|
return lastSocketError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
this.client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.tacnet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class Packet {
|
||||||
|
final static public String PROTOCOL_VERSION = "0";
|
||||||
|
final static public String PROTOCOL_HEADER_SEPERATOR = "|";
|
||||||
|
final static public String PROTOCOL_HEARTBEAT = "heartbeat";
|
||||||
|
private static final String TAG = "TACNET|Packet";
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: PacketType is cast to a char, no more than 255 packet types can exist unless
|
||||||
|
// header is updated.
|
||||||
|
public enum PacketType {
|
||||||
|
HANDSHAKE,
|
||||||
|
HEARTBEAT,
|
||||||
|
DUMP_CONFIG,
|
||||||
|
CHANGE_ACTION,
|
||||||
|
CHANGE_VARIABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
private String protocolVersion;
|
||||||
|
private PacketType packetType;
|
||||||
|
private int contentLength;
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
String rawMessage;
|
||||||
|
|
||||||
|
Packet(String protocolVersion, PacketType packetType, int contentLength, String content) {
|
||||||
|
this.protocolVersion = protocolVersion;
|
||||||
|
this.packetType = packetType;
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Packet fromStream(String message) {
|
||||||
|
String version;
|
||||||
|
PacketType type;
|
||||||
|
int length;
|
||||||
|
String body;
|
||||||
|
|
||||||
|
String[] slice = message.split("\\|", 4);
|
||||||
|
|
||||||
|
if (slice.length < 4) {
|
||||||
|
Log.i(TAG, "Failed to split packet along first 4 " + PROTOCOL_HEADER_SEPERATOR + ". Raw return: " + Arrays.toString(slice));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!slice[0].equals(PROTOCOL_VERSION)) {
|
||||||
|
Log.i(TAG, "Incompatible protocol version received, expected: " + PROTOCOL_VERSION + " got: " + slice[0]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = slice[0];
|
||||||
|
type = PacketType.values()[Integer.parseInt(slice[1])];
|
||||||
|
length = Integer.parseInt(slice[2]);
|
||||||
|
body = slice[slice.length - 1];
|
||||||
|
|
||||||
|
return new Packet(version, type, length, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Packet create(PacketType packetType, String message) {
|
||||||
|
return new Packet(PROTOCOL_VERSION, packetType, message.length(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
if (rawMessage == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = rawMessage.split(PROTOCOL_HEADER_SEPERATOR);
|
||||||
|
|
||||||
|
return parts[0].equals(PROTOCOL_VERSION) &&
|
||||||
|
isPacketTypeValid( Integer.parseInt(parts[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPacketTypeValid(int rawPacketType) {
|
||||||
|
return PacketType.values().length >= rawPacketType && PacketType.values()[rawPacketType] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encodeHeader() {
|
||||||
|
String string = "";
|
||||||
|
string += PROTOCOL_VERSION;
|
||||||
|
string += PROTOCOL_HEADER_SEPERATOR;
|
||||||
|
string += packetType.ordinal();
|
||||||
|
string += PROTOCOL_HEADER_SEPERATOR;
|
||||||
|
string += contentLength;
|
||||||
|
string += PROTOCOL_HEADER_SEPERATOR;
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return ("" + encodeHeader() + content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocolVersion() {
|
||||||
|
return protocolVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketType getPacketType() {
|
||||||
|
return packetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getContentLength() {
|
||||||
|
return contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.tacnet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class PacketHandler {
|
||||||
|
private static final String TAG = "TACNET|PacketHandler";
|
||||||
|
private boolean hostIsAConnection = false;
|
||||||
|
|
||||||
|
public PacketHandler(boolean isHostAConnection) {
|
||||||
|
this.hostIsAConnection = isHostAConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(String message) {
|
||||||
|
Packet packet = Packet.fromStream(message);
|
||||||
|
|
||||||
|
if (packet != null && packet.isValid()) {
|
||||||
|
Log.i(TAG, "Received packet of type: " + packet.getPacketType());
|
||||||
|
handOff(packet);
|
||||||
|
} else {
|
||||||
|
if (packet == null) {
|
||||||
|
Log.i(TAG, "Rejected raw packet: " + message);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Rejected packet: " + packet.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handOff(Packet packet) {
|
||||||
|
switch(packet.getPacketType()) {
|
||||||
|
case HANDSHAKE: {
|
||||||
|
handleHandShake(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HEARTBEAT: {
|
||||||
|
handleHeartBeat(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DUMP_CONFIG: {
|
||||||
|
handleDumpConfig(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CHANGE_ACTION: {
|
||||||
|
handleChangeAction(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NO-OP
|
||||||
|
private void handleHandShake(Packet packet) {}
|
||||||
|
// NO-OP
|
||||||
|
private void handleHeartBeat(Packet packet) {}
|
||||||
|
|
||||||
|
private void handleDumpConfig(Packet packet) {
|
||||||
|
if (
|
||||||
|
packet.getContent().length() > 4 && packet.getContent().charAt(0) == "[".toCharArray()[0] &&
|
||||||
|
packet.getContent().charAt(packet.getContent().length() - 1) == "]".toCharArray()[0]
|
||||||
|
) { /* "unless" keyword anyone? */ } else { return; }
|
||||||
|
|
||||||
|
Log.i(TAG, "Got valid json: " + packet.getContent());
|
||||||
|
|
||||||
|
if (hostIsAConnection) {
|
||||||
|
// save and reload menu
|
||||||
|
// Writer.overwriteConfigFile(packet.getContent());
|
||||||
|
|
||||||
|
// Backend.instance().loadConfig();
|
||||||
|
} else {
|
||||||
|
// save
|
||||||
|
// Writer.overwriteConfigFile(packet.getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleChangeAction(Packet packet) {
|
||||||
|
// TODO: Handle renaming, deleting, and adding.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleChangeVariable(Packet packet) {
|
||||||
|
// TODO: Handle renaming, deleting, and adding.
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Packet packetHandShake(String clientUUID) {
|
||||||
|
return Packet.create(Packet.PacketType.HANDSHAKE, clientUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Packet packetHeartBeat() {
|
||||||
|
return Packet.create(Packet.PacketType.HEARTBEAT, Packet.PROTOCOL_HEARTBEAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Packet packetDumpConfig(String string) {
|
||||||
|
string = string.replace("\n", " ");
|
||||||
|
|
||||||
|
return Packet.create(Packet.PacketType.DUMP_CONFIG, string);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package org.timecrafters.TimeCraftersConfigurationTool.tacnet;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.timecrafters.TimeCraftersConfigurationTool.backend.TACNET;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
|
||||||
|
public class Server {
|
||||||
|
private ServerSocket server;
|
||||||
|
private int port;
|
||||||
|
private Client activeClient;
|
||||||
|
private long lastSyncTime = 0;
|
||||||
|
private long syncInterval = TACNET.SYNC_INTERVAL;
|
||||||
|
|
||||||
|
private String TAG = "TACNET|Server";
|
||||||
|
|
||||||
|
private int packetsSent, packetsReceived, clientLastPacketsSent, clientLastPacketsReceived = 0;
|
||||||
|
private long dataSent, dataReceived, clientLastDataSent, clientLastDataReceived = 0;
|
||||||
|
|
||||||
|
private Runnable handleClientRunner;
|
||||||
|
private PacketHandler packetHandler;
|
||||||
|
|
||||||
|
private long lastHeartBeatSent = 0;
|
||||||
|
private long heartBeatInterval = TACNET.HEARTBEAT_INTERVAL;
|
||||||
|
|
||||||
|
public Server(int port) throws IOException {
|
||||||
|
this.server = new ServerSocket();
|
||||||
|
this.port = port;
|
||||||
|
this.packetHandler = new PacketHandler(false);
|
||||||
|
this.handleClientRunner = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
handleClient();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws IOException {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
int connectionAttempts = 0;
|
||||||
|
|
||||||
|
while(!server.isBound() && connectionAttempts < 10) {
|
||||||
|
try {
|
||||||
|
server.bind(new InetSocketAddress(port));
|
||||||
|
Log.i(TAG, "Server bound and ready!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
connectionAttempts++;
|
||||||
|
Log.e(TAG, "Server failed to bind: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!server.isClosed()) {
|
||||||
|
try {
|
||||||
|
runServer();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error running server: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runServer() throws IOException {
|
||||||
|
while (!isClosed()) {
|
||||||
|
|
||||||
|
final Client client = new Client();
|
||||||
|
client.setSyncInterval(syncInterval);
|
||||||
|
client.setSocket(this.server.accept());
|
||||||
|
|
||||||
|
if (activeClient != null && !activeClient.isClosed()) {
|
||||||
|
Log.i(TAG, "Too many clients, already have one connected!");
|
||||||
|
client.close("Too many clients!");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Writer.writeJSON(Writer.getBackupConfigFilePath(), AppSync.getDataStructs());
|
||||||
|
|
||||||
|
this.activeClient = client;
|
||||||
|
// AppSync.getMainActivity().clientConnected();
|
||||||
|
|
||||||
|
activeClient.puts(PacketHandler.packetHandShake( activeClient.uuid() ).toString());
|
||||||
|
// activeClient.puts(PacketHandler.packetDumpConfig( Reader.rawConfigFile() ).toString());
|
||||||
|
|
||||||
|
Log.i(TAG, "Client connected!");
|
||||||
|
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while(activeClient != null && !activeClient.isClosed()) {
|
||||||
|
if (System.currentTimeMillis() > lastSyncTime + syncInterval) {
|
||||||
|
lastSyncTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
activeClient.sync(handleClientRunner);
|
||||||
|
updateNetStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(syncInterval);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Failed to sleep, i guess.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNetStats();
|
||||||
|
activeClient = null;
|
||||||
|
|
||||||
|
clientLastPacketsSent = 0;
|
||||||
|
clientLastPacketsReceived = 0;
|
||||||
|
clientLastDataSent = 0;
|
||||||
|
clientLastDataReceived = 0;
|
||||||
|
|
||||||
|
// AppSync.getMainActivity().clientDisconnected();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleClient() {
|
||||||
|
if (activeClient != null && !activeClient.isClosed()) {
|
||||||
|
String message = activeClient.gets();
|
||||||
|
|
||||||
|
if (message != null) {
|
||||||
|
packetHandler.handle(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() > lastHeartBeatSent + heartBeatInterval) {
|
||||||
|
lastHeartBeatSent = System.currentTimeMillis();
|
||||||
|
|
||||||
|
activeClient.puts(PacketHandler.packetHeartBeat().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() throws IOException {
|
||||||
|
if (this.activeClient != null) {
|
||||||
|
this.activeClient.close();
|
||||||
|
this.activeClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActiveClient() {
|
||||||
|
return activeClient != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client getActiveClient() {
|
||||||
|
return activeClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPacketsSent() {
|
||||||
|
return packetsSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPacketsReceived() {
|
||||||
|
return packetsReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDataSent() {
|
||||||
|
return dataSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDataReceived() {
|
||||||
|
return dataReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateNetStats() {
|
||||||
|
if (activeClient != null) {
|
||||||
|
// NOTE: In and Out are reversed for Server stats
|
||||||
|
|
||||||
|
packetsSent += activeClient.getPacketsReceived() - clientLastPacketsReceived;
|
||||||
|
packetsReceived += activeClient.getPacketsSent() - clientLastPacketsSent;
|
||||||
|
|
||||||
|
dataSent += activeClient.getDataReceived() - clientLastDataReceived;
|
||||||
|
dataReceived += activeClient.getDataSent() - clientLastDataSent;
|
||||||
|
|
||||||
|
clientLastPacketsSent = activeClient.getPacketsSent();
|
||||||
|
clientLastPacketsReceived = activeClient.getPacketsReceived();
|
||||||
|
clientLastDataSent = activeClient.getDataSent();
|
||||||
|
clientLastDataReceived = activeClient.getDataReceived();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBound() {
|
||||||
|
return this.server.isBound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.server.isClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:state_selected="true">
|
<item android:state_selected="true">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="rectangle">
|
||||||
<solid android:color="@color/navigationButtonSelected" />
|
<solid android:color="@color/navigationButtonSelected" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item android:state_hovered="true">
|
<item android:state_hovered="true">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="rectangle">
|
||||||
<solid android:color="@color/navigationButtonHover" />
|
<solid android:color="@color/navigationButtonHover" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item android:state_pressed="true">
|
<item android:state_pressed="true">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="rectangle">
|
||||||
<solid android:color="@color/navigationButtonActive" />
|
<solid android:color="@color/navigationButtonActive" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<shape android:shape="oval">
|
<shape android:shape="rectangle">
|
||||||
<solid android:color="@color/navigationButton" />
|
<solid android:color="@color/navigationButton" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -31,11 +31,14 @@
|
|||||||
android:src="@drawable/search" />
|
android:src="@drawable/search" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
android:id="@+id/search_results"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
</LinearLayout>
|
<LinearLayout
|
||||||
|
android:id="@+id/search_results"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
</ScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user