@@ -3,6 +3,7 @@ package com.getbubblenow.android.activity; | |||
import androidx.annotation.NonNull; | |||
import androidx.annotation.Nullable; | |||
import androidx.appcompat.widget.AppCompatButton; | |||
import androidx.lifecycle.MutableLiveData; | |||
import androidx.lifecycle.Observer; | |||
import androidx.lifecycle.ViewModelProvider; | |||
@@ -18,6 +19,7 @@ import android.widget.EditText; | |||
import android.widget.Toast; | |||
import com.getbubblenow.android.R; | |||
import com.getbubblenow.android.api.ApiConstants; | |||
import com.getbubblenow.android.repository.DataRepository; | |||
import com.getbubblenow.android.resource.StatusResource; | |||
import com.getbubblenow.android.viewmodel.LoginViewModel; | |||
@@ -25,7 +27,6 @@ import com.getbubblenow.android.viewmodel.LoginViewModel; | |||
public class LoginActivity extends BaseActivityBubble { | |||
private LoginViewModel loginViewModel; | |||
private EditText bubbleName; | |||
private EditText userName; | |||
private EditText password; | |||
private AppCompatButton sign; | |||
@@ -35,14 +36,13 @@ public class LoginActivity extends BaseActivityBubble { | |||
private static final String SEPARATOR = "\\."; | |||
private static final int REQUEST_CODE = 1555; | |||
private static final String CERTIFICATE_NAME = "Bubble Certificate"; | |||
private boolean bubbleNameStateFlag = false; | |||
private boolean userNameStateFlag = false; | |||
private boolean passwordStateFlag = false; | |||
private static final String BUBBLE_NAME_KEY = "bubbleName"; | |||
private static final String USER_NAME_KEY = "userName"; | |||
private static final String PASSWORD_KEY = "password"; | |||
private static final String NO_INTERNET_CONNECTION = "no internet connection"; | |||
private static final String LOGIN_FAILED = "Login Failed"; | |||
private static String bubbleName = ""; | |||
@Override | |||
@@ -61,58 +61,73 @@ public class LoginActivity extends BaseActivityBubble { | |||
private void initListeners() { | |||
sign.setOnClickListener(new OnClickListener() { | |||
@Override public void onClick(final View v) { | |||
final String url = BASE_URL_PREFIX + bubbleName.getText().toString() + BASE_URL_SUFFIX; | |||
if (url.split(SEPARATOR).length != 3) { | |||
Toast.makeText(LoginActivity.this, getResources().getText(R.string.hostname_not_valid), Toast.LENGTH_LONG).show(); | |||
return; | |||
} | |||
if (DataRepository.getRepositoryInstance() == null) { | |||
loginViewModel.buildRepositoryInstance(LoginActivity.this, url); | |||
loginViewModel.buildRepositoryInstance(LoginActivity.this, ApiConstants.BOOTSTRAP_URL); | |||
} else { | |||
loginViewModel.buildClientService(url); | |||
loginViewModel.buildClientService(ApiConstants.BOOTSTRAP_URL); | |||
} | |||
loginViewModel.setUserURL(LoginActivity.this, url); | |||
final String usernameInput = userName.getText().toString().trim(); | |||
final String passwordInput = password.getText().toString().trim(); | |||
showLoadingDialog(); | |||
login(usernameInput, passwordInput); | |||
loginViewModel.getNodeLiveData().observe(LoginActivity.this, new Observer<StatusResource<String>>() { | |||
@Override public void onChanged(final StatusResource<String> stringStatusResource) { | |||
switch (stringStatusResource.status) { | |||
case SUCCESS: | |||
final String url = stringStatusResource.data; | |||
loginViewModel.buildClientService(BASE_URL_PREFIX + url + BASE_URL_SUFFIX); | |||
loginViewModel.setUserURL(LoginActivity.this, BASE_URL_PREFIX + url + BASE_URL_SUFFIX); | |||
bubbleName = url; | |||
loginViewModel.setNodeLiveData(new MutableLiveData<>()); | |||
login(usernameInput, passwordInput); | |||
break; | |||
case ERROR: | |||
closeLoadingDialog(); | |||
if (stringStatusResource.message.equals(NO_INTERNET_CONNECTION)) { | |||
showNetworkNotAvailableMessage(); | |||
} | |||
else if (stringStatusResource.message.equals(LOGIN_FAILED)) { | |||
Toast.makeText(LoginActivity.this, LOGIN_FAILED, Toast.LENGTH_LONG).show(); | |||
} | |||
else { | |||
showErrorDialog(stringStatusResource.message); | |||
} | |||
break; | |||
case LOADING: | |||
break; | |||
} | |||
} | |||
}); | |||
loginViewModel.getSages(LoginActivity.this,usernameInput,passwordInput).observe(LoginActivity.this, new Observer<StatusResource<String>>() { | |||
@Override public void onChanged(final StatusResource<String> stringStatusResource) { | |||
switch (stringStatusResource.status){ | |||
case ERROR: | |||
closeLoadingDialog(); | |||
if(stringStatusResource.message.equals(NO_INTERNET_CONNECTION)){ | |||
showNetworkNotAvailableMessage(); | |||
} | |||
else if(stringStatusResource.message.equals(LOGIN_FAILED)){ | |||
Toast.makeText(LoginActivity.this,LOGIN_FAILED,Toast.LENGTH_LONG).show(); | |||
} | |||
else { | |||
showErrorDialog(stringStatusResource.message); | |||
} | |||
break; | |||
} | |||
} | |||
}); | |||
} | |||
}); | |||
bubbleNameStateListener(); | |||
userNameStateListener(); | |||
passwordStateListener(); | |||
} | |||
private void initViews() { | |||
bubbleName = findViewById(R.id.bubbleName); | |||
userName = findViewById(R.id.userName); | |||
password = findViewById(R.id.password); | |||
sign = findViewById(R.id.signButton); | |||
} | |||
private void bubbleNameStateListener() { | |||
bubbleName.addTextChangedListener(new TextWatcher() { | |||
@Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { | |||
} | |||
@Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { | |||
} | |||
@Override public void afterTextChanged(final Editable s) { | |||
if (bubbleName.getText().toString().trim().isEmpty()) { | |||
bubbleNameStateFlag = false; | |||
} else { | |||
bubbleNameStateFlag = true; | |||
} | |||
setButtonState(); | |||
} | |||
}); | |||
} | |||
private void userNameStateListener() { | |||
userName.addTextChangedListener(new TextWatcher() { | |||
@Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { | |||
@@ -159,7 +174,7 @@ public class LoginActivity extends BaseActivityBubble { | |||
} | |||
private void setButtonState(){ | |||
if(userNameStateFlag && bubbleNameStateFlag && passwordStateFlag){ | |||
if(userNameStateFlag && passwordStateFlag){ | |||
sign.setBackgroundDrawable(getDrawable(R.drawable.sign_in_enable)); | |||
sign.setEnabled(true); | |||
} | |||
@@ -210,7 +225,7 @@ public class LoginActivity extends BaseActivityBubble { | |||
switch (objectStatusResource.status){ | |||
case SUCCESS: | |||
Toast.makeText(LoginActivity.this, getString(R.string.success), Toast.LENGTH_SHORT).show(); | |||
loginViewModel.setHostName(LoginActivity.this,bubbleName.getText().toString().trim()); | |||
loginViewModel.setHostName(LoginActivity.this,bubbleName); | |||
Log.d("TAG", "Success"); | |||
final Intent mainActivityIntent = new Intent(LoginActivity.this, MainActivity.class); | |||
mainActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); | |||
@@ -238,14 +253,12 @@ public class LoginActivity extends BaseActivityBubble { | |||
} | |||
@Override protected void onSaveInstanceState(@NonNull final Bundle outState) { | |||
outState.putBoolean(BUBBLE_NAME_KEY,bubbleNameStateFlag); | |||
outState.putBoolean(USER_NAME_KEY,userNameStateFlag); | |||
outState.putBoolean(PASSWORD_KEY,passwordStateFlag); | |||
super.onSaveInstanceState(outState); | |||
} | |||
@Override protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { | |||
bubbleNameStateFlag = savedInstanceState.getBoolean(BUBBLE_NAME_KEY); | |||
userNameStateFlag = savedInstanceState.getBoolean(USER_NAME_KEY); | |||
passwordStateFlag = savedInstanceState.getBoolean(PASSWORD_KEY); | |||
super.onRestoreInstanceState(savedInstanceState); | |||
@@ -1,6 +1,10 @@ | |||
package com.getbubblenow.android.api; | |||
public class ApiConstants { | |||
public static final String BOOTSTRAP_URL = "https://raw.githubusercontent.com/getbubblenow/"; | |||
public static final String BOOTSTRAP_URL_SUFFIX = "bubble-config/master/boot.json"; | |||
public static String BASE_URL = ""; | |||
public static final String NODE_BASE_URI = "me/networks"; | |||
public static final String LOGIN_URL = "auth/login"; | |||
public static final String ALL_DEVICES_URL = "me/devices"; | |||
public static final String ADD_DEVICE_URL = "me/devices"; | |||
@@ -1,6 +1,8 @@ | |||
package com.getbubblenow.android.api.network; | |||
import com.getbubblenow.android.model.Device; | |||
import com.getbubblenow.android.model.Network; | |||
import com.getbubblenow.android.model.Sages; | |||
import com.getbubblenow.android.model.User; | |||
import com.getbubblenow.android.api.ApiConstants; | |||
import com.getbubblenow.android.model.Device; | |||
@@ -39,4 +41,10 @@ public interface ClientApi { | |||
@GET(ApiConstants.CONFIG_DEVICE_URL+"{deviceID}"+ApiConstants.CONFIG_VPN_URL) | |||
Single<ResponseBody> getConfig(@Path("deviceID") String deviceID , @HeaderMap HashMap<String,String> header); | |||
@GET(ApiConstants.BOOTSTRAP_URL_SUFFIX) | |||
Single<Sages> getSages(); | |||
@GET(ApiConstants.NODE_BASE_URI) | |||
Single<List<Network>> getNodeBaseURI(@HeaderMap HashMap<String,String> header); | |||
} |
@@ -0,0 +1,228 @@ | |||
package com.getbubblenow.android.model; | |||
import com.google.gson.annotations.Expose; | |||
import com.google.gson.annotations.SerializedName; | |||
public class Network { | |||
@SerializedName("uuid") | |||
@Expose | |||
private String uuid; | |||
@SerializedName("related") | |||
@Expose | |||
private String related; | |||
@SerializedName("name") | |||
@Expose | |||
private String name; | |||
@SerializedName("account") | |||
@Expose | |||
private String account; | |||
@SerializedName("domain") | |||
@Expose | |||
private String domain; | |||
@SerializedName("domainName") | |||
@Expose | |||
private String domainName; | |||
@SerializedName("sslPort") | |||
@Expose | |||
private long sslPort; | |||
@SerializedName("installType") | |||
@Expose | |||
private String installType; | |||
@SerializedName("sshKey") | |||
@Expose | |||
private String sshKey; | |||
@SerializedName("computeSizeType") | |||
@Expose | |||
private String computeSizeType; | |||
@SerializedName("footprint") | |||
@Expose | |||
private String footprint; | |||
@SerializedName("storage") | |||
@Expose | |||
private String storage; | |||
@SerializedName("description") | |||
@Expose | |||
private String description; | |||
@SerializedName("locale") | |||
@Expose | |||
private String locale; | |||
@SerializedName("timezone") | |||
@Expose | |||
private String timezone; | |||
@SerializedName("sendMetrics") | |||
@Expose | |||
private boolean sendMetrics; | |||
@SerializedName("tags") | |||
@Expose | |||
private String tags; | |||
@SerializedName("forkHost") | |||
@Expose | |||
private String forkHost; | |||
@SerializedName("state") | |||
@Expose | |||
private String state; | |||
@SerializedName("shortId") | |||
@Expose | |||
private String shortId; | |||
public String getUuid() { | |||
return uuid; | |||
} | |||
public void setUuid(final String uuid) { | |||
this.uuid = uuid; | |||
} | |||
public String getRelated() { | |||
return related; | |||
} | |||
public void setRelated(final String related) { | |||
this.related = related; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
public void setName(final String name) { | |||
this.name = name; | |||
} | |||
public String getAccount() { | |||
return account; | |||
} | |||
public void setAccount(final String account) { | |||
this.account = account; | |||
} | |||
public String getDomain() { | |||
return domain; | |||
} | |||
public void setDomain(final String domain) { | |||
this.domain = domain; | |||
} | |||
public String getDomainName() { | |||
return domainName; | |||
} | |||
public void setDomainName(final String domainName) { | |||
this.domainName = domainName; | |||
} | |||
public long getSslPort() { | |||
return sslPort; | |||
} | |||
public void setSslPort(final long sslPort) { | |||
this.sslPort = sslPort; | |||
} | |||
public String getInstallType() { | |||
return installType; | |||
} | |||
public void setInstallType(final String installType) { | |||
this.installType = installType; | |||
} | |||
public String getSshKey() { | |||
return sshKey; | |||
} | |||
public void setSshKey(final String sshKey) { | |||
this.sshKey = sshKey; | |||
} | |||
public String getComputeSizeType() { | |||
return computeSizeType; | |||
} | |||
public void setComputeSizeType(final String computeSizeType) { | |||
this.computeSizeType = computeSizeType; | |||
} | |||
public String getFootprint() { | |||
return footprint; | |||
} | |||
public void setFootprint(final String footprint) { | |||
this.footprint = footprint; | |||
} | |||
public String getStorage() { | |||
return storage; | |||
} | |||
public void setStorage(final String storage) { | |||
this.storage = storage; | |||
} | |||
public String getDescription() { | |||
return description; | |||
} | |||
public void setDescription(final String description) { | |||
this.description = description; | |||
} | |||
public String getLocale() { | |||
return locale; | |||
} | |||
public void setLocale(final String locale) { | |||
this.locale = locale; | |||
} | |||
public String getTimezone() { | |||
return timezone; | |||
} | |||
public void setTimezone(final String timezone) { | |||
this.timezone = timezone; | |||
} | |||
public boolean isSendMetrics() { | |||
return sendMetrics; | |||
} | |||
public void setSendMetrics(final boolean sendMetrics) { | |||
this.sendMetrics = sendMetrics; | |||
} | |||
public String getTags() { | |||
return tags; | |||
} | |||
public void setTags(final String tags) { | |||
this.tags = tags; | |||
} | |||
public String getForkHost() { | |||
return forkHost; | |||
} | |||
public void setForkHost(final String forkHost) { | |||
this.forkHost = forkHost; | |||
} | |||
public String getState() { | |||
return state; | |||
} | |||
public void setState(final String state) { | |||
this.state = state; | |||
} | |||
public String getShortId() { | |||
return shortId; | |||
} | |||
public void setShortId(final String shortId) { | |||
this.shortId = shortId; | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
package com.getbubblenow.android.model; | |||
import com.google.gson.annotations.Expose; | |||
import com.google.gson.annotations.SerializedName; | |||
import java.util.List; | |||
public class Sages { | |||
@SerializedName("sages") | |||
@Expose | |||
List<String> sages; | |||
public void setSages(final List<String> sages) { | |||
this.sages = sages; | |||
} | |||
public List<String> getSages() { | |||
return sages; | |||
} | |||
} |
@@ -5,11 +5,13 @@ import android.content.Intent; | |||
import android.os.Build; | |||
import android.provider.Settings; | |||
import android.provider.Settings.Secure; | |||
import android.util.Log; | |||
import android.widget.Toast; | |||
import com.getbubblenow.android.Application; | |||
import com.getbubblenow.android.R; | |||
import com.getbubblenow.android.configStore.FileConfigStore; | |||
import com.getbubblenow.android.model.Network; | |||
import com.getbubblenow.android.model.ObservableTunnel; | |||
import com.getbubblenow.android.model.TunnelManager; | |||
import com.getbubblenow.android.activity.MainActivity; | |||
@@ -47,7 +49,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers; | |||
import io.reactivex.disposables.CompositeDisposable; | |||
import io.reactivex.disposables.Disposable; | |||
import io.reactivex.schedulers.Schedulers; | |||
import okhttp3.OkHttpClient; | |||
import okhttp3.Request; | |||
import okio.Buffer; | |||
import retrofit2.Call; | |||
@@ -68,9 +69,22 @@ public class DataRepository { | |||
private static final int REQUEST_CODE_VPN_PERMISSION = 23491; | |||
private static final String NO_INTERNET_CONNECTION = "no internet connection"; | |||
private static final String LOGIN_FAILED = "Login Failed"; | |||
private static final String URL_SUFFIX = "/api/"; | |||
private static final String RUNNING = "running"; | |||
private static String token = ""; | |||
private static String deviceName; | |||
private static String deviceID; | |||
private MutableLiveData<StatusResource<String>> nodeLiveData = new MutableLiveData<>(); | |||
private List<String> nodes; | |||
private int nodeIndex = 0; | |||
public MutableLiveData<StatusResource<String>> getNodeLiveData() { | |||
return nodeLiveData; | |||
} | |||
public void setNodeLiveData(final MutableLiveData<StatusResource<String>> nodeLiveData) { | |||
this.nodeLiveData = nodeLiveData; | |||
} | |||
private DataRepository(Context context, String url) { | |||
BASE_URL = url; | |||
@@ -98,6 +112,76 @@ public class DataRepository { | |||
return instance; | |||
} | |||
private void getNodeIndex(int index, String username , String password, Context context){ | |||
if(index<nodes.size()) | |||
{ | |||
getNetwork(nodes.get(index),username,password,context); | |||
} | |||
else { | |||
nodeLiveData.postValue(StatusResource.error(LOGIN_FAILED)); | |||
} | |||
} | |||
private void getNetwork(String node, String username , String password, Context context){ | |||
HashMap<String, String> data = new HashMap<>(); | |||
data.put(ApiConstants.USERNAME, username); | |||
data.put(ApiConstants.PASSWORD, password); | |||
buildClientService(node + URL_SUFFIX); | |||
Disposable disposableLogin = clientApi.login(data) | |||
.subscribeOn(Schedulers.newThread()) | |||
.observeOn(AndroidSchedulers.mainThread()) | |||
.subscribe(user -> { | |||
token = user.getToken(); | |||
ApiConstants.BASE_URL = node + URL_SUFFIX; | |||
buildClientService(ApiConstants.BASE_URL); | |||
final HashMap<String, String> header = new HashMap<>(); | |||
header.put(ApiConstants.AUTHORIZATION_HEADER, token); | |||
Disposable getNodeBaseURIDisposable = clientApi.getNodeBaseURI(header) | |||
.subscribeOn(Schedulers.newThread()) | |||
.observeOn(AndroidSchedulers.mainThread()) | |||
.subscribe(networks -> { | |||
for (Network network : networks) { | |||
if (network.getState().equals(RUNNING)) { | |||
ApiConstants.BASE_URL = network.getName() + "." + network.getDomainName(); | |||
BASE_URL = ApiConstants.BASE_URL; | |||
nodeLiveData.postValue(StatusResource.success(BASE_URL)); | |||
break; | |||
} | |||
} | |||
}, throwable -> { | |||
Log.d("ERR", "getNodeBaseURI"); | |||
getNodeIndex(nodeIndex++,username,password,context); | |||
}); | |||
compositeDisposable.add(getNodeBaseURIDisposable); | |||
}, throwable -> { | |||
getNodeIndex(nodeIndex++,username,password,context); | |||
Log.d("ERR", "getSages-login"); | |||
}); | |||
compositeDisposable.add(disposableLogin); | |||
} | |||
public MutableLiveData<StatusResource<String>> getSages(Context context, String username, String password){ | |||
return new NetworkBoundStatusResource<String>(){ | |||
@Override protected void createCall() { | |||
Disposable sagesDisposable = clientApi.getSages() | |||
.subscribeOn(Schedulers.newThread()) | |||
.observeOn(AndroidSchedulers.mainThread()) | |||
.subscribe(sages -> { | |||
nodes = sages.getSages(); | |||
getNodeIndex(nodeIndex,username,password,context); | |||
},throwable -> { | |||
Log.d("ERR","getSages"); | |||
setErrorMessage(throwable,this); | |||
}); | |||
compositeDisposable.add(sagesDisposable); | |||
} | |||
}.getMutableLiveData(); | |||
} | |||
public MutableLiveData<StatusResource<byte[]>> login(String username, String password, Context context) { | |||
return new NetworkBoundStatusResource<byte[]>() { | |||
@Override protected void createCall() { | |||
@@ -38,4 +38,16 @@ public class LoginViewModel extends ViewModel { | |||
public MutableLiveData<StatusResource<Object>> createTunnel(Context context) { | |||
return DataRepository.getRepositoryInstance().createTunnel(context); | |||
} | |||
public MutableLiveData<StatusResource<String>> getNodeLiveData() { | |||
return DataRepository.getRepositoryInstance().getNodeLiveData(); | |||
} | |||
public void setNodeLiveData(final MutableLiveData<StatusResource<String>> nodeLiveData) { | |||
DataRepository.getRepositoryInstance().setNodeLiveData(nodeLiveData); | |||
} | |||
public MutableLiveData<StatusResource<String>> getSages(Context context, String username, String password){ | |||
return DataRepository.getRepositoryInstance().getSages(context, username, password); | |||
} | |||
} |