diff --git a/app/build.gradle b/app/build.gradle index 86ee00e2..cad6c524 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,170 +1,173 @@ -apply plugin: 'com.android.application' - -def keyProps = new Properties() -def keyPropsFile = rootProject.file('keystore/keystore.properties') -if (keyPropsFile.exists()) { - keyProps.load(new FileInputStream(keyPropsFile)) -} - -// 读取version.properties -def versionProps = new Properties() -def versionPropsFile = rootProject.file('version.properties') -if (versionPropsFile.exists()) { - versionProps.load(new FileInputStream(versionPropsFile)) -} - -android { - buildToolsVersion '30.0.3' - compileSdkVersion 30 - compileOptions { - sourceCompatibility 11 - targetCompatibility 11 - } - defaultConfig { - applicationId "com.idormy.sms.forwarder" - minSdkVersion 21 - targetSdkVersion 30 - versionCode versionProps['versionCode'].toInteger() - versionName versionProps['versionName'] - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - } - lintOptions { - checkReleaseBuilds false - } - signingConfigs { - release { - keyAlias keyProps['keyAlias'] - keyPassword keyProps['keyPassword'] - storeFile keyProps['storeFile'] ? file(keyProps['storeFile']) : null - storePassword keyProps['storePassword'] - } - debug { - keyAlias keyProps['keyAlias'] - keyPassword keyProps['keyPassword'] - storeFile keyProps['storeFile'] ? file(keyProps['storeFile']) : null - storePassword keyProps['storePassword'] - } - } - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.release - } - debug { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.debug - } - } - //apk file name - android.applicationVariants.all { variant -> - variant.outputs.all { - //def date = new Date().format("yyyyMMdd" , TimeZone.getTimeZone("Asia/Shanghai")) - def date = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08")) - if (variant.buildType.name == 'debug') { - outputFileName = "SmsForwarder_debug_${date}_${versionName}.apk" - } - if (variant.buildType.name == 'release') { - outputFileName = "SmsForwarder_release_${date}_${versionName}.apk" - } - } - } - packagingOptions { - exclude 'META-INF/DEPENDENCIES.txt' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/NOTICE.txt' - exclude 'META-INF/NOTICE' - exclude 'META-INF/LICENSE' - exclude 'META-INF/DEPENDENCIES' - exclude 'META-INF/notice.txt' - exclude 'META-INF/license.txt' - exclude 'META-INF/dependencies.txt' - exclude 'META-INF/LGPL2.1' - } -} - - -task upgradeVersion { - group 'help' - description '构建新版本' - doLast { - println("---自动升级版本号---\n") - String oldVersionCode = versionProps['versionCode'] - String oldVersionName = versionProps['versionName'] - if (oldVersionCode == null || oldVersionName == null || - oldVersionCode.isEmpty() || oldVersionName.isEmpty()) { - println("error:版本号不能为空") - return - } - versionProps['versionCode'] = String.valueOf(versionProps['versionCode'].toInteger() + 1) - String str = versionProps['versionName'].toString() - versionProps['versionName'] = str.substring(0, str.lastIndexOf('.') + 1) + - (str.substring(str.lastIndexOf('.') + 1).toInteger() + 1) - String tip = - "版本号从$oldVersionName($oldVersionCode)升级到${versionProps['versionName']}(${versionProps['versionCode']})" - println(tip) - - def writer = new FileWriter(versionPropsFile) - versionProps.store(writer, null) - writer.flush() - writer.close() - def tag = "v${versionProps['versionName']}" - cmdExecute("git pull") - cmdExecute("git add version.properties") - cmdExecute("git commit -m \"版本号升级为:$tag\"") - cmdExecute("git push origin") - cmdExecute("git tag $tag") - cmdExecute("git push origin $tag") - } -} - -void cmdExecute(String cmd) { - println "\n执行$cmd" - println cmd.execute().text -} - - -dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' - implementation 'com.google.firebase:firebase-crashlytics-buildtools:2.5.2' - testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - - //okhttp - implementation 'com.squareup.okhttp3:okhttp:4.9.1' - implementation 'com.squareup.okio:okio:2.10.0' - - //fastjson - implementation "com.alibaba:fastjson:1.2.78" - - //友盟统计SDK - implementation 'com.umeng.umsdk:common:9.4.4'// 必选 - implementation 'com.umeng.umsdk:asms:1.4.1'// 必选 - //implementation 'com.umeng.umsdk:apm:1.4.2' // 错误分析升级为独立SDK,看crash数据请一定集成,可选 - - //XUpdate - implementation 'com.github.xuexiangjys:XUpdate:2.1.1' - implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.1' - implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-downloader-aria:1.0.1' - - //EmailKit - implementation 'com.github.mailhu:emailkit:4.2.2' - - //Lombok - //noinspection AnnotationProcessorOnCompilePath - compileOnly 'org.projectlombok:lombok:1.18.20' - annotationProcessor 'org.projectlombok:lombok:1.18.20' - - //RxJava - implementation 'io.reactivex.rxjava3:rxjava:3.1.1' - - //AndroidAsync - implementation 'com.koushikdutta.async:androidasync:3.1.0' - - // 权限请求框架:https://github.com/getActivity/XXPermissions - implementation 'com.github.getActivity:XXPermissions:13.2' -} +apply plugin: 'com.android.application' + +def keyProps = new Properties() +def keyPropsFile = rootProject.file('keystore/keystore.properties') +if (keyPropsFile.exists()) { + keyProps.load(new FileInputStream(keyPropsFile)) +} + +// 读取version.properties +def versionProps = new Properties() +def versionPropsFile = rootProject.file('version.properties') +if (versionPropsFile.exists()) { + versionProps.load(new FileInputStream(versionPropsFile)) +} + +android { + buildToolsVersion '30.0.3' + compileSdkVersion 30 + compileOptions { + sourceCompatibility 11 + targetCompatibility 11 + } + defaultConfig { + applicationId "com.idormy.sms.forwarder" + minSdkVersion 21 + targetSdkVersion 30 + versionCode versionProps['versionCode'].toInteger() + versionName versionProps['versionName'] + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + lintOptions { + checkReleaseBuilds false + } + signingConfigs { + release { + keyAlias keyProps['keyAlias'] + keyPassword keyProps['keyPassword'] + storeFile keyProps['storeFile'] ? file(keyProps['storeFile']) : null + storePassword keyProps['storePassword'] + } + debug { + keyAlias keyProps['keyAlias'] + keyPassword keyProps['keyPassword'] + storeFile keyProps['storeFile'] ? file(keyProps['storeFile']) : null + storePassword keyProps['storePassword'] + } + } + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + } + debug { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug + } + } + //apk file name + android.applicationVariants.all { variant -> + variant.outputs.all { + //def date = new Date().format("yyyyMMdd" , TimeZone.getTimeZone("Asia/Shanghai")) + def date = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08")) + if (variant.buildType.name == 'debug') { + outputFileName = "SmsForwarder_debug_${date}_${versionName}.apk" + } + if (variant.buildType.name == 'release') { + outputFileName = "SmsForwarder_release_${date}_${versionName}.apk" + } + } + } + packagingOptions { + exclude 'META-INF/DEPENDENCIES.txt' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/LICENSE' + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/notice.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/dependencies.txt' + exclude 'META-INF/LGPL2.1' + } +} + + +task upgradeVersion { + group 'help' + description '构建新版本' + doLast { + println("---自动升级版本号---\n") + String oldVersionCode = versionProps['versionCode'] + String oldVersionName = versionProps['versionName'] + if (oldVersionCode == null || oldVersionName == null || + oldVersionCode.isEmpty() || oldVersionName.isEmpty()) { + println("error:版本号不能为空") + return + } + versionProps['versionCode'] = String.valueOf(versionProps['versionCode'].toInteger() + 1) + String str = versionProps['versionName'].toString() + versionProps['versionName'] = str.substring(0, str.lastIndexOf('.') + 1) + + (str.substring(str.lastIndexOf('.') + 1).toInteger() + 1) + String tip = + "版本号从$oldVersionName($oldVersionCode)升级到${versionProps['versionName']}(${versionProps['versionCode']})" + println(tip) + + def writer = new FileWriter(versionPropsFile) + versionProps.store(writer, null) + writer.flush() + writer.close() + def tag = "v${versionProps['versionName']}" + cmdExecute("git pull") + cmdExecute("git add version.properties") + cmdExecute("git commit -m \"版本号升级为:$tag\"") + cmdExecute("git push origin") + cmdExecute("git tag $tag") + cmdExecute("git push origin $tag") + } +} + +void cmdExecute(String cmd) { + println "\n执行$cmd" + println cmd.execute().text +} + + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'com.google.firebase:firebase-crashlytics-buildtools:2.5.2' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + //okhttp + implementation 'com.squareup.okhttp3:okhttp:4.9.1' + implementation 'com.squareup.okio:okio:2.10.0' + + //fastjson + implementation "com.alibaba:fastjson:1.2.78" + + //友盟统计SDK + implementation 'com.umeng.umsdk:common:9.4.4'// 必选 + implementation 'com.umeng.umsdk:asms:1.4.1'// 必选 + //implementation 'com.umeng.umsdk:apm:1.4.2' // 错误分析升级为独立SDK,看crash数据请一定集成,可选 + + //XUpdate + implementation 'com.github.xuexiangjys:XUpdate:2.1.1' + implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-easy:1.0.1' + implementation 'com.github.xuexiangjys.XUpdateAPI:xupdate-downloader-aria:1.0.1' + + //EmailKit + implementation 'com.github.mailhu:emailkit:4.2.2' + + //Lombok + //noinspection AnnotationProcessorOnCompilePath + compileOnly 'org.projectlombok:lombok:1.18.20' + annotationProcessor 'org.projectlombok:lombok:1.18.20' + + //RxJava + implementation 'io.reactivex.rxjava3:rxjava:3.1.1' + + //AndroidAsync + implementation 'com.koushikdutta.async:androidasync:3.1.0' + + // 权限请求框架:https://github.com/getActivity/XXPermissions + implementation 'com.github.getActivity:XXPermissions:13.2' + //jetty + implementation "org.eclipse.jetty:jetty-server:$jetty_version" + implementation "org.eclipse.jetty:jetty-servlet:$jetty_version" +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java b/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java index f586cd7b..6d2d2ac2 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java +++ b/app/src/main/java/com/idormy/sms/forwarder/CloneActivity.java @@ -12,11 +12,11 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import com.idormy.sms.forwarder.receiver.RebootBroadcastReceiver; +import com.idormy.sms.forwarder.sender.HttpServer; import com.idormy.sms.forwarder.utils.LogUtil; import com.idormy.sms.forwarder.utils.NetUtil; +import com.idormy.sms.forwarder.utils.SettingUtil; import com.idormy.sms.forwarder.view.IPEditText; -import com.koushikdutta.async.http.WebSocket; -import com.koushikdutta.async.http.server.AsyncHttpServer; import java.io.File; import java.io.FileInputStream; @@ -25,8 +25,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import okhttp3.Call; @@ -38,9 +36,8 @@ import okhttp3.Response; public class CloneActivity extends AppCompatActivity { private final String TAG = "CloneActivity"; private Context context; - private boolean isRunning = false; private String serverIp; - private final String DATABASE_NAME = "sms_forwarder.db"; + public static final String DATABASE_NAME = "sms_forwarder.db"; @Override public void onCreate(Bundle savedInstanceState) { @@ -62,47 +59,46 @@ public class CloneActivity extends AppCompatActivity { IPEditText textServerIp = findViewById(R.id.textServerIp); - List _sockets = new ArrayList<>(); - AsyncHttpServer server = new AsyncHttpServer(); TextView sendTxt = findViewById(R.id.sendTxt); TextView receiveTxt = findViewById(R.id.receiveTxt); Button sendBtn = findViewById(R.id.sendBtn); + serverIp = NetUtil.getLocalIp(CloneActivity.this); + TextView ipText = findViewById(R.id.ipText); + ipText.setText(serverIp); + if (HttpServer.asRunning()) { + sendBtn.setText(R.string.stop); + sendTxt.setText(R.string.server_has_started); + textServerIp.setIP(serverIp); + } else { + sendBtn.setText(R.string.send); + sendTxt.setText(R.string.server_has_stopped); + } sendBtn.setOnClickListener(v -> { - if (NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) { + if (!HttpServer.asRunning() && NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) { Toast.makeText(CloneActivity.this, R.string.no_wifi_network, Toast.LENGTH_SHORT).show(); return; - } else { - serverIp = NetUtil.getLocalIp(CloneActivity.this); - TextView ipText = findViewById(R.id.ipText); - ipText.setText(getString(R.string.local_ip) + serverIp); } - if (!isRunning) { - isRunning = true; - server.get("/", (request, response) -> { - File file = context.getDatabasePath(DATABASE_NAME); - response.getHeaders().add("Content-Disposition", "attachment;filename=" + DATABASE_NAME); - response.sendFile(file); - }); - server.listen(5000); - Toast.makeText(CloneActivity.this, R.string.server_has_started, Toast.LENGTH_SHORT).show(); - sendTxt.setText(R.string.server_has_started); - textServerIp.setIP(serverIp); - sendBtn.setText(R.string.stop); - } else { - isRunning = false; - server.stop(); - Toast.makeText(CloneActivity.this, R.string.server_has_stopped, Toast.LENGTH_SHORT).show(); + SettingUtil.switchEnableHttpServer(!SettingUtil.getSwitchEnableHttpServer()); + if (!HttpServer.update()) { + SettingUtil.switchEnableHttpServer(!SettingUtil.getSwitchEnableHttpServer()); + return; + } + if (!HttpServer.asRunning()) { sendTxt.setText(R.string.server_has_stopped); textServerIp.setIP(""); sendBtn.setText(R.string.send); + } else { + sendTxt.setText(R.string.server_has_started); + textServerIp.setIP(serverIp); + sendBtn.setText(R.string.stop); } }); Button receiveBtn = findViewById(R.id.receiveBtn); receiveBtn.setOnClickListener(v -> { - if (isRunning) { + if (HttpServer.asRunning()) { receiveTxt.setText(R.string.sender_cannot_receive); Toast.makeText(CloneActivity.this, R.string.sender_cannot_receive, Toast.LENGTH_SHORT).show(); return; diff --git a/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java b/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java index 53a572b3..65dae229 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java +++ b/app/src/main/java/com/idormy/sms/forwarder/MainActivity.java @@ -4,9 +4,7 @@ import android.annotation.SuppressLint; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; +import android.os.*; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -23,21 +21,15 @@ import androidx.appcompat.app.AppCompatActivity; import com.idormy.sms.forwarder.adapter.LogAdapter; import com.idormy.sms.forwarder.model.vo.LogVo; +import com.idormy.sms.forwarder.sender.HttpServer; +import com.idormy.sms.forwarder.sender.SmsHubApiTask; import com.idormy.sms.forwarder.service.BatteryService; import com.idormy.sms.forwarder.service.FrontService; -import com.idormy.sms.forwarder.utils.CommonUtil; -import com.idormy.sms.forwarder.utils.KeepAliveUtils; -import com.idormy.sms.forwarder.utils.LogUtil; -import com.idormy.sms.forwarder.utils.NetUtil; -import com.idormy.sms.forwarder.utils.PhoneUtils; -import com.idormy.sms.forwarder.utils.SettingUtil; -import com.idormy.sms.forwarder.utils.SmsUtil; -import com.idormy.sms.forwarder.utils.TimeUtil; +import com.idormy.sms.forwarder.utils.*; import com.umeng.analytics.MobclickAgent; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class MainActivity extends AppCompatActivity implements RefreshListView.IRefreshListener { @@ -69,6 +61,10 @@ public class MainActivity extends AppCompatActivity implements RefreshListView.I SmsUtil.init(this); NetUtil.init(this); + HttpUtil.init(this); + SmsHubApiTask.init(this); + HttpServer.init(this); + //前台服务 try { serviceIntent = new Intent(MainActivity.this, FrontService.class); @@ -86,6 +82,12 @@ public class MainActivity extends AppCompatActivity implements RefreshListView.I } catch (Exception e) { Log.e(TAG, "BatteryService:", e); } + try { + SmsHubApiTask.updateTimer(); + HttpServer.update(); + } catch (Exception e) { + Log.e(TAG, "SmsHubApiTask:", e); + } } @Override diff --git a/app/src/main/java/com/idormy/sms/forwarder/SettingActivity.java b/app/src/main/java/com/idormy/sms/forwarder/SettingActivity.java index 61bfc09f..f923cc99 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/SettingActivity.java +++ b/app/src/main/java/com/idormy/sms/forwarder/SettingActivity.java @@ -23,10 +23,8 @@ import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import com.idormy.sms.forwarder.utils.CommonUtil; -import com.idormy.sms.forwarder.utils.DbHelper; -import com.idormy.sms.forwarder.utils.KeepAliveUtils; -import com.idormy.sms.forwarder.utils.SettingUtil; +import com.idormy.sms.forwarder.sender.SmsHubApiTask; +import com.idormy.sms.forwarder.utils.*; import java.util.List; @@ -85,6 +83,38 @@ public class SettingActivity extends AppCompatActivity { EditText textSmsTemplate = findViewById(R.id.text_sms_template); editSmsTemplate(textSmsTemplate); + + editSmsHubConfig(findViewById(R.id.switch_enable_sms_hub),findViewById(R.id.editText_text_sms_hub_url)); + } + + @SuppressLint("UseSwitchCompatOrMaterialCode") + private void editSmsHubConfig(Switch switch_enable_send_sms, EditText editText_text_send_sms) { + switch_enable_send_sms.setChecked(SettingUtil.getSwitchEnableSmsHubApi()); + switch_enable_send_sms.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked && editText_text_send_sms.getText() != null && editText_text_send_sms.getText().length() < 1) { + HttpUtil.Toast(TAG, "url为空无法启用"); + switch_enable_send_sms.setChecked(false); + return; + } + SettingUtil.switchEnableSmsHubApi(isChecked); + Log.d(TAG, "switchEnableSendApi:" + isChecked); + SmsHubApiTask.updateTimer(); + }); + editText_text_send_sms.setText(SettingUtil.getSmsHubApiUrl()); + editText_text_send_sms.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + SettingUtil.smsHubApiUrl(editText_text_send_sms.getText().toString().trim()); + } + }); } //设置转发短信 diff --git a/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsHubVo.java b/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsHubVo.java new file mode 100644 index 00000000..c6dda739 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/model/vo/SmsHubVo.java @@ -0,0 +1,117 @@ +package com.idormy.sms.forwarder.model.vo; + +import androidx.annotation.NonNull; + +import com.alibaba.fastjson.JSON; +import com.idormy.sms.forwarder.sender.SmsHubApiTask; +import com.idormy.sms.forwarder.utils.*; + +import java.io.Serializable; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import lombok.Data; + +@Data +public class SmsHubVo implements Serializable { + private static final long serialVersionUID = 1L; + + public SmsHubVo() { + } + + public SmsHubVo(Type type, Integer simId, String content, String target) { + this.msgId = UUID.randomUUID().toString(); + if (channel != null) { + String simInfo = simId == 2 ? SettingUtil.getAddExtraSim2() : SettingUtil.getAddExtraSim1(); //自定义备注优先 + if (!simInfo.isEmpty()) { + simInfo = "SIM" + simId + ":" + simInfo; + } else { + simInfo = SimUtil.getSimInfo(simId); + } + this.channel = simInfo; + } + this.content = content; + this.target = target; + this.action = Action.receive.code(); + this.type = type.code; + } + + //唯一id + private String msgId; + //心跳数据时发送的设备名 + private String deviceInfo; + //卡槽信息 + private String channel; + //消息内容 + private String content; + //错误消息 + private String errMsg; + //手机号(;分隔)或包名 + private String target; + //状态或操作 + private String action; + //消息类型 + private String type; + //时间戳 + private String ts; + //两次交互之间接收到的消息 + private List children; + + public static SmsHubVo heartbeatInstance() { + SmsHubVo smsHubVo = new SmsHubVo(); + HashMap deviInfoMap = getDevInfoMap(false); + smsHubVo.setDeviceInfo(JSON.toJSONString(deviInfoMap)); + smsHubVo.setChannel("SIM1:" + SimUtil.getSimInfo(1) + SettingUtil.getAddExtraSim1() + ";SIM2:" + SimUtil.getSimInfo(2) + SettingUtil.getAddExtraSim2()); + smsHubVo.setTs(Long.toString(System.currentTimeMillis())); + smsHubVo.setAction(SmsHubVo.Action.heartbeat.code()); + return smsHubVo; + } + + private static Map cache = new ConcurrentHashMap<>(); + + @NonNull + public static HashMap getDevInfoMap(boolean reflush) { + String key = "deviceInfo"; + if (reflush || !cache.containsKey(key)) { + HashMap deviInfoMap = new HashMap<>(); + deviInfoMap.put("mark", SettingUtil.getAddExtraDeviceMark()); + deviInfoMap.put("simOperatorName", PhoneUtils.getSimOperatorName()); + deviInfoMap.put("phoneNumber", PhoneUtils.getPhoneNumber()); + deviInfoMap.put("imei", PhoneUtils.getIMEI()); + deviInfoMap.put("SDKVersion", PhoneUtils.getSDKVersion() + ""); + deviInfoMap.put("Version", SettingUtil.getVersionName()); + deviInfoMap.put("heartbeat", SmsHubApiTask.DELAY_SECONDS + ""); + cache.put(key, deviInfoMap); + return deviInfoMap; + } + return (HashMap) cache.get(key); + } + + public enum Action { + send("0"), receive("1"), suessces("2"), failure("3"), heartbeat("-1"); + + Action(String code) { + this.code = code; + } + + private final String code; + + public String code() { + return code; + } + } + + public enum Type { + app("app"), phone("phone"), sms("sms"), battery("battery"); + + Type(String code) { + this.code = code; + } + + private final String code; + + public String code() { + return code; + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/BaseServlet.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/BaseServlet.java new file mode 100644 index 00000000..b25989ba --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/BaseServlet.java @@ -0,0 +1,172 @@ +package com.idormy.sms.forwarder.receiver; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.util.IOUtils; +import com.idormy.sms.forwarder.CloneActivity; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; +import com.idormy.sms.forwarder.utils.SmsHubActionHandler; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +import java.io.*; +import java.util.List; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.ServletOutputStream; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.*; + +@WebServlet +@MultipartConfig +public class BaseServlet extends HttpServlet { + + public static final int BUFFER_SIZE = 1 << 12; + public static final String CLONE_PATH = "/"; + public static final String SMSHUB_PATH = "/send_api"; + private static final long serialVersionUID = 1L; + private static final String TAG = "BaseServlet"; + private static final SmsHubActionHandler.SmsHubMode smsHubMode = SmsHubActionHandler.SmsHubMode.server; + + public BaseServlet(String path, Context context) { + this.path = path; + this.context = context; + } + + private final String path; + @SuppressLint("StaticFieldLeak") + private final Context context; + + public static void addServlet(Server jettyServer, Context context) { + ServletContextHandler contextHandler = new ServletContextHandler(); + addHolder(contextHandler, new BaseServlet(BaseServlet.CLONE_PATH, context)); + addHolder(contextHandler, new BaseServlet(BaseServlet.SMSHUB_PATH, context)); + // addholder(contextHandler, new BaseServlet("/", context)); + jettyServer.setHandler(contextHandler); + } + + public static String read(Reader reader) throws IOException { + char[] buffer = new char[BUFFER_SIZE]; + int size; + StringBuilder sb = new StringBuilder(); + while ((size = reader.read(buffer)) != -1) { + char[] chars = new char[size]; + System.arraycopy(buffer, 0, chars, 0, size); + sb.append(chars); + } + return sb.toString(); + } + + private static void addHolder(ServletContextHandler servletContextHandler, BaseServlet baseServlet) { + ServletHolder servletHolder = new ServletHolder(baseServlet); + servletHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(baseServlet.getContext().getCacheDir().getAbsolutePath() + File.pathSeparator + "jettyServer")); + servletContextHandler.addServlet(servletHolder, baseServlet.getPath()); + } + + public Context getContext() { + return context; + } + + public String getPath() { + return path; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (CLONE_PATH.equals(path)) { + clone(req, resp); + } else if (SMSHUB_PATH.equals(path)) { + send_api(req, resp); + } else { + notFound(req, resp); + } + } + + private void notFound(HttpServletRequest req, HttpServletResponse resp) throws IOException { + PrintWriter writer = resp.getWriter(); + try { + String text = "NOT FOUND"; + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + writer.println(text); + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtils.close(writer); + } + } + + private void send_api(HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setCharacterEncoding("utf-8"); + PrintWriter writer = resp.getWriter(); + BufferedReader reader = req.getReader(); + try { + String read = read(reader); + Log.i(TAG, "请求内容:" + read); + List smsHubVos = JSON.parseArray(read, SmsHubVo.class); + if (smsHubVos.size() == 1 && SmsHubVo.Action.heartbeat.code().equals(smsHubVos.get(0).getAction())) { + smsHubVos.clear(); + SmsHubVo smsHubVo = SmsHubVo.heartbeatInstance(); + smsHubVos.add(smsHubVo); + List data = SmsHubActionHandler.getData(smsHubMode); + if (data != null && data.size() > 0) { + smsHubVo.setChildren(data); + } + } else { + for (SmsHubVo vo : smsHubVos) { + SmsHubActionHandler.handle(TAG, vo); + } + List data = SmsHubActionHandler.getData(smsHubMode); + if (data != null && data.size() > 0) { + SmsHubVo smsHubVo = SmsHubVo.heartbeatInstance(); + smsHubVo.setChildren(data); + smsHubVos.add(smsHubVo); + } + } + resp.setContentType("application/json;charset=utf-8"); + String text = JSON.toJSONString(smsHubVos); + writer.println(text); + } catch (Exception e) { + e.printStackTrace(); + printErrMsg(resp, writer, e); + } finally { + IOUtils.close(reader); + IOUtils.close(writer); + } + } + + private void printErrMsg(HttpServletResponse resp, PrintWriter writer, Exception e) { + String text = "服务器内部错误:" + e.getMessage(); + Log.e(TAG, text); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + writer.println(text); + } + + private void clone(HttpServletRequest req, HttpServletResponse resp) throws IOException { + File file = context.getDatabasePath(CloneActivity.DATABASE_NAME); + resp.addHeader("Content-Disposition", "attachment;filename=" + CloneActivity.DATABASE_NAME); + ServletOutputStream outputStream = resp.getOutputStream(); + InputStream inputStream = new FileInputStream(file); + try { + byte[] buffer = new byte[BUFFER_SIZE]; + int size; + while ((size = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, size); + } + } catch (Exception e) { + e.printStackTrace(); + String text = "服务器内部错误:" + e.getMessage(); + Log.e(TAG, text); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } finally { + IOUtils.close(inputStream); + IOUtils.close(outputStream); + } + } + +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.java index 94120a79..335199bb 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.java +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/PhoneStateReceiver.java @@ -10,13 +10,10 @@ import android.util.Log; import com.idormy.sms.forwarder.R; import com.idormy.sms.forwarder.model.CallInfo; import com.idormy.sms.forwarder.model.PhoneBookEntity; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; import com.idormy.sms.forwarder.model.vo.SmsVo; import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.utils.CommonUtil; -import com.idormy.sms.forwarder.utils.ContactHelper; -import com.idormy.sms.forwarder.utils.PhoneUtils; -import com.idormy.sms.forwarder.utils.SettingUtil; -import com.idormy.sms.forwarder.utils.SimUtil; +import com.idormy.sms.forwarder.utils.*; import java.text.SimpleDateFormat; import java.util.Date; @@ -113,5 +110,6 @@ public class PhoneStateReceiver extends BroadcastReceiver { SmsVo smsVo = new SmsVo(phoneNumber, name + context.getString(R.string.calling), new Date(), simInfo); Log.d(TAG, "send_msg" + smsVo.toString()); SendUtil.send_msg(context, smsVo, simId, "call"); + SmsHubActionHandler.putData(new SmsHubVo(SmsHubVo.Type.phone, simId, name + context.getString(R.string.calling), phoneNumber)); } } diff --git a/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsBroadcastReceiver.java b/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsBroadcastReceiver.java index cdf1ba43..db2fe61c 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsBroadcastReceiver.java +++ b/app/src/main/java/com/idormy/sms/forwarder/receiver/SmsBroadcastReceiver.java @@ -7,10 +7,10 @@ import android.os.Bundle; import android.telephony.SmsMessage; import android.util.Log; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; import com.idormy.sms.forwarder.model.vo.SmsVo; import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.utils.SettingUtil; -import com.idormy.sms.forwarder.utils.SimUtil; +import com.idormy.sms.forwarder.utils.*; import java.util.ArrayList; import java.util.Date; @@ -80,12 +80,14 @@ public class SmsBroadcastReceiver extends BroadcastReceiver { mobileToContent.put(mobile, content); } + List smsHubVos = new ArrayList<>(); for (String mobile : mobileToContent.keySet()) { smsVoList.add(new SmsVo(mobile, mobileToContent.get(mobile), date, simInfo)); + smsHubVos.add(new SmsHubVo(SmsHubVo.Type.sms, simId, mobileToContent.get(mobile), mobile)); } + SmsHubActionHandler.putData(smsHubVos.toArray(new SmsHubVo[0])); Log.d(TAG, "短信:" + smsVoList); SendUtil.send_msg_list(context, smsVoList, simId, "sms"); - } } catch (Throwable throwable) { @@ -96,4 +98,4 @@ public class SmsBroadcastReceiver extends BroadcastReceiver { } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/HttpServer.java b/app/src/main/java/com/idormy/sms/forwarder/sender/HttpServer.java new file mode 100644 index 00000000..e90d804b --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/sender/HttpServer.java @@ -0,0 +1,116 @@ +package com.idormy.sms.forwarder.sender; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import com.idormy.sms.forwarder.R; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; +import com.idormy.sms.forwarder.receiver.BaseServlet; +import com.idormy.sms.forwarder.utils.*; + +import org.eclipse.jetty.server.Server; + + +public class HttpServer { + private static Boolean hasInit = false; + private static Server jettyServer; + private static int port = 5000; + @SuppressLint("StaticFieldLeak") + private static Context context; + private static long ts = 0L; + + + @SuppressLint("HandlerLeak") + public static void init(Context context) { + //noinspection SynchronizeOnNonFinalField + synchronized (hasInit) { + if (hasInit) return; + + hasInit = true; + HttpServer.context = context; + SmsHubActionHandler.init(context); + jettyServer = new Server(port); + BaseServlet.addServlet(jettyServer, context); + } + } + + /** + * Checks if the Jetty is running + * boolean - true when server is running/starting/stopping, false otherwise + */ + public synchronized static Boolean asRunning() { + if (jettyServer != null) { + return jettyServer.isRunning() && !jettyServer.isStopping(); + } + return false; + } + + public synchronized static boolean update() { + if (!asRunning() && NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) { + Toast.makeText(context, R.string.no_wifi_network, Toast.LENGTH_SHORT).show(); + return false; + } + long l = System.currentTimeMillis(); + if (l - ts < 3000 && asRunning()) { + Toast.makeText(context, "点击启动后请等待3秒", Toast.LENGTH_SHORT).show(); + return false; + } + if (asRunning().equals(SettingUtil.getSwitchEnableHttpServer())) { + return false; + } + if (SettingUtil.getSwitchEnableHttpServer()) { + SmsHubVo.getDevInfoMap(true); + start(); + ts = System.currentTimeMillis(); + Toast.makeText(context, R.string.server_has_started, Toast.LENGTH_SHORT).show(); + return true; + } else { + stop(); + Toast.makeText(context, R.string.server_has_stopped, Toast.LENGTH_SHORT).show(); + return true; + } + } + + /** + * Checks if Jetty is stopping + * boolean - True when server is stopping + */ + private synchronized static Boolean asStopp() { + if (jettyServer != null) { + return !(jettyServer.isRunning() || jettyServer.isStopping()); + } else { + return true; + } + } + + private static void start() { + stop(); + Log.i("HttpServer", "start"); + //new Thread(() -> { + try { + //Start Jetty + jettyServer.start(); + //jettyServer.join(); + } catch (Exception e) { + e.printStackTrace(); + } + //}).start(); + } + + + private static void stop() { + if (Boolean.FALSE.equals(asStopp())) { + try { + if (jettyServer != null) { + jettyServer.stop(); + // jettyServer = new Server(port); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/sender/SmsHubApiTask.java b/app/src/main/java/com/idormy/sms/forwarder/sender/SmsHubApiTask.java new file mode 100644 index 00000000..05957d30 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/sender/SmsHubApiTask.java @@ -0,0 +1,96 @@ +package com.idormy.sms.forwarder.sender; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; + +import com.alibaba.fastjson.JSON; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; +import com.idormy.sms.forwarder.utils.*; + +import java.util.*; + +/** + * 主动发送短信轮询任务 + * @author xxc + * 2022/1/10 9:53 + */ +public class SmsHubApiTask extends TimerTask { + private static Boolean hasInit = false; + public static final long DELAY_SECONDS = 30; + private static final String TAG = "SmsHubApiTask"; + private static Timer sendApiTimer; + @SuppressLint("StaticFieldLeak") + private static Context context; + private static final SmsHubActionHandler.SmsHubMode smsHubMode = SmsHubActionHandler.SmsHubMode.client; + + + @SuppressLint("HandlerLeak") + public static void init(Context context) { + //noinspection SynchronizeOnNonFinalField + synchronized (hasInit) { + if (hasInit) return; + + hasInit = true; + SmsHubApiTask.context = context; + SmsHubActionHandler.init(SmsHubApiTask.context); + } + } + + @Override + public void run() { + try { + SmsHubVo smsHubVo = SmsHubVo.heartbeatInstance(); + List data = SmsHubActionHandler.getData(smsHubMode); + if (data != null && data.size() > 0) { + smsHubVo.setChildren(data); + } + smsHubVo.setChildren(data); + String url = SettingUtil.getSmsHubApiUrl(); + HttpUtil.asyncPostJson(TAG, url, smsHubVo, response -> { + //HttpUtil.Toast(TAG, "Response:" + response.code() + "," + responseStr); + if (response.code() == 200) { + String responseStr = Objects.requireNonNull(response.body()).string(); + List vos = JSON.parseArray(responseStr, SmsHubVo.class); + for (SmsHubVo vo : vos) { + SmsHubActionHandler.handle(TAG, vo); + } + SmsHubActionHandler.putData(smsHubMode, vos.toArray(new SmsHubVo[0])); + } + }, null); + } catch (Exception e) { + HttpUtil.Toast(TAG, "SmsHubApiTask 执行出错,请检查问题后重新开启" + e.getMessage()); + cancelTimer(); + SettingUtil.switchEnableSmsHubApi(false); + } + } + + + public static void updateTimer() { + cancelTimer(); + if (SettingUtil.getSwitchEnableSmsHubApi()) { + SmsHubVo.getDevInfoMap(true); + startTimer(); + } else { + Log.d(TAG, "Cancel SmsHubApiTaskTimer"); + HttpUtil.Toast(TAG, "Cancel SmsHubApiTaskTimer"); + } + } + + private static void cancelTimer() { + if (sendApiTimer != null) { + sendApiTimer.cancel(); + sendApiTimer = null; + } + } + + private static void startTimer() { + Log.d(TAG, "Start SmsHubApiTimer"); + if (SettingUtil.getSwitchEnableSmsHubApi()) { + long seconds = SmsHubApiTask.DELAY_SECONDS; + Log.d(TAG, "SmsHubApiTimer started " + seconds); + sendApiTimer = new Timer("SmsHubApiTimer", true); + sendApiTimer.schedule(new SmsHubApiTask(), 3000, seconds * 1000); + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.java b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.java index 52a606f1..1302ce07 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.java +++ b/app/src/main/java/com/idormy/sms/forwarder/service/BatteryService.java @@ -10,9 +10,11 @@ import android.os.BatteryManager; import android.os.IBinder; import android.util.Log; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; import com.idormy.sms.forwarder.model.vo.SmsVo; import com.idormy.sms.forwarder.sender.SendUtil; import com.idormy.sms.forwarder.utils.SettingUtil; +import com.idormy.sms.forwarder.utils.SmsHubActionHandler; import java.util.Date; @@ -176,9 +178,10 @@ public class BatteryService extends Service { SmsVo smsVo = new SmsVo("88888888", msg, new Date(), "电池状态监听"); Log.d(TAG, "send_msg" + smsVo.toString()); SendUtil.send_msg(context, smsVo, 1, "app"); + SmsHubActionHandler.putData(new SmsHubVo(SmsHubVo.Type.phone, null, msg, "电池状态监听")); } catch (Exception e) { Log.e(TAG, "getLog e:" + e.getMessage()); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.java b/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.java index 1866db15..14e6af64 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.java +++ b/app/src/main/java/com/idormy/sms/forwarder/service/NotifyService.java @@ -8,10 +8,10 @@ import android.util.Log; import androidx.annotation.RequiresApi; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; import com.idormy.sms.forwarder.model.vo.SmsVo; import com.idormy.sms.forwarder.sender.SendUtil; -import com.idormy.sms.forwarder.utils.CommonUtil; -import com.idormy.sms.forwarder.utils.SettingUtil; +import com.idormy.sms.forwarder.utils.*; import java.text.SimpleDateFormat; import java.util.Date; @@ -79,6 +79,7 @@ public class NotifyService extends NotificationListenerService { SmsVo smsVo = new SmsVo(packageName, text, new Date(), title); Log.d(TAG, "send_msg" + smsVo.toString()); SendUtil.send_msg(this, smsVo, 1, "app"); + SmsHubActionHandler.putData(new SmsHubVo(SmsHubVo.Type.app, null, text, packageName)); } catch (Exception e) { Log.e(TAG, "onNotificationPosted:", e); } diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Define.java b/app/src/main/java/com/idormy/sms/forwarder/utils/Define.java index ef9de3ac..ec4ceed8 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/Define.java +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Define.java @@ -25,6 +25,9 @@ public class Define { public static final String SP_MSG_KEY_STRING_RETRY_DELAY_TIME3 = "tsms_msg_key_string_retry_delay_time3"; public static final String SP_MSG_KEY_STRING_RETRY_DELAY_TIME4 = "tsms_msg_key_string_retry_delay_time4"; public static final String SP_MSG_KEY_STRING_RETRY_DELAY_TIME5 = "tsms_msg_key_string_retry_delay_time5"; + public static final String SP_MSG_KEY_STRING_ENABLE_SMSHUB_API = "tsms_msg_key_string_enable_smshub_api"; + public static final String SP_MSG_KEY_STRING_SMSHUB_API_URL = "tsms_msg_key_string_smshub_api_url"; + public static final String SP_MSG_KEY_STRING_ENABLE_HTTP_SERVER = "tsms_msg_key_string_enable_http_server"; public static final String SP_MSG = "forwarder_msg"; public static final String SP_MSG_SET_KEY = "forwarder_msg_set_key"; diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/HttpUtil.java b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpUtil.java new file mode 100644 index 00000000..c9eeb125 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/HttpUtil.java @@ -0,0 +1,167 @@ +package com.idormy.sms.forwarder.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.*; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.Map; +import java.util.Objects; + +import okhttp3.*; + +public class HttpUtil { + private static final OkHttpClient client = new OkHttpClient(); + private static final String TAG = "HttpUtil"; + private static Boolean hasInit = false; + @SuppressLint("StaticFieldLeak") + private static Context context; + // @SuppressLint("StaticFieldLeak") +// private static Handler handError; + private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json;charset=utf-8"); + + + @SuppressLint("HandlerLeak") + public static void init(Context context) { + //noinspection SynchronizeOnNonFinalField + synchronized (hasInit) { + if (hasInit) return; + + hasInit = true; + HttpUtil.context = context; + } + } + + public static void asyncGet(String tag, String url, Object param, Lamda.Consumer onResponse, Lamda.Consumer onFailure) { + StringBuilder resUrl = appendQueryStr(tag, url, param); + Request request = new Request.Builder().url(resUrl.toString()).get().build(); + Lamda.Func func = call -> { + call.enqueue(new Callback0(tag, onResponse, onFailure)); + return null; + }; + callAndCatch(tag, request, func); + } + + public static void asyncPostJson(String tag, String url, Object param, Lamda.Consumer onResponse, Lamda.Consumer onFailure) { + String jsonString = JSON.toJSONString(param); + Request request = new Request.Builder().url(url).post(RequestBody.create(jsonString, MEDIA_TYPE_JSON)).build(); + Lamda.Func func = call -> { + call.enqueue(new Callback0(tag, onResponse, onFailure)); + return null; + }; + callAndCatch(tag, request, func); + } + + public static String postJson(String tag, String url, Object param) { + String jsonString = JSON.toJSONString(param); + Request request = new Request.Builder().url(url).post(RequestBody.create(jsonString, MEDIA_TYPE_JSON)).build(); + Lamda.Func func = call -> { + Response response = call.execute(); + if (response.code() == 200) { + return Objects.requireNonNull(response.body()).toString(); + } + return null; + }; + return callAndCatch(tag, request, func); + } + + public static String get(String tag, String url, Object param) { + StringBuilder resUrl = appendQueryStr(tag, url, param); + Request request = new Request.Builder().url(resUrl.toString()).get().build(); + Lamda.Func func = call -> { + Response response = call.execute(); + if (response.code() == 200) { + return Objects.requireNonNull(response.body()).toString(); + } + return null; + }; + return callAndCatch(tag, request, func); + } + + public static void Toast(String Tag, String data) { + Log.i(Tag, data); + try { + Toast.makeText(HttpUtil.context, Tag + "-" + data, Toast.LENGTH_LONG).show(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @NonNull + public static StringBuilder appendQueryStr(String tag, String url, Object param) { + StringBuilder resUrl = new StringBuilder(url); + if (!url.contains("?")) { + resUrl.append("?"); + } else { + resUrl.append("&"); + } + Map paramMap = param instanceof Map ? (Map) param + : JSON.parseObject(JSON.toJSONString(param), new TypeReference>() { + }.getType()); + for (Map.Entry entry : paramMap.entrySet()) { + if (entry.getValue() != null) { + resUrl.append(URLEncoder.encode(entry.getKey())).append("=").append(URLEncoder.encode(entry.getValue())); + } + } + Log.i(tag, "url:" + resUrl); + return resUrl; + } + + public static String callAndCatch(String tag, Request request, Lamda.Func func) { + try { + Call call = client.newCall(request); + return func.execute(call); + } catch (Exception e) { + Toast(tag, "请求失败:" + e.getMessage()); + Log.e(tag, "请求失败:" + e.getMessage()); + e.printStackTrace(); + } + return null; + } + + public static class Callback0 implements Callback { + public Callback0(String tag, Lamda.Consumer onResponse, Lamda.Consumer onFailure) { + this.tag = tag; + this.onResponse = onResponse; + this.onFailure = onFailure; + } + + public Callback0(String tag, Lamda.Consumer onResponse) { + this.tag = tag; + this.onResponse = onResponse; + } + + private final String tag; + private final Lamda.Consumer onResponse; + private Lamda.Consumer onFailure; + + @Override + public void onFailure(@NonNull Call call, @NonNull final IOException e) { + Toast(tag, "onFailure:" + e.getMessage()); + Log.d(tag, "onFailure:" + e.getMessage()); + if (onFailure != null) { + onFailure.executeThrowRunTimeExcp(e); + } + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + Log.d(tag, "onResponse:" + response.code() + ":" + Objects.requireNonNull(response.body()).toString()); + if (onResponse != null) + onResponse.executeThrowRunTimeExcp(response); + } + + public String getTag() { + return tag; + } + } + +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/Lamda.java b/app/src/main/java/com/idormy/sms/forwarder/utils/Lamda.java new file mode 100644 index 00000000..39d1f016 --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/Lamda.java @@ -0,0 +1,39 @@ +package com.idormy.sms.forwarder.utils; + +import java.util.Objects; + +public class Lamda { + public interface Consumer extends Func { + void accept(T t) throws Exception; + + default T execute(T t) throws Exception { + accept(t); + return t; + } + } + + public interface Func { + R execute(T t) throws Exception; + + default R executeThrowRunTimeExcp(T t) { + try { + return execute(t); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + default R executeIgnoreExcp(T t) { + try { + return execute(t); + } catch (Exception ignored) { + } + return null; + } + + default Func andThen(Func after) { + Objects.requireNonNull(after); + return (T t) -> after.execute(execute(t)); + } + } +} diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtil.java b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtil.java index b7fe0a29..0cdd855b 100644 --- a/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtil.java +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SettingUtil.java @@ -249,6 +249,36 @@ public class SettingUtil { sp_setting.edit().putString(key, value).apply(); } + public static void switchEnableSmsHubApi(Boolean enable) { + sp_setting.edit() + .putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_SMSHUB_API, enable) + .apply(); + } + + public static boolean getSwitchEnableSmsHubApi() { + return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_SMSHUB_API, false); + } + + public static void switchEnableHttpServer(Boolean enable) { + sp_setting.edit() + .putBoolean(Define.SP_MSG_KEY_STRING_ENABLE_HTTP_SERVER, enable) + .apply(); + } + + public static boolean getSwitchEnableHttpServer() { + return sp_setting.getBoolean(Define.SP_MSG_KEY_STRING_ENABLE_HTTP_SERVER, false); + } + + public static void smsHubApiUrl(String url) { + sp_setting.edit() + .putString(Define.SP_MSG_KEY_STRING_SMSHUB_API_URL, url) + .apply(); + } + + public static String getSmsHubApiUrl() { + return sp_setting.getString(Define.SP_MSG_KEY_STRING_SMSHUB_API_URL, "http://xxx.com/send_api"); + } + //获取当前版本名称 public static String getVersionName() { // 获取PackageManager的实例 diff --git a/app/src/main/java/com/idormy/sms/forwarder/utils/SmsHubActionHandler.java b/app/src/main/java/com/idormy/sms/forwarder/utils/SmsHubActionHandler.java new file mode 100644 index 00000000..9a4fd37f --- /dev/null +++ b/app/src/main/java/com/idormy/sms/forwarder/utils/SmsHubActionHandler.java @@ -0,0 +1,127 @@ +package com.idormy.sms.forwarder.utils; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; + +import com.idormy.sms.forwarder.model.LogModel; +import com.idormy.sms.forwarder.model.vo.SmsHubVo; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class SmsHubActionHandler { + public static final long RULE_ID = -999L; + private static Boolean hasInit = false; + + private static ConcurrentHashMap> cache; + + public enum SmsHubMode { + server, client + } + + @SuppressLint("StaticFieldLeak") + private static Context context; + + @SuppressLint("HandlerLeak") + public static void init(Context context) { + //noinspection SynchronizeOnNonFinalField + synchronized (hasInit) { + if (hasInit) return; + + hasInit = true; + SmsHubActionHandler.context = context; + cache = new ConcurrentHashMap<>(); + for (SmsHubMode smsHubMode : SmsHubMode.values()) { + cache.put(smsHubMode, new ArrayList<>()); + } + } + } + + public static synchronized int size(SmsHubMode smsHubMode) { + return Objects.requireNonNull(cache.get(smsHubMode)).size(); + } + + public static synchronized List getData(SmsHubMode smsHubMode) { + List smsHubVoList = cache.get(smsHubMode); + if (smsHubVoList.size() > 0) { + cache.put(smsHubMode, new ArrayList<>()); + return smsHubVoList; + } else { + return null; + } + } + + public static synchronized void putData(SmsHubMode smsHubMode, SmsHubVo... smsHubVos) { + if (isEnable(smsHubMode)) { + Objects.requireNonNull(cache.get(smsHubMode)).addAll(Arrays.asList(smsHubVos)); + } + } + + public static synchronized void putData(SmsHubVo... smsHubVos) { + for (SmsHubMode smsHubMode : SmsHubMode.values()) { + putData(smsHubMode, smsHubVos); + } + } + + private static boolean isEnable(SmsHubMode smsHubMode) { + boolean enable = false; + if (smsHubMode == SmsHubMode.client) { + enable = SettingUtil.getSwitchEnableSmsHubApi(); + } else if (smsHubMode == SmsHubMode.server) { + enable = SettingUtil.getSwitchEnableHttpServer(); + } + return enable; + } + + public static void handle(String tag, SmsHubVo vo) { + String action = vo.getAction(); + if (SmsHubVo.Action.send.code().equals(action)) { + send(tag, vo); + } else { + String errMsg = "暂不支持的action:" + action; + vo.setErrMsg(errMsg); + vo.setAction(SmsHubVo.Action.failure.code()); + } + vo.setTs(Long.toString(System.currentTimeMillis())); + } + + public static void send(String tag, SmsHubVo vo) { + boolean failure = true; + String msg = ""; + Long logId = null; + try { + if (SmsHubVo.Action.send.code().equals(vo.getAction())) { + vo.setType(SmsHubVo.Type.sms.code()); + logId = LogUtil.addLog(new LogModel(vo.getType(), vo.getTarget(), vo.getContent(), "SIM" + vo.getChannel(), RULE_ID)); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) { + int simId = Integer.parseInt(vo.getChannel()); + vo.setChannel("SIM" + simId); + msg = SmsUtil.sendSms(SimUtil.getSubscriptionIdBySimId(simId), vo.getTarget(), vo.getContent()); + if (msg == null) { + failure = false; + HttpUtil.Toast(tag, "短信发送成功"); + Log.i(tag, "短信发送成功"); + vo.setAction(SmsHubVo.Action.suessces.code()); + LogUtil.updateLog(logId, 2, SmsHubVo.Action.suessces.code()); + } + } else { + msg = "api<22"; + } + } + } catch (Exception e) { + msg += e.getMessage(); + e.printStackTrace(); + } + if (failure) { + msg = "短信发送失败:" + msg; + HttpUtil.Toast(tag, msg); + Log.i(tag, msg); + vo.setAction(SmsHubVo.Action.failure.code()); + vo.setErrMsg(msg); + if (logId != null) { + LogUtil.updateLog(logId, 0, msg); + } + } + } +} diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml index d38726da..7d403675 100644 --- a/app/src/main/res/layout/activity_setting.xml +++ b/app/src/main/res/layout/activity_setting.xml @@ -8,7 +8,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index d7b9eb50..7c9196a4 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -207,7 +207,7 @@ Local IP: Instructions: \n1. Please keep the SOURCE and DESTINATION phones in the same Wi-Fi network, and do not turn on isolation. \n2. Tap "Send" on SOURCE mobile phone, and get "server IP" \n3. After filling in "Server IP" on DESTINATION phone, tap "Receive". \n [NOTE:] sender(s), forwarding rule(s) and log(s) will be overwritten after cloning! - Send + Start Server Stop I\'m the SCOURCE phone Receive @@ -263,4 +263,6 @@ Proxy Authenticator Username Password + SmsHub client Mode + Execute the operation returned by the interface diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d65a4525..36cb41dd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -206,7 +206,7 @@ GET 本机IP: 操作说明:\n1.新旧手机连接同一个WiFi网络(禁用AP隔离)\n2.旧手机直接点【发送】按钮,获取到【服务端IP】\n3.新手机填写【服务端IP】后,点【接收】按钮\n【注意】新手机接收后,发送通道、转发规则将完全被覆盖,清空历史记录! - 发送 + 启动服务 停止 我是旧手机 接收 @@ -262,4 +262,6 @@ 代理身份验证 用户 密码 + SmsHub client 模式 + 执行接口返回的操作 diff --git a/build.gradle b/build.gradle index 4cb3b375..5fff9af2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.jetty_version = '9.2.30.v20200428' repositories { google() jcenter()