updated android/osx project and example project structure

This commit is contained in:
Joseph Henry
2016-06-23 11:36:08 -07:00
parent ee54905035
commit 6e16aad79c
158 changed files with 530 additions and 16 deletions

View File

@@ -0,0 +1,13 @@
package com.zerotier.one;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zerotier.one" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:debuggable="true"
android:name=".AnalyticsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<service
android:name=".service.ZeroTierOneService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_VPN_SERVICE" >
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
</service>
<activity
android:name=".ui.NetworkListActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.JoinNetworkActivity"
android:label="Join Network" >
</activity>
<activity
android:name=".ui.PrefsActivity"
android:label="@string/app_name" />
<receiver
android:name=".service.StartupReceiver"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!-- Optionally, register AnalyticsReceiver and AnalyticsService to support background
dispatching on non-Google Play devices -->
<receiver android:name="com.google.android.gms.analytics.AnalyticsReceiver"
android:enabled="true">
<intent-filter>
<action android:name="com.google.android.gms.analytics.ANALYTICS_DISPATCH" />
</intent-filter>
</receiver>
<service android:name="com.google.android.gms.analytics.AnalyticsService"
android:enabled="true"
android:exported="false"/>
<!-- Optionally, register CampaignTrackingReceiver and CampaignTrackingService to enable
installation campaign reporting -->
<receiver android:name="com.google.android.gms.analytics.CampaignTrackingReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>
<service android:name="com.google.android.gms.analytics.CampaignTrackingService" />
<!--
<receiver
android:name=".service.NetworkStateReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
-->
<service
android:name=".service.RuntimeService"
android:enabled="true"
android:exported="true" >
</service>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,15 @@
package Netcon;
/**
* Created by Joseph Henry on 3/14/16.
*/
public class ZT_SDK_Wrapper {
public native int loadsymbols();
// From SDK_ServiceSetup.cpp
public native void startOneService();
static {
System.loadLibrary("ZeroTierOneJNI");
}
}

View File

@@ -0,0 +1,48 @@
package com.zerotier.one;
import android.app.Application;
import android.content.SharedPreferences;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import java.util.UUID;
/**
* Created by Grant on 8/25/2015.
*/
public class AnalyticsApplication extends Application {
private Tracker mTracker;
/**
* Gets the default {@link Tracker} for this {@link Application}.
* @return tracker
*/
synchronized public Tracker getDefaultTracker() {
if (mTracker == null) {
SharedPreferences prefs = getSharedPreferences("user", MODE_PRIVATE);
String uuid = UUID.randomUUID().toString();
String savedUUID = prefs.getString("uuid", UUID.randomUUID().toString());
if(savedUUID.equals(uuid)) {
// this is a newly generated UUID. Save it
SharedPreferences.Editor e = prefs.edit();
e.putString("uuid", savedUUID);
e.apply();
}
GoogleAnalytics analytics = GoogleAnalytics.getInstance(this);
// To enable debug logging use: adb shell setprop log.tag.GAv4 DEBUG
mTracker = analytics.newTracker(R.xml.app_tracker);
mTracker.set("&uid", savedUUID);
mTracker.send(new HitBuilders.ScreenViewBuilder()
.setNewSession()
.build());
}
return mTracker;
}
}

View File

@@ -0,0 +1,71 @@
package com.zerotier.one;
import android.content.Context;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.Tracker;
import java.util.HashMap;
import java.util.Map;
/**
* A collection of Google Analytics trackers. Fetch the tracker you need using
* {@code AnalyticsTrackers.getInstance().get(...)}
* <p/>
* This code was generated by Android Studio but can be safely modified by
* hand at this point.
* <p/>
* TODO: Call {@link #initialize(Context)} from an entry point in your app
* before using this!
*/
public final class AnalyticsTrackers {
public enum Target {
APP,
// Add more trackers here if you need, and update the code in #get(Target) below
}
private static AnalyticsTrackers sInstance;
public static synchronized void initialize(Context context) {
if (sInstance != null) {
throw new IllegalStateException("Extra call to initialize analytics trackers");
}
sInstance = new AnalyticsTrackers(context);
}
public static synchronized AnalyticsTrackers getInstance() {
if (sInstance == null) {
throw new IllegalStateException("Call initialize() before getInstance()");
}
return sInstance;
}
private final Map<Target, Tracker> mTrackers = new HashMap<Target, Tracker>();
private final Context mContext;
/**
* Don't instantiate directly - use {@link #getInstance()} instead.
*/
private AnalyticsTrackers(Context context) {
mContext = context.getApplicationContext();
}
public synchronized Tracker get(Target target) {
if (!mTrackers.containsKey(target)) {
Tracker tracker;
switch (target) {
case APP:
tracker = GoogleAnalytics.getInstance(mContext).newTracker(R.xml.app_tracker);
break;
default:
throw new IllegalArgumentException("Unhandled analytics target " + target);
}
mTrackers.put(target, tracker);
}
return mTrackers.get(target);
}
}

View File

@@ -0,0 +1,18 @@
package com.zerotier.one.events;
import com.zerotier.sdk.ResultCode;
/**
* Created by Grant on 6/23/2015.
*/
public class ErrorEvent {
ResultCode result;
public ErrorEvent(ResultCode rc) {
result = rc;
}
public String getError() {
return result.toString();
}
}

View File

@@ -0,0 +1,18 @@
package com.zerotier.one.events;
import com.zerotier.one.ui.JoinNetworkFragment;
/**
* Created by Grant on 6/23/2015.
*/
public class JoinNetworkEvent {
private long networkId;
public JoinNetworkEvent(long nwid) {
networkId = nwid;
}
public long getNetworkId() {
return networkId;
}
}

View File

@@ -0,0 +1,16 @@
package com.zerotier.one.events;
/**
* Created by Grant on 6/23/2015.
*/
public class LeaveNetworkEvent {
long networkId;
public LeaveNetworkEvent(long nwid) {
networkId = nwid;
}
public long getNetworkId() {
return networkId;
}
}

View File

@@ -0,0 +1,8 @@
package com.zerotier.one.events;
/**
* Created by Grant on 8/4/2015.
*/
public class ManualDisconnectEvent {
public ManualDisconnectEvent() {}
}

View File

@@ -0,0 +1,20 @@
package com.zerotier.one.events;
import android.net.NetworkInfo;
import com.zerotier.sdk.VirtualNetworkConfig;
/**
* Created by Grant on 6/23/2015.
*/
public class NetworkInfoReplyEvent {
private VirtualNetworkConfig vnc;
public NetworkInfoReplyEvent(VirtualNetworkConfig vnc) {
this.vnc = vnc;
}
public VirtualNetworkConfig getNetworkInfo() {
return vnc;
}
}

View File

@@ -0,0 +1,18 @@
package com.zerotier.one.events;
import com.zerotier.sdk.VirtualNetworkConfig;
/**
* Created by Grant on 6/23/2015.
*/
public class NetworkListReplyEvent {
private VirtualNetworkConfig[] networks;
public NetworkListReplyEvent(VirtualNetworkConfig[] networks) {
this.networks = networks;
}
public VirtualNetworkConfig[] getNetworkList() {
return networks;
}
}

View File

@@ -0,0 +1,10 @@
package com.zerotier.one.events;
import android.net.Network;
/**
* Created by Grant on 6/24/2015.
*/
public class NetworkReconfigureEvent {
public NetworkReconfigureEvent() {}
}

View File

@@ -0,0 +1,16 @@
package com.zerotier.one.events;
/**
* Created by Grant on 6/23/2015.
*/
public class NetworkRemovedEvent {
private long networkId;
public NetworkRemovedEvent(long nwid) {
networkId = nwid;
}
public long getNetworkId() {
return networkId;
}
}

View File

@@ -0,0 +1,8 @@
package com.zerotier.one.events;
/**
* Created by Grant on 7/28/2015.
*/
public class NodeDestroyedEvent {
public NodeDestroyedEvent() {}
}

View File

@@ -0,0 +1,16 @@
package com.zerotier.one.events;
/**
* Created by Grant on 7/9/2015.
*/
public class NodeIDEvent {
private long nodeId;
public NodeIDEvent(long nodeId) {
this.nodeId = nodeId;
}
public long getNodeId() {
return nodeId;
}
}

View File

@@ -0,0 +1,18 @@
package com.zerotier.one.events;
import com.zerotier.sdk.NodeStatus;
/**
* Created by Grant on 7/9/2015.
*/
public class NodeStatusEvent {
private NodeStatus status;
public NodeStatusEvent(NodeStatus status) {
this.status = status;
}
public NodeStatus getStatus() {
return status;
}
}

View File

@@ -0,0 +1,16 @@
package com.zerotier.one.events;
/**
* Created by Grant on 6/23/2015.
*/
public class RequestNetworkInfoEvent {
private long networkId;
public RequestNetworkInfoEvent(long nwid) {
networkId = nwid;
}
public long getNetworkId() {
return networkId;
}
}

View File

@@ -0,0 +1,10 @@
package com.zerotier.one.events;
/**
* Created by Grant on 6/23/2015.
*/
public class RequestNetworkListEvent {
public RequestNetworkListEvent() {
}
}

View File

@@ -0,0 +1,10 @@
package com.zerotier.one.events;
/**
* Created by Grant on 8/1/2015.
*/
public class RequestNodeStatusEvent {
public RequestNodeStatusEvent() {
}
}

View File

@@ -0,0 +1,8 @@
package com.zerotier.one.events;
/**
* Created by Grant on 7/9/2015.
*/
public class StopEvent {
public StopEvent() {}
}

View File

@@ -0,0 +1,289 @@
package com.zerotier.one.service;
import android.util.Log;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
public class ARPTable {
public static final String TAG = "ARPTable";
private static final int REQUEST = 1;
private static final int REPLY = 2;
private final HashMap<InetAddress, Long> inetAddressToMacAddress;
private final HashMap<Long, InetAddress> macAddressToInetAdddress;
private static final long ENTRY_TIMEOUT = 120000L; // 2 minutes
private final ArrayList<ArpEntry> entries;
public ARPTable() {
entries = new ArrayList<ArpEntry>();
inetAddressToMacAddress = new HashMap<InetAddress, Long>();
macAddressToInetAdddress = new HashMap<Long, InetAddress>();
timeoutThread.start();
}
@Override
protected void finalize() throws Throwable {
timeoutThread.interrupt();
super.finalize();
}
void setAddress(InetAddress addr, long mac) {
synchronized (inetAddressToMacAddress) {
inetAddressToMacAddress.put(addr, mac);
}
synchronized (macAddressToInetAdddress) {
macAddressToInetAdddress.put(mac, addr);
}
synchronized (entries) {
ArpEntry entry = new ArpEntry(mac, addr);
if(entries.contains(entry)) {
// update the entry time
int index = entries.indexOf(entry);
entries.get(index).updateTime();
} else {
entries.add(entry);
}
}
}
private void updateArpEntryTime(long mac) {
synchronized (entries) {
for(ArpEntry e : entries) {
if(mac == e.getMac()) {
e.updateTime();
}
}
}
}
private void updateArpEntryTime(InetAddress addr) {
synchronized (entries) {
for(ArpEntry e : entries) {
if(addr.equals(e.getAddress())) {
e.updateTime();
}
}
}
}
/**
* Gets the MAC address for a given InetAddress
*
* @param addr address to get the MAC for
* @return MAC address as long. -1 if the address isn't known
*/
long getMacForAddress(InetAddress addr) {
synchronized (inetAddressToMacAddress) {
if(inetAddressToMacAddress.containsKey(addr)) {
long mac = inetAddressToMacAddress.get(addr);
updateArpEntryTime(mac);
return mac;
}
}
return -1;
}
/**
* Get the InetAddress for a given MAC address
*
* @param mac mac address to lookup.
* @return InetAddress if it's in the map. Otherwise null.
*/
InetAddress getAddressForMac(long mac) {
synchronized (macAddressToInetAdddress) {
if (macAddressToInetAdddress.containsKey(mac)) {
InetAddress addr = macAddressToInetAdddress.get(mac);
updateArpEntryTime(addr);
return addr;
}
}
return null;
}
public boolean hasMacForAddress(InetAddress addr) {
synchronized (inetAddressToMacAddress) {
return inetAddressToMacAddress.containsKey(addr);
}
}
public boolean hasAddressForMac(long mac) {
synchronized (macAddressToInetAdddress) {
return macAddressToInetAdddress.containsKey(mac);
}
}
public static byte[] longToBytes(long l) {
// allocate an 8 byte buffer. Long.SIZE returns number of bits so divide by 8
ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE/8);
buffer.putLong(l);
return buffer.array();
}
public byte[] getRequestPacket(long senderMac, InetAddress senderAddress, InetAddress destinationAddress) {
return getARPPacket(REQUEST, senderMac, 0, senderAddress, destinationAddress);
}
public byte[] getReplyPacket(long senderMac, InetAddress senderAddress, long destMac, InetAddress destAddress) {
return getARPPacket(REPLY, senderMac, destMac, senderAddress, destAddress);
}
public byte[] getARPPacket(int packetType, long senderMac, long destMac, InetAddress senderAddress, InetAddress destinationAddress) {
byte[] packet = new byte[28];
// Ethernet packet type
packet[0] = 0;
packet[1] = 1;
// IPV4 Protocol. 0x0800
packet[2] = 0x08;
packet[3] = 0;
// Hardware MAC address length
packet[4] = 6;
// IP address length
packet[5] = 4;
packet[6] = 0;
packet[7] = (byte)packetType;
byte[] senderMacBuffer = longToBytes(senderMac);
System.arraycopy(senderMacBuffer, 2, packet, 8, 6);
byte[] addrBuffer = senderAddress.getAddress();
System.arraycopy(addrBuffer, 0, packet, 14, 4);
byte[] destMacBuffer = longToBytes(destMac);
System.arraycopy(destMacBuffer, 2, packet, 18, 6);
byte[] destAddrBuffer = destinationAddress.getAddress();
System.arraycopy(destAddrBuffer, 0, packet, 24, 4);
return packet;
}
/**
* Returns true if a reply is needed
*
* @param replyBuffer
* @return
*/
public ARPReplyData processARPPacket(byte[] replyBuffer) {
Log.d(TAG, "Processing ARP packet");
byte[] srcMacBuffer = new byte[8];
System.arraycopy(replyBuffer, 8, srcMacBuffer, 2, 6);
byte[] senderAddressBuffer = new byte[4];
System.arraycopy(replyBuffer, 14, senderAddressBuffer, 0, 4);
byte[] destMacBuffer = new byte[8];
System.arraycopy(replyBuffer, 18, destMacBuffer, 2, 6);
byte[] destAddressBuffer = new byte[4];
System.arraycopy(replyBuffer, 24, destAddressBuffer, 0, 4);
InetAddress senderAddress = null;
try {
senderAddress = InetAddress.getByAddress(senderAddressBuffer);
} catch (Exception e) {
}
InetAddress destAddress = null;
try {
destAddress = InetAddress.getByAddress(destAddressBuffer);
} catch (Exception e) {
}
long sourceMac = ByteBuffer.wrap(srcMacBuffer).getLong();
long destMac = ByteBuffer.wrap(destMacBuffer).getLong();
if(sourceMac != 0 && senderAddress != null) {
setAddress(senderAddress,sourceMac);
}
if(destMac != 0 && destAddress != null) {
setAddress(destAddress, destMac);
}
if(replyBuffer[7] == 1) {
Log.d(TAG, "Reply needed");
ARPReplyData data = new ARPReplyData();
data.destMac = sourceMac;
data.destAddress = senderAddress;
return data;
}
return null;
}
public class ARPReplyData {
public long senderMac;
public InetAddress senderAddress;
public long destMac;
public InetAddress destAddress;
}
private class ArpEntry {
private long mac;
private InetAddress address;
private long time;
ArpEntry(long mac, InetAddress address) {
this.mac = mac;
this.address = address;
updateTime();
}
public long getMac() { return this.mac; }
public InetAddress getAddress() { return this.address; }
public void updateTime() {
this.time = System.currentTimeMillis();
}
public boolean equals(ArpEntry e) {
// purposely do not check time
// mac & address alone determine equality
return (mac == e.mac) &&
(address.equals(e.address));
}
}
private Thread timeoutThread = new Thread("ARP Timeout Thread") {
public void run() {
try {
synchronized (entries) {
for (ArpEntry e : entries) {
if(e.time < (System.currentTimeMillis() + ENTRY_TIMEOUT) ) {
synchronized(macAddressToInetAdddress) {
macAddressToInetAdddress.remove(e.mac);
}
synchronized (inetAddressToMacAddress) {
inetAddressToMacAddress.remove(e.address);
}
entries.remove(e);
}
}
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}

View File

@@ -0,0 +1,141 @@
package com.zerotier.one.service;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import com.zerotier.sdk.DataStoreGetListener;
import com.zerotier.sdk.DataStorePutListener;
public class DataStore implements DataStoreGetListener, DataStorePutListener {
private static final String TAG = "DataStore";
private Context _ctx;
public DataStore(Context ctx) {
this._ctx = ctx;
}
@Override
public int onDataStorePut(String name, byte[] buffer, boolean secure) {
Log.d(TAG, "Writing File: " + name);
try {
if(name.contains("/")) {
// filename has a directory separator.
int ix = name.lastIndexOf('/');
String path = name.substring(0, ix);
File f = new File(_ctx.getFilesDir(), path);
if(!f.exists()) {
f.mkdirs();
}
File outputFile = new File(f, name.substring(name.lastIndexOf("/")+1));
FileOutputStream fos = new FileOutputStream(outputFile);
fos.write(buffer);
fos.flush();
fos.close();
return 0;
} else {
FileOutputStream fos = _ctx.openFileOutput(name, Context.MODE_PRIVATE);
fos.write(buffer);
fos.flush();
fos.close();
return 0;
}
} catch (FileNotFoundException fnf) {
fnf.printStackTrace();
return -1;
} catch (IOException io) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
io.printStackTrace(pw);
Log.e(TAG, sw.toString());
return -2;
} catch(IllegalArgumentException ie) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ie.printStackTrace(pw);
Log.e(TAG, sw.toString());
return -3;
}
}
@Override
public int onDelete(String name) {
Log.d(TAG, "Deleting File: " + name);
boolean deleted = false;
if(name.contains("/")) {
// filename has a directory separator.
File f = new File(_ctx.getFilesDir(), name);
if (!f.exists()) {
deleted = true;
} else {
deleted = f.delete();
}
} else {
deleted = _ctx.deleteFile(name);
}
return deleted ? 0 : 1;
}
@Override
public long onDataStoreGet(String name, byte[] out_buffer,
long bufferIndex, long[] out_objectSize) {
Log.d(TAG, "Reading File: " + name);
try {
if(name.contains("/")) {
// filename has a directory separator.
int ix = name.lastIndexOf('/');
String path = name.substring(0, ix);
File f = new File(_ctx.getFilesDir(), path);
if(!f.exists()) {
f.mkdirs();
}
File inputFile = new File(f, name.substring(name.lastIndexOf('/')+1));
if(!inputFile.exists()) {
return 0;
}
FileInputStream fin = new FileInputStream(inputFile);
if(bufferIndex > 0) {
fin.skip(bufferIndex);
}
int read = fin.read(out_buffer);
fin.close();
return read;
} else {
FileInputStream fin = _ctx.openFileInput(name);
out_objectSize[0] = fin.getChannel().size();
if (bufferIndex > 0) {
fin.skip(bufferIndex);
}
int read = fin.read(out_buffer);
fin.close();
return read;
}
} catch (FileNotFoundException fnf) {
// Can't read a file that doesn't exist!
out_objectSize[0] = 0;
return -1;
} catch (IOException io) {
io.printStackTrace();
return -2;
} catch (Exception ex) {
ex.printStackTrace();
return -3;
}
}
}

View File

@@ -0,0 +1,40 @@
package com.zerotier.one.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import com.zerotier.one.events.StopEvent;
import de.greenrobot.event.EventBus;
/**
* Created by Grant on 7/7/2015.
*/
public class NetworkStateReceiver extends BroadcastReceiver {
private static String TAG = "NetworkStateReceiver";
@Override
public void onReceive(Context ctx, Intent intent) {
ConnectivityManager cm =
(ConnectivityManager)ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = (activeNetwork != null &&
activeNetwork.isConnectedOrConnecting());
Intent i = new Intent(ctx, RuntimeService.class);
if(isConnected) {
Log.d(TAG, "Network State: Connected");
i.putExtra(RuntimeService.START_TYPE, RuntimeService.START_NETWORK_CHANGE);
ctx.startService(i);
} else {
Log.d(TAG, "Network State: Disconnected");
i.putExtra(RuntimeService.START_TYPE, RuntimeService.STOP_NETWORK_CHANGE);
ctx.startService(i);
}
}
}

View File

@@ -0,0 +1,30 @@
package com.zerotier.one.service;
import com.zerotier.one.util.InetAddressUtils;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
/**
* Created by Grant on 6/13/2015.
*/
public class Route {
InetAddress address;
int prefix;
public Route(InetAddress address, int prefix) {
this.address = address;
this.prefix = prefix;
}
public boolean belongsToRoute(InetAddress otherAddr) {
InetAddress route = InetAddressUtils.addressToRoute(otherAddr, prefix);
return address.equals(route);
}
public boolean equals(Route r) {
return address.equals(r.address) && prefix == r.prefix;
}
}

View File

@@ -0,0 +1,116 @@
package com.zerotier.one.service;
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.util.Log;
import com.zerotier.one.events.ManualDisconnectEvent;
import com.zerotier.one.events.StopEvent;
import de.greenrobot.event.EventBus;
public class RuntimeService extends Service {
public static final String START_TYPE = "com.zerotier.one.service.RuntimeService.start_type";
public static final int START_NETWORK_CHANGE = 1;
public static final int START_BOOT = 2;
public static final int START_USER_INTERFACE = 3;
public static final int STOP_NETWORK_CHANGE = 4;
public static final int STOP_USER_INTERFACE = 5;
private static final String TAG = "RuntimeService";
private EventBus eventBus = EventBus.getDefault();
private boolean serviceStarted = false;
private NetworkStateReceiver nsReceiver = null;
public RuntimeService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "RuntimeService started");
if(nsReceiver == null) {
IntentFilter filter = new IntentFilter();
filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
nsReceiver = new NetworkStateReceiver();
registerReceiver(nsReceiver, filter);
}
if(intent == null) {
return START_STICKY;
}
int startMode = intent.getIntExtra(START_TYPE, 0);
// in each of the following cases, Prepare should always return null as the prepare call
// is called the first time the UI is started granting permission for ZeroTier to use
// the VPN API
switch (startMode) {
case START_NETWORK_CHANGE: {
Log.d(TAG, "Start Network change");
if(serviceStarted) {
// start ZeroTierOne service.
Log.d(TAG, "Start Network Change");
Intent i = ZeroTierOneService.prepare(this);
if(i == null) {
i = new Intent(this, ZeroTierOneService.class);
startService(i);
serviceStarted = true;
}
} else {
Log.d(TAG, "Ignore Start Network Change: Service has not been manually started.");
}
break;
}
case START_BOOT: {
// if start on boot, start service
Log.d(TAG, "Start Boot");
Intent i = ZeroTierOneService.prepare(this);
if(i == null) {
i = new Intent(this, ZeroTierOneService.class);
startService(i);
serviceStarted = true;
}
break;
}
case START_USER_INTERFACE: {
Log.d(TAG, "Start User Interface");
Intent i = ZeroTierOneService.prepare(this);
if(i == null) {
i = new Intent(this, ZeroTierOneService.class);
startService(i);
serviceStarted = true;
}
break;
}
case STOP_NETWORK_CHANGE: {
Log.d(TAG, "Stop Network Change. Ignored.");
// Intent i = new Intent(this, ZeroTierOneService.class);
// stopService(i);
// EventBus.getDefault().post(new StopEvent());
break;
}
case STOP_USER_INTERFACE: {
Log.d(TAG, "Stop User Interface");
Intent i = new Intent(this, ZeroTierOneService.class);
stopService(i);
EventBus.getDefault().post(new ManualDisconnectEvent());
break;
}
default:
Log.e(TAG, "Unknown start ID: " + startId);
break;
}
return START_STICKY;
}
}

View File

@@ -0,0 +1,34 @@
package com.zerotier.one.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* Broadcast receiver that listens for system bootup and starts
* the ZeroTier service
*/
public class StartupReceiver extends BroadcastReceiver {
private static final String TAG = "StartupReceiver";
@Override
public void onReceive(Context ctx, Intent intent) {
Log.i(TAG, "Received: " + intent.getAction()+ ". Starting ZeroTier One service.");
// TODO: Start service
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
boolean shouldStart = prefs.getBoolean("general_start_zerotier_on_boot", true);
if(shouldStart) {
Log.i(TAG, "Preferences set to start ZeroTier on boot");
Intent i = new Intent(ctx, RuntimeService.class);
i.putExtra(RuntimeService.START_TYPE, RuntimeService.START_BOOT);
ctx.startService(i);
} else {
Log.i(TAG, "Preferences set to not start ZeroTier on boot");
}
}
}

View File

@@ -0,0 +1,272 @@
package com.zerotier.one.service;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.zerotier.one.util.IPPacketUtils;
import com.zerotier.sdk.Node;
import com.zerotier.sdk.ResultCode;
import com.zerotier.sdk.VirtualNetworkConfig;
import com.zerotier.sdk.VirtualNetworkFrameListener;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
/**
* Created by Grant on 6/3/2015.
*/
public class TunTapAdapter implements VirtualNetworkFrameListener {
public static final String TAG = "TunTapAdapter";
private static final int ARP_PACKET = 0x0806;
private static final int IPV4_PACKET = 0x0800;
private static final int IPV6_PACKET = 0x86dd;
public static final long BROADCAST_MAC = 0xffffffffffffL;
private ARPTable arpTable;
private Node node;
private ZeroTierOneService ztService;
private ParcelFileDescriptor vpnSocket;
private FileInputStream in;
private FileOutputStream out;
private final HashMap<Route, Long> routeMap;
private Thread receiveThread;
public TunTapAdapter(ZeroTierOneService ztService) {
this.ztService = ztService;
arpTable = new ARPTable();
routeMap = new HashMap<Route, Long>();
}
public void setNode(Node node) {
this.node = node;
}
public void setVpnSocket(ParcelFileDescriptor vpnSocket) {
this.vpnSocket = vpnSocket;
}
public void setFileStreams(FileInputStream in, FileOutputStream out) {
this.in = in;
this.out = out;
}
public void addRouteAndNetwork(Route route, long networkId) {
synchronized (routeMap) {
routeMap.put(route, networkId);
}
}
public void clearRouteMap() {
synchronized (routeMap) {
routeMap.clear();
}
}
public void startThreads() {
receiveThread = new Thread("Tunnel Receive Thread") {
public void run() {
try {
Log.d(TAG, "TUN Receive Thread Started");
ByteBuffer buffer = ByteBuffer.allocate(32767);
buffer.order(ByteOrder.LITTLE_ENDIAN);
try {
while (!isInterrupted()) {
boolean idle = true;
int length = in.read(buffer.array());
if (length > 0) {
Log.d(TAG, "Sending packet to ZeroTier. " + length + " bytes.");
idle = false;
byte packet[] = new byte[length];
System.arraycopy(buffer.array(), 0, packet, 0, length);
InetAddress destAddress = IPPacketUtils.getDestIP(packet);
InetAddress sourceAddress = IPPacketUtils.getSourceIP(packet);
long nwid = networkIdForDestination(destAddress);
if (nwid == 0) {
Log.e(TAG, "Unable to find network ID for destination address: " + destAddress);
continue;
}
VirtualNetworkConfig cfg = node.networkConfig(nwid);
InetAddress myAddress = cfg.assignedAddresses()[0].getAddress();
long srcMac = cfg.macAddress();
long bgt_dl[] = new long[1];
if (arpTable.hasMacForAddress(destAddress)) {
long destMac = arpTable.getMacForAddress(destAddress);
ResultCode rc = node.processVirtualNetworkFrame(
System.currentTimeMillis(),
nwid,
srcMac,
destMac,
IPV4_PACKET,
0,
packet,
bgt_dl);
if (rc != ResultCode.RESULT_OK) {
Log.e(TAG, "Error calling processVirtualNetworkFrame: " + rc.toString());
} else {
Log.d(TAG, "Packet sent to ZT");
ztService.setNextBackgroundTaskDeadline(bgt_dl[0]);
}
} else {
Log.d(TAG, "Unknown dest MAC address. Need to look it up. " + destAddress);
byte[] arpRequest = arpTable.getRequestPacket(srcMac, myAddress, destAddress);
ResultCode rc = node.processVirtualNetworkFrame(
System.currentTimeMillis(),
nwid,
srcMac,
BROADCAST_MAC,
ARP_PACKET,
0,
arpRequest,
bgt_dl);
if (rc != ResultCode.RESULT_OK) {
Log.e(TAG, "Error sending ARP packet: " + rc.toString());
} else {
Log.d(TAG, "ARP Request Sent!");
ztService.setNextBackgroundTaskDeadline(bgt_dl[0]);
}
}
buffer.clear();
} else {
//Log.d(TAG, "No bytes read: " + length);
}
if (idle) {
Thread.sleep(100);
}
}
} catch (InterruptedException e) {
throw e;
} catch (Exception e) {
Log.e(TAG, "Error in TUN Receive: " + e.getMessage());
}
} catch (Exception e) {
// swallow InterruptedException
}
Log.d(TAG, "TUN Receive Thread ended");
}
};
receiveThread.start();
}
public void interrupt() {
if (receiveThread != null) {
receiveThread.interrupt();
try {
receiveThread.join();
} catch (InterruptedException e) {
// swallow
}
}
}
public boolean isRunning() {
if (receiveThread == null) {
return false;
} else {
return receiveThread.isAlive();
}
}
public void onVirtualNetworkFrame(
long nwid,
long srcMac,
long destMac,
long etherType,
long vlanId,
byte[] frameData) {
Log.d(TAG, "Got Virtual Network Frame. Network ID: " + Long.toHexString(nwid) + " Source MAC: " + Long.toHexString(srcMac) +
" Dest MAC: " + Long.toHexString(destMac) + " Ether type: " + etherType + " VLAN ID: " + vlanId + " Frame Length: " + frameData.length);
if (vpnSocket == null) {
Log.e(TAG, "vpnSocket is null!");
return;
}
if (in == null || out == null) {
Log.e(TAG, "no in/out streams");
return;
}
if (etherType == ARP_PACKET) {
Log.d(TAG, "Got ARP Packet");
ARPTable.ARPReplyData data = arpTable.processARPPacket(frameData);
if (data != null && data.destMac != 0 && data.destAddress != null) {
// We need to reply here.
long deadline[] = new long[1];
VirtualNetworkConfig cfg = node.networkConfig(nwid);
InetAddress myAddress = cfg.assignedAddresses()[0].getAddress();
byte[] replyPacket = arpTable.getReplyPacket(cfg.macAddress(), myAddress, data.destMac, data.destAddress);
ResultCode rc = node.processVirtualNetworkFrame(
System.currentTimeMillis(),
nwid,
cfg.macAddress(),
srcMac,
ARP_PACKET,
0,
replyPacket,
deadline);
if (rc != ResultCode.RESULT_OK) {
Log.e(TAG, "Error sending ARP packet: " + rc.toString());
} else {
Log.d(TAG, "ARP Reply Sent!");
ztService.setNextBackgroundTaskDeadline(deadline[0]);
}
}
} else if ((etherType == IPV4_PACKET) ||
(etherType == IPV6_PACKET)) {
// Got IPv4 or IPv6 packet!
Log.d(TAG, "Got IP packet. Length: " + frameData.length + " Bytes");
try {
InetAddress sourceAddress = IPPacketUtils.getSourceIP(frameData);
if (sourceAddress != null) {
arpTable.setAddress(sourceAddress, srcMac);
}
out.write(frameData);
} catch (Exception e) {
Log.e(TAG, "Error writing data to vpn socket: " + e.getMessage());
}
} else {
Log.d(TAG, "Unknown Packet Type Received: 0x" + String.format("%02X%02X", frameData[12], frameData[13]));
}
}
private long networkIdForDestination(InetAddress destination) {
synchronized (routeMap) {
for (Route r : routeMap.keySet()) {
if (r.belongsToRoute(destination)) {
return routeMap.get(r);
}
}
}
return 0L;
}
}

View File

@@ -0,0 +1,91 @@
package com.zerotier.one.service;
import android.util.Log;
import com.zerotier.sdk.Node;
import com.zerotier.sdk.PacketSender;
import com.zerotier.sdk.ResultCode;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
/**
* Created by Grant on 6/3/2015.
*/
public class UdpCom implements PacketSender, Runnable {
private final static String TAG = "UdpCom";
Node node;
ZeroTierOneService ztService;
DatagramSocket svrSocket;
UdpCom(ZeroTierOneService service, DatagramSocket socket) {
svrSocket = socket;
this.ztService = service;
}
public void setNode(Node node) {
this.node = node;
}
public int onSendPacketRequested(
InetSocketAddress localAddress,
InetSocketAddress remoteAddress,
byte[] packetData,
int ttl) {
// TTL is ignored. No way to set it on a UDP packet in Java
if(svrSocket == null) {
Log.e(TAG, "Attempted to send packet on a null socket");
return -1;
}
try {
DatagramPacket p = new DatagramPacket(packetData, packetData.length, remoteAddress);
Log.d(TAG, "onSendPacketRequested: Sent "+ p.getLength() + " bytes to " + remoteAddress.toString());
svrSocket.send(p);
} catch (Exception ex) {
return -1;
}
return 0;
}
public void run() {
Log.d(TAG, "UDP Listen Thread Started.");
try {
long[] bgtask = new long[1];
byte[] buffer = new byte[16384];
while(!Thread.interrupted()) {
bgtask[0] = 0;
DatagramPacket p = new DatagramPacket(buffer, buffer.length);
try {
svrSocket.receive(p);
if(p.getLength() > 0)
{
byte[] received = new byte[p.getLength()];
System.arraycopy(p.getData(), 0, received, 0, p.getLength());
Log.d(TAG, "Got " + p.getLength() + " Bytes From: " + p.getAddress().toString() +":" + p.getPort());
ResultCode rc = node.processWirePacket(System.currentTimeMillis(), null, new InetSocketAddress(p.getAddress(), p.getPort()), received, bgtask);
if(rc != ResultCode.RESULT_OK) {
Log.e(TAG, "procesWirePacket returned: " + rc.toString());
ztService.shutdown();
}
ztService.setNextBackgroundTaskDeadline( bgtask[0] );
}
} catch (SocketTimeoutException e) {
Log.d(TAG, "Socket Timeout");
}
}
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "UDP Listen Thread Ended.");
}
}

View File

@@ -0,0 +1,537 @@
package com.zerotier.one.service;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.VpnService;
import android.content.Intent;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import com.zerotier.one.events.ErrorEvent;
import com.zerotier.one.events.JoinNetworkEvent;
import com.zerotier.one.events.LeaveNetworkEvent;
import com.zerotier.one.events.ManualDisconnectEvent;
import com.zerotier.one.events.NetworkInfoReplyEvent;
import com.zerotier.one.events.NetworkListReplyEvent;
import com.zerotier.one.events.NetworkReconfigureEvent;
import com.zerotier.one.events.NetworkRemovedEvent;
import com.zerotier.one.events.NodeDestroyedEvent;
import com.zerotier.one.events.NodeIDEvent;
import com.zerotier.one.events.NodeStatusEvent;
import com.zerotier.one.events.RequestNetworkInfoEvent;
import com.zerotier.one.events.RequestNetworkListEvent;
import com.zerotier.one.events.RequestNodeStatusEvent;
import com.zerotier.one.events.StopEvent;
import com.zerotier.one.util.InetAddressUtils;
import com.zerotier.one.util.NetworkIdUtils;
import com.zerotier.sdk.Event;
import com.zerotier.sdk.EventListener;
import com.zerotier.sdk.Node;
import com.zerotier.sdk.NodeException;
import com.zerotier.sdk.NodeStatus;
import com.zerotier.sdk.ResultCode;
import com.zerotier.sdk.VirtualNetworkConfig;
import com.zerotier.sdk.VirtualNetworkConfigListener;
import com.zerotier.sdk.VirtualNetworkConfigOperation;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.HashMap;
import de.greenrobot.event.EventBus;
public class ZeroTierOneService extends VpnService implements Runnable, EventListener, VirtualNetworkConfigListener {
private static final String TAG = "ZT1_Service";
public static final String ZT1_NETWORK_ID = "com.zerotier.one.network_id";
public static final int MSG_JOIN_NETWORK = 1;
public static final int MSG_LEAVE_NETWORK = 2;
private Thread vpnThread;
private UdpCom udpCom;
private Thread udpThread;
private TunTapAdapter tunTapAdapter;
private Node node;
private DataStore dataStore;
DatagramSocket svrSocket;
ParcelFileDescriptor vpnSocket;
FileInputStream in;
FileOutputStream out;
private final HashMap<Long, VirtualNetworkConfig> networkConfigs;
private EventBus eventBus = EventBus.getDefault();
private long nextBackgroundTaskDeadline = 0;
private long lastMulticastGroupCheck = 0;
protected void setNextBackgroundTaskDeadline(long deadline) {
synchronized (this) {
nextBackgroundTaskDeadline = deadline;
}
}
public ZeroTierOneService() {
super();
dataStore = new DataStore(this);
networkConfigs = new HashMap<>();
eventBus.register(this);
Netcon.ZT_SDK_Wrapper wrapper = new Netcon.ZT_SDK_Wrapper();
wrapper.startOneService();
/*
if(wrapper.loadsymbols() == 4)
{
Log.e(TAG,"loadsymbols(): Symbol found");
//Toast t = Toast.makeText(this, "WORKED", Toast.LENGTH_SHORT);
}
else {
Log.e(TAG,"loadsymbols(): Symbol NOT found");
//Toast t = Toast.makeText(this, "DIDNT WORK", Toast.LENGTH_SHORT);
}
*/
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
public void stopZeroTier() {
if(udpThread != null && udpThread.isAlive()) {
udpThread.interrupt();
udpThread = null;
}
if(vpnThread != null && vpnThread.isAlive()) {
vpnThread.interrupt();
vpnThread = null;
}
if(svrSocket != null) {
svrSocket.close();
svrSocket = null;
}
if(node != null) {
eventBus.post(new NodeDestroyedEvent());
node.close();
node = null;
}
}
@Override
public void onDestroy() {
stopZeroTier();
super.onDestroy();
}
@Override
public void onRevoke() {
Intent i = new Intent(this, RuntimeService.class);
i.putExtra(RuntimeService.START_TYPE, RuntimeService.STOP_USER_INTERFACE);
this.startService(i);
stopZeroTier();
try {
vpnSocket.close();
} catch (Exception e) {
// swallow it
}
vpnSocket = null;
stopSelf();
Intent stopIntent = new Intent(this, RuntimeService.class);
stopService(stopIntent);
}
public void run() {
/*
Log.d(TAG, "ZeroTierOne Service Started");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean autoRejoin = prefs.getBoolean("network_auto_rejoin_networks", true);
if(autoRejoin) {
// find all local network configs and join them
File networksFolder = new File(getFilesDir(), "networks.d");
if (networksFolder.exists()) {
File[] networks = networksFolder.listFiles();
for (File f : networks) {
if (f.getName().endsWith(".conf")) {
String filename = f.getName();
filename = filename.substring(0, filename.lastIndexOf('.'));
Log.d(TAG, "Loading network: " + filename);
ResultCode rc = node.join(NetworkIdUtils.hexStringToLong(filename));
if (rc != ResultCode.RESULT_OK) {
Log.d(TAG, "Error joining network: " + rc.toString());
}
}
}
}
}
Log.d(TAG, "This Node Address: " + Long.toHexString(node.address()));
while(!Thread.interrupted()) {
try {
long dl = nextBackgroundTaskDeadline;
long now = System.currentTimeMillis();
if (dl <= now) {
long[] returnDeadline = {0};
ResultCode rc = node.processBackgroundTasks(now, returnDeadline);
synchronized(this) {
nextBackgroundTaskDeadline = returnDeadline[0];
}
if(rc != ResultCode.RESULT_OK) {
Log.e(TAG, "Error on processBackgroundTasks: " + rc.toString());
shutdown();
}
}
long delay = (dl > now) ? (dl - now) : 100;
Thread.sleep(delay);
} catch (InterruptedException ie) {
break;
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
}
Log.d(TAG, "ZeroTierOne Service Ended");
*/
}
//
// EventBus events
//
public void onEvent(StopEvent e) {
stopZeroTier();
}
public void onEvent(ManualDisconnectEvent e) {
stopZeroTier();
}
public void onEventBackgroundThread(JoinNetworkEvent e) {
/*
Log.d(TAG, "Join Network Event");
if(node == null) {
return;
}
// TODO: Remove this once multi-network support is in place
for(long nwid : networkConfigs.keySet()) {
onEventBackgroundThread(new LeaveNetworkEvent(nwid));
}
networkConfigs.clear();
ResultCode rc = node.join(e.getNetworkId());
if(rc != ResultCode.RESULT_OK) {
eventBus.post(new ErrorEvent(rc));
}
*/
}
public void onEventBackgroundThread(LeaveNetworkEvent e) {
/*
Log.d(TAG, "Leave Network Event");
if(node != null) {
ResultCode rc = node.leave(e.getNetworkId());
if (rc != ResultCode.RESULT_OK) {
eventBus.post(new ErrorEvent(rc));
return;
}
String certsFile = "networks.d/" + Long.toHexString(e.getNetworkId()) + ".mcerts";
String confFile = "networks.d/" + Long.toHexString(e.getNetworkId()) + ".conf";
dataStore.onDelete(confFile);
dataStore.onDelete(certsFile);
}
*/
}
public void onEventBackgroundThread(RequestNetworkInfoEvent e) {
/*
if(node == null) {
return;
}
VirtualNetworkConfig vnc = node.networkConfig(e.getNetworkId());
if(vnc != null) {
eventBus.post(new NetworkInfoReplyEvent(vnc));
}
*/
}
public void onEventBackgroundThread(RequestNetworkListEvent e) {
/*
if(node == null) {
return;
}
VirtualNetworkConfig[] networks = node.networks();
if(networks != null && networks.length > 0) {
eventBus.post(new NetworkListReplyEvent(networks));
}
*/
}
public void onEventBackgroundThread(RequestNodeStatusEvent e) {
/*
if (node == null) {
return;
}
NodeStatus ns = node.status();
eventBus.post(new NodeStatusEvent(ns));
*/
}
public void onEventAsync(NetworkReconfigureEvent e) {
updateTunnelConfig();
}
//
// Event Listener Overrides
//
public void onEvent(Event e) {
/*
Log.d(TAG, "Event: " + e.toString());
if(node != null) {
NodeStatus status = node.status();
NodeStatusEvent nse = new NodeStatusEvent(status);
eventBus.post(nse);
}
*/
}
public void onTrace(String msg) {
Log.d(TAG, "Trace: " + msg);
}
//
// Virtual Network Config Listener overrides
//
public int onNetworkConfigurationUpdated(
long nwid,
VirtualNetworkConfigOperation op,
VirtualNetworkConfig config) {
/*
Log.d(TAG, "Virtual Network Config Operation: " + op.toString());
switch(op) {
case VIRTUAL_NETWORK_CONFIG_OPERATION_UP: {
Log.d(TAG, "Network Type:" + config.networkType().toString() + " " +
"Network Status: " + config.networkStatus().toString() + " " +
"Network Name: " + config.name() + " ");
eventBus.post(new NetworkInfoReplyEvent(config));
}
break;
case VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE: {
Log.d(TAG, "Network Config Update!");
VirtualNetworkConfig cfg = null;
synchronized (networkConfigs) {
if (networkConfigs.containsKey(nwid)) {
cfg = networkConfigs.get(nwid);
}
}
if(cfg == null) {
// we don't already have this network config
Log.d(TAG, "Adding new network.");
synchronized (networkConfigs) {
networkConfigs.put(nwid, config);
}
eventBus.post(new NetworkReconfigureEvent());
eventBus.post(new NetworkInfoReplyEvent(config));
break;
}
if(!cfg.equals(config)) {
Log.d(TAG, "Updating network config");
synchronized (networkConfigs) {
networkConfigs.remove(nwid);
networkConfigs.put(nwid, config);
}
eventBus.post(new NetworkReconfigureEvent());
}
eventBus.post(new NetworkInfoReplyEvent(config));
}
break;
case VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN:
case VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY:
Log.d(TAG, "Network Down!");
synchronized (networkConfigs) {
if (networkConfigs.containsKey(nwid)) {
networkConfigs.remove(nwid);
}
}
eventBus.post(new NetworkReconfigureEvent());
eventBus.post(new NetworkRemovedEvent(nwid));
break;
default:
Log.e(TAG, "Unknown Network Config Operation!");
break;
}
*/
return 0;
}
protected void shutdown() {
stopZeroTier();
this.stopSelf();
}
/**
* This should ONLY be called from onEventAsync(NetworkReconfigureEvent)
*/
private void updateTunnelConfig() {
/*
synchronized (networkConfigs) {
if (networkConfigs.isEmpty()) {
return;
}
if (tunTapAdapter.isRunning()) {
tunTapAdapter.interrupt();
}
tunTapAdapter.clearRouteMap();
if (vpnSocket != null) {
try {
vpnSocket.close();
in.close();
out.close();
} catch (Exception e) {
// ignore
}
vpnSocket = null;
in = null;
out = null;
}
Builder builder = new Builder();
int highestMtu = 0;
for (VirtualNetworkConfig config : networkConfigs.values()) {
if (config.isEnabled()) {
long nwid = config.networkId();
InetSocketAddress addresses[] = config.assignedAddresses();
int adi = 0;
for (int i = 0; i < addresses.length; ++i) {
Log.d(TAG, "Adding VPN Address: " + addresses[i].getAddress() + " Mac: " + Long.toHexString(config.macAddress()));
byte[] addrBytes = addresses[i].getAddress().getAddress();
try {
adi = ByteBuffer.wrap(addrBytes).getInt();
} catch (Exception e) {
Log.e(TAG, "Exception calculating multicast ADI: " + e.getMessage());
continue;
}
int routeSub = addresses[i].getPort();
InetAddress address = addresses[i].getAddress();
// TODO: Support IPv6
if(address instanceof Inet6Address) {
Log.d(TAG, "Got an IPV6 Address. Not adding it to the adapter");
continue;
}
InetAddress route = InetAddressUtils.addressToRoute(address, routeSub);
if(route == null) {
Log.e(TAG, "NULL route calculated!");
continue;
}
ResultCode rc = node.multicastSubscribe(nwid, TunTapAdapter.BROADCAST_MAC, adi);
if (rc != ResultCode.RESULT_OK) {
Log.e(TAG, "Error joining multicast group");
} else {
Log.d(TAG, "Joined multicast group");
}
builder.addAddress(address, routeSub);
builder.addRoute(route, routeSub);
Route r = new Route(route, routeSub);
tunTapAdapter.addRouteAndNetwork(r, nwid);
int mtu = config.mtu();
if (mtu > highestMtu) {
highestMtu = mtu;
}
}
}
}
builder.setMtu(highestMtu);
builder.setSession("ZeroTier One");
vpnSocket = builder.establish();
if(vpnSocket == null) {
Log.e(TAG, "vpnSocket is NULL after builder.establish()!!!!");
stopZeroTier();
return;
}
in = new FileInputStream(vpnSocket.getFileDescriptor());
out = new FileOutputStream(vpnSocket.getFileDescriptor());
tunTapAdapter.setVpnSocket(vpnSocket);
tunTapAdapter.setFileStreams(in, out);
tunTapAdapter.startThreads();
Log.i(TAG, "ZeroTier One Connected");
}
*/
}
}

View File

@@ -0,0 +1,213 @@
/**
* Copyright 2013 Maarten Pennings
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* If you use this software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*/
package com.zerotier.one.ui;
import android.app.Activity;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
import android.text.Editable;
import android.text.InputType;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
/**
* When an activity hosts a keyboardView, this class allows several EditText's to register for it.
*
* @author Maarten Pennings
* @date 2012 December 23
*/
class HexKeyboard {
private final static String TAG = "HexKeyboard";
/** A link to the KeyboardView that is used to render this CustomKeyboard. */
private KeyboardView mKeyboardView;
/** A link to the activity that hosts the {@link #mKeyboardView}. */
private Activity mHostActivity;
/** The key (code) handler. */
private OnKeyboardActionListener mOnKeyboardActionListener = new OnKeyboardActionListener() {
public final static int CodeDelete = -5; // Keyboard.KEYCODE_DELETE
public final static int CodeCancel = -3; // Keyboard.KEYCODE_CANCEL
public final static int CodePrev = 55000;
public final static int CodeAllLeft = 55001;
public final static int CodeLeft = 55002;
public final static int CodeRight = 55003;
public final static int CodeAllRight = 55004;
public final static int CodeNext = 55005;
public final static int CodeClear = 55006;
@Override
public void onKey(int primaryCode, int[] keyCodes) {
// NOTE We can say '<Key android:codes="49,50" ... >' in the xml file; all codes come in keyCodes, the first in this list in primaryCode
// Get the EditText and its Editable
View focusCurrent = mHostActivity.getWindow().getCurrentFocus();
if( focusCurrent==null) return;
EditText edittext = (EditText) focusCurrent;
Editable editable = edittext.getText();
int start = edittext.getSelectionStart();
// Apply the key to the edittext
if( primaryCode==CodeCancel ) {
hideCustomKeyboard();
} else if( primaryCode==CodeDelete ) {
if( editable!=null && start>0 ) editable.delete(start - 1, start);
} else if( primaryCode==CodeClear ) {
if( editable!=null ) editable.clear();
} else if( primaryCode==CodeLeft ) {
if( start>0 ) edittext.setSelection(start - 1);
} else if( primaryCode==CodeRight ) {
if (start < edittext.length()) edittext.setSelection(start + 1);
} else if( primaryCode==CodeAllLeft ) {
edittext.setSelection(0);
} else if( primaryCode==CodeAllRight ) {
edittext.setSelection(edittext.length());
} else if( primaryCode==CodePrev ) {
View focusNew= edittext.focusSearch(View.FOCUS_LEFT);
if( focusNew!=null ) focusNew.requestFocus();
} else if( primaryCode==CodeNext ) {
View focusNew= edittext.focusSearch(View.FOCUS_RIGHT);
if( focusNew!=null ) focusNew.requestFocus();
} else { // insert character
editable.insert(start, Character.toString((char) primaryCode));
}
}
@Override
public void onPress(int arg0) {
}
@Override
public void onRelease(int primaryCode) {
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeDown() {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeRight() {
}
@Override
public void swipeUp() {
}
};
/**
* Create a custom keyboard, that uses the KeyboardView (with resource id <var>viewid</var>) of the <var>host</var> activity,
* and load the keyboard layout from xml file <var>layoutid</var> (see {@link Keyboard} for description).
* Note that the <var>host</var> activity must have a <var>KeyboardView</var> in its layout (typically aligned with the bottom of the activity).
* Note that the keyboard layout xml file may include key codes for navigation; see the constants in this class for their values.
* Note that to enable EditText's to use this custom keyboard, call the {@link #registerEditText(int)}.
*
* @param host The hosting activity.
* @param viewid The id of the KeyboardView.
* @param layoutid The id of the xml file containing the keyboard layout.
*/
public HexKeyboard(Activity host, int viewid, int layoutid) {
mHostActivity= host;
mKeyboardView= (KeyboardView)mHostActivity.findViewById(viewid);
mKeyboardView.setKeyboard(new Keyboard(mHostActivity, layoutid));
mKeyboardView.setPreviewEnabled(false); // NOTE Do not show the preview balloons
mKeyboardView.setOnKeyboardActionListener(mOnKeyboardActionListener);
// Hide the standard keyboard initially
mHostActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
/** Returns whether the CustomKeyboard is visible. */
public boolean isCustomKeyboardVisible() {
return mKeyboardView.getVisibility() == View.VISIBLE;
}
/** Make the CustomKeyboard visible, and hide the system keyboard for view v. */
public void showCustomKeyboard( View v ) {
mKeyboardView.setVisibility(View.VISIBLE);
mKeyboardView.setEnabled(true);
if( v!=null ) ((InputMethodManager)mHostActivity.getSystemService(Activity.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(v.getWindowToken(), 0);
}
/** Make the CustomKeyboard invisible. */
public void hideCustomKeyboard() {
mKeyboardView.setVisibility(View.GONE);
mKeyboardView.setEnabled(false);
}
/**
* Register <var>EditText<var> with resource id <var>resid</var> (on the hosting activity) for using this custom keyboard.
*
* @param resid The resource id of the EditText that registers to the custom keyboard.
*/
public void registerEditText(int resid) {
// Find the EditText 'resid'
EditText edittext= (EditText)mHostActivity.findViewById(resid);
// Make the custom keyboard appear
edittext.setOnFocusChangeListener(new OnFocusChangeListener() {
// NOTE By setting the on focus listener, we can show the custom keyboard when the edit box gets focus, but also hide it when the edit box loses focus
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) showCustomKeyboard(v);
else hideCustomKeyboard();
}
});
edittext.setOnClickListener(new OnClickListener() {
// NOTE By setting the on click listener, we can show the custom keyboard again, by tapping on an edit box that already had focus (but that had the keyboard hidden).
@Override public void onClick(View v) {
showCustomKeyboard(v);
}
});
// Disable standard keyboard hard way
// NOTE There is also an easy way: 'edittext.setInputType(InputType.TYPE_NULL)' (but you will not have a cursor, and no 'edittext.setCursorVisible(true)' doesn't work )
edittext.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
EditText edittext = (EditText) v;
int inType = edittext.getInputType(); // Backup the input type
edittext.setInputType(InputType.TYPE_NULL); // Disable standard keyboard
edittext.onTouchEvent(event); // Call native handler
edittext.setInputType(inType); // Restore input type
return true; // Consume touch event
}
return false;
}
});
// Disable spell check (hex strings look like words to Android)
edittext.setInputType(edittext.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
}

View File

@@ -0,0 +1,12 @@
package com.zerotier.one.ui;
import android.app.Fragment;
/**
* Created by Grant on 5/20/2015.
*/
public class JoinNetworkActivity extends SingleFragmentActivity {
public Fragment createFragment() {
return new JoinNetworkFragment();
}
}

View File

@@ -0,0 +1,240 @@
package com.zerotier.one.ui;
import android.app.Fragment;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import com.zerotier.one.AnalyticsApplication;
import com.zerotier.one.R;
import com.zerotier.one.events.JoinNetworkEvent;
import com.zerotier.one.util.NetworkIdUtils;
import org.json.JSONArray;
import java.util.ArrayList;
import de.greenrobot.event.EventBus;
/**
* Created by Grant on 5/20/2015.
*/
public class JoinNetworkFragment extends Fragment {
public final static String TAG = "JoinNetwork";
private Tracker tracker = null;
private Button mJoinButton;
private EditText mNetworkIdTextView;
private ListView mRecentNetworksList;
EventBus eventBus = EventBus.getDefault();
HexKeyboard mHexKeyboard;
public JoinNetworkFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(tracker == null) {
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
}
tracker.setScreenName("Join Network");
tracker.send(new HitBuilders.ScreenViewBuilder().build());
}
@Override
public void onResume() {
super.onResume();
if(tracker == null) {
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
}
tracker.setScreenName("Join Network");
tracker.send(new HitBuilders.ScreenViewBuilder().build());
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mHexKeyboard = new HexKeyboard(getActivity(), R.id.join_network_keyboard, R.xml.hex_keyboard);
mHexKeyboard.registerEditText(R.id.join_network_edit_text);
mHexKeyboard.hideCustomKeyboard();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
super.onCreateView(inflater, parent, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_join_network, parent, false);
mNetworkIdTextView = (EditText)v.findViewById(R.id.join_network_edit_text);
mNetworkIdTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().length() == 16) {
mJoinButton.setEnabled(true);
} else {
mJoinButton.setEnabled(false);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
mJoinButton = (Button)v.findViewById(R.id.button_join_network);
mJoinButton.setEnabled(false);
mJoinButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Log.d(TAG, "Joining network " + mNetworkIdTextView.getText().toString());
String netIdString = mNetworkIdTextView.getText().toString();
long networkId = NetworkIdUtils.hexStringToLong(netIdString);
SharedPreferences prefs = getActivity().getSharedPreferences("recent_networks", Context.MODE_PRIVATE);
try {
String nws = prefs.getString("recent_networks", (new JSONArray()).toString());
JSONArray jArray = new JSONArray(nws);
ArrayList<String> array = new ArrayList<>();
// convert the JSON array to an actual array for ease of modification
for(int i = 0; i < jArray.length(); ++i) {
array.add(jArray.getString(i));
}
boolean containsValue = false;
for(String id : array) {
if(id.equals(netIdString)) {
containsValue = true;
break;
}
}
if(containsValue) {
// remove the item
array.remove(netIdString);
} else {
// pop off the last item
if (array.size() > 4) {
array.remove(4);
}
}
// insert ID at the beginning of the list
array.add(0, mNetworkIdTextView.getText().toString());
// convert the list back to a JSON array
jArray = new JSONArray();
for(String id : array) {
jArray.put(id);
}
// write JSON array to preferences
SharedPreferences.Editor e = prefs.edit();
e.putString("recent_networks", jArray.toString());
e.apply();
} catch (Exception e) {
Log.e(TAG, "Exception setting recent networks: " + e.getMessage());
}
eventBus.post(new JoinNetworkEvent(networkId));
} catch (Throwable t) {
t.printStackTrace();
Log.d(TAG, "Join Network: Error parsing network ID");
} finally {
getActivity().onBackPressed();
}
}
});
mRecentNetworksList = (ListView) v.findViewById(R.id.recent_networks_list);
SharedPreferences prefs = getActivity().getSharedPreferences("recent_networks", Context.MODE_PRIVATE);
try {
String nws = prefs.getString("recent_networks", new JSONArray().toString());
JSONArray networks = new JSONArray(nws);
TextView recentNetworksText = (TextView) v.findViewById(R.id.recent_networks);
if (networks.length() == 0) {
mRecentNetworksList.setVisibility(View.GONE);
recentNetworksText.setVisibility(View.GONE);
} else {
mRecentNetworksList.setVisibility(View.VISIBLE);
recentNetworksText.setVisibility(View.VISIBLE);
ArrayList<String> recentNetworks = new ArrayList<>();
for(int i = 0; i < networks.length(); ++i) {
recentNetworks.add(networks.getString(i));
}
final NetworkIDAdapter adapter = new NetworkIDAdapter(recentNetworks);
mRecentNetworksList.setAdapter(adapter);
mRecentNetworksList.setOnItemClickListener(new ListView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mNetworkIdTextView.setText(adapter.getItem(position));
}
});
}
} catch (Exception e) {
Log.e(TAG, "JSON Error: " + e.getMessage());
}
return v;
}
private class NetworkIDAdapter extends ArrayAdapter<String> {
public NetworkIDAdapter(ArrayList<String> config) {
super(getActivity(), 0, config);
Log.d(TAG, "Created network list item adapter");
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_recent_network, null);
}
String networkId = getItem(position);
TextView network = (TextView) convertView.findViewById(R.id.list_recent_network_id);
network.setText(networkId);
return convertView;
}
}
}

View File

@@ -0,0 +1,13 @@
package com.zerotier.one.ui;
import android.app.Fragment;
/**
* Created by Grant on 5/20/2015.
*/
public class NetworkListActivity extends SingleFragmentActivity {
@Override
public Fragment createFragment() {
return new NetworkListFragment();
}
}

View File

@@ -0,0 +1,487 @@
package com.zerotier.one.ui;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import com.zerotier.one.AnalyticsApplication;
import com.zerotier.one.R;
import com.zerotier.one.events.LeaveNetworkEvent;
import com.zerotier.one.events.ManualDisconnectEvent;
import com.zerotier.one.events.NetworkInfoReplyEvent;
import com.zerotier.one.events.NetworkListReplyEvent;
import com.zerotier.one.events.NetworkRemovedEvent;
import com.zerotier.one.events.NodeDestroyedEvent;
import com.zerotier.one.events.NodeIDEvent;
import com.zerotier.one.events.NodeStatusEvent;
import com.zerotier.one.events.RequestNetworkListEvent;
import com.zerotier.one.events.RequestNodeStatusEvent;
import com.zerotier.one.events.StopEvent;
import com.zerotier.one.service.RuntimeService;
import com.zerotier.one.service.ZeroTierOneService;
import com.zerotier.sdk.NodeStatus;
import com.zerotier.sdk.VirtualNetworkConfig;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import de.greenrobot.event.EventBus;
public class NetworkListFragment extends Fragment {
enum ConnectStatus {
CONNECTED,
DISCONNECTED
}
private Tracker tracker = null;
public static final String TAG = "NetworkListFragment";
public static final int START_VPN = 2;
private ArrayList<VirtualNetworkConfig> mNetworkConfigs;
private EventBus eventBus;
private NetworkAdapter adapter;
private ListView listView;
private TextView nodeIdView;
private TextView nodeStatusView;
private Button connectButton;
private MenuItem joinNetworkMenu;
private ConnectStatus cstatus = ConnectStatus.DISCONNECTED;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public NetworkListFragment() {
Log.d(TAG, "Network List Fragment created");
mNetworkConfigs = new ArrayList<VirtualNetworkConfig>();
eventBus = EventBus.getDefault();
}
@Override
public void onStart() {
super.onStart();
eventBus.register(this);
eventBus.post(new RequestNetworkListEvent());
eventBus.post(new RequestNodeStatusEvent());
}
public void onStop() {
super.onStop();
eventBus.unregister(this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
super.onCreateView(inflater, parent, savedInstanceState);
View v = inflater.inflate(R.layout.network_list_fragment, parent, false);
listView = (ListView)v.findViewById(R.id.joined_networks_list);
adapter = new NetworkAdapter(mNetworkConfigs);
listView.setAdapter(adapter);
nodeIdView = (TextView) v.findViewById(R.id.node_id);
nodeStatusView = (TextView) v.findViewById(R.id.node_status);
connectButton = (Button) v.findViewById(R.id.connect_button);
if(cstatus == ConnectStatus.CONNECTED) {
connectButton.setText(getResources().getText(R.string.button_disconnect));
} else {
connectButton.setText(getResources().getText(R.string.button_connect));
}
connectButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
if (cstatus == ConnectStatus.CONNECTED) {
Intent i = new Intent(getActivity(), RuntimeService.class);
i.putExtra(RuntimeService.START_TYPE, RuntimeService.STOP_USER_INTERFACE);
getActivity().startService(i);
cstatus = ConnectStatus.DISCONNECTED;
nodeStatusView.setText("OFFLINE");
connectButton.setText(getResources().getText(R.string.button_connect));
} else {
sendStartServiceIntent();
}
}
});
return v;
}
private void sendStartServiceIntent() {
Intent i = ZeroTierOneService.prepare(getActivity());
if(i != null) {
startActivityForResult(i, START_VPN);
} else {
Log.d(TAG, "Intent is NULL. Already approved.");
startService();
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "NetworkListFragment.onCreate");
super.onCreate(savedInstanceState);
PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false);
setHasOptionsMenu(true);
if(tracker == null) {
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
}
tracker.setScreenName("Network List");
tracker.send(new HitBuilders.ScreenViewBuilder().build());
}
@Override
public void onResume() {
super.onResume();
cstatus = ConnectStatus.DISCONNECTED;
connectButton.setText(getResources().getText(R.string.button_connect));
nodeStatusView.setText("OFFLINE");
mNetworkConfigs.clear();
sortNetworkListAndNotify();
eventBus.post(new RequestNetworkListEvent());
eventBus.post(new RequestNodeStatusEvent());
if(tracker == null) {
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
}
tracker.setScreenName("Network List");
tracker.send(new HitBuilders.ScreenViewBuilder().build());
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
Log.d(TAG, "NetworkListFragment.onCreateOptionsMenu");
inflater.inflate(R.menu.menu_network_list, menu);
joinNetworkMenu = menu.findItem(R.id.menu_item_join_network);
joinNetworkMenu.setEnabled(false);
super.onCreateOptionsMenu(menu, inflater);
eventBus.post(new RequestNodeStatusEvent());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_join_network: {
Log.d(TAG, "Selected Join Network");
Intent i = new Intent(getActivity(), JoinNetworkActivity.class);
startActivity(i);
return true;
}
case R.id.menu_item_prefs: {
Log.d(TAG, "Selected Preferences");
Intent i = new Intent(getActivity(), PrefsActivity.class);
startActivity(i);
return true;
}
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode)
{
case START_VPN:
{
// Start ZeroTierOneService
startService();
}
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.d(TAG, "NetworkListFragment.onAttach");
}
@Override
public void onDetach() {
Log.d(TAG, "NetworkListFragment.onDetach");
super.onDetach();
}
public void onEventMainThread(NetworkListReplyEvent e) {
Log.d(TAG, "Got network list");
mNetworkConfigs.clear();
for(int i = 0; i < e.getNetworkList().length; ++i) {
mNetworkConfigs.add(e.getNetworkList()[i]);
}
sortNetworkListAndNotify();
}
public void onEventMainThread(NetworkInfoReplyEvent e) {
Log.d(TAG, "Got Network Info");
VirtualNetworkConfig vnc = e.getNetworkInfo();
boolean hasNetworkWithId = false;
for(VirtualNetworkConfig c : mNetworkConfigs) {
if(c.networkId() == vnc.networkId()) {
hasNetworkWithId = true;
int index = mNetworkConfigs.indexOf(c);
mNetworkConfigs.set(index, vnc);
break;
}
}
if(!hasNetworkWithId) {
mNetworkConfigs.add(vnc);
}
sortNetworkListAndNotify();
}
public void onEventMainThread(NetworkRemovedEvent e) {
Log.d(TAG, "Removing network: " + Long.toHexString(e.getNetworkId()));
for(VirtualNetworkConfig c : mNetworkConfigs) {
if(c.networkId() == e.getNetworkId()) {
mNetworkConfigs.remove(c);
break;
}
}
sortNetworkListAndNotify();
}
public void onEventMainThread(NodeIDEvent e) {
long nodeId = e.getNodeId();
String nodeHex = Long.toHexString(nodeId);
while(nodeHex.length() < 10) {
nodeHex = "0" + nodeHex;
}
nodeIdView.setText(nodeHex);
}
public void onEventMainThread(NodeStatusEvent e) {
NodeStatus status = e.getStatus();
if(status.isOnline()) {
nodeStatusView.setText("ONLINE");
if(joinNetworkMenu != null) {
joinNetworkMenu.setEnabled(true);
}
cstatus = ConnectStatus.CONNECTED;
if(connectButton != null) {
connectButton.setText(getResources().getText(R.string.button_disconnect));
}
if(nodeIdView != null) {
nodeIdView.setText(Long.toHexString(status.getAddres()));
}
} else {
setOfflineState();
}
}
public void onEventMainThread(NodeDestroyedEvent e) {
setOfflineState();
}
private void setOfflineState() {
if(nodeStatusView != null) {
nodeStatusView.setText("OFFLINE");
}
if(joinNetworkMenu != null) {
joinNetworkMenu.setEnabled(false);
}
cstatus = ConnectStatus.DISCONNECTED;
if(connectButton != null) {
connectButton.setText(getResources().getText(R.string.button_connect));
}
}
private void sortNetworkListAndNotify() {
Collections.sort(mNetworkConfigs);
adapter.notifyDataSetChanged();
listView.invalidateViews();
}
private void startService() {
Intent i = new Intent(getActivity(), RuntimeService.class);
i.putExtra(RuntimeService.START_TYPE, RuntimeService.START_USER_INTERFACE);
getActivity().startService(i);
}
private class NetworkAdapter extends ArrayAdapter<VirtualNetworkConfig> {
public NetworkAdapter(ArrayList<VirtualNetworkConfig> config) {
super(getActivity(), 0, config);
Log.d(TAG, "Created network list item adapter");
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(!listView.getItemsCanFocus()) {
listView.setItemsCanFocus(true);
}
if(convertView == null) {
convertView = getActivity().getLayoutInflater().inflate(R.layout.list_item_network, null);
}
VirtualNetworkConfig vnc = getItem(position);
TextView networkId = (TextView)convertView.findViewById(R.id.network_list_network_id);
networkId.setText(Long.toHexString(vnc.networkId()));
TextView networkName = (TextView) convertView.findViewById(R.id.network_list_network_name);
networkName.setText(vnc.name());
TextView networkStatus = (TextView)convertView.findViewById(R.id.network_status_textview);
CharSequence statusText;
switch(vnc.networkStatus()) {
case NETWORK_STATUS_OK:
statusText = getResources().getText(R.string.network_status_ok);
break;
case NETWORK_STATUS_ACCESS_DENIED:
statusText = getResources().getText(R.string.network_status_access_denied);
break;
case NETWORK_STATUS_CLIENT_TOO_OLD:
statusText = getResources().getText(R.string.network_status_client_too_old);
break;
case NETWORK_STATUS_NOT_FOUND:
statusText = getResources().getText(R.string.network_status_not_found);
break;
case NETWORK_STATUS_PORT_ERROR:
statusText = getResources().getText(R.string.network_status_port_error);
break;
case NETWORK_STATUS_REQUESTING_CONFIGURATION:
statusText = getResources().getText(R.string.network_status_requesting_configuration);
break;
default:
statusText = getResources().getText(R.string.network_status_unknown);
break;
}
networkStatus.setText(statusText);
TextView networkType = (TextView)convertView.findViewById(R.id.network_type_textview);
switch(vnc.networkType()) {
case NETWORK_TYPE_PUBLIC:
networkType.setText(getResources().getText(R.string.network_type_public));
break;
case NETWORK_TYPE_PRIVATE:
networkType.setText(getResources().getText(R.string.network_type_private));
break;
default:
networkType.setText(getResources().getText(R.string.network_type_unknown));
}
String mac = Long.toHexString(vnc.macAddress());
while(mac.length() < 12) {
mac = "0" + mac;
}
StringBuilder displayMac = new StringBuilder();
displayMac.append(mac.charAt(0));
displayMac.append(mac.charAt(1));
displayMac.append(':');
displayMac.append(mac.charAt(2));
displayMac.append(mac.charAt(3));
displayMac.append(':');
displayMac.append(mac.charAt(4));
displayMac.append(mac.charAt(5));
displayMac.append(':');
displayMac.append(mac.charAt(6));
displayMac.append(mac.charAt(7));
displayMac.append(':');
displayMac.append(mac.charAt(8));
displayMac.append(mac.charAt(9));
displayMac.append(':');
displayMac.append(mac.charAt(10));
displayMac.append(mac.charAt(11));
TextView macView = (TextView)convertView.findViewById(R.id.network_mac_textview);
macView.setText(displayMac.toString());
TextView mtuView = (TextView) convertView.findViewById(R.id.network_mtu_textview);
mtuView.setText(Integer.toString(vnc.mtu()));
TextView broadcastEnabledView = (TextView) convertView.findViewById(R.id.network_broadcast_textview);
broadcastEnabledView.setText(vnc.broadcastEnabled() ? "Enabled" : "Disabled");
TextView bridgingEnabledView = (TextView) convertView.findViewById(R.id.network_bridging_textview);
bridgingEnabledView.setText(vnc.isBridgeEnabled() ? "Enabled" : "Disabled");
InetSocketAddress[] addresses = vnc.assignedAddresses();
StringBuilder addrText = new StringBuilder();
for(InetSocketAddress addr : addresses) {
InetAddress ipaddr = addr.getAddress();
if(ipaddr instanceof Inet6Address) {
continue;
}
String address = addr.toString();
if(address.startsWith("/")) {
address = address.substring(1);
}
int lastSlashPosition = address.lastIndexOf(':');
address = address.substring(0, lastSlashPosition) + "/" + address.substring(lastSlashPosition+1, address.length());
addrText.append(address);
addrText.append('\n');
}
TextView addressesView = (TextView) convertView.findViewById(R.id.network_ipaddresses_textview);
addressesView.setText(addrText.toString());
Button leaveButton = (Button) convertView.findViewById(R.id.network_leave_button);
leaveButton.setOnClickListener(new LeaveButtonClickListener(vnc.networkId()));
return convertView;
}
}
private class LeaveButtonClickListener implements Button.OnClickListener {
private long nwid;
public LeaveButtonClickListener(long nwid) {
this.nwid = nwid;
}
@Override
public void onClick(View v) {
Log.d(TAG, "Leave Button Pressed for network: " + Long.toHexString(nwid));
EventBus.getDefault().post(new LeaveNetworkEvent(nwid));
}
}
}

View File

@@ -0,0 +1,13 @@
package com.zerotier.one.ui;
import android.app.Fragment;
/**
* Created by Grant on 7/7/2015.
*/
public class PrefsActivity extends SingleFragmentActivity {
@Override
public Fragment createFragment() {
return new PrefsFragment();
}
}

View File

@@ -0,0 +1,54 @@
package com.zerotier.one.ui;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import com.zerotier.one.AnalyticsApplication;
import com.zerotier.one.R;
import com.zerotier.one.service.ZeroTierOneService;
/**
* Created by Grant on 7/7/2015.
*/
public class PrefsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
private Tracker tracker = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
if(tracker == null) {
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
}
tracker.setScreenName("Preferences");
tracker.send(new HitBuilders.ScreenViewBuilder().build());
}
@Override
public void onResume() {
super.onResume();
if(tracker == null) {
tracker = ((AnalyticsApplication) getActivity().getApplication()).getDefaultTracker();
}
tracker.setScreenName("Preferences");
tracker.send(new HitBuilders.ScreenViewBuilder().build());
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals("network_use_cellular_data")) {
if(sharedPreferences.getBoolean("network_use_cellular_data", false)) {
Intent i = new Intent(getActivity(), ZeroTierOneService.class);
getActivity().startService(i);
}
}
}
}

View File

@@ -0,0 +1,31 @@
package com.zerotier.one.ui;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.zerotier.one.R;
/**
* Created by Grant on 5/16/2015.
*/
public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fm = getFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if(fragment == null) {
fragment = createFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}
}
}

View File

@@ -0,0 +1,82 @@
package com.zerotier.one.util;
import android.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Created by Grant on 6/19/2015.
*/
public class IPPacketUtils {
private static final String TAG = "IPPacketUtils";
public static InetAddress getSourceIP(byte[] data) {
byte[] addrBuffer = new byte[4];
System.arraycopy(data, 12, addrBuffer, 0, 4);
try {
return InetAddress.getByAddress(addrBuffer);
} catch (UnknownHostException e) {
Log.e(TAG, "Error creating InetAddress: " + e.getMessage());
}
return null;
}
public static InetAddress getDestIP(byte[] data) {
byte[] addrBuffer = new byte[4];
System.arraycopy(data, 16, addrBuffer, 0, 4);
try {
return InetAddress.getByAddress(addrBuffer);
} catch (UnknownHostException e) {
Log.e(TAG, "Error creating InetAddress: " + e.getMessage());
}
return null;
}
/**
* Calculates the 1's complement checksum of a region of bytes
*
* @param buffer
* @param startValue
* @param startByte
* @param endByte
* @return
*/
public static long calculateChecksum(byte[] buffer, long startValue, int startByte, int endByte) {
int length = endByte - startByte;
int i = startByte;
long sum = startValue;
long data;
// Handle all pairs
while (length > 1) {
data = (((buffer[i] << 8) & 0xFF00) | ((buffer[i + 1]) & 0xFF));
sum += data;
// 1's complement carry bit correction in 16-bits (detecting sign extension)
if ((sum & 0xFFFF0000) > 0) {
sum = sum & 0xFFFF;
sum += 1;
}
i += 2;
length -= 2;
}
// Handle remaining byte in odd length buffers
if (length > 0) {
sum += (buffer[i] << 8 & 0xFF00);
// 1's complement carry bit correction in 16-bits (detecting sign extension)
if ((sum & 0xFFFF0000) > 0) {
sum = sum & 0xFFFF;
sum += 1;
}
}
// Final 1's complement value correction to 16-bits
sum = ~sum;
sum = sum & 0xFFFF;
return sum;
}
}

View File

@@ -0,0 +1,52 @@
package com.zerotier.one.util;
import android.util.Log;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
/**
* Created by Grant on 6/13/2015.
*/
public class InetAddressUtils {
public static final String TAG = "InetAddressUtils";
public static byte[] addressToNetmask(InetAddress addr, int prefix) {
int numAddressBytes = addr.getAddress().length;
if(numAddressBytes > 4) {
throw new UnsupportedOperationException("IPv6 is not yet supported");
}
int numAddressBits = numAddressBytes * 8;
byte[] maskBytes = new byte[numAddressBytes];
for(int i = 0; i < numAddressBytes; ++i) {
maskBytes[i] = (byte)255;
}
int mask = ByteBuffer.wrap(maskBytes).getInt();
mask = mask >> (numAddressBits - prefix);
mask = mask << (numAddressBits - prefix);
return ByteBuffer.allocate(4).putInt(mask).array();
}
public static InetAddress addressToRoute(InetAddress addr, int prefix) {
byte[] maskBytes = addressToNetmask(addr, prefix);
byte[] routeBytes = new byte[maskBytes.length];
InetAddress route = null;
try {
for(int i = 0; i < maskBytes.length; ++i) {
routeBytes[i] = (byte)(addr.getAddress()[i] & maskBytes[i]);
}
route = InetAddress.getByAddress(routeBytes);
} catch (UnknownHostException e) {
Log.e(TAG, "Uknown Host Exception calculating route");
}
return route;
}
}

View File

@@ -0,0 +1,20 @@
package com.zerotier.one.util;
import java.math.BigInteger;
/**
* Created by Grant on 5/26/2015.
*/
public class NetworkIdUtils {
/**
* Long will not parse a 64-bit hex string when the highest bit is 1 for some reasoon. So
* for converting network IDs in hex to a long, we must use big integer.
*
* @param hexString
* @return
*/
public static long hexStringToLong(String hexString) {
BigInteger value = new BigInteger(hexString, 16);
return value.longValue();
}
}

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<solid
android:color="#ffb354" />
<stroke
android:width="2dp"
android:color="#000000" />
<corners
android:radius="3dp" />
<padding
android:left="5dp"
android:top="5dp"
android:right="5dp"
android:bottom="5dp" />
</shape>
</item>
<item android:state_focused="true" >
<shape>
<solid
android:color="#ffb354" />
<stroke
android:width="1dp"
android:color="#000000" />
<corners
android:radius="3dp" />
<padding
android:left="5dp"
android:top="5dp"
android:right="5dp"
android:bottom="5dp" />
</shape>
</item>
<item>
<shape>
<solid
android:color="#ffb354" />
<stroke
android:width="0dp"
android:color="#000000" />
<corners
android:radius="3dp" />
<padding
android:left="5dp"
android:top="5dp"
android:right="5dp"
android:bottom="5dp" />
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

View File

@@ -0,0 +1,4 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentJoinNetwork"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal">
<TextView
android:id="@+id/network_id_textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/network_id_string"
android:textStyle="bold"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
style="?android:listSeparatorTextViewStyle"/>
<EditText
android:id="@+id/join_network_edit_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:digits="0123456789ABCDEF"
android:inputType="textCapCharacters"
android:layout_below="@id/network_id_textview"
android:layout_centerHorizontal="true"
android:maxLength="16"
android:textCursorDrawable="@null"
android:backgroundTint="#ffb354"/>
<Button
android:id="@+id/button_join_network"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_below="@id/join_network_edit_text"
android:layout_centerHorizontal="true"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="@string/join_network"/>
<TextView
android:id="@+id/recent_networks"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/recent_networks"
android:textStyle="bold"
android:layout_below="@id/button_join_network"
style="?android:listSeparatorTextViewStyle"/>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/recent_networks_list"
android:layout_below="@+id/recent_networks"
android:layout_centerHorizontal="true" />
<android.inputmethodservice.KeyboardView
android:id="@+id/join_network_keyboard"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:visibility="gone" />
</RelativeLayout>

View File

@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView android:id="@+id/network_list_network_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:text="Network ID"
android:fontFamily="monospace"
android:textColor="#555555" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
<TextView android:id="@+id/network_list_network_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingTop="4dp"
android:text="Network Name"
android:textColor="#000000" />
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"/>
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:layout_margin="8dp"
android:orientation="horizontal"
android:gravity="end"
android:stretchColumns="1">
<TableRow android:layout_width="fill_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/network_status"
android:paddingLeft="40dp"
android:paddingRight="20dp"
android:layout_gravity="right"/>
<TextView android:id="@+id/network_status_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text=""/>
</TableRow>
<TableRow android:layout_width="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/network_type"
android:paddingRight="20dp"
android:paddingLeft="40dp"
android:layout_gravity="right"/>
<TextView android:id="@+id/network_type_textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text=""/>
</TableRow>
<TableRow android:layout_width="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/network_mac"
android:paddingRight="20dp"
android:paddingLeft="40dp"
android:layout_gravity="right"/>
<TextView android:id="@+id/network_mac_textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text=""/>
</TableRow>
<TableRow android:layout_width="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/network_mtu"
android:paddingRight="20dp"
android:paddingLeft="40dp"
android:layout_gravity="right"/>
<TextView android:id="@+id/network_mtu_textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text=""/>
</TableRow>
<TableRow android:layout_width="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/network_broadcast"
android:paddingRight="20dp"
android:paddingLeft="40dp"
android:layout_gravity="right"/>
<TextView android:id="@+id/network_broadcast_textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text=""/>
</TableRow>
<TableRow android:layout_width="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/network_bridging"
android:paddingRight="20dp"
android:paddingLeft="40dp"
android:layout_gravity="right"/>
<TextView android:id="@+id/network_bridging_textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text=""/>
</TableRow>
<TableRow android:layout_width="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/network_ips"
android:paddingRight="20dp"
android:paddingLeft="40dp"
android:layout_gravity="right"/>
<TextView android:id="@+id/network_ipaddresses_textview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:gravity="right"
android:text=""/>
</TableRow>
<TableRow>
<View android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
android:id="@+id/network_leave_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="@string/leave_button_text"
/>
</TableRow>
</TableLayout>
</LinearLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/list_recent_network_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5dp"/>
</LinearLayout>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/network_list_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/status_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_gravity="center_vertical"
android:padding="5dp">
<TextView android:id="@+id/node_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text=""
android:layout_centerVertical="true"
android:gravity="center"/>
<TextView android:id="@+id/node_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:layout_centerVertical="true"
android:text="OFFLINE"
android:layout_toRightOf="@id/node_id"/>
<Button
android:id="@+id/connect_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="5dp"
android:text="@string/button_connect"
android:layout_alignParentRight="true"/>
</RelativeLayout>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/status_bar"
android:id="@+id/joined_networks_list"
android:layout_alignParentTop="true" />
</RelativeLayout>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_item_join_network"
android:icon="@android:drawable/ic_menu_add"
android:title="@string/menu_join_network"
app:showAsAction="ifRoom|withText"/>
<item android:id="@+id/menu_item_prefs"
android:icon="@android:drawable/ic_menu_preferences"
android:title="@string/settings"
app:showAsAction="ifRoom|withText"/>
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,55 @@
<resources>
<string name="app_name">ZeroTier One</string>
<string name="title_activity_zero_tier_one__main">ZeroTier One</string>
<string name="string_n_a">N/A</string>
<string name="network_name_string">Network Name:</string>
<string name="network_id_string">Network ID:</string>
<string name="recent_networks">Recent Networks:</string>
<string name="network_type_label">Type:</string>
<string name="status_label">Status:</string>
<string name="mac_label">MAC:</string>
<string name="device_label">Device:</string>
<string name="ip_addresses_label">IP Addresses</string>
<string name="leave_button_text">Leave Network</string>
<string name="menu_join_network">Join Network</string>
<string name="join_network">Join Network</string>
<string name="button_connect">Connect</string>
<string name="button_disconnect">Disconnect</string>
<string name="network_status">Status</string>
<string name="network_type">Type</string>
<string name="network_mac">MAC</string>
<string name="network_mtu">MTU</string>
<string name="network_broadcast">Broadcast</string>
<string name="network_bridging">Bridging</string>
<string name="network_ips">Managed IPs</string>
<!-- Network Statuses -->
<string name="network_status_ok">OK</string>
<string name="network_status_access_denied">Access Denied</string>
<string name="network_status_client_too_old">Client Out of Date</string>
<string name="network_status_not_found">Network Not Found</string>
<string name="network_status_port_error">Port Error</string>
<string name="network_status_requesting_configuration">Requesting Configuration</string>
<string name="network_status_unknown">Unknown</string>
<!-- Network Types -->
<string name="network_type_public">Public</string>
<string name="network_type_private">Private</string>
<string name="network_type_unknown">Unknown</string>
<!-- preferences -->
<string name="settings">Settings</string>
<string name="preferences_general">General Settings</string>
<string name="preferences_general_start_on_boot">Start ZeroTier One on Device Startup</string>
<string name="preferences_network">Network Settings</string>
<string name="preferences_network_use_cell_data">Use Cellular Data</string>
<string name="preferences_network_auto_rejoin">Automatically Re-join Networks on Start</string>
</resources>

View File

@@ -0,0 +1,13 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:buttonStyle">@style/button</item>
</style>
<style name="button" parent="@android:style/Widget.Button">
<item name="android:background">@drawable/button_style</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- End current session if app sleeps for a period of time -->
<integer name="ga_sessionTimeout">300</integer>
<!-- Enable automatic Activity measurement -->
<bool name="ga_autoActivityTracking">false</bool>
<!-- The property id associated with this analytics tracker -->
<string name="ga_trackingId">UA-40176068-3</string>
<bool name="ga_anonymizeIp">true</bool>
<!--
See Project Structure -> Analytics -> Google Analytics -> Learn More
to learn more about configuring this file.
-->
</resources>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:keyWidth="12.4%p"
android:keyHeight="10%p" >
<Row>
<Key android:codes="55" android:keyLabel="7" android:keyEdgeFlags="left" />
<Key android:codes="56" android:keyLabel="8" />
<Key android:codes="57" android:keyLabel="9" />
<Key android:codes="65" android:keyLabel="A" android:horizontalGap="6.25%p" />
<Key android:codes="66" android:keyLabel="B" />
<Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:isRepeatable="true" android:horizontalGap="6.25%p" />
<Key android:codes="55006" android:keyLabel="CLR" android:keyEdgeFlags="right"/>
</Row>
<Row>
<Key android:codes="52" android:keyLabel="4" android:keyEdgeFlags="left" />
<Key android:codes="53" android:keyLabel="5" />
<Key android:codes="54" android:keyLabel="6" />
<Key android:codes="67" android:keyLabel="C" android:horizontalGap="6.25%p" />
<Key android:codes="68" android:keyLabel="D" />
<Key android:codes="55002" android:keyIcon="@drawable/sym_keyboard_left" android:isRepeatable="true" android:horizontalGap="6.25%p" />
<Key android:codes="55003" android:keyIcon="@drawable/sym_keyboard_right" android:isRepeatable="true" android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left" />
<Key android:codes="50" android:keyLabel="2" />
<Key android:codes="51" android:keyLabel="3" />
<Key android:codes="69" android:keyLabel="E" android:horizontalGap="6.25%p" />
<Key android:codes="70" android:keyLabel="F" />
<Key android:codes="55001" android:keyIcon="@drawable/sym_keyboard_allleft" android:horizontalGap="6.25%p" />
<Key android:codes="55004" android:keyIcon="@drawable/sym_keyboard_allright" android:keyEdgeFlags="right" />
</Row>
<Row>
<Key android:codes="48" android:keyLabel="0" android:keyWidth="25%p" android:horizontalGap="6.25%p" android:keyEdgeFlags="left" />
<Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" android:keyWidth="25%p" android:horizontalGap="43.65%p" android:keyEdgeFlags="right" />
<!--<Key android:codes="55000" android:keyLabel="PREV" android:horizontalGap="6.25%p" />
<Key android:codes="55005" android:keyLabel="NEXT" android:keyEdgeFlags="right" />-->
</Row>
</Keyboard>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/preferences_general">
<CheckBoxPreference
android:key="general_start_zerotier_on_boot"
android:title="@string/preferences_general_start_on_boot"
android:defaultValue="true"/>
<CheckBoxPreference
android:key="network_auto_rejoin_networks"
android:title="@string/preferences_network_auto_rejoin"
android:defaultValue="true"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences_network">
<CheckBoxPreference
android:key="network_use_cellular_data"
android:title="@string/preferences_network_use_cell_data"
android:defaultValue="false"/>
</PreferenceCategory>
</PreferenceScreen>