Implement demo to show OpenVPN for Android API
@ -2,9 +2,19 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.blinkt.openvpn.remote" >
|
||||
|
||||
<permission
|
||||
android:name="de.blinkt.openvpn.REMOTE_API"
|
||||
android:description="@string/permission_description"
|
||||
android:label="Control OpenVPN"
|
||||
android:permissionGroup="android.permission-group.NETWORK"
|
||||
android:protectionLevel="dangerous" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="de.blinkt.openvpn.REMOTE_API" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher2"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
|
@ -0,0 +1,3 @@
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
parcelable APIVpnProfile;
|
@ -0,0 +1,50 @@
|
||||
// IOpenVPNAPIService.aidl
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import de.blinkt.openvpn.api.APIVpnProfile;
|
||||
import de.blinkt.openvpn.api.IOpenVPNStatusCallback;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
interface IOpenVPNAPIService {
|
||||
List<APIVpnProfile> getProfiles();
|
||||
|
||||
void startProfile (String profileUUID);
|
||||
|
||||
/** Use a profile with all certificates etc. embedded */
|
||||
boolean addVPNProfile (String name, String config);
|
||||
|
||||
/** start a profile using an config */
|
||||
void startVPN (String inlineconfig);
|
||||
|
||||
/** This permission framework is used to avoid confused deputy style attack to the VPN
|
||||
* calling this will give null if the app is allowed to use the external API and an Intent
|
||||
* that can be launched to request permissions otherwise */
|
||||
Intent prepare (String packagename);
|
||||
|
||||
/** Used to trigger to the Android VPN permission dialog (VPNService.prepare()) in advance,
|
||||
* if this return null OpenVPN for ANdroid already has the permissions otherwise you can start the returned Intent
|
||||
* to let OpenVPN for Android request the permission */
|
||||
Intent prepareVPNService ();
|
||||
|
||||
/* Disconnect the VPN */
|
||||
void disconnect();
|
||||
|
||||
/* Pause the VPN (same as using the pause feature in the notifcation bar) */
|
||||
void pause();
|
||||
|
||||
/* Resume the VPN (same as using the pause feature in the notifcation bar) */
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* Registers to receive OpenVPN Status Updates
|
||||
*/
|
||||
void registerStatusCallback(IOpenVPNStatusCallback cb);
|
||||
|
||||
/**
|
||||
* Remove a previously registered callback interface.
|
||||
*/
|
||||
void unregisterStatusCallback(IOpenVPNStatusCallback cb);
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
/**
|
||||
* Example of a callback interface used by IRemoteService to send
|
||||
* synchronous notifications back to its clients. Note that this is a
|
||||
* one-way interface so the server does not block waiting for the client.
|
||||
*/
|
||||
oneway interface IOpenVPNStatusCallback {
|
||||
/**
|
||||
* Called when the service has a new status for you.
|
||||
*/
|
||||
void newStatus(String uuid, String state, String message, String level);
|
||||
}
|
61
remoteExample/src/main/assets/hd.conf
Normal file
@ -0,0 +1,61 @@
|
||||
# This config does not work,
|
||||
# it only thought as demo to show starting a profile
|
||||
client
|
||||
|
||||
proto tcp
|
||||
dev tun
|
||||
topology subnet
|
||||
|
||||
remote openvpn.blinkt.de 2000
|
||||
tls-remote openvpn.blinkt.de
|
||||
|
||||
<auth-user-pass>
|
||||
wronguser
|
||||
wrongpassword
|
||||
</auth-user-pass>
|
||||
|
||||
comp-lzo
|
||||
<ca>
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
|
||||
IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
|
||||
IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
|
||||
Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
|
||||
BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
|
||||
MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
|
||||
ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||
CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
|
||||
8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
|
||||
zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
|
||||
fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
|
||||
w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
|
||||
G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
|
||||
epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
|
||||
laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
|
||||
QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
|
||||
fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
|
||||
YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
|
||||
ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
|
||||
gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
|
||||
MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
|
||||
IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
|
||||
dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
|
||||
czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
|
||||
dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
|
||||
aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
|
||||
AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
|
||||
b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
|
||||
ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
|
||||
nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
|
||||
18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
|
||||
gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
|
||||
Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
|
||||
sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
|
||||
SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
|
||||
CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
|
||||
GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
|
||||
zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
|
||||
omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
|
||||
-----END CERTIFICATE-----
|
||||
</ca>
|
||||
verb 2
|
@ -0,0 +1,51 @@
|
||||
package de.blinkt.openvpn.api;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class APIVpnProfile implements Parcelable {
|
||||
|
||||
public final String mUUID;
|
||||
public final String mName;
|
||||
public final boolean mUserEditable;
|
||||
|
||||
public APIVpnProfile(Parcel in) {
|
||||
mUUID = in.readString();
|
||||
mName = in.readString();
|
||||
mUserEditable = in.readInt() != 0;
|
||||
}
|
||||
|
||||
public APIVpnProfile(String uuidString, String name, boolean userEditable) {
|
||||
mUUID=uuidString;
|
||||
mName = name;
|
||||
mUserEditable=userEditable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mUUID);
|
||||
dest.writeString(mName);
|
||||
if(mUserEditable)
|
||||
dest.writeInt(0);
|
||||
else
|
||||
dest.writeInt(1);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<APIVpnProfile> CREATOR
|
||||
= new Parcelable.Creator<APIVpnProfile>() {
|
||||
public APIVpnProfile createFromParcel(Parcel in) {
|
||||
return new APIVpnProfile(in);
|
||||
}
|
||||
|
||||
public APIVpnProfile[] newArray(int size) {
|
||||
return new APIVpnProfile[size];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -2,16 +2,320 @@ package de.blinkt.openvpn.remote;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
|
||||
import de.blinkt.openvpn.api.APIVpnProfile;
|
||||
import de.blinkt.openvpn.api.IOpenVPNAPIService;
|
||||
import de.blinkt.openvpn.api.IOpenVPNStatusCallback;
|
||||
|
||||
public class MainFragment extends Fragment implements View.OnClickListener, Handler.Callback {
|
||||
|
||||
private TextView mHelloWorld;
|
||||
private Button mStartVpn;
|
||||
private TextView mMyIp;
|
||||
private TextView mStatus;
|
||||
|
||||
public class MainFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_main,container,false);
|
||||
View v = inflater.inflate(R.layout.fragment_main, container, false);
|
||||
v.findViewById(R.id.disconnect).setOnClickListener(this);
|
||||
v.findViewById(R.id.getMyIP).setOnClickListener(this);
|
||||
v.findViewById(R.id.startembedded).setOnClickListener(this);
|
||||
mHelloWorld = (TextView) v.findViewById(R.id.helloworld);
|
||||
mStartVpn = (Button) v.findViewById(R.id.startVPN);
|
||||
mStatus = (TextView) v.findViewById(R.id.status);
|
||||
mMyIp = (TextView) v.findViewById(R.id.MyIpText);
|
||||
|
||||
|
||||
return v;
|
||||
|
||||
}
|
||||
|
||||
private static final int MSG_UPDATE_STATE = 0;
|
||||
private static final int MSG_UPDATE_MYIP = 1;
|
||||
private static final int START_PROFILE_EMBEDDED = 2;
|
||||
private static final int START_PROFILE_BYUUID = 3;
|
||||
private static final int ICS_OPENVPN_PERMISSION = 7;
|
||||
|
||||
protected IOpenVPNAPIService mService=null;
|
||||
private Handler mHandler;
|
||||
|
||||
|
||||
|
||||
|
||||
private void startEmbeddedProfile()
|
||||
{
|
||||
try {
|
||||
InputStream conf = getActivity().getAssets().open("test.conf");
|
||||
InputStreamReader isr = new InputStreamReader(conf);
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String config="";
|
||||
String line;
|
||||
while(true) {
|
||||
line = br.readLine();
|
||||
if(line == null)
|
||||
break;
|
||||
config += line + "\n";
|
||||
}
|
||||
br.readLine();
|
||||
|
||||
// mService.addVPNProfile("test", config);
|
||||
mService.startVPN(config);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (RemoteException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHandler = new Handler(this);
|
||||
bindService();
|
||||
}
|
||||
|
||||
|
||||
private IOpenVPNStatusCallback mCallback = new IOpenVPNStatusCallback.Stub() {
|
||||
/**
|
||||
* This is called by the remote service regularly to tell us about
|
||||
* new values. Note that IPC calls are dispatched through a thread
|
||||
* pool running in each process, so the code executing here will
|
||||
* NOT be running in our main thread like most other things -- so,
|
||||
* to update the UI, we need to use a Handler to hop over there.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void newStatus(String uuid, String state, String message, String level)
|
||||
throws RemoteException {
|
||||
Message msg = Message.obtain(mHandler, MSG_UPDATE_STATE, state + "|" + message);
|
||||
msg.sendToTarget();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class for interacting with the main interface of the service.
|
||||
*/
|
||||
private ServiceConnection mConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName className,
|
||||
IBinder service) {
|
||||
// This is called when the connection with the service has been
|
||||
// established, giving us the service object we can use to
|
||||
// interact with the service. We are communicating with our
|
||||
// service through an IDL interface, so get a client-side
|
||||
// representation of that from the raw service object.
|
||||
|
||||
mService = IOpenVPNAPIService.Stub.asInterface(service);
|
||||
|
||||
try {
|
||||
// Request permission to use the API
|
||||
Intent i = mService.prepare(getActivity().getPackageName());
|
||||
if (i!=null) {
|
||||
startActivityForResult(i, ICS_OPENVPN_PERMISSION);
|
||||
} else {
|
||||
onActivityResult(ICS_OPENVPN_PERMISSION, Activity.RESULT_OK,null);
|
||||
}
|
||||
|
||||
} catch (RemoteException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
// This is called when the connection with the service has been
|
||||
// unexpectedly disconnected -- that is, its process crashed.
|
||||
mService = null;
|
||||
|
||||
}
|
||||
};
|
||||
private String mStartUUID=null;
|
||||
|
||||
private void bindService() {
|
||||
getActivity().bindService(new Intent(IOpenVPNAPIService.class.getName()),
|
||||
mConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
protected void listVPNs() {
|
||||
|
||||
try {
|
||||
List<APIVpnProfile> list = mService.getProfiles();
|
||||
String all="List:";
|
||||
for(APIVpnProfile vp:list) {
|
||||
all = all + vp.mName + ":" + vp.mUUID + "\n";
|
||||
}
|
||||
|
||||
if(list.size()> 0) {
|
||||
Button b= mStartVpn;
|
||||
b.setOnClickListener(this);
|
||||
b.setVisibility(View.VISIBLE);
|
||||
b.setText(list.get(0).mName);
|
||||
mStartUUID = list.get(0).mUUID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
mHelloWorld.setText(all);
|
||||
|
||||
} catch (RemoteException e) {
|
||||
// TODO Auto-generated catch block
|
||||
mHelloWorld.setText(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void unbindService() {
|
||||
getActivity().unbindService(mConnection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
unbindService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.startVPN:
|
||||
try {
|
||||
prepareStartProfile(START_PROFILE_BYUUID);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
case R.id.disconnect:
|
||||
try {
|
||||
mService.disconnect();
|
||||
} catch (RemoteException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
case R.id.getMyIP:
|
||||
|
||||
// Socket handling is not allowed on main thread
|
||||
new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String myip = getMyOwnIP();
|
||||
Message msg = Message.obtain(mHandler,MSG_UPDATE_MYIP,myip);
|
||||
msg.sendToTarget();
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}.start();
|
||||
|
||||
break;
|
||||
case R.id.startembedded:
|
||||
try {
|
||||
prepareStartProfile(START_PROFILE_EMBEDDED);
|
||||
} catch (RemoteException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void prepareStartProfile(int requestCode) throws RemoteException {
|
||||
Intent requestpermission = mService.prepareVPNService();
|
||||
if(requestpermission == null) {
|
||||
onActivityResult(requestCode, Activity.RESULT_OK, null);
|
||||
} else {
|
||||
// Have to call an external Activity since services cannot used onActivityResult
|
||||
startActivityForResult(requestpermission, requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if(requestCode==START_PROFILE_EMBEDDED)
|
||||
startEmbeddedProfile();
|
||||
if(requestCode==START_PROFILE_BYUUID)
|
||||
try {
|
||||
mService.startProfile(mStartUUID);
|
||||
} catch (RemoteException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (requestCode == ICS_OPENVPN_PERMISSION) {
|
||||
listVPNs();
|
||||
try {
|
||||
mService.registerStatusCallback(mCallback);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
String getMyOwnIP() throws UnknownHostException, IOException, RemoteException,
|
||||
IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException
|
||||
{
|
||||
String resp="";
|
||||
Socket client = new Socket();
|
||||
// Setting Keep Alive forces creation of the underlying socket, otherwise getFD returns -1
|
||||
client.setKeepAlive(true);
|
||||
|
||||
|
||||
client.connect(new InetSocketAddress("v4address.com", 23),20000);
|
||||
client.shutdownOutput();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
|
||||
while (true) {
|
||||
String line = in.readLine();
|
||||
if( line == null)
|
||||
return resp;
|
||||
resp+=line;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
if(msg.what == MSG_UPDATE_STATE) {
|
||||
mStatus.setText((CharSequence) msg.obj);
|
||||
} else if (msg.what == MSG_UPDATE_MYIP) {
|
||||
|
||||
mMyIp.setText((CharSequence) msg.obj);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
BIN
remoteExample/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 7.6 KiB |
BIN
remoteExample/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 917 B |
Before Width: | Height: | Size: 3.7 KiB |
BIN
remoteExample/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 24 KiB |
@ -6,11 +6,65 @@
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="de.blinkt.openvpn.remote.MainActivity$PlaceholderFragment">
|
||||
tools:context="de.blinkt.openvpn.remote.MainFragment">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/helloworld"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="No Status yet" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startVPN"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignRight="@+id/helloworld"
|
||||
android:layout_below="@+id/helloworld"
|
||||
android:text="Not now"
|
||||
android:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:text="@string/hello_world"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:id="@+id/helloworld"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:text="@string/hello_world" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/MyIpText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/helloworld"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:text="TextView" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/getMyIP"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/startVPN"
|
||||
android:text="Show my IP" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/disconnect"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/startVPN"
|
||||
android:layout_toRightOf="@+id/startVPN"
|
||||
android:text="Disconnect" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startembedded"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/getMyIP"
|
||||
android:text="Start embedded profile" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">My Module</string>
|
||||
<string name="app_name">OpenVPN Remote</string>
|
||||
<string name="hello_world">Hello world!</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="permission_description">Allows another app to control OpenVPN</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|