diff --git a/app/build.gradle b/app/build.gradle
index 25c07ef..d83b116 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -75,6 +75,8 @@ dependencies {
// implementation files('libs/ksoap2-android-assembly-3.6.0-jar-with-dependencies.jar')
implementation files('libs/badgeview.jar')
implementation files('libs/TbsFileSdk_base_arm64_release_1.0.5.6000030.20231109143447.aar')
+ //深信服零信任SDK
+ implementation files('libs/SangforSDK.aar')
implementation libs.androidx.core.ktx
diff --git a/app/libs/SangforSDK.aar b/app/libs/SangforSDK.aar
new file mode 100644
index 0000000..c4a5324
Binary files /dev/null and b/app/libs/SangforSDK.aar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f643103..59f96ac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -81,6 +81,12 @@
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:allowBackup"
tools:targetApi="s">
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/rehome/zhdcoa/App.java b/app/src/main/java/com/rehome/zhdcoa/App.java
index c698adf..3d74391 100644
--- a/app/src/main/java/com/rehome/zhdcoa/App.java
+++ b/app/src/main/java/com/rehome/zhdcoa/App.java
@@ -1,20 +1,16 @@
package com.rehome.zhdcoa;
-import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
-import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
+import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.emoji.bundled.BundledEmojiCompatConfig;
import androidx.emoji.text.EmojiCompat;
-import androidx.multidex.BuildConfig;
import androidx.multidex.MultiDex;
//import com.blankj.utilcode.util.Utils;
@@ -25,9 +21,18 @@ import com.github.mikephil.charting.utils.Utils;
//import com.hjq.http.model.HttpHeaders;
//import com.hjq.http.model.HttpParams;
import com.rehome.zhdcoa.bean.UserInfoBean;
+import com.rehome.zhdcoa.ui.activity.VpnAuthActivity;
+import com.rehome.zhdcoa.utils.DpPxSpTransformUtil;
import com.rehome.zhdcoa.utils.SPUtils;
//import com.squareup.leakcanary.LeakCanary;
//import com.squareup.leakcanary.RefWatcher;
+import com.rehome.zhdcoa.vpn.GlobalListenerManager;
+import com.sangfor.sdk.SFUemSDK;
+import com.sangfor.sdk.base.SFBaseMessage;
+import com.sangfor.sdk.base.SFSDKFlags;
+import com.sangfor.sdk.base.SFSDKMode;
+import com.sangfor.sdk.base.SFSetSpaConfigListener;
+import com.sangfor.sdk.utils.SFLogN;
import com.tencent.bugly.crashreport.CrashReport;
import com.xuexiang.xui.XUI;
import com.yolanda.nohttp.NoHttp;
@@ -35,11 +40,6 @@ import com.zhy.autolayout.config.AutoLayoutConifg;
import org.litepal.LitePalApplication;
-import java.util.HashMap;
-import java.util.List;
-
-import okhttp3.OkHttpClient;
-
public class App extends LitePalApplication {
private UserInfoBean.UserInfo userInfo;
@@ -60,6 +60,8 @@ public class App extends LitePalApplication {
private int activityAount = 0;
private boolean isForeground = false;
+ public static Context mContext;
+
public static App getInstance() {
return mInstance;
@@ -152,6 +154,37 @@ public class App extends LitePalApplication {
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
+ mContext = base;
+
+ /**
+ * 初始化深信服sdk,推荐在attachBaseContext中调用,因为sdk延后初始化会导致多进程场景下,子进程无法拥有sdk的能力
+ * 初始化深信服注销监听回调
+ * 初始化深信服dp,sp转px的工具类
+ */
+ if (isApkInDebug(base)) {
+ showLog("attachBaseContext base");
+ initSdk(base);
+ GlobalListenerManager.getInstance().init(base);
+ DpPxSpTransformUtil.init(getResources().getDisplayMetrics().density);
+// String config = "{\"loginAddress\":\"https://vpn.zhp.geg.com.cn:55420\", \"spaSecret\":\"7FFQ-rWE5-V6xX\"}";
+// SFUemSDK.setSpaConfig(config, new SFSetSpaConfigListener() {
+// @Override
+// public void onSetSpaConfig(String result, SFBaseMessage error) {
+// SFLogN.info("app", "spa result:"+ result + ", error:" + error);
+// Log.i("app", "spa result:"+ result + ", error:" + error);
+// if (error.mErrCode != 0) {
+// Toast.makeText(mContext,
+// "SPA设置失败"+ ", Error Message:" + error.toString(),
+// Toast.LENGTH_SHORT)
+// .show();
+// } else {
+// Toast.makeText(mContext, "SPA设置成功, result:" + result,
+// Toast.LENGTH_SHORT)
+// .show();
+// }
+// }
+// });
+ }
}
public void clearUser() {
@@ -211,5 +244,45 @@ public class App extends LitePalApplication {
}
}
+ public void showLog(String logText) {
+ if (isApkInDebug(mContext)) {
+ if (TextUtils.isEmpty(logText)) {
+ Log.i("app", "logText is null");
+ } else {
+ Log.i("app", logText);
+ }
+ }
+ }
+ void initSdk(Context context) {
+ SFSDKMode sdkMode = SFSDKMode.MODE_SUPPORT_MUTABLE; // 表明启用可变授权功能,详情参考集成指导文档
+ switch (sdkMode) {
+ case MODE_SUPPORT_MUTABLE:{
+ int sdkFlags = SFSDKFlags.FLAGS_HOST_APPLICATION; //表明是单应用或者是主应用
+ sdkFlags |= SFSDKFlags.FLAGS_VPN_MODE_TCP; //表明使用VPN功能中的TCP模式
+ SFUemSDK.getInstance().initSDK(this, sdkMode,sdkFlags,null);
+ break;
+ }
+ case MODE_VPN: { //只使用VPN功能场景
+ int sdkFlags = SFSDKFlags.FLAGS_HOST_APPLICATION; //表明是单应用或者是主应用
+ sdkFlags |= SFSDKFlags.FLAGS_VPN_MODE_TCP; //表明使用VPN功能中的TCP模式
+ SFUemSDK.getInstance().initSDK(this, sdkMode,sdkFlags,null);
+ break;
+ }
+ case MODE_SANDBOX: { //只使用安全沙箱功能场景
+ int sdkFlags = SFSDKFlags.FLAGS_HOST_APPLICATION; //表明是单应用或者是主应用
+ SFUemSDK.getInstance().initSDK(this, sdkMode,sdkFlags,null);
+ break;
+ }
+ case MODE_VPN_SANDBOX: { //同时使用VPN功能+安全沙箱功能场景
+ int sdkFlags = SFSDKFlags.FLAGS_HOST_APPLICATION; //表明是单应用或者是主应用
+ sdkFlags |= SFSDKFlags.FLAGS_VPN_MODE_TCP; //表明使用VPN功能中的TCP模式
+ SFUemSDK.getInstance().initSDK(this, sdkMode,sdkFlags,null);
+ break;
+ }
+ default: {
+ Toast.makeText(context, "SDK模式错误", Toast.LENGTH_LONG).show();
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/rehome/zhdcoa/Contans.java b/app/src/main/java/com/rehome/zhdcoa/Contans.java
index 111d5f0..8ab7b1d 100644
--- a/app/src/main/java/com/rehome/zhdcoa/Contans.java
+++ b/app/src/main/java/com/rehome/zhdcoa/Contans.java
@@ -93,6 +93,9 @@ public class Contans {
/********************广播字段(唯一性)********************************/
public static final String ACTION_REFLASH_PRODUCE_FRAGMENT = "ACTION_REFLASH_PRODUCE_FRAGMENT";
+ public static final String USER_NAME_VPN = "usernameVpn";//vpn用户名
+ public static final String PASS_WORD_VPN = "pwdVpn";//vpn密码
+
public class KEY {
public static final String SELECT_PERSON = "select_person";//选择的人数
public static final String SELECT_ADDR = "select_addr";//选择的乘车地址
diff --git a/app/src/main/java/com/rehome/zhdcoa/ui/activity/AuthSuccessActivity.java b/app/src/main/java/com/rehome/zhdcoa/ui/activity/AuthSuccessActivity.java
new file mode 100644
index 0000000..a75effc
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/ui/activity/AuthSuccessActivity.java
@@ -0,0 +1,679 @@
+package com.rehome.zhdcoa.ui.activity;
+
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.net.http.SslError;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.webkit.DownloadListener;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.sangfor.sdk.SFUemSDK;
+import com.sangfor.sdk.base.SFBaseMessage;
+import com.sangfor.sdk.base.SFTunnelStatus;
+import com.sangfor.sdk.base.SFTunnelStatusListener;
+import com.sangfor.sdk.utils.SFLogN;
+//import com.sangfor.sdkdemo.BuildConfig;
+import com.rehome.zhdcoa.R;
+//import com.sangfor.sdkdemo.primary.L3VPNPrimaryAuthActivity;
+import com.rehome.zhdcoa.vpn.SignatureUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class AuthSuccessActivity extends AppCompatActivity implements View.OnClickListener, SFTunnelStatusListener {
+
+ private static final String TAG = "app";
+ private static String sTestURL = "https://mobile.zhp.geg.com.cn:7082/ApkUpdate/GetData.aspx";
+ private final int TEST_URL_TIMEOUT_MILLIS = 8 * 1000;// 测试vpn资源的超时时间
+ private EditText mEtUrl;
+ private FrameLayout mWebViewContainer = null;
+ private WebView mWebView = null;
+ private RadioGroup mRadioGroup_authMethod = null;
+ private RadioButton mRadioButton_selected_authMethod = null;
+ private AutoCompleteTextView mAutoCompleteTextView = null;
+
+ //L3VPN全局vpn模式才需要关注的处理逻辑
+ private boolean mIsFromL3vpn = false;
+
+ private static ThreadPoolExecutor sBackupExecutor =
+ new ThreadPoolExecutor(3, 3, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactory() {
+ private final AtomicInteger mCount = new AtomicInteger(1);
+
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "downloadfile thread #" + mCount.getAndIncrement());
+ }
+ });
+
+ static {
+ sBackupExecutor.allowCoreThreadTimeOut(true);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate...");
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.activity_auth_success);
+
+ initView();
+ initClickEvents();
+ }
+
+ private void initView() {
+ mEtUrl = findViewById(R.id.et_url);
+ mRadioGroup_authMethod = findViewById(R.id.svpn_resource_tabheader);
+ mAutoCompleteTextView = findViewById(R.id.autoComTextView_url);
+ mWebViewContainer = findViewById(R.id.web_view_container);
+ mWebView = new WebView(getApplicationContext());
+ mWebViewContainer.addView(mWebView);
+ setWebViewSettings(); //设置webview配置参数
+
+ String[] str_extraResource_url = getResources().getStringArray(R.array.extra_resource_url);
+ ArrayAdapter arrayAdapter =
+ new ArrayAdapter(AuthSuccessActivity.this, R.layout.simple_spinner_item,
+ str_extraResource_url);
+ mAutoCompleteTextView.setAdapter(arrayAdapter);
+
+ mEtUrl.setText(sTestURL);
+
+
+
+ Intent intent = getIntent();
+ mIsFromL3vpn = true;
+ Button startTunnelBtn = findViewById(R.id.btn_start_tunnel);
+ Button stopTunnelBtn = findViewById(R.id.btn_stop_tunnel);
+ //L3VPN模式才显示隧道启动停止按钮和注册隧道状态回调
+ if (mIsFromL3vpn) {
+ startTunnelBtn.setVisibility(View.VISIBLE);
+ stopTunnelBtn.setVisibility(View.VISIBLE);
+ startTunnelBtn.setOnClickListener(this);
+ stopTunnelBtn.setOnClickListener(this);
+ SFUemSDK.getInstance().getSFTunnel().registerTunnelStatusListener(this);
+ } else {
+ startTunnelBtn.setVisibility(View.INVISIBLE);
+ stopTunnelBtn.setVisibility(View.INVISIBLE);
+ }
+
+ }
+
+ //注册监听事件
+ private void initClickEvents() {
+ findViewById(R.id.btn_file).setOnClickListener(this);
+ findViewById(R.id.btn_log).setOnClickListener(this);
+ findViewById(R.id.btn_master_slave).setOnClickListener(this);
+ findViewById(R.id.btn_modify_psw).setOnClickListener(this);
+
+ //资源展示按钮变动监听
+ mRadioGroup_authMethod.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ //监听认证按钮,动态改变布局显示
+ if(mRadioGroup_authMethod.getCheckedRadioButtonId()==R.id.svpn_intraResource_tabheader){
+ findViewById(R.id.et_url).setVisibility(View.VISIBLE);
+ findViewById(R.id.autoComTextView_url).setVisibility(View.GONE);
+ }
+ if(mRadioGroup_authMethod.getCheckedRadioButtonId()==R.id.svpn_extraResource_tabheader){
+ findViewById(R.id.et_url).setVisibility(View.GONE);
+ findViewById(R.id.autoComTextView_url).setVisibility(View.VISIBLE);
+ }
+// switch (mRadioGroup_authMethod.getCheckedRadioButtonId()) {
+// case R.id.svpn_intraResource_tabheader:
+// findViewById(R.id.et_url).setVisibility(View.VISIBLE);
+// findViewById(R.id.autoComTextView_url).setVisibility(View.GONE);
+// break;
+// case R.id.svpn_extraResource_tabheader:
+// findViewById(R.id.et_url).setVisibility(View.GONE);
+// findViewById(R.id.autoComTextView_url).setVisibility(View.VISIBLE);
+// break;
+// default:
+// break;
+// }
+
+ }
+ });
+
+ //AutoCompleteTextView控件获取焦点时,展示提示资源
+ mAutoCompleteTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ ((AutoCompleteTextView) v).showDropDown();
+ }
+ }
+ });
+
+
+ //内部测试代码,不需要,可以删除
+ Intent intent = getIntent();
+ if (intent != null && intent.getBooleanExtra("inner_test",false)) {
+ findViewById(R.id.btn_udp).setOnClickListener(this);
+ } else {
+ findViewById(R.id.btn_udp).setVisibility(View.GONE);
+ }
+
+ if (intent != null && intent.getBooleanExtra("inner_test",false)) {
+ findViewById(R.id.btn_atrust_auth).setOnClickListener(this);
+ } else {
+ findViewById(R.id.btn_atrust_auth).setVisibility(View.GONE);
+ }
+
+
+ }
+
+ @Override
+ public void onClick(View v) {
+ if(v.getId()==R.id.btn_test_res){
+ doTestResource();
+ }
+ if(v.getId()==R.id.btn_logout){
+ doVPNLogout();
+ }
+ if(v.getId()==R.id.btn_file){
+ entryFileTestPage();
+ }
+ if(v.getId()==R.id.btn_udp){
+ entryUdpPage();
+ }
+ if(v.getId()==R.id.btn_atrust_auth){
+ entryAtrustAuthPage();
+ }
+ if(v.getId()==R.id.btn_log){
+ entryLogTestPage();
+ }
+ if(v.getId()==R.id.btn_start_tunnel){
+ SFUemSDK.getInstance().getSFTunnel().startTunnel();
+ showLog(String.valueOf(SFUemSDK.getInstance().getAuthStatus()));
+ }
+ if(v.getId()==R.id.btn_stop_tunnel){
+ SFUemSDK.getInstance().getSFTunnel().stopTunnel();
+ }
+ if(v.getId()==R.id.btn_master_slave){
+ //startActivity(new Intent(this, MasterSlaveTestActivity.class));
+ }
+ if(v.getId()==R.id.btn_modify_psw){
+ if (!SFUemSDK.getInstance().allowResetPassword()) {
+ Toast.makeText(AuthSuccessActivity.this, "当前不支持修改密码", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ //startActivity(new Intent(this, ModifyPswActivity.class));
+ }
+ }
+
+ private void entryUdpPage() {
+ //内部测试代码,不需要可以删除
+
+ }
+
+ private void entryAtrustAuthPage() {
+ //startActivity(new Intent(this, ThirdCodeAuthInnerTestActivity.class));
+ }
+
+ private void entryLogTestPage() {
+ //startActivity(new Intent(this, LogTestActivity.class));
+ }
+
+ private void entryFileTestPage() {
+ //startActivity(new Intent(this, FileTestActivtiy.class));
+ }
+
+ private void doTestProxyDomain() {
+ // 获取选定的认证方式
+ mRadioButton_selected_authMethod = (RadioButton) findViewById(mRadioGroup_authMethod.getCheckedRadioButtonId());
+ String resourceType = mRadioButton_selected_authMethod.getText().toString().trim();
+ final EditText editText = mEtUrl;
+
+ int port = (int) SFUemSDK.getInstance().getSocks5ProxyPort();
+ SFLogN.info(TAG, "Access the domain " + editText.getText().toString() + " through port " + port + " via a proxy");
+ showLog( "Access the domain " + editText.getText().toString() + " through port " + port + " via a proxy");
+
+ // 设置代理
+ Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", port));
+
+ /**
+ * 测试用的很多域名证书校验通不过,进行自校验
+ * 创建自定义的 TrustManager
+ */
+ TrustManager[] trustAllCerts = new TrustManager[]{
+ new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ // 不进行客户端证书校验
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ // 不进行服务器证书校验
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ }
+ };
+
+ // 创建 SSLContext
+ SSLContext sslContext;
+ try {
+ sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+ } catch (Exception e) {
+ SFLogN.error2(TAG, "Failed to create SSL context: ", e.toString());
+ return;
+ }
+
+ OkHttpClient client = new OkHttpClient.Builder()
+ .proxy(proxy)
+ .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0])
+ .hostnameVerifier((hostname, session) -> true) // 关闭主机名验证
+ .build();
+
+ // 使用 AsyncTask 执行网络请求
+ new ProxyAsyncTask(this, client, editText.getText().toString()).execute();
+ }
+
+ private static class ProxyAsyncTask extends AsyncTask {
+ private WeakReference activityReference;
+ private OkHttpClient client;
+ private String url;
+
+ ProxyAsyncTask(AuthSuccessActivity context, OkHttpClient client, String url) {
+ activityReference = new WeakReference<>(context);
+ this.client = client;
+ this.url = url;
+ }
+
+ @Override
+ protected String doInBackground(Void... voids) {
+ // 创建请求对象
+ Request request = new Request.Builder()
+ .url(url)
+ .header("Proxy-Connection", "Keep-Alive")
+ .build();
+
+ // 发送请求并获取响应
+ Response response = null;
+ try {
+ response = client.newCall(request).execute();
+ if (response.isSuccessful()) {
+ return response.body().string(); // 获取响应体内容
+ } else {
+ SFLogN.info(TAG, "The request was failed, and the response code is " + response.code());
+ return null;
+ }
+ } catch (IOException e) {
+ SFLogN.error2(TAG, "An exception occurred, and the exception message is: ", e.toString());
+ if (response != null) {
+ response.body().close();
+ }
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(String responseBody) {
+ AuthSuccessActivity activity = activityReference.get();
+ if (activity == null || activity.isFinishing()) {
+ return; // Activity 已经被销毁,直接返回
+ }
+
+ if (responseBody != null) {
+ SFLogN.info(TAG, "The request " + url + " was successful.");
+ activity.mWebView.loadDataWithBaseURL(url, responseBody, "text/html", "utf-8", null);
+ } else {
+ SFLogN.info(TAG, "The request " + url + " was failed.");
+ }
+ }
+ }
+
+ /**
+ * 测试资源
+ */
+ private void doTestResource() {
+ //获取选定的认证方式
+ mRadioButton_selected_authMethod = (RadioButton) findViewById(mRadioGroup_authMethod.getCheckedRadioButtonId());
+ String resourceType = mRadioButton_selected_authMethod.getText().toString().trim();
+ if(resourceType.equals(getString(R.string.str_proxy_domain))) {
+ SFLogN.info(TAG, "begin to test proxy domain");
+ doTestProxyDomain();
+ return;
+ }
+ final EditText editText =
+ resourceType.equals(getString(R.string.str_intranet_resource)) ? mEtUrl : mAutoCompleteTextView;
+ LoadPageByWebView(editText.getText().toString());
+ }
+
+ /**
+ * 注销流程
+ */
+ private void doVPNLogout() {
+ // 注销VPN登录.
+ SFUemSDK.getInstance().logout();
+ Toast.makeText(AuthSuccessActivity.this, R.string.str_vpn_logout, Toast.LENGTH_SHORT).show();
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ public void LoadPageByWebView(String url) {
+ if (url == null || url.equals("")) {
+ SFLogN.info(TAG, "LoadPageByWebView url is wrong!");
+ return;
+ }
+ if (!url.contains("http")) {
+ url = "http://" + url;
+ }
+
+ showLog(url);
+ mWebView.loadUrl(url);
+ }
+
+ /**
+ * 获取应用签名,用于设置签名白名单,只有签名白名单内的子应用才能正常和主应用通信
+ *
+ * @param packageName
+ * @return
+ */
+ private String getSafeAppSignature(String packageName) {
+ PackageManager packageManager = getPackageManager();
+ ApplicationInfo applicationInfo;
+
+ try {
+ applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ SFLogN.warn2(TAG, "getSafeAppSignature failed", "application not found: " + packageName, e);
+ return "";
+ }
+ String pubkeyInfo = SignatureUtils.getPubKeyInfo(applicationInfo, this);
+ if (TextUtils.isEmpty(pubkeyInfo)) {
+ SFLogN.warn2(TAG, "getSafeAppSignature failed", "get pubkeyInfo failed: " + packageName);
+ return "";
+ }
+ SFLogN.info(TAG, "pubKeyInfo: " + pubkeyInfo + ",packageName: " + packageName);
+ return pubkeyInfo;
+ }
+
+ private boolean downloadFile(String url, File dir) {
+ try {
+
+ SFLogN.info(TAG, "run startDownload");
+ long start = SystemClock.elapsedRealtime();
+
+ URL url1 = new URL(url);
+ String decodeUrl = URLDecoder.decode(url1.getFile(), "utf-8");
+ SFLogN.info(TAG, "downloadFile decodeUrl:" + decodeUrl);
+ File dstFile = new File(dir, decodeUrl);
+ try {
+ if (!dstFile.exists()) {
+ dstFile.getParentFile().mkdirs();
+ dstFile.createNewFile();
+ }
+ } catch (Exception e) {
+ SFLogN.error(TAG, "downloadFile failed.", e);
+ }
+
+ SFLogN.info(TAG, "downloadFile dstFile:" + dstFile.getAbsolutePath() + " exist:" + dstFile.exists());
+
+ URLConnection connection = url1.openConnection();
+ connection.connect();
+ long contentLen = 0;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ contentLen = connection.getContentLengthLong();
+ } else {
+ contentLen = connection.getContentLength();
+ }
+ SFLogN.info(TAG, "downloadFile content len:" + contentLen);
+
+ InputStream inputStream = connection.getInputStream();
+ OutputStream outputStream = new FileOutputStream(dstFile);
+ byte[] buf = new byte[2048];
+ int len = 0;
+ long totlen = 0;
+ long percent = 1;
+ long lastCount = 0, lastTime = SystemClock.elapsedRealtime();
+ while ((len = inputStream.read(buf)) >= 0) {
+ totlen += len;
+ outputStream.write(buf, 0, len);
+ if (contentLen > 0 && totlen * 100 > contentLen * percent) {
+ percent++;
+ SFLogN.info(TAG, "downloadFile %d%% speed: %.2fMB/s",
+ (totlen * 100 / contentLen),
+ (totlen - lastCount) / 1024.0 / 1024 * 1000 / (SystemClock.elapsedRealtime() - lastTime));
+ lastCount = totlen;
+ lastTime = SystemClock.elapsedRealtime();
+ }
+ }
+ inputStream.close();
+ outputStream.close();
+
+ SFLogN.info(TAG, "downloadFile over,totlen:" + totlen);
+
+ long cost = SystemClock.elapsedRealtime() - start;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(AuthSuccessActivity.this, "下載完成!", Toast.LENGTH_SHORT).show();
+ }
+ });
+ SFLogN.info(TAG, "downloadFile cost:%.2fs, speed:%.2fMB/s", (cost / 1000.0f),
+ (totlen / 1024.0 / 1024.0 / cost * 1000));
+ return true;
+ } catch (Exception e) {
+ SFLogN.error(TAG, "downloadFile failed.", e);
+ }
+ return false;
+ }
+
+ private void setWebViewSettings() {
+ mWebView.setWebViewClient(new MyWebViewClient());
+
+ WebSettings webSettings = mWebView.getSettings();
+ // 不使用缓存,只从网络获取数据。
+ webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
+ // 开启 DOM storage API 功能
+ webSettings.setDomStorageEnabled(true);
+ // 开启 database storage API 功能
+ webSettings.setDatabaseEnabled(true);
+ // 设置可以支持缩放
+ webSettings.setSupportZoom(true);
+ // 设置出现缩放工具
+ webSettings.setBuiltInZoomControls(true);
+ // 设置可在大视野范围内上下左右拖动,并且可以任意比例缩放
+ webSettings.setUseWideViewPort(true);
+ // 设置默认加载的可视范围是大视野范围
+ webSettings.setLoadWithOverviewMode(true);
+ // 网页中包含JavaScript内容需调用以下方法,参数为true
+ webSettings.setJavaScriptEnabled(true);
+ mWebView.setDownloadListener(new DownloadListener() {
+ @Override
+ public void onDownloadStart(final String url, String userAgent, String contentDisposition, String mimetype,
+ long contentLength) {
+ SFLogN.info(TAG, "onDownloadStart url:" + url + " len:" + contentLength);
+ SFLogN.info(TAG, "onDownloadStart download dir:" + getExternalFilesDir(null).getAbsolutePath());
+ Toast.makeText(AuthSuccessActivity.this, String.format("下载地址Url:%s 下载文件路径:%s",
+ url, getExternalFilesDir(null).getAbsolutePath()), Toast.LENGTH_LONG).show();
+ sBackupExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ SFLogN.info(TAG, "run startDownload");
+ boolean ret = downloadFile(url, new File(getExternalFilesDir(null) + "/download/"));
+ SFLogN.info(TAG, "run downloadFile ret:" + ret);
+ }
+ });
+ }
+ });
+ }
+
+ private class MyWebViewClient extends WebViewClient {
+
+ public MyWebViewClient() {
+ }
+
+ @Override
+ // 覆盖WebView默认使用第三方或系统默认浏览器打开网页的行为,使网页用WebView打开
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ view.loadUrl(url);
+ return true;// 返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ SFLogN.info(TAG, "onPageStarted url = " + url);
+
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ //清除缓存
+ mWebView.clearCache(true);
+ mWebView.clearHistory();
+ super.onPageFinished(view, url);
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ handler.proceed();// 忽略证书错误
+ }
+ }
+
+ /**
+ * WebView的回收销毁,防止内存泄漏
+ */
+ private void destroyWebView() {
+ mWebViewContainer.removeAllViews();
+ if (mWebView != null) {
+ mWebView.clearHistory();
+ mWebView.clearCache(true);
+ mWebView.loadUrl("about:blank");
+ mWebView.freeMemory();
+// mWebView.pauseTimers();
+ mWebView.destroy();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ Log.i(TAG, "onResume...");
+ super.onResume();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ Log.i(TAG, "onNewIntent...");
+ super.onNewIntent(intent);
+ }
+
+ @Override
+ protected void onDestroy() {
+ destroyWebView();
+ if (mIsFromL3vpn) {
+ SFUemSDK.getInstance().getSFTunnel().unRegisterTunnelStatusListener(this);
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onTunnelStatusChanged(SFTunnelStatus curStatus) {
+ SFLogN.info(TAG, "onTunnelStatusChanged called: curStatus: " + curStatus);
+ }
+
+ @Override
+ public void onTunnelStarted(SFBaseMessage msg) {
+ SFLogN.info(TAG, "onTunnelStarted, msg: " + msg.mErrStr + ",errCode: " + msg.mErrCode);
+
+ if (msg.mErrCode != 0) {
+ SFLogN.warn2(TAG, "start Tunnel failed", msg.mErrStr);
+ showToast("L3VPN隧道启动失败: " + msg.mErrStr);
+ return;
+ }
+ SFLogN.info(TAG, "startTunnel finished");
+ showToast("L3VPN隧道启动成功");
+ }
+
+ @Override
+ public void onTunnelStoped() {
+ SFLogN.info(TAG, "onTunnelStoped");
+ showToast("隧道已经关闭");
+ }
+
+
+ //封装Toast
+ public void showToast(final String text) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(AuthSuccessActivity.this, text, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public void showLog(String logText) {
+ if (isApkInDebug(AuthSuccessActivity.this)) {
+ if(TextUtils.isEmpty(logText)){
+ Log.i("app", "logText is null");
+ }else{
+ Log.i("app", logText);
+ }
+ }
+ }
+
+ /**
+ * 判断当前应用是否是debug状态
+ */
+
+ public static boolean isApkInDebug(Context context) {
+ try {
+ ApplicationInfo info = context.getApplicationInfo();
+ return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/ui/activity/LoginActivity.kt b/app/src/main/java/com/rehome/zhdcoa/ui/activity/LoginActivity.kt
index 85544bd..d8708cb 100644
--- a/app/src/main/java/com/rehome/zhdcoa/ui/activity/LoginActivity.kt
+++ b/app/src/main/java/com/rehome/zhdcoa/ui/activity/LoginActivity.kt
@@ -18,6 +18,8 @@ import android.view.View
import android.widget.CompoundButton
import android.widget.RadioGroup
import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import com.google.gson.Gson
import com.rehome.zhdcoa.App
@@ -29,6 +31,7 @@ import com.rehome.zhdcoa.bean.UserInfoBean
import com.rehome.zhdcoa.databinding.ActivityLoginBinding
import com.rehome.zhdcoa.ui.toastview.toastviewbymyself
import com.rehome.zhdcoa.utils.*
+import com.sangfor.sdk.SFUemSDK
import com.tencent.bugly.crashreport.CrashReport
import com.yolanda.nohttp.NoHttp
import com.yolanda.nohttp.RequestMethod
@@ -43,6 +46,10 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
private lateinit var networkCheckType: String
+ private var usernameVpn = ""
+ private var pwdVpn = ""
+ private lateinit var launcherResultVpn: ActivityResultLauncher
+
override fun getViewBinding() = ActivityLoginBinding.inflate(layoutInflater)
override fun getToolbar() = binding.toolbarView.toolbar
@@ -50,6 +57,7 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
override fun initView() {
StatusBarUtil.transparencyBar(this)
+ launcherResultVpn = createActivityResultLauncherVpn()
val wifi = SPUtils.get(context, Contans.YX_WIFI_IP, "") as String
val mob = SPUtils.get(context, Contans.YX_4G_IP, "") as String
if (wifi == "" && mob == "") {
@@ -101,6 +109,15 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
Contans.BASE_URL_AI_3D_SERVER =
Contans.BASE_URL_AI_3D_SERVER_EXTRANET //AI三维可视化安防平台登录 电厂外网
}
+ if (networkCheckType == "aTrust") {
+ binding.rg.check(R.id.rb3)
+ Contans.IP = Contans.IP_INTRANET //运行IP内网
+ Contans.BASE_URL = Contans.BASE_URL_INTRANET //行政IP内网
+ Contans.BASE_URL_COMPANY_SERVER =
+ Contans.BASE_URL_COMPANY_SERVER_INTRANET //智慧安防平台登录 电厂内网
+ Contans.BASE_URL_AI_3D_SERVER =
+ Contans.BASE_URL_AI_3D_SERVER_INTRANET //AI三维可视化安防平台登录 电厂外网
+ }
} else {
binding.rg.check(R.id.rb2)
SPUtils.put(this, Contans.NETWORK_CHECK_TYPE, "外网")
@@ -133,6 +150,16 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
Contans.BASE_URL_AI_3D_SERVER =
Contans.BASE_URL_AI_3D_SERVER_EXTRANET //AI三维可视化安防平台登录 电厂外网
}
+
+ R.id.rb3 -> {
+ SPUtils.put(this, Contans.NETWORK_CHECK_TYPE, "aTrust")
+ Contans.IP = Contans.IP_INTRANET //运行IP内网
+ Contans.BASE_URL = Contans.BASE_URL_INTRANET //行政IP内网
+ Contans.BASE_URL_COMPANY_SERVER =
+ Contans.BASE_URL_COMPANY_SERVER_INTRANET //智慧安防平台登录 电厂内网
+ Contans.BASE_URL_AI_3D_SERVER =
+ Contans.BASE_URL_AI_3D_SERVER_INTRANET //AI三维可视化安防平台登录 电厂外网
+ }
}
})
binding.cbAgreement.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
@@ -145,14 +172,25 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
binding.tvServiceAgreement.setOnClickListener {
val intent = Intent(this, ServiceAgreementActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
}
binding.tvPrivacy.setOnClickListener {
val intent = Intent(this, PrivacyAgreementActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
}
-
+ //vpn登录
+ binding.tvVpn.setOnClickListener {
+ goToVpnLogin()
+ }
+ //vpn注销
+ binding.tvVpnLogout.setOnClickListener {
+ // 注销VPN登录.
+ SFUemSDK.getInstance().logout()
+ showToast(R.string.str_vpn_logout)
+ }
}
@SuppressLint("SetTextI18n")
@@ -174,8 +212,8 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
if (isApkInDebug(context)) {
// 管理员
-// binding.etUsername.setText("ZHPS_Admin")
-// binding.etPassword.setText("Rehome.zhps@996")
+ binding.etUsername.setText("ZHPS_Admin")
+ binding.etPassword.setText("Rehome.zhps@996")
//公司内网215管理员
@@ -256,6 +294,7 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
this,
WjmmGetVerificationCodeActivity::class.java
)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
})
@@ -560,6 +599,75 @@ class LoginActivity : BaseActivityOaToolbarViewBinding() {
System.exit(0)
}
+ private fun checkInput(): Boolean{
+ if (TextUtils.isEmpty(binding.etUsername.text.toString())) {
+ showToast("用户名不能为空")
+ return false
+ }
+ if (TextUtils.isEmpty(binding.etPassword.text.toString())) {
+ showToast("密码不能为空")
+ return false
+ }
+ return true;
+ }
+
+ private fun goToVpnLogin() {
+ val usernameVpnTemp = SPUtils.get(context, Contans.USER_NAME_VPN, "") as String
+ val pwdVpnTemp = SPUtils.get(context, Contans.PASS_WORD_VPN, "") as String
+
+
+ if ((!TextUtils.isEmpty(usernameVpnTemp)) && (!TextUtils.isEmpty(pwdVpnTemp))) {
+ usernameVpn = RSAUtils.decryptBASE64StrLocal(usernameVpnTemp)
+ pwdVpn = RSAUtils.decryptBASE64StrLocal(pwdVpnTemp)
+ }
+
+ //val intent = Intent(context, VpnLoginActivity::class.java)
+
+ val intent = Intent(context, VpnAuthActivity::class.java)
+
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ if ((!TextUtils.isEmpty(usernameVpn)) && (!TextUtils.isEmpty(pwdVpn))) {
+ intent.putExtra("usernameVpn",usernameVpn)
+ intent.putExtra("pwdVpn",pwdVpn)
+ }
+ launcherResultVpn.launch(intent)
+ }
+ //创建一个ActivityResultLauncher
+ private fun createActivityResultLauncherVpn(): ActivityResultLauncher {
+ //kotlin写法
+ return registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ val data = it.data
+ val resultCode = it.resultCode
+ if (resultCode == RESULT_OK) {
+ if (data != null) {
+ usernameVpn = data.getStringExtra("usernameVpn").toString()
+ pwdVpn = data.getStringExtra("pwdVpn").toString()
+ showLog(usernameVpn)
+ showLog(pwdVpn)
+ SPUtils.put(
+ context,
+ Contans.USER_NAME_VPN,
+ RSAUtils.encryptBASE64StrLocal(usernameVpn)
+ )
+ SPUtils.put(
+ context,
+ Contans.PASS_WORD_VPN,
+ RSAUtils.encryptBASE64StrLocal(pwdVpn)
+ )
+ if(checkInput()){
+ //登录
+ appLogin()
+
+// val intent = Intent(context, AuthSuccessActivity::class.java)
+// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+// startActivity(intent)
+// finish()
+
+ }
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/rehome/zhdcoa/ui/activity/PrimaryAuthActivity.java b/app/src/main/java/com/rehome/zhdcoa/ui/activity/PrimaryAuthActivity.java
new file mode 100644
index 0000000..a9df515
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/ui/activity/PrimaryAuthActivity.java
@@ -0,0 +1,490 @@
+package com.rehome.zhdcoa.ui.activity;
+
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.sangfor.sdk.SFUemSDK;
+import com.sangfor.sdk.base.SFAuthResultListener;
+import com.sangfor.sdk.base.SFAuthType;
+import com.sangfor.sdk.base.SFBaseMessage;
+import com.sangfor.sdk.base.SFChangePswMessage;
+import com.sangfor.sdk.base.SFConstants;
+import com.sangfor.sdk.base.SFSetSpaConfigListener;
+import com.sangfor.sdk.base.SFSmsMessage;
+import com.sangfor.sdk.utils.SFLogN;
+import com.rehome.zhdcoa.R;
+import com.rehome.zhdcoa.vpn.Constants;
+//import com.rehome.zhdcoa.activity.AuthSuccessActivity;
+import com.rehome.zhdcoa.vpn.SFDialogHelper;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class PrimaryAuthActivity extends AppCompatActivity implements SFAuthResultListener {
+ private static final String TAG = "app";
+
+ private String mServerAddress = "https://vpn.zhp.geg.com.cn:55420"; //服务器地址
+ private String mUserName = "rehome5"; //用户名
+ private String mUserPassword = "452131wW,./"; //密码
+
+ private EditText mServerAddressEditText = null;
+ private EditText mUserNameEditView = null;
+ private EditText mUserPasswordEditView = null;
+ private Button mLoginButton = null;
+ private AlertDialog mAuthDialog = null;
+
+ private ProgressDialog mProgressDialog = null; // 进度条对话框
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate...");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_primary_auth);
+ //加载UI
+ initView();
+
+ //SPA初始化,设置安全码
+ String config = "{\"loginAddress\":\"https://vpn.zhp.geg.com.cn:55420\", \"spaSecret\":\"7FFQ-rWE5-V6xX\"}";
+ SFUemSDK.setSpaConfig(config, new SFSetSpaConfigListener() {
+ @Override
+ public void onSetSpaConfig(String result, SFBaseMessage error) {
+ SFLogN.info(TAG, "spa result:"+ result + ", error:" + error);
+ Log.i(TAG, "spa result:"+ result + ", error:" + error);
+ if (error.mErrCode != 0) {
+ Toast.makeText(PrimaryAuthActivity.this,
+ "SPA设置失败"+ ", Error Message:" + error.toString(),
+ Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(PrimaryAuthActivity.this, "SPA设置成功, result:" + result,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ });
+
+
+ //加载上次登录信息
+ setLoginInfo();
+ /**
+ * 设置认证回调,认证结果在SFAuthResultListener的onAuthSuccess、onAuthFailed、onAuthProgress中返回
+ * 如果不设置,将接收不到认证结果回调
+ */
+ SFUemSDK.getInstance().setAuthResultListener(this);
+
+ //免密认证
+ startAutoTicket();
+ }
+
+ private void startAutoTicket() {
+ /**
+ * 这里是自动免密认证接口,返回true表示认证成功,此时用户就可以进行资源访问了,
+ * 如果返回false,表示当前不满足自动免密条件,需要用户主动调用用户名密码认证接口
+ */
+ if (SFUemSDK.getInstance().startAutoTicket()){
+ showToast("免密成功");
+// startActivity(new Intent(PrimaryAuthActivity.this, AuthSuccessActivity.class));
+// finish();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ Log.i(TAG,"onResume... ");
+ super.onResume();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ Log.i(TAG, "onNewIntent...");
+ super.onNewIntent(intent);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ //只有走回收流程的时候的那种onPause,isFinishing才为true
+ if (isFinishing()) {
+ //取消认证回调
+ SFLogN.info(TAG,"SFUemSDK setAuthResultListener null");
+ /**
+ * 注意: 清除回调建议放到onPause()方法而不是onDestroy()中,
+ * 避免出现onDestroy()在onCreate()之后执行,onCreate注册的认证回调被onDestory清空的问题
+ */
+ SFUemSDK.getInstance().setAuthResultListener(null);
+ }
+ }
+
+ // 初始化界面元素
+ private void initView() {
+ mServerAddressEditText = findViewById(R.id.vpn_addr_editView);
+ mUserNameEditView = findViewById(R.id.svpn_username_editView);
+ mUserPasswordEditView = findViewById(R.id.svpn_userPassword_editView);
+ //登录按钮
+ mLoginButton = findViewById(R.id.svpn_login_button);
+ mLoginButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mServerAddress = mServerAddressEditText.getText().toString();
+ mUserName = mUserNameEditView.getText().toString();
+ mUserPassword = mUserPasswordEditView.getText().toString();
+ //开始主认证
+ startPrimaryAuth();
+ }
+ });
+ }
+
+ //开始用户名密码认证
+ private void startPrimaryAuth() {
+
+ if (TextUtils.isEmpty(mServerAddress)) {
+ showToast("VPN服务器地址不能为空");
+ return;
+ }
+
+ if (TextUtils.isEmpty(mUserName)) {
+ showToast("用户名不能为空");
+ return;
+ }
+
+ showWaitingDialog(false);
+
+ /**
+ * 开始用户名密码认证,认证结果会在认证回调onAuthSuccess,onAuthFailed,onAuthProgress中返回
+ */
+ SFUemSDK.getInstance().startPasswordAuth(mServerAddress, mUserName, mUserPassword);
+
+ }
+
+ /**
+ * 认证成功
+ *
+ * @param message 认证成功信息
+ */
+ @Override
+ public void onAuthSuccess(final SFBaseMessage message) {
+ SFLogN.info(TAG, "auth success");
+
+ //关闭进度框
+ dismissWaitingDialog();
+
+ saveLoginInfo(); //保存登录信息
+
+ //认证完成,跳转认证成功界面,可以开始访问资源
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showToast(getString(R.string.auth_success));
+// startActivity(new Intent(PrimaryAuthActivity.this, AuthSuccessActivity.class));
+// finish();
+ }
+ });
+ }
+
+ /**
+ * 认证失败
+ *
+ * @param message 认证失败信息
+ */
+ @Override
+ public void onAuthFailed(final SFBaseMessage message) {
+ SFLogN.error2(TAG, "auth failed", "errMsg: " + message.mErrStr);
+
+ //关闭进度框
+ dismissWaitingDialog();
+
+ //认证失败
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showErrorMessage(message.mErrStr);
+ }
+ });
+ }
+
+ /**
+ * 主认证成功,但需要辅助认证(下一步认证)
+ *
+ * @param nextAuthType 下一步认证类型
+ * @param message 下一步认证信息
+ */
+ @Override
+ public void onAuthProgress(SFAuthType nextAuthType, SFBaseMessage message) {
+ SFLogN.info(TAG, "need next auth, authType: " + nextAuthType.name());
+ dismissWaitingDialog();
+
+ /**
+ * 1. 二次认证回调onAuthProgress, 不仅仅是管理员配置了二次认证才会回调;
+ * 如果管理员启用了"首次登录强制修改密码", 用户首次认证时会回调onAuthProgress;
+ * 如果管理员设置了密码有效期, 在密码过期后, 重新登录的时候也会回调onAuthProgress.
+ *
+ * 2. 二次认证回调中建议对修改密码进行处理, 对其他不做支持的二次认证需要做一个兜底处理:
+ * 针对不支持的认证方式做一个提示, 避免出现管理员配置了二次认证后,
+ * 用户侧收到了二次认证回调, 认证流程未完成但是无任何感知.
+ */
+ if (nextAuthType == SFAuthType.AUTH_TYPE_RENEW_PASSWORD) {
+ //显示下一步认证UI界面
+ showAuthDialog(nextAuthType, message);
+ return;
+ }
+
+ showErrorMessage("暂不支持此种认证类型(" + nextAuthType.toString() + ")");
+ }
+
+
+ //显示“请稍候...”提示框
+ public void showWaitingDialog(final boolean isCancelable) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mProgressDialog == null || !mProgressDialog.isShowing()) {
+ mProgressDialog = new ProgressDialog(PrimaryAuthActivity.this);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setTitle("");
+ mProgressDialog.setMessage(PrimaryAuthActivity.this.getString(R.string.waiting));
+ mProgressDialog.setCancelable(isCancelable);
+ mProgressDialog.show();
+ }
+ }
+ });
+ }
+
+ //取消“请稍候...”提示框
+ public void dismissWaitingDialog() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mProgressDialog != null && mProgressDialog.isShowing()) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+ });
+ }
+
+ //显示错误提示对话框
+ public void showErrorMessage(final String errMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Dialog messageDialog = new AlertDialog.Builder(PrimaryAuthActivity.this)
+ .setTitle(PrimaryAuthActivity.this.getString(R.string.info))
+ .setMessage(errMsg)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .create();
+
+ messageDialog.show();
+ }
+ });
+ }
+
+ //封装Toast
+ public void showToast(final String text) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(PrimaryAuthActivity.this, text, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+
+ //恢复登录信息,重启应用后记录原来的登陆信息
+ private void setLoginInfo() {
+ SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREF_FILE_NAME, MODE_PRIVATE);
+ mServerAddress = sharedPreferences.getString(Constants.KEY_VPN_ADDRESS, mServerAddress);
+ mUserName = sharedPreferences.getString(Constants.KEY_USER_NAME, mUserName);
+ mUserPassword = sharedPreferences.getString(Constants.KEY_USER_PASSWORD, mUserPassword);
+
+ mServerAddressEditText.setText(mServerAddress);
+ mUserNameEditView.setText(mUserName);
+ mUserPasswordEditView.setText(mUserPassword);
+ }
+
+ //更新保存的登录信息
+ private void saveLoginInfo() {
+ SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREF_FILE_NAME, MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putString(Constants.KEY_VPN_ADDRESS, mServerAddress);
+ //保存用户名和密码,真实场景请加密存储
+ editor.putString(Constants.KEY_USER_NAME, mUserName);
+ editor.putString(Constants.KEY_USER_PASSWORD, mUserPassword);
+ editor.apply();
+ }
+
+ /**
+ * 显示修改密码UI
+ */
+ private void showAuthDialog(final SFAuthType authType, final SFBaseMessage message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ closeDialog();
+ //获取认证类型对应的标题
+ String dialogTitle = SFDialogHelper.getDialogTitle(authType);
+ if (TextUtils.isEmpty(dialogTitle)) {
+ showErrorMessage("暂不支持此种认证类型(" + authType.toString() + ")");
+ return;
+ }
+ //获取认证类型对应的layout布局
+ int viewLayoutId = SFDialogHelper.getAuthDialogViewId(authType);
+ handlerSecondAuth(dialogTitle, viewLayoutId, authType, message);
+ }
+ });
+ }
+
+ /**
+ * 关闭对话框
+ */
+ private void closeDialog() {
+ if (mAuthDialog != null && mAuthDialog.isShowing()) {
+ mAuthDialog.dismiss();
+ mAuthDialog = null;
+ }
+ }
+
+ /**
+ * 处理二次验证
+ * @param dialogTitle
+ * @param viewLayoutId
+ * @param authType
+ * @param message
+ */
+ private void handlerSecondAuth(String dialogTitle, int viewLayoutId, SFAuthType authType, SFBaseMessage message) {
+
+ //创建认证类型对应的对话框显示视图
+ final View dialogView = createDialogView(authType, viewLayoutId, message);
+ mAuthDialog = new AlertDialog.Builder(PrimaryAuthActivity.this)
+ .setTitle(dialogTitle)
+ .setView(dialogView)
+ .setCancelable(false)
+ .setPositiveButton(R.string.str_commit, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ closeDialog();
+ //构建辅助认证类型所需参数
+ Map authParams = new HashMap();
+ String errorMsg = buildAuthParams(authType, dialogView, authParams);
+ //检查参数构建是否成功
+ if (TextUtils.isEmpty(errorMsg)) {
+ showWaitingDialog(false);
+ //开始辅助认证
+ SFUemSDK.getInstance().doSecondaryAuth(authType, authParams);
+ } else {
+ //参数构建失败,进行提示
+ showErrorMessage(errorMsg);
+ }
+ return;
+ }
+ })
+ .setNegativeButton(R.string.str_cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //放弃继续认证
+ closeDialog();
+ return;
+ }
+ })
+ .create();
+ //显示认证对话框
+ mAuthDialog.show();
+ }
+
+ /**
+ * 创建认证对话框中间显示的视图
+ *
+ * @param sfAuthType 认证类型
+ * @param layoutId 要加载的视图的布局ID
+ * @param message 认证附加信息
+ * @return 认证对话框视图
+ */
+ private View createDialogView(SFAuthType sfAuthType, int layoutId, final SFBaseMessage message) {
+ LayoutInflater inflater = getLayoutInflater();
+ View dialogView = inflater.inflate(layoutId, null);
+ switch (sfAuthType) {
+ case AUTH_TYPE_RENEW_PASSWORD: {
+ TextView tvPolicy = (TextView) dialogView.findViewById(R.id.tv_policy);
+ String policy = "";
+ //获取密码策略
+ if (message instanceof SFChangePswMessage) {
+ policy = ((SFChangePswMessage) message).policyMsg;
+ }
+ if (TextUtils.isEmpty(policy)) {
+ tvPolicy.setText(R.string.str_no_policy);
+ } else {
+ tvPolicy.setText(getString(R.string.str_policy_hint) + "\n" + policy);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return dialogView;
+ }
+
+ /**
+ * 构建辅助认证所需参数
+ *
+ * @param sfAuthType 认证类型
+ * @param dialogView 认证视图
+ * @param authParams 保存认证参数
+ * @return 参数错误信息
+ */
+ private String buildAuthParams(SFAuthType sfAuthType, View dialogView, Map authParams) {
+ String errorMsg = "";
+ switch (sfAuthType) {
+ case AUTH_TYPE_RENEW_PASSWORD: {
+ EditText etNewPwd = dialogView.findViewById(R.id.et_newpwd);
+ EditText etOldPwd = dialogView.findViewById(R.id.et_oldpwd);
+ EditText etReNewPwd = dialogView.findViewById(R.id.et_renewpwd);
+ String oldPwd = etOldPwd.getText().toString();
+ String newPwd = etNewPwd.getText().toString();
+ String reNewPwd = etReNewPwd.getText().toString();
+
+ if (TextUtils.isEmpty(newPwd) || TextUtils.isEmpty(reNewPwd)) {
+ errorMsg = "新密码不能为空";
+ break;
+ }
+
+ if (!newPwd.equals(reNewPwd)) {
+ errorMsg = "新密码与确认密码不一致";
+ break;
+ }
+
+ authParams.put(SFConstants.AUTH_KEY_RENEW_OLD_PASSWORD, oldPwd);
+ authParams.put(SFConstants.AUTH_KEY_REWNEW_NEW_PASSWORD, newPwd);
+ break;
+ }
+ default:
+ errorMsg = "暂不支持的认证类型";
+ break;
+ }
+
+ return errorMsg;
+ }
+
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/ui/activity/PrimarySmsLoginActivity.java b/app/src/main/java/com/rehome/zhdcoa/ui/activity/PrimarySmsLoginActivity.java
new file mode 100644
index 0000000..6cc0713
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/ui/activity/PrimarySmsLoginActivity.java
@@ -0,0 +1,719 @@
+package com.rehome.zhdcoa.ui.activity;
+
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.sangfor.sdk.SFUemSDK;
+import com.sangfor.sdk.base.SFAuthResultListener;
+import com.sangfor.sdk.base.SFAuthType;
+import com.sangfor.sdk.base.SFBaseMessage;
+import com.sangfor.sdk.base.SFChangePswMessage;
+import com.sangfor.sdk.base.SFConstants;
+import com.sangfor.sdk.base.SFRegetRandCodeListener;
+import com.sangfor.sdk.base.SFRegetSmsListener;
+import com.sangfor.sdk.base.SFSetSpaConfigListener;
+import com.sangfor.sdk.base.SFSmsMessage;
+import com.sangfor.sdk.utils.SFLogN;
+
+import com.rehome.zhdcoa.R;
+import com.rehome.zhdcoa.vpn.CertUtils;
+import com.rehome.zhdcoa.vpn.Constants;
+import com.rehome.zhdcoa.vpn.PermissionUtil;
+import com.rehome.zhdcoa.vpn.SFDialogHelper;
+
+import java.io.ByteArrayInputStream;
+import java.security.MessageDigest;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class PrimarySmsLoginActivity extends AppCompatActivity implements SFAuthResultListener, SFRegetSmsListener,
+ SFRegetRandCodeListener {
+ private static final String TAG = "app";
+ private static final int PERMISSON_REQUEST_CODE = 32; //动态权限请求码
+ private static final int CERTFILE_REQUEST_CODE = 33; //当证书认证是主认证时获取证书路径请求码
+ private static final int DIALOG_CERTFILE_REQUEST_CODE = 34; //当证书认证是辅助认证时获取证书路径请求码
+ private static final int DEFAULT_SMS_COUN_TDOWN_TIME = 30; //短信验证码默认倒计时时间, 以秒为单位
+
+ private String mVpnAddress = "https://vpn.zhp.geg.com.cn:55420";
+
+// enum SERVER_MODE {
+// VPN,
+// SDP
+// }
+
+ //private SERVER_MODE mServerMode = SERVER_MODE.SDP;
+
+ //private String mSdpAddress = "https://10.242.4.235:443";
+ private String mPhoneNumber = "15307858521";
+
+ //View
+ private AlertDialog mAuthDialog = null;
+ private EditText mVpnAddressEditText = null;
+ private EditText mPhoneNumberEditView = null;
+ private Button mLoginButton = null;
+
+ private View mRandCodeDialogView = null; //图形校验码对话框视图
+ private View mSmsCodeDialogView = null; //短信验证码对话框视图
+ private EditText mCertPathDialogEditView = null;//证书路径输入框
+ private ProgressDialog mProgressDialog = null; // 对话框对象
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_vpn_auth);
+ //加载UI
+ initView();
+ //SPA初始化,设置安全码
+ String config = "{\"loginAddress\":\"https://vpn.zhp.geg.com.cn:55420\", \"spaSecret\":\"7FFQ-rWE5-V6xX\"}";
+ SFUemSDK.setSpaConfig(config, new SFSetSpaConfigListener() {
+ @Override
+ public void onSetSpaConfig(String result, SFBaseMessage error) {
+ SFLogN.info(TAG, "spa result:"+ result + ", error:" + error);
+ Log.i(TAG, "spa result:"+ result + ", error:" + error);
+ if (error.mErrCode != 0) {
+ Toast.makeText(PrimarySmsLoginActivity.this,
+ "SPA设置失败"+ ", Error Message:" + error.toString(),
+ Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(PrimarySmsLoginActivity.this, "SPA设置成功, result:" + result,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ });
+
+// SFUemSDK.setSpaConfig(config, new SFSetSpaConfigListener() {
+// @Override
+// public void onSetSpaConfig(String result, SFBaseMessage error) {
+// Log.i(TAG, "spa result:"+ result + ", error:" + error);
+// if (error.mErrCode != 0) {
+// Toast.makeText(PrimarySmsLoginActivity.this,
+// "SPA设置失败"+ ", Error Message:" + error.toString(),
+// Toast.LENGTH_SHORT)
+// .show();
+// } else {
+// Toast.makeText(PrimarySmsLoginActivity.this, "SPA设置成功, result:" + result,
+// Toast.LENGTH_SHORT)
+// .show();
+//
+// }
+// }
+// });
+
+
+ //加载上次登录信息
+ setLoginInfo();
+ /**
+ * 设置认证回调,认证结果在SFAuthResultListener的onAuthSuccess、onAuthFailed、onAuthProgress中返回
+ * 如果不设置,将接收不到认证结果
+ */
+ SFUemSDK.getInstance().setAuthResultListener(this);
+
+ if (SFUemSDK.getInstance().startAutoTicket()){
+ showToast("免密成功");
+ //startActivity(new Intent(PrimarySmsLoginActivity.this, AuthSuccessActivity.class));
+ }
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ //取消认证回调
+ SFLogN.info(TAG,"SFUemSDK setAuthResultListener ");
+ SFUemSDK.getInstance().setAuthResultListener(null);
+ }
+
+ /**
+ * 初始化界面元素
+ */
+ private void initView() {
+ mVpnAddressEditText = findViewById(R.id.vpn_addr_editView);
+ mPhoneNumberEditView = findViewById(R.id.svpn_username_editView);
+ //登录按钮
+ mLoginButton = findViewById(R.id.svpn_login_button);
+ mLoginButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ //开始主认证,用户名密码或者证书认证,在认证回调中返回认证结果
+ startPrimaryAuth();
+ }
+ });
+ }
+
+ /**
+ * 开始主认证,用户名密码或者证书认证
+ */
+ private void startPrimaryAuth() {
+ mVpnAddress = mVpnAddressEditText.getText().toString();
+ mPhoneNumber = mPhoneNumberEditView.getText().toString();
+
+ if (TextUtils.isEmpty(mVpnAddress)) {
+ showToast("VPN服务器地址不能为空");
+ return;
+ }
+
+ if (TextUtils.isEmpty(mPhoneNumber)) {
+ showToast("手机号码不能为空");
+ return;
+ }
+
+ showWaitingDialog(false);
+ SFUemSDK.getInstance().startPrimarySmsAuth(mVpnAddress, mPhoneNumber);
+ }
+
+ /**
+ * 认证成功
+ *
+ * @param message 认证成功信息
+ */
+ @Override
+ public void onAuthSuccess(final SFBaseMessage message) {
+ SFLogN.info(TAG, "auth success");
+ dismissWaitingDialog();
+
+ saveLoginInfo(); //保存登录信息
+
+ //认证完成,跳转认证成功界面,可以开始访问资源
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showToast(getString(R.string.auth_success));
+ //startActivity(new Intent(PrimarySmsLoginActivity.this, AuthSuccessActivity.class));
+ }
+ });
+ }
+
+ /**
+ * 认证失败
+ *
+ * @param message 认证失败信息
+ */
+ @Override
+ public void onAuthFailed(final SFBaseMessage message) {
+ SFLogN.error2(TAG, "auth failed", "errMsg: " + message.mErrStr);
+ dismissWaitingDialog();
+
+ //认证失败
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showErrorMessage(message.mErrStr);
+ }
+ });
+ }
+
+ /**
+ * 主认证成功,但需要辅助认证(下一步认证)
+ *
+ * @param nextAuthType 下一步认证类型
+ * @param message 下一步认证信息
+ */
+ @Override
+ public void onAuthProgress(SFAuthType nextAuthType, SFBaseMessage message) {
+ SFLogN.info(TAG, "need next auth, authType: " + nextAuthType.name());
+ dismissWaitingDialog();
+ //显示下一步认证UI界面
+ showAuthDialog(nextAuthType, message);
+ }
+
+ /**
+ * 显示下一步认证UI界面
+ */
+ public void showAuthDialog(final SFAuthType authType, final SFBaseMessage message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ closeDialog();
+ //获取认证类型对应的标题
+ String dialogTitle = SFDialogHelper.getDialogTitle(authType);
+ if (TextUtils.isEmpty(dialogTitle)) {
+ showErrorMessage("暂不支持此种认证类型(" + authType.toString() + ")");
+ return;
+ }
+ //获取认证类型对应的layout布局
+ int viewLayoutId = SFDialogHelper.getAuthDialogViewId(authType);
+ //创建认证类型对应的对话框显示视图
+ final View dialogView = createDialogView(authType, viewLayoutId, message);
+ mAuthDialog = new AlertDialog.Builder(PrimarySmsLoginActivity.this)
+ .setTitle(dialogTitle)
+ .setView(dialogView)
+ .setCancelable(false)
+ .setPositiveButton(R.string.str_commit, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ closeDialog();
+ //构建辅助认证类型所需参数
+ Map authParams = new HashMap();
+ String errorMsg = buildAuthParams(authType, dialogView, authParams);
+ //检查参数构建是否成功
+ if (TextUtils.isEmpty(errorMsg)) {
+ showWaitingDialog(false);
+ //开始辅助认证
+ SFUemSDK.getInstance().doSecondaryAuth(authType, authParams);
+ } else {
+ //参数构建失败,进行提示
+ showErrorMessage(errorMsg);
+ }
+ return;
+ }
+ })
+ .setNegativeButton(R.string.str_cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //放弃继续认证
+ closeDialog();
+ return;
+ }
+ })
+ .create();
+ //显示认证对话框
+ mAuthDialog.show();
+ }
+ });
+ }
+
+ /**
+ * 创建认证对话框中间显示的视图
+ *
+ * @param sfAuthType 认证类型
+ * @param layoutId 要加载的视图的布局ID
+ * @param message 认证附加信息
+ * @return 认证对话框视图
+ */
+ private View createDialogView(SFAuthType sfAuthType, int layoutId, final SFBaseMessage message) {
+ LayoutInflater inflater = getLayoutInflater();
+ View dialogView = inflater.inflate(layoutId, null);
+ switch (sfAuthType) {
+ case AUTH_TYPE_SMS:
+ case AUTH_TYPE_PRIMARY_SMS: {
+ mSmsCodeDialogView = dialogView;
+ TextView tvTel = dialogView.findViewById(R.id.tv_tel);
+ Button btnGetVerficationCode = dialogView.findViewById(R.id.bt_getVerficationCode);
+ btnGetVerficationCode.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ v.setEnabled(false);
+ //调用重新获取短信接口
+ SFUemSDK.getInstance().getSFAuth().regetSmsCode(PrimarySmsLoginActivity.this);
+ }
+ });
+
+ SFSmsMessage smsMessage = ((SFSmsMessage) message);
+ //获取手机号码
+ String smsPhoneNum = smsMessage.getPhoneNum();
+ if (TextUtils.isEmpty(smsPhoneNum)) {
+ tvTel.setText(R.string.get_phone_number_failed);
+ } else {
+ tvTel.setText(getString(R.string.phone_number) + smsPhoneNum);
+ }
+ //上次发送的短信验证码是否在有效期
+ if (smsMessage.isStillValid()) {
+ showToast("上次发送的短信验证码仍在有效期");
+ }
+ //开启短信码倒计时
+ smsCountDownTimer(btnGetVerficationCode, smsMessage.getCountDown());
+ break;
+ }
+ case AUTH_TYPE_RENEW_PASSWORD: {
+ TextView tvPolicy = (TextView) dialogView.findViewById(R.id.tv_policy);
+ String policy = "";
+ //获取密码策略
+ if (message instanceof SFChangePswMessage) {
+ policy = ((SFChangePswMessage) message).policyMsg;
+ }
+ if (TextUtils.isEmpty(policy)) {
+ tvPolicy.setText(R.string.str_no_policy);
+ } else {
+ tvPolicy.setText(getString(R.string.str_policy_hint) + "\n" + policy);
+ }
+ break;
+ }
+ case AUTH_TYPE_RAND: {
+ mRandCodeDialogView = dialogView;// 保存一份图形校验码对话框视图
+ final ImageView imgError = dialogView.findViewById(R.id.randcode_imgError);
+ final ImageView imgRandCode = dialogView.findViewById(R.id.iv_graphCode);
+
+ // 按图片刷新校验码
+ View.OnClickListener refreshListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ long lastRefresh = (Long) imgRandCode.getTag();
+ //检查更新间隔,暂定间隔3秒
+ if (System.currentTimeMillis() - lastRefresh >= 3 * 1000) {
+ // 启动刷新任务
+ SFUemSDK.getInstance().getSFAuth().regetRandCode(PrimarySmsLoginActivity.this);
+ imgRandCode.setTag(System.currentTimeMillis());
+ }
+ }
+ };
+
+ imgError.setOnClickListener(refreshListener);
+ imgRandCode.setOnClickListener(refreshListener);
+
+ SFUemSDK.getInstance().getSFAuth().regetRandCode(PrimarySmsLoginActivity.this); //首次自动触发获取校验码
+ imgRandCode.setTag(System.currentTimeMillis());// 记录上次更新时间到imgChecksum控件的tag里,初始化为0,即没有上次更新
+ break;
+ }
+ default:
+ break;
+ }
+
+ return dialogView;
+ }
+
+ /**
+ * 短信验证码倒计时器
+ *
+ * @param button 显示计时的按钮控件
+ */
+ private void smsCountDownTimer(final Button button, final int countDown) {
+ int smsRefreshTime = countDown < 0 ? DEFAULT_SMS_COUN_TDOWN_TIME : countDown;
+ //开启短信验证码倒计时,第一个参数为倒计时时间(毫秒),第二个为时间间隔
+ new CountDownTimer(smsRefreshTime * 1000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ button.setText(millisUntilFinished / 1000 + getString(R.string.after_time_resend));
+ button.setTextColor(Color.parseColor("#708090"));
+ button.setEnabled(false);
+ }
+
+ @Override
+ public void onFinish() {
+ button.setText(R.string.str_resend);
+ button.setTextColor(Color.parseColor("#000000"));
+ button.setEnabled(true);
+ }
+ }.start();
+ }
+
+ /**
+ * 构建辅助认证所需参数
+ *
+ * @param sfAuthType 认证类型
+ * @param dialogView 认证视图
+ * @param authParams 保存认证参数
+ * @return 参数错误信息
+ */
+ private String buildAuthParams(SFAuthType sfAuthType, View dialogView, Map authParams) {
+ String errorMsg = "";
+ switch (sfAuthType) {
+ case AUTH_TYPE_SMS: {
+ EditText etSmsCode = dialogView.findViewById(R.id.et_verficationCode);
+ String smsCode = etSmsCode.getText().toString();
+
+ if (TextUtils.isEmpty(smsCode)) {
+ errorMsg = "短信验证码不能为空";
+ break;
+ }
+
+ authParams.put(SFConstants.AUTH_KEY_SMS, smsCode);
+ break;
+ }
+ case AUTH_TYPE_PRIMARY_SMS: {
+ EditText etSmsCode = dialogView.findViewById(R.id.et_verficationCode);
+ String smsCode = etSmsCode.getText().toString();
+
+ if (TextUtils.isEmpty(smsCode)) {
+ errorMsg = "短信验证码不能为空";
+ break;
+ }
+
+ authParams.put(SFConstants.AUTH_KEY_PRIMARYSMS_CODE, smsCode);
+ break;
+ }
+ case AUTH_TYPE_RADIUS: {
+ EditText etRadiusAnswer = dialogView.findViewById(R.id.et_authAnswer);
+ String radiusAnswer = etRadiusAnswer.getText().toString();
+
+ if (TextUtils.isEmpty(radiusAnswer)) {
+ errorMsg = "认证答案不能为空";
+ break;
+ }
+
+ authParams.put(SFConstants.AUTH_KEY_RADIUS_CODE, radiusAnswer);
+ break;
+ }
+ case AUTH_TYPE_TOKEN: {
+ EditText etDynamicToken = dialogView.findViewById(R.id.et_dynamicToken);
+ String dynamicToken = etDynamicToken.getText().toString();
+
+ if (TextUtils.isEmpty(dynamicToken)) {
+ errorMsg = "动态口令不能为空";
+ break;
+ }
+
+ authParams.put(SFConstants.AUTH_KEY_TOKEN, dynamicToken);
+ break;
+ }
+
+ case AUTH_TYPE_RENEW_PASSWORD: {
+ EditText etNewPwd = dialogView.findViewById(R.id.et_newpwd);
+ EditText etOldPwd = dialogView.findViewById(R.id.et_oldpwd);
+ EditText etReNewPwd = dialogView.findViewById(R.id.et_renewpwd);
+ String oldPwd = etOldPwd.getText().toString();
+ String newPwd = etNewPwd.getText().toString();
+ String reNewPwd = etReNewPwd.getText().toString();
+
+ if (TextUtils.isEmpty(newPwd) || TextUtils.isEmpty(reNewPwd)) {
+ errorMsg = "新密码不能为空";
+ break;
+ }
+
+ if (!newPwd.equals(reNewPwd)) {
+ errorMsg = "新密码与确认密码不一致";
+ break;
+ }
+
+ authParams.put(SFConstants.AUTH_KEY_RENEW_OLD_PASSWORD, oldPwd);
+ authParams.put(SFConstants.AUTH_KEY_REWNEW_NEW_PASSWORD, newPwd);
+ break;
+ }
+ default:
+ errorMsg = "暂不支持的认证类型";
+ break;
+ }
+
+ return errorMsg;
+ }
+
+ /**
+ * 关闭对话框
+ */
+ private void closeDialog() {
+// if (mAuthDialog != null && mAuthDialog.isShowing()) {
+// mAuthDialog.dismiss();
+// mAuthDialog = null;
+// }
+ }
+
+ /**
+ * @param success true: 成功, false: 失败
+ * @param bytes 图片二进制数据,success为true是有效
+ * @param byteLength 图片二进制数据长度,success为true是有效
+ * @brief SFRegetRandCodeListener获取图形校验码回调
+ */
+ @Override
+ public void onRegetRandCode(boolean success, byte[] bytes, int byteLength) {
+ Drawable randCode = null;
+ if (success) {
+ randCode = Drawable.createFromStream(new ByteArrayInputStream(bytes), "rand_code");
+ } else {
+ SFLogN.error2(TAG, "get RandCode failed", "maybe network is wrong");
+ }
+
+ final Drawable drawable = randCode;
+ //显示图形校验码
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mRandCodeDialogView == null) {
+ showToast("图形校验码对话框视图未加载");
+ return;
+ }
+
+ final ImageView imgError = mRandCodeDialogView.findViewById(R.id.randcode_imgError);
+ final ImageView imgRandCode = mRandCodeDialogView.findViewById(R.id.iv_graphCode);
+
+ imgError.setVisibility(drawable == null ? View.VISIBLE : View.GONE);
+ imgRandCode.setVisibility(drawable == null ? View.GONE : View.VISIBLE);
+
+ // 显示图形校验码
+ imgRandCode.setImageDrawable(drawable);
+ // 更新刷新时间
+ imgRandCode.setTag(System.currentTimeMillis());
+ }
+ });
+ }
+
+ /**
+ * 重新获取短信验证码回调
+ *
+ * @param success 是否成功获取,true表示成功,false表示失败
+ * @param message 包含短信相关信息
+ */
+ @Override
+ public void onRegetSmsCode(final boolean success, final SFSmsMessage message) {
+ SFLogN.info(TAG, "onRegetSmsCode result: " + success + ", msg: " + message.mErrStr);
+
+ //更新短信验证码信息
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mSmsCodeDialogView == null) {
+ showToast("短信验证码对话框视图未加载");
+ return;
+ }
+
+ final Button btnGetVerficationCode = mSmsCodeDialogView.findViewById(R.id.bt_getVerficationCode);
+ if (success) {
+ //更新倒计时
+ smsCountDownTimer(btnGetVerficationCode, message.getCountDown());
+ showToast(getString(R.string.reget_sms_code_success));
+ } else {
+ btnGetVerficationCode.setEnabled(true);
+ showToast(getString(R.string.reget_sms_code_failed) + message.mErrStr);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ //登录界面证书选择
+ case CERTFILE_REQUEST_CODE: {
+ //获取证书选择器结果
+ String certPath = "";
+ if (resultCode == Activity.RESULT_OK) {
+ certPath = CertUtils.fromUriGetRealPath(this, data.getData()).trim();
+ }
+ break;
+ }
+ case DIALOG_CERTFILE_REQUEST_CODE: {
+ //当证书认证是辅助认证时获取证书选择器结果
+ String certPathDialog = "";
+ if (resultCode == Activity.RESULT_OK) {
+ certPathDialog = CertUtils.fromUriGetRealPath(this, data.getData()).trim();
+ }
+ mCertPathDialogEditView.setText(certPathDialog);
+ break;
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ //显示“请稍候...”提示框
+ public void showWaitingDialog(final boolean isCancelable) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mProgressDialog == null || !mProgressDialog.isShowing()) {
+ mProgressDialog = new ProgressDialog(PrimarySmsLoginActivity.this);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setTitle("");
+ mProgressDialog.setMessage(PrimarySmsLoginActivity.this.getString(R.string.waiting));
+ mProgressDialog.setCancelable(isCancelable);
+ mProgressDialog.show();
+ }
+ }
+ });
+ }
+
+ //取消“请稍候...”提示框
+ public void dismissWaitingDialog() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mProgressDialog != null && mProgressDialog.isShowing()) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+ });
+ }
+
+ //显示错误提示对话框
+ public void showErrorMessage(final String errMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Dialog messageDialog = new AlertDialog.Builder(PrimarySmsLoginActivity.this)
+ .setTitle(PrimarySmsLoginActivity.this.getString(R.string.info))
+ .setMessage(errMsg)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .create();
+
+ messageDialog.show();
+ }
+ });
+ }
+
+ //封装Toast
+ public void showToast(final String text) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(PrimarySmsLoginActivity.this, text, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ /**
+ * 权限授权结果回调
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] paramArrayOfInt) {
+ super.onRequestPermissionsResult(requestCode, permissions, paramArrayOfInt);
+ if (requestCode == PERMISSON_REQUEST_CODE) {
+ //处理权限申请结果
+ // do nothing
+ }
+ }
+
+ /**
+ * 设置登录信息
+ */
+ private void setLoginInfo() {
+ SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREF_FILE_NAME, MODE_PRIVATE);
+ mVpnAddress = sharedPreferences.getString(Constants.KEY_VPN_ADDRESS, mVpnAddress);
+ mPhoneNumber = sharedPreferences.getString(Constants.KEY_PHONE_NUMBER, mPhoneNumber);
+
+ mVpnAddressEditText.setText(mVpnAddress);
+ mPhoneNumberEditView.setText(mPhoneNumber);
+ }
+
+ /**
+ * 恢复保存的登录信息
+ */
+ private void saveLoginInfo() {
+ SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREF_FILE_NAME, MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putString(Constants.KEY_VPN_ADDRESS, mVpnAddress);
+ //保存手机号码,真实场景请加密存储
+ editor.putString(Constants.KEY_PHONE_NUMBER, mPhoneNumber);
+ editor.apply();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rehome/zhdcoa/ui/activity/TrainInfoMoreActivity.kt b/app/src/main/java/com/rehome/zhdcoa/ui/activity/TrainInfoMoreActivity.kt
index 4cbb10d..ee33167 100644
--- a/app/src/main/java/com/rehome/zhdcoa/ui/activity/TrainInfoMoreActivity.kt
+++ b/app/src/main/java/com/rehome/zhdcoa/ui/activity/TrainInfoMoreActivity.kt
@@ -57,7 +57,7 @@ class TrainInfoMoreActivity : BaseActivityOaToolbarViewBinding = mutableListOf()
private val dialogDatas: MutableList = mutableListOf()
- private lateinit var launcherResult: ActivityResultLauncher
+ private var launcherResult: ActivityResultLauncher? = null
override fun getViewBinding() = ActivityTrainInfoMoreBinding.inflate(layoutInflater)
@@ -166,7 +166,7 @@ class TrainInfoMoreActivity : BaseActivityOaToolbarViewBinding
+// }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.activity_vpn_auth;
+ }
+
+ private void startAutoTicket() {
+ /**
+ * 这里是自动免密认证接口,返回true表示认证成功,此时用户就可以进行资源访问了,
+ * 如果返回false,表示当前不满足自动免密条件,需要用户主动调用用户名密码认证接口
+ */
+ if (SFUemSDK.getInstance().startAutoTicket()){
+ showToast("vpn免密登录认证成功");
+ //startActivity(new Intent(PrimaryAuthActivity.this, AuthSuccessActivity.class));
+
+ /**
+ * 免密场景, 启动L3VPN隧道,需要等待onTunnelStarted返回启动隧道成功才能开始访问资源
+ */
+ SFUemSDK.getInstance().getSFTunnel().startTunnel();
+ Intent intent = new Intent();
+ intent.putExtra("usernameVpn", mUserName);
+ intent.putExtra("pwdVpn", mUserPassword);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ }
+
+
+ @Override
+ protected void onResume() {
+ Log.i(TAG,"onResume... ");
+ super.onResume();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ Log.i(TAG, "onNewIntent...");
+ super.onNewIntent(intent);
+ }
+
+ // 初始化界面元素
+ @Override
+ public void initView() {
+ mServerAddressEditText = findViewById(R.id.vpn_addr_editView);
+ mUserNameEditView = findViewById(R.id.svpn_username_editView);
+ mUserPasswordEditView = findViewById(R.id.svpn_userPassword_editView);
+ //登录按钮
+ mLoginButton = findViewById(R.id.svpn_login_button);
+ mLoginButton.setOnClickListener(this);
+
+ initToolbar("VPN登录", "", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ }
+ });
+
+ //SPA初始化,设置安全码
+ String config = "{\"loginAddress\":\"https://vpn.zhp.geg.com.cn:55420\", \"spaSecret\":\"7FFQ-rWE5-V6xX\"}";
+ SFUemSDK.setSpaConfig(config, new SFSetSpaConfigListener() {
+ @Override
+ public void onSetSpaConfig(String result, SFBaseMessage error) {
+ SFLogN.info(TAG, "spa result:"+ result + ", error:" + error);
+ Log.i(TAG, "spa result:"+ result + ", error:" + error);
+ if (error.mErrCode != 0) {
+ Toast.makeText(VpnAuthActivity.this,
+ "SPA设置失败"+ ", Error Message:" + error.toString(),
+ Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(VpnAuthActivity.this, "SPA设置成功, result:" + result,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ });
+
+ //加载上次登录信息
+ setLoginInfo();
+
+ mUserNameEditView.setText("rehome5");
+ mUserPasswordEditView.setText("452131wW,./");
+
+
+ /**
+ * 设置认证回调,认证结果在SFAuthResultListener的onAuthSuccess、onAuthFailed、onAuthProgress中返回
+ * 如果不设置,将接收不到认证结果回调
+ */
+ SFUemSDK.getInstance().setAuthResultListener(this);
+
+ /**
+ * 设置隧道状态监听回调,启动,停止隧道的结果以及隧道状态变化会在SFTunnelStatusListener的onTunnelStarted、
+ * onTunnelStoped,onTunnelStatusChanged中回调通知
+ */
+ SFUemSDK.getInstance().getSFTunnel().registerTunnelStatusListener(this);
+
+ //免密认证
+ startAutoTicket();
+
+ }
+
+ @Override
+ public void initData() {
+
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ //只有走回收流程的时候的那种onPause,isFinishing才为true
+ if (isFinishing()) {
+ //取消认证回调
+ SFLogN.info(TAG,"SFUemSDK setAuthResultListener null");
+ /**
+ * 注意: 清除回调建议放到onPause()方法而不是onDestroy()中,
+ * 避免出现onDestroy()在onCreate()之后执行,onCreate注册的认证回调被onDestory清空的问题
+ */
+ SFUemSDK.getInstance().setAuthResultListener(null);
+ SFUemSDK.getInstance().getSFTunnel().unRegisterTunnelStatusListener(this);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if(v.getId()==R.id.svpn_login_button){
+ mServerAddress = mServerAddressEditText.getText().toString();
+ mUserName = mUserNameEditView.getText().toString();
+ mUserPassword = mUserPasswordEditView.getText().toString();
+
+ //开始主认证
+ startPrimaryAuth();
+ }
+// switch (v.getId()) {
+// case R.id.svpn_login_button: {
+// mServerAddress = mServerAddressEditText.getText().toString();
+// mUserName = mUserNameEditView.getText().toString();
+// mUserPassword = mUserPasswordEditView.getText().toString();
+//
+// //开始主认证
+// startPrimaryAuth();
+// break;
+// }
+// default:
+// break;
+// }
+ }
+
+ //开始用户名密码认证
+ private void startPrimaryAuth() {
+
+ if (TextUtils.isEmpty(mServerAddress)) {
+ showToast("VPN服务器地址不能为空");
+ return;
+ }
+
+ if (TextUtils.isEmpty(mUserName)) {
+ showToast("vpn帐号必填");
+ return;
+ }
+
+ if (TextUtils.isEmpty(mUserName)) {
+ showToast("vpn密码必填");
+ return;
+ }
+
+ showWaitingDialog(false);
+
+ /**
+ * 开始用户名密码认证,认证结果会在认证回调onAuthSuccess,onAuthFailed,onAuthProgress中返回
+ */
+ SFUemSDK.getInstance().startPasswordAuth(mServerAddress, mUserName, mUserPassword);
+ }
+
+ /**
+ * 认证成功
+ *
+ * @param message 认证成功信息
+ */
+ @Override
+ public void onAuthSuccess(final SFBaseMessage message) {
+ showLog("auth success");
+
+ //关闭进度框
+ dismissWaitingDialog();
+
+ saveLoginInfo(); //保存登录信息
+
+ /**
+ * 启动L3VPN隧道,需要等待onTunnelStarted返回启动隧道成功才能开始访问资源
+ */
+ SFUemSDK.getInstance().getSFTunnel().startTunnel();
+
+
+ showLog(String.valueOf(SFUemSDK.getInstance().getAuthStatus()));
+
+ //认证完成,跳转认证成功界面,可以开始访问资源
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showToast(getString(R.string.auth_success));
+ //startActivity(new Intent(PrimaryAuthActivity.this, AuthSuccessActivity.class));
+
+ Intent intent = new Intent();
+ intent.putExtra("usernameVpn", mUserName);
+ intent.putExtra("pwdVpn", mUserPassword);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ });
+ }
+
+ /**
+ * 认证失败
+ *
+ * @param message 认证失败信息
+ */
+ @Override
+ public void onAuthFailed(final SFBaseMessage message) {
+ SFLogN.error2(TAG, "auth failed", "errMsg: " + message.mErrStr);
+
+ //关闭进度框
+ dismissWaitingDialog();
+
+ //认证失败
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showErrorMessage(message.mErrStr);
+ }
+ });
+ }
+
+ /**
+ * 主认证成功,但需要辅助认证(下一步认证)
+ *
+ * @param nextAuthType 下一步认证类型
+ * @param message 下一步认证信息
+ */
+ @Override
+ public void onAuthProgress(SFAuthType nextAuthType, SFBaseMessage message) {
+ SFLogN.info(TAG, "need next auth, authType: " + nextAuthType.name());
+ dismissWaitingDialog();
+
+ /**
+ * 1. 二次认证回调onAuthProgress, 不仅仅是管理员配置了二次认证才会回调;
+ * 如果管理员启用了"首次登录强制修改密码", 用户首次认证时会回调onAuthProgress;
+ * 如果管理员设置了密码有效期, 在密码过期后, 重新登录的时候也会回调onAuthProgress.
+ *
+ * 2. 二次认证回调中建议对修改密码进行处理, 对其他不做支持的二次认证需要做一个兜底处理:
+ * 针对不支持的认证方式做一个提示, 避免出现管理员配置了二次认证后,
+ * 用户侧收到了二次认证回调, 认证流程未完成但是无任何感知.
+ */
+ if (nextAuthType == SFAuthType.AUTH_TYPE_RENEW_PASSWORD) {
+ //显示下一步认证UI界面
+ showAuthDialog(nextAuthType, message);
+ return;
+ }
+ if (nextAuthType == SFAuthType.AUTH_TYPE_SMS) {
+ //显示下一步认证UI界面
+ showAuthDialog(nextAuthType, message);
+ return;
+ }
+
+ showErrorMessage("暂不支持此种认证类型(" + nextAuthType.toString() + ")");
+ }
+
+
+ //显示“请稍候...”提示框
+ public void showWaitingDialog(final boolean isCancelable) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mProgressDialog == null || !mProgressDialog.isShowing()) {
+ mProgressDialog = new ProgressDialog(VpnAuthActivity.this);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setTitle("");
+ mProgressDialog.setMessage(VpnAuthActivity.this.getString(R.string.waiting));
+ mProgressDialog.setCancelable(isCancelable);
+ mProgressDialog.show();
+ }
+ }
+ });
+ }
+
+ //取消“请稍候...”提示框
+ public void dismissWaitingDialog() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mProgressDialog != null && mProgressDialog.isShowing()) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+ });
+ }
+
+ //显示错误提示对话框
+ public void showErrorMessage(final String errMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ AlertDialog messageDialog = new AlertDialog.Builder(VpnAuthActivity.this)
+ .setTitle(VpnAuthActivity.this.getString(R.string.info))
+ .setMessage(errMsg)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .create();
+
+ messageDialog.show();
+ //确定按钮
+ Button btnPos = messageDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ //取消按钮
+ Button btnNeg = messageDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
+ btnPos.setTextColor(Color.BLACK);
+ btnNeg.setTextColor(Color.BLACK);
+ }
+ });
+ }
+
+ //封装Toast
+ public void showToast(final String text) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(VpnAuthActivity.this, text, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+
+ //恢复登录信息,重启应用后记录原来的登陆信息
+ private void setLoginInfo() {
+// SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREF_FILE_NAME, MODE_PRIVATE);
+// mServerAddress = sharedPreferences.getString(Constants.KEY_VPN_ADDRESS, mServerAddress);
+// mUserName = sharedPreferences.getString(Constants.KEY_USER_NAME, mUserName);
+// mUserPassword = sharedPreferences.getString(Constants.KEY_USER_PASSWORD, mUserPassword);
+
+ Intent intent = getIntent();
+ mUserName = intent.getStringExtra("usernameVpn");
+ mUserPassword = intent.getStringExtra("pwdVpn");
+ mServerAddressEditText.setText(mServerAddress);
+
+ if(TextUtils.isEmpty(mUserName)){
+ mUserNameEditView.setText("");
+ }else{
+ mUserNameEditView.setText(mUserName);
+ }
+ if(TextUtils.isEmpty(mUserPassword)){
+ mUserPasswordEditView.setText("");
+ }else{
+ mUserPasswordEditView.setText(mUserPassword);
+ }
+ }
+
+ //更新保存的登录信息
+ private void saveLoginInfo() {
+ SharedPreferences sharedPreferences = getSharedPreferences(Constants.PREF_FILE_NAME, MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putString(Constants.KEY_VPN_ADDRESS, mServerAddress);
+ //保存用户名和密码,真实场景请加密存储
+ editor.putString(Constants.KEY_USER_NAME, mUserName);
+ editor.putString(Constants.KEY_USER_PASSWORD, mUserPassword);
+ editor.apply();
+ }
+
+ /**
+ * 显示修改密码UI
+ */
+ private void showAuthDialog(final SFAuthType authType, final SFBaseMessage message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ closeDialog();
+ //获取认证类型对应的标题
+ String dialogTitle = SFDialogHelper.getDialogTitle(authType);
+ if (TextUtils.isEmpty(dialogTitle)) {
+ showErrorMessage("暂不支持此种认证类型(" + authType.toString() + ")");
+ return;
+ }
+ //获取认证类型对应的layout布局
+ int viewLayoutId = SFDialogHelper.getAuthDialogViewId(authType);
+ handlerSecondAuth(dialogTitle, viewLayoutId, authType, message);
+ }
+ });
+ }
+
+ /**
+ * 关闭对话框
+ */
+ private void closeDialog() {
+ if (mAuthDialog != null && mAuthDialog.isShowing()) {
+ mAuthDialog.dismiss();
+ mAuthDialog = null;
+ }
+ }
+
+ /**
+ * 处理二次验证
+ * @param dialogTitle
+ * @param viewLayoutId
+ * @param authType
+ * @param message
+ */
+ private void handlerSecondAuth(String dialogTitle, int viewLayoutId, SFAuthType authType, SFBaseMessage message) {
+
+ //创建认证类型对应的对话框显示视图
+ final View dialogView = createDialogView(authType, viewLayoutId, message);
+ mAuthDialog = new AlertDialog.Builder(VpnAuthActivity.this)
+ .setTitle(dialogTitle)
+ .setView(dialogView)
+ .setCancelable(false)
+ .setPositiveButton(R.string.str_commit, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ closeDialog();
+ //构建辅助认证类型所需参数
+ Map authParams = new HashMap();
+ String errorMsg = buildAuthParams(authType, dialogView, authParams);
+ //检查参数构建是否成功
+ if (TextUtils.isEmpty(errorMsg)) {
+ showWaitingDialog(false);
+ //开始辅助认证
+ SFUemSDK.getInstance().doSecondaryAuth(authType, authParams);
+ } else {
+ //参数构建失败,进行提示
+ showErrorMessage(errorMsg);
+ }
+ return;
+ }
+ })
+ .setNegativeButton(R.string.str_cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //放弃继续认证
+ closeDialog();
+ return;
+ }
+ })
+ .create();
+ //显示认证对话框
+ mAuthDialog.show();
+
+ //确定按钮
+ Button btnPos = mAuthDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ //取消按钮
+ Button btnNeg = mAuthDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
+ btnPos.setTextColor(Color.BLACK);
+ btnNeg.setTextColor(Color.BLACK);
+ }
+
+ /**
+ * 创建认证对话框中间显示的视图
+ *
+ * @param sfAuthType 认证类型
+ * @param layoutId 要加载的视图的布局ID
+ * @param message 认证附加信息
+ * @return 认证对话框视图
+ */
+ private View createDialogView(SFAuthType sfAuthType, int layoutId, final SFBaseMessage message) {
+ LayoutInflater inflater = getLayoutInflater();
+ View dialogView = inflater.inflate(layoutId, null);
+ switch (sfAuthType) {
+ case AUTH_TYPE_RENEW_PASSWORD: {
+ TextView tvPolicy = (TextView) dialogView.findViewById(R.id.tv_policy);
+ String policy = "";
+ //获取密码策略
+ if (message instanceof SFChangePswMessage) {
+ policy = ((SFChangePswMessage) message).policyMsg;
+ }
+ if (TextUtils.isEmpty(policy)) {
+ tvPolicy.setText(R.string.str_no_policy);
+ } else {
+ tvPolicy.setText(getString(R.string.str_policy_hint) + "\n" + policy);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return dialogView;
+ }
+
+ /**
+ * 构建辅助认证所需参数
+ *
+ * @param sfAuthType 认证类型
+ * @param dialogView 认证视图
+ * @param authParams 保存认证参数
+ * @return 参数错误信息
+ */
+ private String buildAuthParams(SFAuthType sfAuthType, View dialogView, Map authParams) {
+ String errorMsg = "";
+ switch (sfAuthType) {
+ case AUTH_TYPE_RENEW_PASSWORD: {
+ EditText etNewPwd = dialogView.findViewById(R.id.et_newpwd);
+ EditText etOldPwd = dialogView.findViewById(R.id.et_oldpwd);
+ EditText etReNewPwd = dialogView.findViewById(R.id.et_renewpwd);
+ String oldPwd = etOldPwd.getText().toString();
+ String newPwd = etNewPwd.getText().toString();
+ String reNewPwd = etReNewPwd.getText().toString();
+
+ if (TextUtils.isEmpty(newPwd) || TextUtils.isEmpty(reNewPwd)) {
+ errorMsg = "新密码不能为空";
+ break;
+ }
+
+ if (!newPwd.equals(reNewPwd)) {
+ errorMsg = "新密码与确认密码不一致";
+ break;
+ }
+
+ authParams.put(SFConstants.AUTH_KEY_RENEW_OLD_PASSWORD, oldPwd);
+ authParams.put(SFConstants.AUTH_KEY_REWNEW_NEW_PASSWORD, newPwd);
+ break;
+ }
+ case AUTH_TYPE_SMS: {
+ EditText etSmsCode = dialogView.findViewById(R.id.et_verficationCode);
+ String smsCode = etSmsCode.getText().toString().trim();
+
+ if (TextUtils.isEmpty(smsCode)) {
+ errorMsg = "短信验证码不能为空";
+ break;
+ }
+
+ showLog("-------smsCode---------");
+ showLog(smsCode);
+ authParams.put(SFConstants.AUTH_KEY_SMS, smsCode);
+ break;
+ }
+ default:
+ errorMsg = "暂不支持的认证类型";
+ break;
+ }
+
+ return errorMsg;
+ }
+
+
+ /**
+ * 隧道状态变化监听
+ * @param curStatus 变化后的隧道状态
+ */
+ @Override
+ public void onTunnelStatusChanged(SFTunnelStatus curStatus) {
+ showLog("onTunnelStatusChanged called: curStatus: " + curStatus);
+ }
+
+ /**
+ * 隧道启动完成回调监听,这里需要关注返回的错误码,如果隧道启动失败会在msg里面返回错误码和失败原因
+ * @param msg
+ */
+ @Override
+ public void onTunnelStarted(SFBaseMessage msg) {
+ //隧道启动完成,跳转认证成功界面,可以开始访问资源
+ showLog( "onTunnelStarted, msg: " + msg.mErrStr + ",errCode: " + msg.mErrCode);
+
+ if (msg.mErrCode != 0) {
+ showLog("start Tunnel failed" + msg.mErrStr);
+ showLog( "L3VPN隧道启动失败: " + msg.mErrStr);
+ showToast(msg.mErrStr);
+ showToast("L3VPN隧道启动失败: " + msg.mErrStr);
+ return;
+ }
+ showLog( "startTunnel finished");
+ showLog( "L3VPN隧道启动成功");
+ showToast("L3VPN隧道启动成功");
+
+ }
+
+ /**
+ * 隧道停止回调
+ */
+ @Override
+ public void onTunnelStoped() {
+ showLog( "onTunnelStoped");
+ showLog( "隧道已经关闭");
+ showToast("隧道已经关闭");
+ }
+
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/ui/fragment/HomeFragment.kt b/app/src/main/java/com/rehome/zhdcoa/ui/fragment/HomeFragment.kt
index 137bbdf..5a2a661 100644
--- a/app/src/main/java/com/rehome/zhdcoa/ui/fragment/HomeFragment.kt
+++ b/app/src/main/java/com/rehome/zhdcoa/ui/fragment/HomeFragment.kt
@@ -91,6 +91,7 @@ import retrofit2.Call
import retrofit2.Response
import java.text.SimpleDateFormat
import java.util.Calendar
+import androidx.core.view.isVisible
/**
@@ -146,7 +147,10 @@ class HomeFragment : BaseViewBindingFragment() {
Handler(Looper.getMainLooper()).postDelayed({ // 在指定时间后要执行的代码
//隐藏掉 本平台为非涉密平台,严禁处理、传输国家秘密、工作秘密、商业秘密、敏感信息
//showLog("隐藏掉 本平台为非涉密平台,严禁处理、传输国家秘密、工作秘密、商业秘密、敏感信息")
- binding.llTip.visibility=View.GONE;
+ if(binding!=null){
+ binding.llTip.visibility=View.GONE;
+ }
+
}, 5000)
}
}
@@ -170,13 +174,15 @@ class HomeFragment : BaseViewBindingFragment() {
override fun onResume() {
super.onResume()
showLog("onResume")
- if (this.isAdded && this.isVisible&&binding.llTip.visibility==View.VISIBLE) {
+ if (this.isAdded && this.isVisible) {
showLog("this.isAdded && this.isVisible&&binding.llTip.visibility==View.VISIBLE")
// Fragment存在且可见,可以安全访问
Handler(Looper.getMainLooper()).postDelayed({ // 在指定时间后要执行的代码
//隐藏掉 本平台为非涉密平台,严禁处理、传输国家秘密、工作秘密、商业秘密、敏感信息
//showLog("隐藏掉 本平台为非涉密平台,严禁处理、传输国家秘密、工作秘密、商业秘密、敏感信息")
- binding.llTip.visibility=View.GONE;
+ if(binding!=null&&binding.llTip.isVisible){
+ binding.llTip.visibility=View.GONE
+ }
}, 5000)
}
}
diff --git a/app/src/main/java/com/rehome/zhdcoa/utils/DpPxSpTransformUtil.java b/app/src/main/java/com/rehome/zhdcoa/utils/DpPxSpTransformUtil.java
new file mode 100644
index 0000000..792fb9b
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/utils/DpPxSpTransformUtil.java
@@ -0,0 +1,47 @@
+package com.rehome.zhdcoa.utils;
+
+/**
+ * @ProjectName: SDKDemo
+ * @Package: com.sangfor.sdkdemo.utils
+ * @ClassName: DpPxSpTransformUtil
+ * @Description: java类作用描述
+ * @Author: yanghua
+ * @CreateDate: 2022/9/21 下午6:11
+ */
+public class DpPxSpTransformUtil {
+ private static float scale;
+ /**
+ * 初始化获得屏幕密度
+ * @return 屏幕密度
+ */
+ public static void init(float scale){
+ DpPxSpTransformUtil.scale=scale;
+ }
+ /**
+ * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
+ */
+ public static int dip2px(float dpValue) {
+ return (int)(dpValue * scale + 0.5f);
+ }
+ /**
+ * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
+ */
+ public static int px2dip(float pxValue) {
+ return (int)(pxValue / scale + 0.5f);
+ }
+ /**
+ * 将px值转换为sp值,保证文字大小不变
+ * @return
+ */
+ public static int px2sp(float pxValue) {
+ return (int) (pxValue / scale + 0.5f);
+ }
+
+ /**
+ * 将sp值转换为px值,保证文字大小不变
+ * @return
+ */
+ public static int sp2px(float spValue) {
+ return (int) (spValue * scale + 0.5f);
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/AppInfoUtils.java b/app/src/main/java/com/rehome/zhdcoa/vpn/AppInfoUtils.java
new file mode 100755
index 0000000..9bc3277
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/AppInfoUtils.java
@@ -0,0 +1,67 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.app.Activity;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * 获取app信息的公共类
+ */
+public class AppInfoUtils {
+
+ private static String TAG = "AppInfoUtils";
+
+ /**
+ * 获取当前应用名称
+ *
+ * @param activity
+ * @return
+ */
+ public static String getApplicationName(Activity activity) {
+ PackageManager packageManager;
+ ApplicationInfo applicationInfo;
+ try {
+ packageManager = activity.getApplicationContext().getPackageManager();
+ applicationInfo = packageManager.getApplicationInfo(activity.getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(TAG,"PackageManager.NameNotFoundException");
+ return "";
+ }
+ String applicationName = (String) packageManager.getApplicationLabel(applicationInfo);
+ if (TextUtils.isEmpty(applicationName)) {
+ Log.i(TAG,"applicationName is Empty");
+ return "";
+ } else {
+ return applicationName;
+ }
+ }
+
+ /**
+ * 获取当前应用名称
+ *
+ * @param activity
+ * @return
+ */
+ public static String getApplicationName(Activity activity, String packAgeName) {
+ PackageManager packageManager;
+ ApplicationInfo applicationInfo;
+ try {
+ packageManager = activity.getApplicationContext().getPackageManager();
+ applicationInfo = packageManager.getApplicationInfo(packAgeName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(TAG,"PackageManager.NameNotFoundException");
+ return "";
+ }
+
+ String applicationName = (String) packageManager.getApplicationLabel(applicationInfo);
+ if (TextUtils.isEmpty(applicationName)) {
+ Log.i(TAG,"applicationName is Empty");
+ return "";
+ } else {
+ return applicationName;
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/AssetFileUtil.java b/app/src/main/java/com/rehome/zhdcoa/vpn/AssetFileUtil.java
new file mode 100755
index 0000000..628e02b
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/AssetFileUtil.java
@@ -0,0 +1,47 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.content.Context;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class AssetFileUtil {
+
+ /**
+ * 获取assert 目录下某个文件内容
+ * @param context
+ * @param fileName
+ * @return
+ */
+ public static String getAssertFileContent(Context context, String fileName){
+ StringBuffer stringBuffer = new StringBuffer();
+
+ InputStream inputStream = null;
+ try {
+ inputStream = context.getAssets().open(fileName);
+ InputStreamReader inputreader = new InputStreamReader(inputStream);
+ BufferedReader buffreader = new BufferedReader(inputreader);
+ String line;
+ // 分行读取
+ while ((line = buffreader.readLine()) != null) {
+ stringBuffer.append(line);
+ }
+ buffreader.close();
+ inputreader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if(inputStream != null){
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return stringBuffer.toString();
+ }
+
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/AuthSessionDialog.java b/app/src/main/java/com/rehome/zhdcoa/vpn/AuthSessionDialog.java
new file mode 100755
index 0000000..00ac223
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/AuthSessionDialog.java
@@ -0,0 +1,89 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import com.rehome.zhdcoa.R;
+import com.sangfor.sdk.entry.SFLaunchInfo;
+
+
+
+/**
+ * 是否给子应用授权弹窗
+ */
+public class AuthSessionDialog {
+
+ private Activity mActivity;
+
+ private DialogClickListener mClickListener;
+
+ private AlertDialog mDialog;
+ private SFLaunchInfo mSFLaunchInfo;
+
+ public void setClickListener(DialogClickListener clickListener) {
+ mClickListener = clickListener;
+ }
+
+ public AuthSessionDialog(Activity activity, SFLaunchInfo launchInfo) {
+ mActivity = activity;
+ mSFLaunchInfo = launchInfo;
+ }
+
+ public void show() {
+ if (mDialog == null) {
+ String hostAppName = AppInfoUtils.getApplicationName(mActivity);
+ String subAppName = AppInfoUtils.getApplicationName(mActivity, mSFLaunchInfo.getPackageName());
+ String dialogMessage = String.format(mActivity.getString(R.string.request_session_can_you_agree), subAppName, hostAppName);
+
+ mDialog = new AlertDialog.Builder(mActivity)
+ .setMessage(dialogMessage)
+ .setCancelable(false)
+ .setPositiveButton(mActivity.getString(R.string.agree), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+
+ if (mClickListener != null) {
+ mClickListener.onPositiveClick(mSFLaunchInfo);
+ }
+ }
+ })
+ .setNegativeButton(mActivity.getString(R.string.disagree), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+
+ if (mClickListener != null) {
+ mClickListener.onNegativeClick(mSFLaunchInfo);
+ }
+ }
+ })
+ .create();
+ }
+
+ mDialog.show();
+ }
+
+ public void dismiss() {
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ }
+
+ public boolean isShowing() {
+ if (mDialog != null) {
+ return mDialog.isShowing();
+ }
+
+ return false;
+ }
+
+ public SFLaunchInfo getSFLaunchInfo() {
+ return mSFLaunchInfo;
+ }
+
+ public interface DialogClickListener {
+ void onPositiveClick(SFLaunchInfo launchInfo);
+ void onNegativeClick(SFLaunchInfo launchInfo);
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/CertUtils.java b/app/src/main/java/com/rehome/zhdcoa/vpn/CertUtils.java
new file mode 100755
index 0000000..4b63629
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/CertUtils.java
@@ -0,0 +1,212 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+
+import androidx.annotation.RequiresApi;
+
+import com.sangfor.sdk.utils.SFLogN;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by sangfor on 17-9-28.
+ */
+public class CertUtils {
+ private static final String TAG = "CertUtils";
+
+ /**
+ * 根据Uri的不同Scheme解析出在本机的路径
+ * @param context
+ * @param uri
+ * @return Uri的真实路径
+ */
+ @TargetApi(19)
+ public static String fromUriGetRealPath(Context context, Uri uri) {
+ if (uri == null) {
+ return "";
+ }
+
+ final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ // DocumentProvider
+ if (isKitKat && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && DocumentsContract.isDocumentUri(context, uri) ) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+
+ // TODO handle non-primary volumes
+ }
+ // DownloadsProvider
+ else if (isDownloadsDocument(uri)) {
+ final String id = DocumentsContract.getDocumentId(uri);
+ final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+ return getDataColumn(context, contentUri, null, null);
+ }
+ // MediaProvider
+ else if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[]{split[1]};
+
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ }
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return uriToFileApiQ(context, uri);
+ }
+ // MediaStore (and general)
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+
+ // Return the remote address
+ if (isGooglePhotosUri(uri))
+ return uri.getLastPathSegment();
+ SFLogN.info(TAG, "uri authority is "+uri.getAuthority());
+ if (isHuaWeiUri(uri)) {
+ String uriPath = uri.getPath();
+ // content://com.huawei.filemanager.share.fileprovider/root/storage/emulated/0/hbz1.p12
+ if (uriPath != null && uriPath.startsWith("/root")) {
+ return uriPath.replaceAll("/root", "");
+ }
+ }
+
+ return getDataColumn(context, uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return "";
+ }
+ /**
+ * Android 10 以上适配
+ * @param context
+ * @param uri
+ * @return
+ */
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ private static String uriToFileApiQ(Context context, Uri uri) {
+ File file = null;
+ //android10以上转换
+ if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
+ file = new File(uri.getPath());
+ } else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ //把文件复制到沙盒目录
+ ContentResolver contentResolver = context.getContentResolver();
+ Cursor cursor = contentResolver.query(uri, null, null, null, null);
+ if (cursor.moveToFirst()) {
+ @SuppressLint("Range")
+ String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+ try {
+ InputStream is = contentResolver.openInputStream(uri);
+ File file1 = new File(context.getExternalCacheDir().getAbsolutePath()+"/"+System.currentTimeMillis());
+ if (!file1.exists())
+ {
+ file1.mkdir();
+ }
+ File cache = new File(file1.getPath(), displayName);
+ FileOutputStream fos = new FileOutputStream(cache);
+ FileUtils.copy(is, fos);
+ file = cache;
+ fos.close();
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return file.getAbsolutePath();
+ }
+
+ public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
+
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {column};
+
+ try {
+ cursor = context.getContentResolver().query(uri, null, selection, selectionArgs, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return "";
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ */
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ */
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ */
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is Google Photos.
+ */
+ public static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+ /**
+ * 适配 华为手机
+ * @param uri
+ * @return
+ */
+ public static boolean isHuaWeiUri(Uri uri) {
+ return "com.huawei.filemanager.share.fileprovider".equals(uri.getAuthority());
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/Constants.java b/app/src/main/java/com/rehome/zhdcoa/vpn/Constants.java
new file mode 100755
index 0000000..936930e
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/Constants.java
@@ -0,0 +1,12 @@
+package com.rehome.zhdcoa.vpn;
+
+public class Constants {
+ public final static String PREF_FILE_NAME = "loginConfig";
+ public final static String KEY_VPN_ADDRESS = "vpnAddress";
+ public final static String KEY_USER_NAME = "userName";
+ public final static String KEY_USER_PASSWORD = "userPassword";
+ public final static String KEY_CERT_PATH = "certPath";
+ public final static String KEY_CERT_PASSWORD = "certPassword";
+ public final static String KEY_PHONE_NUMBER = "phoneNumber";
+ public final static String KEY_SUB_APP_PKG_NAME = "subAppPkgName";
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/GlobalListenerManager.java b/app/src/main/java/com/rehome/zhdcoa/vpn/GlobalListenerManager.java
new file mode 100644
index 0000000..7747529
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/GlobalListenerManager.java
@@ -0,0 +1,140 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Toast;
+import com.sangfor.sdk.SFUemSDK;
+import com.sangfor.sdk.base.SFAuthStatus;
+import com.sangfor.sdk.base.SFBaseMessage;
+import com.sangfor.sdk.base.SFLaunchReason;
+import com.sangfor.sdk.base.SFLogoutListener;
+import com.sangfor.sdk.base.SFLogoutType;
+import com.sangfor.sdk.entry.SFLaunchEntry;
+import com.sangfor.sdk.entry.SFLaunchInfo;
+import com.sangfor.sdk.utils.SFLogN;
+
+
+public class GlobalListenerManager {
+ private static final String TAG = "GlobalListenerManager";
+
+ private Context mAppContext;
+
+ private AuthSessionDialog mAuthSessionDialog = null;
+
+ public static GlobalListenerManager getInstance() {
+ return SingletonHolder.INSTANCE;
+ }
+
+ public void init(Context context) {
+ mAppContext = context.getApplicationContext() == null ? context : context.getApplicationContext();
+ addSFListener();
+ }
+
+ /**
+ * 增加sdk事件监听
+ */
+ private void addSFListener() {
+
+ /**
+ * 注销事件监听回调,推荐在Application里面监听, 避免出现认证开始了还未监听的问题
+ */
+ SFUemSDK.getInstance().registerLogoutListener(new SFLogoutListener() {
+ @Override
+ public void onLogout(SFLogoutType type, SFBaseMessage message) {
+ SFLogN.info(TAG, "onLogout, message: " + message);
+ /**
+ * 收到注销事件,demo的处理是跳转到认证前的入口页面
+ */
+// Intent intent = new Intent(mAppContext, EntryActivity.class);
+// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+// mAppContext.startActivity(intent);
+ }
+ });
+
+ /**
+ * 主从场景需要
+ * 子应用拉起事件监听回调,存在主从场景时才需要使用,推荐在Application里面监听,避免出现子应用拉起事件发生还未监听的问题
+ */
+ SFUemSDK.getInstance().getSFLaunch().setAppLaunchListener(new SFLaunchEntry.SFAppLaunchListener() {
+ @Override
+ public void onAppLaunched(SFLaunchInfo launchInfo) {
+ SFLogN.info(TAG, "setAppLaunchListener onAppLaunched, launchInfo is :" + launchInfo);
+
+ if (!SFLaunchEntry.isSubApp()) {
+
+ /**
+ * 只处子应用拉起获取登录凭据事件
+ */
+ if (launchInfo.getLaunchReason() == SFLaunchReason.Launch_HOSTAPP_AUTH_AUTHORIZATION) {
+
+ /**
+ * 如果主应用当前是未认证成功状态,无法进行登陆授权,主应用需要先进行认证
+ */
+ if (SFUemSDK.getInstance().getAuthStatus() != SFAuthStatus.AuthStatusAuthOk) {
+ SFLogN.info(TAG, "host app not auth ok, igore it");
+ Toast.makeText(mAppContext, "主应用尚未认证成功,请先认证", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ String subAppPkgName = launchInfo.getSubAppInfo().getSubAppPackageName();
+
+ if (!SFUemSDK.getInstance().getSFLaunch().checkAppAuthorized(launchInfo)) {
+ SFLogN.info(TAG, "sub app not Authorized, igore it");
+ Toast.makeText(mAppContext, "管理员未授权该应用", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ /**
+ * sdk内部会确保主应用进入前台后才回调onAppLaunched,且会在launchInfo中提供当前主应用前台的activity获取方式
+ * ,方便用户可以直接使用此activity做弹框交互
+ */
+ Activity activity = launchInfo.getForegroundActivity();
+
+ /**
+ * 这里弹框让用户确认是否要给子应用授权,如果需求上不需要做弹框让用户确认,也可以直接在此处调用
+ * launcherSubApp拉回子应用
+ */
+ showAuthSessionDialog(activity, launchInfo);
+
+ }
+ }
+ }
+ });
+ }
+
+ private void showAuthSessionDialog(Activity activity, SFLaunchInfo sfLaunchInfo) {
+ if (mAuthSessionDialog != null) {
+ mAuthSessionDialog.dismiss();
+ mAuthSessionDialog = null;
+ }
+
+ mAuthSessionDialog = new AuthSessionDialog(activity, sfLaunchInfo);
+ mAuthSessionDialog.setClickListener(new AuthSessionDialog.DialogClickListener() {
+ @Override
+ public void onPositiveClick(SFLaunchInfo launchInfo) {
+ SFLogN.info(TAG, "AuthSessionDialog positiveClick.");
+ /**
+ * 拉回到子应用进行授权
+ */
+ SubAppManager.getInstance().launcherSubApp(mAppContext, launchInfo);
+ }
+
+ @Override
+ public void onNegativeClick(SFLaunchInfo launchInfo) {
+ SFLogN.info(TAG, "AuthSessionDialog negativeClick.");
+ }
+ });
+
+ SFLogN.info(TAG, "showAuthSessionDialog.");
+ mAuthSessionDialog.show();
+ }
+
+ private GlobalListenerManager() {
+
+ }
+
+ private final static class SingletonHolder {
+ private static final GlobalListenerManager INSTANCE = new GlobalListenerManager();
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/PermissionUtil.java b/app/src/main/java/com/rehome/zhdcoa/vpn/PermissionUtil.java
new file mode 100755
index 0000000..a1c243d
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/PermissionUtil.java
@@ -0,0 +1,88 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.Settings;
+import android.widget.Toast;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.sangfor.sdk.utils.SFLogN;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PermissionUtil {
+ private static final String TAG = "PermissionUtil";
+
+ public static boolean isNeedRequestSDCardPermission(Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return false;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ // 先判断有没有权限
+ //context.checkSelfPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
+ if (!Environment.isExternalStorageManager()) {
+ return true;
+ }
+ } else {
+ if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ if (context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public static void requestSDCardPermissions(Activity activity, int requestCode) {
+ if (activity == null) {
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ //android 11
+ Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+ intent.setData(Uri.parse("package:" + activity.getPackageName()));
+ activity.startActivityForResult(intent, requestCode);
+ Toast.makeText(activity, "请授予所有文件管理权限~", Toast.LENGTH_SHORT).show();
+ } else {
+ List permissionsList = new ArrayList();
+ if (ContextCompat.checkSelfPermission(activity.getBaseContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ SFLogN.info(TAG,"No WRITE_EXTERNAL_STORAGE permission");
+ permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+ if (ContextCompat.checkSelfPermission(activity.getBaseContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ SFLogN.info(TAG,"No READ_EXTERNAL_STORAGE permission");
+ permissionsList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+ }
+ if (permissionsList.isEmpty()) {
+ return;
+ }
+ String[] permissions = permissionsList.toArray(new String[permissionsList.size()]);
+ ActivityCompat.requestPermissions(activity, permissions, requestCode);
+ }
+
+ }
+
+ /**
+ * 跳转至系统设置内的应用详情页面
+ * @param context
+ */
+ public static void gotoAppPermissionManageActivity(Context context) {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.parse("package:" + context.getPackageName()));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/SFDialogHelper.java b/app/src/main/java/com/rehome/zhdcoa/vpn/SFDialogHelper.java
new file mode 100755
index 0000000..e3a1ef7
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/SFDialogHelper.java
@@ -0,0 +1,68 @@
+package com.rehome.zhdcoa.vpn;
+
+import com.sangfor.sdk.SFMobileSecuritySDK;
+import com.sangfor.sdk.SFUemSDK;
+import com.sangfor.sdk.base.SFAuthType;
+import com.rehome.zhdcoa.R;
+
+public class SFDialogHelper {
+ /**
+ * 对话框标题
+ *
+ * @param authType 认证类型
+ * @return 对话框标题
+ */
+ public static String getDialogTitle(SFAuthType authType) {
+ switch (authType) {
+ case AUTH_TYPE_PASSWORD:
+ return "密码认证";
+ case AUTH_TYPE_CERTIFICATE:
+ return "证书认证";
+ case AUTH_TYPE_SMS:
+ case AUTH_TYPE_PRIMARY_SMS:
+ return "短信认证";
+ case AUTH_TYPE_RADIUS:
+ return "挑战认证";
+ case AUTH_TYPE_TOKEN:
+ case AUTH_TYPE_TOKEN_RADIUS:
+ case AUTH_TYPE_TOKEN_TOTP:
+ case AUTH_TYPE_TOKEN_HTTPS:
+ return "令牌认证";
+ case AUTH_TYPE_RAND:
+ return "图形校验码";
+ case AUTH_TYPE_RENEW_PASSWORD:
+ return "修改密码";
+ default:
+ return "";
+ }
+ }
+
+ public static int getAuthDialogViewId(SFAuthType authType) {
+ switch (authType) {
+ case AUTH_TYPE_PASSWORD:
+ return R.layout.dialog_pwd;
+ case AUTH_TYPE_CERTIFICATE:
+ return R.layout.dialog_certificate;
+ case AUTH_TYPE_SMS:
+ case AUTH_TYPE_PRIMARY_SMS:
+ return R.layout.dialog_sms;
+ case AUTH_TYPE_RADIUS:
+ return R.layout.dialog_challenge;
+ case AUTH_TYPE_TOKEN:
+ case AUTH_TYPE_TOKEN_RADIUS:
+ case AUTH_TYPE_TOKEN_TOTP:
+ case AUTH_TYPE_TOKEN_HTTPS:
+ return R.layout.dialog_token;
+ case AUTH_TYPE_RAND:
+ if (SFUemSDK.getInstance().isSDPServce()) {
+ //sdp服务器,图形验证码返回当前layout
+ return R.layout.dialog_graph_check_sdp;
+ }
+ return R.layout.dialog_graph_check;
+ case AUTH_TYPE_RENEW_PASSWORD:
+ return R.layout.dialog_force_update_pwd;
+ default:
+ return -1;
+ }
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/SignatureUtils.java b/app/src/main/java/com/rehome/zhdcoa/vpn/SignatureUtils.java
new file mode 100755
index 0000000..4e118ac
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/SignatureUtils.java
@@ -0,0 +1,74 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.text.TextUtils;
+
+import com.sangfor.sdk.utils.SFLogN;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SignatureUtils {
+ private static final String TAG = "SignatureUtils";
+
+ /**
+ * 获取应用中的公钥信息
+ * @param appInfo
+ * @return 应用中公钥的字符串形式
+ */
+ public static String getPubKeyInfo(ApplicationInfo appInfo, Context context) {
+ PackageManager mPackageManager = context.getPackageManager();
+ if (mPackageManager != null){
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(appInfo.packageName, mPackageManager.GET_SIGNATURES);
+ Signature[] signatures = packageInfo.signatures;
+ Signature signature = signatures[0];
+ byte[] signatureBytes = signature.toByteArray();
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(signatureBytes));
+ String pubKeyStr = cert.getPublicKey().toString();
+ return parsePubKey(pubKeyStr);
+ } catch (PackageManager.NameNotFoundException e) {
+ SFLogN.error(TAG, "failed to get packageInfo ", e);
+ } catch (CertificateException e) {
+ SFLogN.error(TAG, "failed to get cert", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 根据解析安装apk的信息得到公钥信息
+ * OpenSSLRSAPublicKey{modulus=98cfceafd123bc2d480af0bf31d41ca542736f8353031b01efad25dcef2dd566491635f23c0fbc950c93624dff4436c0910b0cdfe85bf6a86bcd7e42d790110ef4eec3eb8d3fb279734e71c6afd715f33712207101fa40ed0d196ad7c499bbc55d096de61f94117efaea996eed3a3a7c5bb875164bcb890439f6ccd2c0b2acb5,publicExponent=10001}";
+ * @param info
+ * @return
+ */
+ private static String parsePubKey(String info) {
+ if (TextUtils.isEmpty(info)) {
+ return null;
+ }
+ String indexStr = "modulus=";
+ int index = info.indexOf(indexStr);
+ if (index != -1) {
+ int startIndex = index + indexStr.length();
+ indexStr = info.substring(startIndex);
+ String pubkey = indexStr.split(",")[0];
+ //因为解析出来的公钥信息中可能含有空格,换行等干扰信息,因此需要对其进行去杂处理,方便后续的字符串比较
+ if (pubkey != null) {
+ Pattern pattern = Pattern.compile("\\s*|\t|\r|\n");
+ Matcher matcher = pattern.matcher(pubkey);
+ pubkey = matcher.replaceAll("");
+ }
+ return pubkey;
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/vpn/SubAppManager.java b/app/src/main/java/com/rehome/zhdcoa/vpn/SubAppManager.java
new file mode 100755
index 0000000..f13ba9d
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/vpn/SubAppManager.java
@@ -0,0 +1,69 @@
+package com.rehome.zhdcoa.vpn;
+
+import android.content.Context;
+
+import com.sangfor.sdk.SFSecuritySDKFactory;
+import com.sangfor.sdk.SFUemSDK;
+import com.sangfor.sdk.base.SFLaunchReason;
+import com.sangfor.sdk.entry.SFLaunchInfo;
+import com.sangfor.sdk.utils.SFLogN;
+
+/**
+ * 子应用管理类,保持子应用传递过来的信息,主要方便主应用拉起子应用
+ */
+public class SubAppManager {
+
+ private static final String TAG = "SubAppManager";
+
+ private SFLaunchInfo mSubAppLaunchInfo;
+ private static volatile SubAppManager sInstance;
+
+ private SubAppManager() {
+ }
+
+ public static SubAppManager getInstance() {
+ if (sInstance == null) {
+ synchronized (SubAppManager.class) {
+ if (sInstance == null) {
+ sInstance = new SubAppManager();
+ }
+ }
+ }
+
+ return sInstance;
+ }
+
+ public void setSubAppLaunchInfo(SFLaunchInfo subAppLaunchInfo) {
+ this.mSubAppLaunchInfo = subAppLaunchInfo;
+ }
+
+ public SFLaunchInfo getSubAppLaunchInfo() {
+ return mSubAppLaunchInfo;
+ }
+
+ /**
+ * 拉回子应用
+ * @param context
+ */
+ public void launcherSubApp(Context context, SFLaunchInfo launchInfo) {
+ if (context == null) {
+ SFLogN.error2(TAG, "cannot launcherSubApp", "context=null");
+ return;
+ }
+ SFLogN.info(TAG, "will launcherSubApp..." + launchInfo.getSubAppInfo().getSubAppPackageName());
+ if (launchInfo != null) {
+ SFLogN.info(TAG, "will start sub app...");
+ SFUemSDK.getInstance().getSFLaunch().launchSubApp(context, launchInfo);
+ } else {
+ SFLogN.warn2(TAG, "can not start sub app...", "launchInfo is null");
+ }
+
+ // 子应用被拉起后,表示一个流程执行完成,需要清空数据(主应用被子应用拉起-->主应用拉回子应用)
+ clearCache();
+ }
+
+ public void clearCache() {
+ this.mSubAppLaunchInfo = null;
+ }
+
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/weiget/RandCodeCircleView.java b/app/src/main/java/com/rehome/zhdcoa/weiget/RandCodeCircleView.java
new file mode 100644
index 0000000..249ec34
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/weiget/RandCodeCircleView.java
@@ -0,0 +1,53 @@
+package com.rehome.zhdcoa.weiget;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.rehome.zhdcoa.R;
+
+/**
+ * SDP图形验证码点击圆圈自定义view
+ * author yanghua
+ * date 20220915
+ */
+public class RandCodeCircleView extends RelativeLayout {
+
+ private Context context;
+ private View view;
+ private SangforTextView tvNum;
+ private RandCodeLayout.ClickPoint mPoint;
+
+ public RandCodeCircleView(Context context) {
+ super(context);
+
+ }
+
+ public RandCodeCircleView(Context context, RandCodeLayout.ClickPoint point) {
+ super(context);
+ this.context = context;
+ this.mPoint = point;
+ initViews();
+ }
+
+ /**
+ * 初始化视图
+ **/
+ protected void initViews() {
+ view = LayoutInflater.from(context).inflate(R.layout.randcode_cricle_view, this, true);
+ tvNum = view.findViewById(R.id.tvNum);
+ tvNum.setText(mPoint.getNum() + "");
+ }
+
+ public int getNum() {
+ return mPoint.getNum();
+ }
+
+ public void setPoint(RandCodeLayout.ClickPoint point) {
+ this.mPoint = point;
+ tvNum.setText(mPoint.getNum() + "");
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rehome/zhdcoa/weiget/RandCodeLayout.java b/app/src/main/java/com/rehome/zhdcoa/weiget/RandCodeLayout.java
new file mode 100644
index 0000000..ed3e578
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/weiget/RandCodeLayout.java
@@ -0,0 +1,358 @@
+package com.rehome.zhdcoa.weiget;
+
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import com.sangfor.sdk.base.SFAuthType;
+import com.sangfor.sdk.utils.SFLogN;
+
+import java.util.ArrayList;
+
+
+/**
+ * SDP图形验证码容器类自定义控件
+ * author yanghua
+ * date 20220915
+ */
+@SuppressLint("NewApi")
+public class RandCodeLayout extends RelativeLayout implements OnTouchListener {
+
+ private final String TAG = "RandCodeLayout";
+ private final int LIMT_NUM = 3;
+ private Context mContext;
+ private RandCodeCircleView mTouchView;
+ private int mStartX;
+ private int mStartY;
+ private int mChildHeight;
+ private int mChildWidth;
+ private int mlayoutWith;
+ private int mlayoutHeight;
+ private String user_position;
+ private ArrayList mPoints;
+ private OnFinishClickListener mFinishClickCallback;
+ private boolean isCheckCodeing = false;
+
+ public void setOnFinishClickListener(OnFinishClickListener callback) {
+ this.mFinishClickCallback = callback;
+ }
+
+ public RandCodeLayout(Context context) {
+ super(context, null);
+ }
+
+ public RandCodeLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ this.mContext = context;
+ init();
+ }
+
+ private void init() {
+ this.setOnTouchListener(this);
+ mPoints = new ArrayList();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ SFLogN.info(TAG, "onSizeChanged:" + w + "/" + h);
+ //缓存原来的大小
+ float cacheOriWith = mlayoutWith;
+ float cacheOriHeight = mlayoutHeight;
+ //保存新view大小
+ mlayoutWith = w;
+ mlayoutHeight = h;
+
+ ArrayList cachePoints = new ArrayList<>();
+ if (mPoints == null || mPoints.size() == 0) {
+ return;
+ }
+
+ //处理数据,计算新的布局,点的位置
+ int size = mPoints.size();
+ for (int i = 0; i < size; i++) {
+ ClickPoint clickPoint = mPoints.get(i);
+ int cacheX = (int) ((clickPoint.clickX/cacheOriWith) * w);
+ int cacheY = (int) ((clickPoint.clickY/cacheOriHeight) * h);
+ cachePoints.add(new ClickPoint(cacheX,cacheY, i+1));
+ }
+
+ //清除数据
+ clearView();
+
+ //重新添加数据
+ if (cachePoints.size() > 0) {
+ for (int i = 0; i < size; i++) {
+ ClickPoint cachePoint = cachePoints.get(i);
+ addPopup(cachePoint.clickX, cachePoint.clickY);
+ }
+ }
+ }
+
+ //开始的位置小于结束的位置 向左滑动
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ break;
+ case MotionEvent.ACTION_MOVE:
+ break;
+ case MotionEvent.ACTION_UP:
+ if (isCheckCodeing) {
+ SFLogN.info(TAG, "onTouch is checking" );
+ break;
+ }
+ mStartX = (int) event.getX();
+ mStartY = (int) event.getY();
+ if (!hasView(mStartX, mStartY) && isClickableRect(mStartX, mStartY)) {
+ //如果点击位置没有点击过,并且点击的位置在可点击的返回内
+ addPopup(mStartX, mStartY);
+ } else {
+ //点击的位置包含已经存在的view,则移除当前的view
+ removePopup();
+ }
+ break;
+
+ }
+ return true;
+ }
+
+ /**
+ * 删除标记
+ */
+ private void removePopup() {
+ if (mTouchView != null) {
+ int num = mTouchView.getNum();
+ removeAllViews();
+ mTouchView = null;
+ user_position = "";
+
+ //移除数据
+ for (int i = 0; i < mPoints.size(); i++) {
+ ClickPoint point = mPoints.get(i);
+ if (point.getNum() == num) {
+ mPoints.remove(i);
+ }
+ }
+
+ //更新数据
+ for (int i = 0; i < mPoints.size(); i++) {
+ mPoints.get(i).setNum(i + 1);
+ }
+
+ //更新UI
+ for (int i = 0; i < mPoints.size(); i++) {
+ this.addView(mPoints.get(i).getRandCdoeView());
+ }
+ }
+
+ }
+
+ /**
+ * 添加标记
+ */
+ public void addPopup(int x, int y) {
+ SFLogN.info(TAG, "cricle num:" + mPoints.size());
+ if (mPoints.size() < LIMT_NUM) {
+ addData(x, y);
+ }
+ }
+
+ public void addData(int clickX, int clickY) {
+ SFLogN.info(TAG, "click x:" + clickX + "/y:" + clickY);
+ if (mPoints != null) {
+ addItem(clickX, clickY);
+
+ if (mPoints.size() == LIMT_NUM && !isCheckCodeing) {
+ if (mFinishClickCallback != null) {
+ mFinishClickCallback.onFinishClick(getRondCode(), SFAuthType.AUTH_TYPE_RAND);
+ }
+ //记录当前正在校验code中
+ isCheckCodeing = true;
+ }
+ } else {
+ SFLogN.warn2(TAG, "addData is fail", "points list is null");
+ }
+ }
+
+ private void addItem(int x, int y) {
+ //添加元素
+ int num = mPoints.size() + 1;
+ ClickPoint point = new ClickPoint(x, y, num);
+
+ LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ //第一次点击添加标签是 PictureTagView.Direction.Measure 让TagView自己测量方向
+ RandCodeCircleView view = new RandCodeCircleView(getContext(), point);
+ view.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ int w = MeasureSpec.makeMeasureSpec(0,
+ MeasureSpec.UNSPECIFIED);
+ int h = MeasureSpec.makeMeasureSpec(0,
+ MeasureSpec.UNSPECIFIED);
+ view.measure(w, h);
+ mChildHeight = view.getMeasuredHeight();
+ mChildWidth = view.getMeasuredWidth();
+ //标签在右面 点击位置 x-标签宽度 右边的标签并不是以圆点开始的 而是以左边的wei
+ params.leftMargin = x - mChildWidth / 2;
+ params.topMargin = y - mChildHeight / 2;
+
+ //上下位置在视图内
+ if (params.topMargin <= 0) {
+ params.topMargin = 0;
+ } else if ((params.topMargin + mChildHeight) > getHeight()) {
+ params.topMargin = getHeight() - mChildHeight;
+ }
+ if (params.leftMargin <= 0) {
+ params.leftMargin = 0;
+ }
+ if (params.rightMargin >= getWidth()) {
+ params.rightMargin = getWidth();
+ }
+
+ point.setRandCdoeView(view);
+ mPoints.add(point);
+
+ this.addView(view, params);
+ }
+
+ private boolean isClickableRect(int clickX, int clickY) {
+ if (mlayoutHeight <= 0 || mlayoutWith <= 0) {
+ return false;
+ }
+
+ int clickHeight = (int) (mlayoutHeight - 32 * getResources().getDisplayMetrics().density);
+ Rect rect = new Rect(0, 0, mlayoutWith, clickHeight);
+ return rect.contains(clickX, clickY);
+ }
+
+ private boolean hasView(int x, int y) {
+ //循环获取子view,判断xy是否在子view上,即判断是否按住了子view
+ for (int index = 0; index < this.getChildCount(); index++) {
+ View view = this.getChildAt(index);
+ int left = (int) view.getX();
+ int top = (int) view.getY();
+ int right = view.getRight();
+ int bottom = view.getBottom();
+ Rect rect = new Rect(left, top, right, bottom);
+ boolean contains = rect.contains(x, y);
+ //如果是与子view重叠则返回真,表示已经有了view不需要添加新view了
+ if (contains) {
+ mTouchView = (RandCodeCircleView) view;
+ return true;
+ }
+
+ }
+
+ return false;
+ }
+
+ /**
+ * 获取座标转换为string
+ * 拼接的json格式:{"coordinates":[[125,196],[255,93],[555,246]],"width":816,"height":576}
+ *
+ * @return
+ */
+ private String getRondCode() {
+ for (int i = 0 ; i < mPoints.size(); i++) {
+ ClickPoint point = mPoints.get(i);
+ if (i == 0) {
+ user_position = "[" + point.getClickX() + "," + point.getClickY() + "]";
+ } else {
+ user_position = user_position + ",[" + point.getClickX() + "," + point.getClickY() + "]";
+ }
+ }
+
+ String json = "{\"coordinates\"" + ":" + "[" + user_position + "]," + "\"width\":" + mlayoutWith + ",\"height\":" + mlayoutHeight + "}";
+ return json;
+ }
+
+ /**
+ * 清楚数据
+ */
+ public void clearView() {
+ removeAllViews();
+ if (mPoints != null && mPoints.size() > 0) {
+ mPoints.clear();
+ }
+ }
+
+ /**
+ * 设置状态
+ * @param checkCodeing
+ */
+ public void setCheckCodeing(boolean checkCodeing) {
+ isCheckCodeing = checkCodeing;
+ }
+
+ /**
+ * 获取当前是否正在校验图形验证码
+ * @return
+ */
+ public boolean getCheckCodeing() {
+ return isCheckCodeing;
+ }
+
+
+ /**
+ * 点击完成的的回调接口
+ */
+ public interface OnFinishClickListener {
+ void onFinishClick(String authJson, SFAuthType authType);
+ }
+
+ /**
+ * 保存点击的数据
+ */
+ class ClickPoint {
+ private int clickX;
+ private int clickY;
+ private int num;
+ private RandCodeCircleView mRandCodeview;
+
+ public ClickPoint(int x, int y, int num) {
+ this.clickX = x;
+ this.clickY = y;
+ this.num = num;
+ }
+
+ public int getClickX() {
+ return clickX;
+ }
+
+ public void setClickX(int clickX) {
+ this.clickX = clickX;
+ }
+
+ public int getClickY() {
+ return clickY;
+ }
+
+ public void setClickY(int clickY) {
+ this.clickY = clickY;
+ }
+
+ public int getNum() {
+ return num;
+ }
+
+ public void setNum(int num) {
+ this.num = num;
+ mRandCodeview.setPoint(this);
+ }
+
+ public void setRandCdoeView(RandCodeCircleView view) {
+ this.mRandCodeview = view;
+ }
+
+ public RandCodeCircleView getRandCdoeView() {
+ return mRandCodeview;
+ }
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/weiget/SFEditText.java b/app/src/main/java/com/rehome/zhdcoa/weiget/SFEditText.java
new file mode 100755
index 0000000..bab49e7
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/weiget/SFEditText.java
@@ -0,0 +1,149 @@
+package com.rehome.zhdcoa.weiget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import androidx.appcompat.widget.AppCompatEditText;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+public class SFEditText extends AppCompatEditText implements TextWatcher {
+
+ private Drawable leftDrawable = null;
+ private Drawable rightDrawable = null;
+
+ // 控件是否有焦点
+ private boolean hasFocus;
+
+ private IMyRightDrawableClick mRightDrawableClick;
+
+ public SFEditText(Context context) {
+ super(context);
+ }
+
+ public SFEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public SFEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, android.R.attr.editTextStyle);
+ init();
+ }
+
+
+ private void init() {
+ //获取RadioButton的图片集合
+ Drawable[] drawables = getCompoundDrawables();
+
+ leftDrawable = drawables[0];
+ rightDrawable = drawables[2];
+
+ setCompoundDrawablesWithIntrinsicBounds(leftDrawable, null, null, null);
+
+ //设置输入框里面内容发生改变的监听
+ addTextChangedListener(this);
+ }
+
+
+ //设置显示图片的大小
+ public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom) {
+ super.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
+
+ //这里只要改后面两个参数就好了,一个宽一个是高,如果想知道为什么可以查找源码
+ if (left != null) {
+ left.setBounds(0, 0, 50, 50);
+ }
+ if (right != null) {
+ right.setBounds(0, 0, 50, 50);
+ }
+ if (top != null) {
+ top.setBounds(0, 0, 100, 100);
+ }
+ if (bottom != null) {
+ bottom.setBounds(0, 0, 100, 100);
+ }
+ setCompoundDrawables(left, top, right, bottom);
+ }
+
+
+ //光标选中时判断
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ this.hasFocus = focused;
+ if (focused)
+ setImageVisible(getText().length() > 0);
+ else
+ setImageVisible(false);
+
+ }
+
+ //设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
+ protected void setImageVisible(boolean flag) {
+ if (getCompoundDrawables()[2] != null) {
+ rightDrawable = getCompoundDrawables()[2];
+ }
+ if (flag) {
+ setCompoundDrawables(getCompoundDrawables()[0], null, rightDrawable, null);
+ } else {
+ setCompoundDrawables(getCompoundDrawables()[0], null, null, null);
+ }
+ }
+
+ //文本框监听事件
+ @Override
+ public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+
+ if (hasFocus) {
+ if (text.length() > 0)
+ setImageVisible(true);
+ else
+ setImageVisible(false);
+
+ }
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ public void afterTextChanged(Editable s) {
+
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (getCompoundDrawables()[2] != null) {
+
+ boolean touchable = event.getX() > (getWidth() - getTotalPaddingRight())
+ && (event.getX() < ((getWidth() - getPaddingRight())));
+ if (touchable) {
+ if (mRightDrawableClick != null) {
+ //调用点击事件
+ mRightDrawableClick.rightDrawableClick();
+ }
+ }
+ }
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ public void setDrawableClick(IMyRightDrawableClick mMightDrawableClick) {
+ this.mRightDrawableClick = mMightDrawableClick;
+ }
+
+ public void setRightDrawable(Drawable drawable) {
+ rightDrawable = drawable;
+ setCompoundDrawablesWithIntrinsicBounds(leftDrawable, null, rightDrawable, null);
+ }
+
+ //自定义接口(实现右边图片点击事件)
+ public interface IMyRightDrawableClick {
+ void rightDrawableClick();
+ }
+}
diff --git a/app/src/main/java/com/rehome/zhdcoa/weiget/SangforTextView.java b/app/src/main/java/com/rehome/zhdcoa/weiget/SangforTextView.java
new file mode 100644
index 0000000..cd2ff03
--- /dev/null
+++ b/app/src/main/java/com/rehome/zhdcoa/weiget/SangforTextView.java
@@ -0,0 +1,93 @@
+package com.rehome.zhdcoa.weiget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.AppCompatTextView;
+
+import com.rehome.zhdcoa.R;
+import com.rehome.zhdcoa.utils.DpPxSpTransformUtil;
+
+/**
+ * @ProjectName: SDKDemo
+ * @Package: com.sangfor.sdkdemo.utils
+ * @ClassName: SangforTextView
+ * @Description: java类作用描述
+ * @Author: yanghua
+ * @CreateDate: 2022/9/21 下午5:40
+ */
+public class SangforTextView extends View {
+
+ String mText;
+
+ public void setText(String text) {
+ mText = text;
+ invalidate();
+ }
+
+ //构造方法
+ public SangforTextView(@NonNull Context context) {
+ super(context);
+ }
+
+ public SangforTextView(@NonNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SangforTextView(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ //super.onDraw(canvas);
+ //画水平中心线和垂直中心线
+ //drawCenterLineX(canvas);
+ //drawCenterLineY(canvas);
+
+ //画底层黑色
+ drawTextCenter(canvas);
+
+ }
+
+ //将文字写到中央
+ private void drawTextCenter(Canvas canvas){
+ //创建画笔
+ Paint paint = new Paint();
+ paint.setTextSize(DpPxSpTransformUtil.sp2px(12));
+ paint.setColor(getResources().getColor(R.color.white));
+ //让他垂直居中
+ Paint.FontMetrics fontMetrics = paint.getFontMetrics();
+ float baseLine = getHeight()/2 -(fontMetrics.descent + fontMetrics.ascent)/2;
+ //让他水平居中
+ float x = getWidth()/2 - paint.measureText(mText)/2;
+
+ canvas.drawText(mText,x,baseLine,paint);
+ }
+
+ //画x轴的中心线
+ private void drawCenterLineX(final Canvas canvas){
+ Paint paint = new Paint();
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(Color.RED);
+ paint.setStrokeWidth(3);
+
+ canvas.drawLine(getWidth()/2,0,getWidth()/2,getHeight(),paint);
+ }
+
+ //画y轴的中心线
+ private void drawCenterLineY(final Canvas canvas){
+ Paint paint = new Paint();
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(Color.RED);
+ paint.setStrokeWidth(3);
+
+ canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,paint);
+ }
+}
diff --git a/app/src/main/res/drawable/auth_button_login.xml b/app/src/main/res/drawable/auth_button_login.xml
new file mode 100755
index 0000000..226a114
--- /dev/null
+++ b/app/src/main/res/drawable/auth_button_login.xml
@@ -0,0 +1,10 @@
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/auth_tab_header_text.xml b/app/src/main/res/drawable/auth_tab_header_text.xml
new file mode 100755
index 0000000..54692d7
--- /dev/null
+++ b/app/src/main/res/drawable/auth_tab_header_text.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/check_fail.png b/app/src/main/res/drawable/check_fail.png
new file mode 100644
index 0000000..4c328b3
Binary files /dev/null and b/app/src/main/res/drawable/check_fail.png differ
diff --git a/app/src/main/res/drawable/edit_bg.xml b/app/src/main/res/drawable/edit_bg.xml
new file mode 100755
index 0000000..b494d9f
--- /dev/null
+++ b/app/src/main/res/drawable/edit_bg.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/fresh_button.png b/app/src/main/res/drawable/fresh_button.png
new file mode 100644
index 0000000..9299eb6
Binary files /dev/null and b/app/src/main/res/drawable/fresh_button.png differ
diff --git a/app/src/main/res/drawable/randcode_back.png b/app/src/main/res/drawable/randcode_back.png
new file mode 100644
index 0000000..ca447b8
Binary files /dev/null and b/app/src/main/res/drawable/randcode_back.png differ
diff --git a/app/src/main/res/drawable/shape_check_fail.xml b/app/src/main/res/drawable/shape_check_fail.xml
new file mode 100644
index 0000000..e06abc0
--- /dev/null
+++ b/app/src/main/res/drawable/shape_check_fail.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_check_fl.xml b/app/src/main/res/drawable/shape_check_fl.xml
new file mode 100644
index 0000000..fd01dbd
--- /dev/null
+++ b/app/src/main/res/drawable/shape_check_fl.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_circle_select.xml b/app/src/main/res/drawable/shape_circle_select.xml
new file mode 100644
index 0000000..c613d91
--- /dev/null
+++ b/app/src/main/res/drawable/shape_circle_select.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/shape_corner.xml b/app/src/main/res/drawable/shape_corner.xml
new file mode 100755
index 0000000..a9575a0
--- /dev/null
+++ b/app/src/main/res/drawable/shape_corner.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_auth_success.xml b/app/src/main/res/layout/activity_auth_success.xml
new file mode 100644
index 0000000..85d761a
--- /dev/null
+++ b/app/src/main/res/layout/activity_auth_success.xml
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 89b20ff..6e8478f 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -83,6 +83,7 @@
+
+
+ android:text="已阅读并同意" />
+
+ android:text="服务协议"
+ android:textColor="@color/colorPrimaryDark" />
+
+ android:text="和" />
+
+ android:text="隐私保护"
+ android:textColor="@color/colorPrimaryDark" />
@@ -128,10 +133,10 @@
android:id="@+id/sw_remember_pwd"
android:layout_width="wrap_content"
android:layout_height="40dp"
+ android:checked="true"
android:shadowColor="@color/gray"
android:switchMinWidth="40dp"
android:switchPadding="10dp"
- android:checked="true"
android:text="@string/remember_me"
android:textSize="14sp"
android:thumb="@drawable/thumb"
@@ -161,9 +166,18 @@
style="@style/Widget.AppCompat.CompoundButton.RadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginEnd="10dp"
android:checked="true"
android:text="外网" />
+
+
@@ -175,19 +189,48 @@
android:gravity="center"
android:orientation="vertical">
+
+
+
+
+
+
@@ -202,10 +245,10 @@
android:id="@+id/text_wjmm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/colorPrimaryDark"
android:layout_weight="1"
android:gravity="center"
android:text="忘记密码"
+ android:textColor="@color/colorPrimaryDark"
android:textSize="16sp" />
@@ -220,6 +263,7 @@
android:text="版本号:2.0.6"
android:textSize="12sp"
android:textStyle="normal" />
+
+
+
diff --git a/app/src/main/res/layout/activity_primary_auth.xml b/app/src/main/res/layout/activity_primary_auth.xml
new file mode 100644
index 0000000..bb92770
--- /dev/null
+++ b/app/src/main/res/layout/activity_primary_auth.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_primary_sms_login.xml b/app/src/main/res/layout/activity_primary_sms_login.xml
new file mode 100644
index 0000000..4d5dde0
--- /dev/null
+++ b/app/src/main/res/layout/activity_primary_sms_login.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_vpn_auth.xml b/app/src/main/res/layout/activity_vpn_auth.xml
new file mode 100644
index 0000000..ec6350f
--- /dev/null
+++ b/app/src/main/res/layout/activity_vpn_auth.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/auth_layout.xml b/app/src/main/res/layout/auth_layout.xml
new file mode 100755
index 0000000..f58b2b2
--- /dev/null
+++ b/app/src/main/res/layout/auth_layout.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_certificate.xml b/app/src/main/res/layout/dialog_certificate.xml
new file mode 100755
index 0000000..5674d1a
--- /dev/null
+++ b/app/src/main/res/layout/dialog_certificate.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_challenge.xml b/app/src/main/res/layout/dialog_challenge.xml
new file mode 100755
index 0000000..850d8f7
--- /dev/null
+++ b/app/src/main/res/layout/dialog_challenge.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_force_update_pwd.xml b/app/src/main/res/layout/dialog_force_update_pwd.xml
new file mode 100755
index 0000000..1db1fca
--- /dev/null
+++ b/app/src/main/res/layout/dialog_force_update_pwd.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_graph_check.xml b/app/src/main/res/layout/dialog_graph_check.xml
new file mode 100755
index 0000000..22e9fde
--- /dev/null
+++ b/app/src/main/res/layout/dialog_graph_check.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_graph_check_sdp.xml b/app/src/main/res/layout/dialog_graph_check_sdp.xml
new file mode 100755
index 0000000..cabbe25
--- /dev/null
+++ b/app/src/main/res/layout/dialog_graph_check_sdp.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_pwd.xml b/app/src/main/res/layout/dialog_pwd.xml
new file mode 100755
index 0000000..3a78186
--- /dev/null
+++ b/app/src/main/res/layout/dialog_pwd.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_sms.xml b/app/src/main/res/layout/dialog_sms.xml
new file mode 100755
index 0000000..39e815d
--- /dev/null
+++ b/app/src/main/res/layout/dialog_sms.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_token.xml b/app/src/main/res/layout/dialog_token.xml
new file mode 100755
index 0000000..18090a1
--- /dev/null
+++ b/app/src/main/res/layout/dialog_token.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/randcode_cricle_view.xml b/app/src/main/res/layout/randcode_cricle_view.xml
new file mode 100644
index 0000000..6ecb5ae
--- /dev/null
+++ b/app/src/main/res/layout/randcode_cricle_view.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/simple_spinner_item.xml b/app/src/main/res/layout/simple_spinner_item.xml
new file mode 100644
index 0000000..be41c47
--- /dev/null
+++ b/app/src/main/res/layout/simple_spinner_item.xml
@@ -0,0 +1,27 @@
+
+
+
diff --git a/app/src/main/res/layout/sms_auth_layout.xml b/app/src/main/res/layout/sms_auth_layout.xml
new file mode 100755
index 0000000..5b3232a
--- /dev/null
+++ b/app/src/main/res/layout/sms_auth_layout.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-hdpi/icon_folder.png b/app/src/main/res/mipmap-hdpi/icon_folder.png
new file mode 100755
index 0000000..0f8a38b
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icon_folder.png differ
diff --git a/app/src/main/res/mipmap-hdpi/icon_psw.png b/app/src/main/res/mipmap-hdpi/icon_psw.png
new file mode 100755
index 0000000..07a3f60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icon_psw.png differ
diff --git a/app/src/main/res/mipmap-hdpi/icon_user.png b/app/src/main/res/mipmap-hdpi/icon_user.png
new file mode 100755
index 0000000..fe9c85f
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/icon_user.png differ
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 0e123f7..14eec3b 100755
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -36,6 +36,15 @@
- 新增信息
+
+ - https://www.test-ipv6.com
+ - https://www.baidu.com
+ - https://www.taobao.com
+ - https://www.qq.com
+ - https://www.sina.com.cn
+ - https://vpn.zhp.geg.com.cn
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 80422f7..846d6cc 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -76,5 +76,8 @@
#24B56C
#2479E4
+ #90505050
+ #90FF0000
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 07943a4..2df1164 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -54,5 +54,46 @@
签到
请扫描二维码进行签到
+ 同意
+ 拒绝
+ "'%s'申请获取'%s'的网关登录信息,是否同意?"
+ IP
+ 443
+ 短信主认证
+ 认证成功
+ 提交
+ 取消
+ 手机号:无法获取
+ 手机号:
+ 暂无相关密码策略
+ 密码策略:
+ 秒后重新发送
+ 重新发送
+ 秒后重新发送
+ 重新获取短信验证码成功
+ 重新获取短信验证码失败,失败信息:
+ 请稍候...
+ 提示
+ 确认
+ 确定
+ 账号登录
+ 注销vpn登录成功
+ 文件测试
+ 启动隧道
+ 关闭隧道
+ 主从测试
+ 拉起aTrust认证测试
+ 启动子应用
+ 请输入子应用包名
+ 使用应用自定义的intent
+ com.example.testapp
+ udp测试
+ 日志测试
+ 目录
+ 修改密码
+ 内网资源
+ 外网资源
+ 代理域名
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 07ad5f6..ac3878e 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -176,4 +176,20 @@
- ?attr/listChoiceIndicatorSingleAnimated
- ?attr/controlBackground
+
+