diff --git a/build.gradle b/build.gradle index 6e60a78..6555854 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,9 @@ buildscript { constraintlayoutVersion = '1.1.3' lifecycleExtensionVersion = '2.2.0' lifecycleViewModelVersion = '2.2.0' + rxandroidVersion = '2.0.1' + rxjavaVersion = '2.1.6' + adapterrxjava2Version = '2.3.0' groupName = 'com.wireguard.android' } diff --git a/ui/build.gradle b/ui/build.gradle index a855be0..8bd3f6a 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -90,6 +90,10 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleExtensionVersion" implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycleViewModelVersion" + //RxJava + implementation "io.reactivex.rxjava2:rxandroid:$rxandroidVersion" + implementation "io.reactivex.rxjava2:rxjava:$rxjavaVersion" + implementation "com.squareup.retrofit2:adapter-rxjava2:$adapterrxjava2Version" } tasks.withType(JavaCompile) { diff --git a/ui/src/main/AndroidManifest.xml b/ui/src/main/AndroidManifest.xml index 299ac2c..a708955 100644 --- a/ui/src/main/AndroidManifest.xml +++ b/ui/src/main/AndroidManifest.xml @@ -72,7 +72,6 @@ - >() { @Override public void onChanged(final StatusResource userStatusResource) { - switch (userStatusResource.status){ + switch (userStatusResource.status) { case SUCCESS: - Toast.makeText(LoginActivity.this,"Success",Toast.LENGTH_SHORT).show(); - Log.d("TAG","Success"); - break; - case LOADING: - Log.d("TAG","Loading"); - break; - case ERROR: - Toast.makeText(LoginActivity.this,"Login Failed",Toast.LENGTH_SHORT).show(); - Log.d("TAG","Error"); - break; + Toast.makeText(LoginActivity.this, "Success", Toast.LENGTH_SHORT).show(); + Log.d("TAG", "Success"); + closeLoadingDialog(); + break; + case LOADING: + Log.d("TAG", "Loading"); + break; + case ERROR: + closeLoadingDialog(); + Toast.makeText(LoginActivity.this, "Login Failed", Toast.LENGTH_SHORT).show(); + Log.d("TAG", "Error"); + break; } } }); diff --git a/ui/src/main/java/com/wireguard/android/api/ApiConstants.java b/ui/src/main/java/com/wireguard/android/api/ApiConstants.java index 6b35206..6498069 100644 --- a/ui/src/main/java/com/wireguard/android/api/ApiConstants.java +++ b/ui/src/main/java/com/wireguard/android/api/ApiConstants.java @@ -3,6 +3,11 @@ package com.wireguard.android.api; public class ApiConstants { public static final String BASE_URL = "https://jtest2.bubblesecure.com:1443/api/"; 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"; public static final String USERNAME = "username"; public static final String PASSWORD = "password"; + public static final String AUTHORIZATION_HEADER = "X-Bubble-Session"; + public static final String DEVICE_NAME = "name"; + public static final String DEVICE_TYPE = "deviceType"; } diff --git a/ui/src/main/java/com/wireguard/android/api/network/ClientApi.java b/ui/src/main/java/com/wireguard/android/api/network/ClientApi.java index c28f08a..4a40cfc 100644 --- a/ui/src/main/java/com/wireguard/android/api/network/ClientApi.java +++ b/ui/src/main/java/com/wireguard/android/api/network/ClientApi.java @@ -1,13 +1,20 @@ package com.wireguard.android.api.network; import com.wireguard.android.api.ApiConstants; +import com.wireguard.android.model.Device; import com.wireguard.android.model.User; import java.util.HashMap; +import java.util.List; +import io.reactivex.Single; import retrofit2.Call; import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.HeaderMap; import retrofit2.http.POST; +import retrofit2.http.PUT; + /** Interface for API Calls @@ -15,5 +22,11 @@ import retrofit2.http.POST; public interface ClientApi { @POST(ApiConstants.LOGIN_URL) - Call login(@Body HashMap params); + Single login(@Body HashMap params); + + @GET(ApiConstants.ALL_DEVICES_URL) + Single> getAllDevices(@HeaderMap HashMap header); + + @PUT(ApiConstants.ADD_DEVICE_URL) + Single addDevice(@HeaderMap HashMap header , @Body HashMap body); } diff --git a/ui/src/main/java/com/wireguard/android/api/network/ClientService.java b/ui/src/main/java/com/wireguard/android/api/network/ClientService.java index 4fc0d7a..2b2b419 100644 --- a/ui/src/main/java/com/wireguard/android/api/network/ClientService.java +++ b/ui/src/main/java/com/wireguard/android/api/network/ClientService.java @@ -6,6 +6,7 @@ import com.wireguard.android.api.interceptor.UserAgentInterceptor; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; public class ClientService { @@ -41,6 +42,7 @@ public class ClientService { return new Retrofit.Builder() .baseUrl(ApiConstants.BASE_URL) .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(httpClient.build()) .build().create(ClientApi.class); } diff --git a/ui/src/main/java/com/wireguard/android/fragment/LoadingDialogFragment.java b/ui/src/main/java/com/wireguard/android/fragment/LoadingDialogFragment.java new file mode 100644 index 0000000..1bcfea1 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/fragment/LoadingDialogFragment.java @@ -0,0 +1,57 @@ +package com.wireguard.android.fragment; + +import android.app.Dialog; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import com.wireguard.android.R; +import com.wireguard.android.activity.BaseActivityBubble; +import com.wireguard.android.activity.LoginActivity; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; + +public class LoadingDialogFragment extends DialogFragment { + + + private ProgressBar progressBar; + + public static LoadingDialogFragment newInstance() { + LoadingDialogFragment fragment = new LoadingDialogFragment(); + Bundle args = new Bundle(); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final Dialog dialog = new Dialog(getActivity(), getTheme()) { + @Override + public void onBackPressed() { + if (getActivity().getSupportFragmentManager().findFragmentByTag(BaseActivityBubble.LOADING_TAG) != null) { + getActivity().onBackPressed(); + } + super.onBackPressed(); + } + }; + return dialog; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_loading_dialog, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + progressBar = view.findViewById(R.id.progress); + progressBar.getIndeterminateDrawable().setColorFilter(ContextCompat.getColor(getContext(), R.color.colorPrimaryDark), PorterDuff.Mode.SRC_IN); + } +} diff --git a/ui/src/main/java/com/wireguard/android/model/Device.java b/ui/src/main/java/com/wireguard/android/model/Device.java new file mode 100644 index 0000000..7820e68 --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/model/Device.java @@ -0,0 +1,106 @@ +package com.wireguard.android.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Device { + @SerializedName("uuid") + @Expose + private String uuid; + @SerializedName("related") + @Expose + private String related; + @SerializedName("ctime") + @Expose + private long ctime; + @SerializedName("name") + @Expose + private String name; + @SerializedName("account") + @Expose + private String account; + @SerializedName("deviceType") + @Expose + private String deviceType; + @SerializedName("enabled") + @Expose + private boolean enabled; + @SerializedName("network") + @Expose + private String network; + @SerializedName("shortId") + @Expose + private String shortId; + + public void setUuid(final String uuid) { + this.uuid = uuid; + } + + public void setRelated(final String related) { + this.related = related; + } + + public void setCtime(final long ctime) { + this.ctime = ctime; + } + + public void setName(final String name) { + this.name = name; + } + + public void setAccount(final String account) { + this.account = account; + } + + public void setDeviceType(final String deviceType) { + this.deviceType = deviceType; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + public void setNetwork(final String network) { + this.network = network; + } + + public void setShortId(final String shortId) { + this.shortId = shortId; + } + + public String getUuid() { + return uuid; + } + + public String getRelated() { + return related; + } + + public long getCtime() { + return ctime; + } + + public String getName() { + return name; + } + + public String getAccount() { + return account; + } + + public String getDeviceType() { + return deviceType; + } + + public boolean isEnabled() { + return enabled; + } + + public String getNetwork() { + return network; + } + + public String getShortId() { + return shortId; + } +} diff --git a/ui/src/main/java/com/wireguard/android/repository/DataRepository.java b/ui/src/main/java/com/wireguard/android/repository/DataRepository.java index 6e855c6..a8c4873 100644 --- a/ui/src/main/java/com/wireguard/android/repository/DataRepository.java +++ b/ui/src/main/java/com/wireguard/android/repository/DataRepository.java @@ -1,30 +1,40 @@ package com.wireguard.android.repository; import android.content.Context; - +import android.os.Build; +import android.provider.Settings; +import android.provider.Settings.Secure; import com.wireguard.android.api.ApiConstants; import com.wireguard.android.api.network.ClientApi; import com.wireguard.android.api.network.ClientService; import com.wireguard.android.api.network.NetworkBoundStatusResource; +import com.wireguard.android.model.Device; import com.wireguard.android.model.User; import com.wireguard.android.resource.StatusResource; import com.wireguard.android.util.UserStore; - +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import androidx.lifecycle.MutableLiveData; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; public class DataRepository { private static volatile DataRepository instance; private ClientApi clientApi; + private CompositeDisposable compositeDisposable; public static final String NO_INTERNET_CONNECTION = "no_internet_connection"; + private static final String SEPARATOR = ":"; + private static final String SPACE = " "; + private static final int ANDROID_ID = 1; - private DataRepository() - { + private DataRepository() { clientApi = ClientService.getInstance().createClientApi(); + compositeDisposable = new CompositeDisposable(); } public static void buildRepositoryInstance() { @@ -41,36 +51,163 @@ public class DataRepository { return instance; } - public MutableLiveData> login(String username,String password , Context context){ - return new NetworkBoundStatusResource(){ + public MutableLiveData> login(String username, String password, Context context) { + return new NetworkBoundStatusResource() { @Override protected void createCall() { - HashMap data = new HashMap<>(); - data.put(ApiConstants.USERNAME,username); - data.put(ApiConstants.PASSWORD,password); - clientApi.login(data).enqueue(new Callback() { - @Override public void onResponse(final Call call, final Response response) { - if(response.isSuccessful()) { - String token = response.body().getToken(); - UserStore.getInstance(context).setToken(token); - setMutableLiveData(StatusResource.success()); - } - else { - String errorMessage = createErrorMessage(call,response); - setMutableLiveData(StatusResource.error(errorMessage)); - } - } - - @Override public void onFailure(final Call call, final Throwable t) { - if(t instanceof Exception){ + HashMap data = new HashMap<>(); + data.put(ApiConstants.USERNAME, username); + data.put(ApiConstants.PASSWORD, password); + Disposable disposableLogin = clientApi.login(data) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(user -> { + UserStore.getInstance(context).setToken(user.getToken()); + if (!isDeviceLoggedIn(context)) { + addDevice(context); + } + else { + getAllDevices(context); + } + }, throwable -> { + setMutableLiveData(StatusResource.error(throwable.getMessage())); + }); + compositeDisposable.add(disposableLogin); + } + + private void getAllDevices(final Context context) { + final String token = UserStore.getInstance(context).getToken(); + final HashMap header = new HashMap<>(); + header.put(ApiConstants.AUTHORIZATION_HEADER,token); + Disposable disposableAllDevices = clientApi.getAllDevices(header) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(listDevices->{ + boolean hasDevice = false; + for (Device item : listDevices) { + if (UserStore.getInstance(context).getDeviceID().equals(item.getUuid())) { + setMutableLiveData(StatusResource.success()); + hasDevice = true; + break; + } + } + if(!hasDevice) { + addDevice(context); + } + },throwable -> { + + }); + compositeDisposable.add(disposableAllDevices); + } + + private void addDevice(final Context context) { + final String brand = getBrand(); + final String model = getDeviceModel(); + final String imei = getDeviceID(context); + final String deviceName = brand + SPACE + model + SPACE + SEPARATOR + SPACE + imei; + final String token = UserStore.getInstance(context).getToken(); + final HashMap header = new HashMap<>(); + header.put(ApiConstants.AUTHORIZATION_HEADER, token); + final Disposable disposableAllDevices = clientApi.getAllDevices(header) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(listDevices -> { + String brandModel = brand + SPACE + model + SPACE; + final List list = listDevices; + final List arrayListDevicesName = new ArrayList<>(); + boolean hasDevice = false; + for (final Device device : list) { + final String[] deviceNameItem = device.getName().split(SEPARATOR); + final String[] myDeviceName = deviceName.split(SEPARATOR); + if (deviceNameItem.length > 1) { + if (deviceNameItem[ANDROID_ID].equals(myDeviceName[ANDROID_ID])) { + UserStore.getInstance(context).setDeviceName(device.getName()); + UserStore.getInstance(context).setDeviceID(device.getUuid()); + hasDevice = true; + setMutableLiveData(StatusResource.success()); + break; + } else { + final String[] itemDevice = device.getName().split(SEPARATOR); + if (itemDevice.length != 1) { + if (itemDevice[0].contains(brandModel)) { + arrayListDevicesName.add(itemDevice[0]); + } + } + } + + } + } + if (!hasDevice) { + if (arrayListDevicesName.isEmpty()) { + brandModel = deviceName; + final HashMap body = new HashMap<>(); + body.put(ApiConstants.DEVICE_NAME, brandModel); + body.put(ApiConstants.DEVICE_TYPE, "android"); + final Disposable disposableAddDevice = clientApi.addDevice(header, body) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(device -> { + UserStore.getInstance(context).setDeviceName(device.getName()); + UserStore.getInstance(context).setDeviceID(device.getUuid()); + setMutableLiveData(StatusResource.success()); + }, throwable -> { + setMutableLiveData(StatusResource.error(throwable.getMessage())); + }); + compositeDisposable.add(disposableAddDevice); + } + else { + for (int i = (arrayListDevicesName.size() - 1); i >= arrayListDevicesName.size() - 1; i--) { + if (arrayListDevicesName.get(i).contains(brandModel)) { + final char[] arr = arrayListDevicesName.get(i).toCharArray(); + if (arr[arr.length - 2] != ')') { + brandModel += "(2)" + SPACE + SEPARATOR + SPACE + imei; + } else { + String countDevice = ""; + int indexfirst = 0; + int indexlast = 0; + for (int j = arr.length - 1; j >= 0; j--) { + if (arr[j] == '(') { + indexfirst = j; + } + if (arr[j] == ')') { + indexlast = j; + } + } + final String device = new String(arr); + countDevice += device.substring(indexfirst + 1, indexlast); + int count = Integer.parseInt(countDevice); + count++; + brandModel += "(" + count + ")" + SPACE + SEPARATOR + SPACE + imei; + } + } + final HashMap body = new HashMap<>(); + body.put(ApiConstants.DEVICE_NAME, brandModel); + body.put(ApiConstants.DEVICE_TYPE, "android"); + Disposable disposableAddDevice = clientApi.addDevice(header,body) + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(device -> { + UserStore.getInstance(context).setDeviceName(device.getName()); + UserStore.getInstance(context).setDeviceID(device.getUuid()); + setMutableLiveData(StatusResource.success()); + },throwable -> { + setMutableLiveData(StatusResource.error(throwable.getMessage())); + }); + compositeDisposable.add(disposableAddDevice); + } + } + } + }, throwable -> { setMutableLiveData(StatusResource.error(NO_INTERNET_CONNECTION)); - } - } - }); + }); + compositeDisposable.add(disposableAllDevices); } }.getMutableLiveData(); } + private boolean isDeviceLoggedIn(Context context) { + return !UserStore.DEVICE_DEFAULT_VALUE.equals(UserStore.getInstance(context).getDeviceName()); + } private String createErrorMessage(Call call, retrofit2.Response response) { return "Error: User agent: " + System.getProperty("http.agent") + ", Request body: " + call.request().body() + ", URL: " + @@ -78,9 +215,33 @@ public class DataRepository { response.message(); } - public boolean isUserLoggedIn(Context context) - { + public boolean isUserLoggedIn(Context context) { return !UserStore.USER_TOKEN_DEFAULT_VALUE.equals(UserStore.getInstance(context).getToken()); } + private String getBrand() { + final String brand = Build.MANUFACTURER; + return capitalize(brand); + } + + + private String capitalize(final String brand) { + if (brand == null || brand.isEmpty()) { + return ""; + } + final char first = brand.charAt(0); + return Character.isUpperCase(first) ? brand : Character.toUpperCase(first) + brand.substring(1); + } + + private String getDeviceID(final Context context) { + return Settings.Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); + } + + private String getDeviceModel() { + return Build.MODEL; + } + + public void clearDisposable(){ + compositeDisposable.clear(); + } } diff --git a/ui/src/main/java/com/wireguard/android/util/UserStore.java b/ui/src/main/java/com/wireguard/android/util/UserStore.java index 6c378a6..8537f31 100644 --- a/ui/src/main/java/com/wireguard/android/util/UserStore.java +++ b/ui/src/main/java/com/wireguard/android/util/UserStore.java @@ -9,7 +9,11 @@ public class UserStore { private static final String USER_SHARED_PREF = "com.wireguard.android.util.bubbleUserSharedPref"; private static final String USER_DATA_KEY = "com.wireguard.android.util.bubbleUserResponse"; + private static final String DEVICE_DATA_KEY = "com.wireguard.android.util.bubbleDeviceResponse"; + private static final String DEVICE_ID_KEY = "com.wireguard.android.util.bubbleDeviceIDResponse"; public static final String USER_TOKEN_DEFAULT_VALUE = ""; + public static final String DEVICE_DEFAULT_VALUE = ""; + public static final String DEVICE_ID_DEFAULT_VALUE = ""; public static UserStore getInstance(Context context) { if (instance == null) { @@ -35,4 +39,19 @@ public class UserStore { return sharedPreferences.getString(USER_DATA_KEY, USER_TOKEN_DEFAULT_VALUE); } + public void setDeviceName(String device){ + sharedPreferences.edit().putString(DEVICE_DATA_KEY, device).apply(); + } + + public String getDeviceName(){ + return sharedPreferences.getString(DEVICE_DATA_KEY, DEVICE_DEFAULT_VALUE); + } + + public void setDeviceID(String id){ + sharedPreferences.edit().putString(DEVICE_ID_KEY,id).apply(); + } + + public String getDeviceID(){ + return sharedPreferences.getString(DEVICE_ID_KEY,DEVICE_ID_DEFAULT_VALUE); + } } diff --git a/ui/src/main/java/com/wireguard/android/viewmodel/LoginViewModel.java b/ui/src/main/java/com/wireguard/android/viewmodel/LoginViewModel.java index 3669b85..b804581 100644 --- a/ui/src/main/java/com/wireguard/android/viewmodel/LoginViewModel.java +++ b/ui/src/main/java/com/wireguard/android/viewmodel/LoginViewModel.java @@ -1,13 +1,9 @@ package com.wireguard.android.viewmodel; import android.content.Context; - import com.wireguard.android.model.User; import com.wireguard.android.repository.DataRepository; import com.wireguard.android.resource.StatusResource; - -import java.util.HashMap; - import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; @@ -15,4 +11,9 @@ public class LoginViewModel extends ViewModel { public LiveData> login(String username,String password, Context context){ return DataRepository.getRepositoryInstance().login(username,password,context); } + + @Override protected void onCleared() { + super.onCleared(); + DataRepository.getRepositoryInstance().clearDisposable(); + } } diff --git a/ui/src/main/res/layout/fragment_loading_dialog.xml b/ui/src/main/res/layout/fragment_loading_dialog.xml new file mode 100644 index 0000000..c088797 --- /dev/null +++ b/ui/src/main/res/layout/fragment_loading_dialog.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/main/res/values/colors.xml b/ui/src/main/res/values/colors.xml index 989c6fc..38e173c 100644 --- a/ui/src/main/res/values/colors.xml +++ b/ui/src/main/res/values/colors.xml @@ -25,4 +25,6 @@ #aa0000 #00aa00 #aaaa00 + + #C04B26 diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index 5bdabba..997f8f4 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -241,4 +241,5 @@ Connect Disable Apps Please turn on internet connection + Loading