优化:一键克隆机制优化,提高成功率

This commit is contained in:
pppscn 2022-01-26 00:53:54 +08:00
parent 18b1efaf93
commit f37ce20fcb
16 changed files with 749 additions and 135 deletions

View File

@ -1,8 +1,12 @@
package com.idormy.sms.forwarder; package com.idormy.sms.forwarder;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
@ -11,26 +15,32 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.alibaba.fastjson.JSON;
import com.idormy.sms.forwarder.model.vo.CloneInfoVo;
import com.idormy.sms.forwarder.receiver.BaseServlet;
import com.idormy.sms.forwarder.receiver.RebootBroadcastReceiver; import com.idormy.sms.forwarder.receiver.RebootBroadcastReceiver;
import com.idormy.sms.forwarder.sender.HttpServer; import com.idormy.sms.forwarder.sender.HttpServer;
import com.idormy.sms.forwarder.utils.LogUtil; import com.idormy.sms.forwarder.utils.BackupDbTask;
import com.idormy.sms.forwarder.utils.Define;
import com.idormy.sms.forwarder.utils.DownloadUtil;
import com.idormy.sms.forwarder.utils.HttpUtil;
import com.idormy.sms.forwarder.utils.NetUtil; import com.idormy.sms.forwarder.utils.NetUtil;
import com.idormy.sms.forwarder.utils.SettingUtil; import com.idormy.sms.forwarder.utils.SettingUtil;
import com.idormy.sms.forwarder.view.IPEditText; import com.idormy.sms.forwarder.view.IPEditText;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.util.HashMap;
import java.io.OutputStream; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.Callback; import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
public class CloneActivity extends AppCompatActivity { public class CloneActivity extends AppCompatActivity {
@ -38,6 +48,28 @@ public class CloneActivity extends AppCompatActivity {
private Context context; private Context context;
private String serverIp; private String serverIp;
public static final String DATABASE_NAME = "sms_forwarder.db"; public static final String DATABASE_NAME = "sms_forwarder.db";
private IPEditText textServerIp;
private TextView sendTxt;
private TextView receiveTxt;
private Button sendBtn;
public static final int TOAST = 0x9731994;
public static final int DOWNLOAD = 0x9731995;
//消息处理者,创建一个Handler的子类对象,目的是重写Handler的处理消息的方法(handleMessage())
@SuppressWarnings("deprecation")
@SuppressLint("HandlerLeak")
private final Handler handError = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == TOAST) {
Toast.makeText(CloneActivity.this, msg.getData().getString("DATA"), Toast.LENGTH_LONG).show();
} else if (msg.what == DOWNLOAD) {
String savePath = context.getCacheDir().getPath() + File.separator + BackupDbTask.BACKUP_FILE;
Log.d(TAG, savePath);
downloadFile(msg.getData().getString("URL"), context.getCacheDir().getPath(), BackupDbTask.BACKUP_FILE);
}
}
};
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -49,24 +81,27 @@ public class CloneActivity extends AppCompatActivity {
setContentView(R.layout.activity_clone); setContentView(R.layout.activity_clone);
Log.d(TAG, "onCreate: " + RebootBroadcastReceiver.class.getName()); Log.d(TAG, "onCreate: " + RebootBroadcastReceiver.class.getName());
HttpUtil.init(this);
HttpServer.init(this);
} }
@SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
Log.d(TAG, "onStart"); Log.d(TAG, "onStart");
IPEditText textServerIp = findViewById(R.id.textServerIp); sendBtn = findViewById(R.id.sendBtn);
sendTxt = findViewById(R.id.sendTxt);
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); TextView ipText = findViewById(R.id.ipText);
textServerIp = findViewById(R.id.textServerIp);
receiveTxt = findViewById(R.id.receiveTxt);
Button receiveBtn = findViewById(R.id.receiveBtn);
serverIp = NetUtil.getLocalIp(CloneActivity.this);
ipText.setText(serverIp); ipText.setText(serverIp);
if (HttpServer.asRunning()) { if (HttpServer.asRunning()) {
sendBtn.setText(R.string.stop); sendBtn.setText(R.string.stop);
sendTxt.setText(R.string.server_has_started); sendTxt.setText(R.string.server_has_started);
@ -77,9 +112,15 @@ public class CloneActivity extends AppCompatActivity {
} }
sendBtn.setOnClickListener(v -> { sendBtn.setOnClickListener(v -> {
if (!HttpServer.asRunning() && 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(); Toast(handError, TAG, getString(R.string.no_wifi_network));
return; return;
} }
//备份文件
BackupDbTask task = new BackupDbTask(this);
String backup_version = task.doInBackground(BackupDbTask.COMMAND_BACKUP);
Log.d(TAG, "backup_version = " + backup_version);
SettingUtil.switchEnableHttpServer(!SettingUtil.getSwitchEnableHttpServer()); SettingUtil.switchEnableHttpServer(!SettingUtil.getSwitchEnableHttpServer());
if (!HttpServer.update()) { if (!HttpServer.update()) {
SettingUtil.switchEnableHttpServer(!SettingUtil.getSwitchEnableHttpServer()); SettingUtil.switchEnableHttpServer(!SettingUtil.getSwitchEnableHttpServer());
@ -96,128 +137,106 @@ public class CloneActivity extends AppCompatActivity {
} }
}); });
Button receiveBtn = findViewById(R.id.receiveBtn);
receiveBtn.setOnClickListener(v -> { receiveBtn.setOnClickListener(v -> {
if (HttpServer.asRunning()) { if (HttpServer.asRunning()) {
receiveTxt.setText(R.string.sender_cannot_receive); receiveTxt.setText(R.string.sender_cannot_receive);
Toast.makeText(CloneActivity.this, R.string.sender_cannot_receive, Toast.LENGTH_SHORT).show(); Toast(handError, TAG, getString(R.string.sender_cannot_receive));
return; return;
} }
if (NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) { if (NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) {
receiveTxt.setText(R.string.no_wifi_network); receiveTxt.setText(R.string.no_wifi_network);
Toast.makeText(CloneActivity.this, R.string.no_wifi_network, Toast.LENGTH_SHORT).show(); Toast(handError, TAG, getString(R.string.no_wifi_network));
return; return;
} }
serverIp = textServerIp.getIP(); serverIp = textServerIp.getIP();
if (serverIp == null || serverIp.isEmpty()) { if (serverIp == null || serverIp.isEmpty()) {
receiveTxt.setText(R.string.invalid_server_ip); receiveTxt.setText(R.string.invalid_server_ip);
Toast.makeText(CloneActivity.this, R.string.invalid_server_ip, Toast.LENGTH_SHORT).show(); Toast(handError, TAG, getString(R.string.invalid_server_ip));
return; return;
} }
//下载连接 OkHttpClient.Builder builder = new OkHttpClient.Builder();
final String url = "http://" + serverIp + ":5000/"; //设置读取超时时间
Log.d(TAG, url); OkHttpClient client = builder
//保存路径 .readTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
final String savePath = context.getCacheDir().getPath() + File.separator + DATABASE_NAME; .writeTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
Log.d(TAG, savePath); .connectTimeout(Define.REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)
final long startTime = System.currentTimeMillis(); .build();
Log.i(TAG, "startTime=" + startTime);
OkHttpClient okHttpClient = new OkHttpClient(); Map msgMap = new HashMap();
Request request = new Request.Builder().url(url).addHeader("Connection", "close").build(); msgMap.put("versionCode", SettingUtil.getVersionCode());
okHttpClient.newCall(request).enqueue(new Callback() { msgMap.put("versionName", SettingUtil.getVersionName());
String requestMsg = JSON.toJSONString(msgMap);
Log.i(TAG, "requestMsg:" + requestMsg);
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), requestMsg);
//请求链接post 获取版本信息get 下载备份文件
final String requestUrl = "http://" + serverIp + ":" + Define.HTTP_SERVER_PORT + BaseServlet.CLONE_PATH + "?" + System.currentTimeMillis();
Log.i(TAG, "requestUrl:" + requestUrl);
//获取版本信息
final Request request = new Request.Builder()
.url(requestUrl)
.addHeader("Content-Type", "application/json; charset=utf-8")
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override @Override
public void onFailure(@NonNull Call call, @NonNull IOException e) { public void onFailure(@NonNull Call call, @NonNull final IOException e) {
e.printStackTrace(); Toast(handError, TAG, "从发送端获取一键克隆信息失败");
//Toast.makeText(CloneActivity.this, R.string.download_failed + e.getMessage(), Toast.LENGTH_SHORT).show();
} }
@Override @Override
public void onResponse(@NonNull Call call, @NonNull Response response) { public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
InputStream is = null; final String responseStr = Objects.requireNonNull(response.body()).string();
byte[] buf = new byte[2048]; Log.d(TAG, "Response" + response.code() + "" + responseStr);
int len;
FileOutputStream fos = null; if (TextUtils.isEmpty(responseStr)) {
Toast(handError, TAG, "从发送端获取一键克隆信息失败");
return;
}
try { try {
is = Objects.requireNonNull(response.body()).byteStream(); CloneInfoVo cloneInfoVo = JSON.parseObject(responseStr, CloneInfoVo.class);
long total = Objects.requireNonNull(response.body()).contentLength(); if (SettingUtil.getVersionCode() != cloneInfoVo.getVersionCode()) {
File file = new File(savePath, url.substring(url.lastIndexOf("/") + 1)); Toast(handError, TAG, "发送端与接收端的APP版本不一致无法一键克隆");
fos = new FileOutputStream(file); return;
long sum = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
int progress = (int) (sum * 1.0f / total * 100);
Log.e(TAG, "download progress : " + progress);
} }
fos.flush();
Log.e(TAG, "download success"); //应用配置
Log.e(TAG, "totalTime=" + (System.currentTimeMillis() - startTime)); SettingUtil.switchEnableSms(cloneInfoVo.isEnableSms());
//Toast.makeText(CloneActivity.this, R.string.download_success, Toast.LENGTH_SHORT).show(); SettingUtil.switchEnablePhone(cloneInfoVo.isEnablePhone());
SettingUtil.switchCallType1(cloneInfoVo.isCallType1());
SettingUtil.switchCallType2(cloneInfoVo.isCallType2());
SettingUtil.switchCallType3(cloneInfoVo.isCallType3());
SettingUtil.switchEnableAppNotify(cloneInfoVo.isEnableAppNotify());
SettingUtil.switchCancelAppNotify(cloneInfoVo.isCancelAppNotify());
SettingUtil.smsHubApiUrl(cloneInfoVo.getSmsHubApiUrl());
SettingUtil.setBatteryLevelAlarmMin(cloneInfoVo.getBatteryLevelAlarmMin());
SettingUtil.setBatteryLevelAlarmMax(cloneInfoVo.getBatteryLevelAlarmMax());
SettingUtil.switchBatteryLevelAlarmOnce(cloneInfoVo.isBatteryLevelAlarmOnce());
SettingUtil.setRetryTimes(cloneInfoVo.getRetryTimes());
SettingUtil.setDelayTime(cloneInfoVo.getDelayTime());
SettingUtil.switchSmsTemplate(cloneInfoVo.isEnableSmsTemplate());
SettingUtil.setSmsTemplate(cloneInfoVo.getSmsTemplate());
//下载备份文件
Message msg = new Message();
msg.what = DOWNLOAD;
Bundle bundle = new Bundle();
bundle.putString("URL", requestUrl);
msg.setData(bundle);
handError.sendMessage(msg);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Toast(handError, TAG, "一键克隆失败:" + e.getMessage());
//Toast.makeText(CloneActivity.this, R.string.download_failed + e.getMessage(), Toast.LENGTH_SHORT).show();
} finally {
try {
if (is != null) is.close();
} catch (IOException ignored) {
}
try {
if (fos != null) fos.close();
} catch (IOException ignored) {
}
} }
} }
}); });
//TODO:替换sqlite
File dbFile = new File(savePath);
FileInputStream fis;
try {
fis = new FileInputStream(dbFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
String outFileName = context.getDatabasePath(DATABASE_NAME).getAbsolutePath();
Log.d(TAG, outFileName);
// Open the empty db as the output stream
OutputStream output;
try {
output = new FileOutputStream(outFileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
// Transfer bytes from the input file to the output file
byte[] buffer = new byte[1024];
int length;
while (true) {
try {
if (!((length = fis.read(buffer)) > 0)) break;
output.write(buffer, 0, length);
} catch (IOException e) {
e.printStackTrace();
}
}
// Close the streams
try {
output.flush();
output.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
LogUtil.delLog(null, null);
receiveTxt.setText(R.string.download_success);
}); });
} }
@ -231,4 +250,63 @@ public class CloneActivity extends AppCompatActivity {
TextView ipText = findViewById(R.id.ipText); TextView ipText = findViewById(R.id.ipText);
ipText.setText(getString(R.string.local_ip) + serverIp); ipText.setText(getString(R.string.local_ip) + serverIp);
} }
/**
* 文件下载
*
* @param url 下载链接
*/
public void downloadFile(String url, final String destFileDir, final String destFileName) {
ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setTitle("正在下载");
progressDialog.setMessage("请稍后...");
progressDialog.setProgress(0);
progressDialog.setMax(100);
progressDialog.show();
progressDialog.setCancelable(false);
DownloadUtil.get().download(url, destFileDir, destFileName, new DownloadUtil.OnDownloadListener() {
@Override
public void onDownloadSuccess(File file) {
if (progressDialog.isShowing()) {
Toast(handError, TAG, "下载完成,正准备还原数据...");
progressDialog.dismiss();
}
//下载完成进行相关逻辑操作
Log.d(TAG, file.getPath());
//还原数据库
BackupDbTask task = new BackupDbTask(context);
String backup_version = task.doInBackground(BackupDbTask.COMMAND_RESTORE);
Log.d(TAG, "backup_version = " + backup_version);
Toast(handError, TAG, "一键克隆操作成功!请进入通用设置检查各项开关是否已开启!");
}
@Override
public void onDownloading(int progress) {
progressDialog.setProgress(progress);
}
@SuppressLint("SetTextI18n")
@Override
public void onDownloadFailed(Exception e) {
//下载异常进行相关提示操作
Log.e(TAG, "下载失败:" + e.getMessage());
Toast(handError, TAG, "下载失败:" + e.getMessage());
}
});
}
public static void Toast(Handler handError, String Tag, String data) {
Log.i(Tag, data);
if (handError != null) {
Message msg = new Message();
msg.what = TOAST;
Bundle bundle = new Bundle();
bundle.putString("DATA", data);
msg.setData(bundle);
handError.sendMessage(msg);
}
}
} }

View File

@ -29,7 +29,7 @@ import java.util.Objects;
* UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告
*/ */
public class CrashHandler implements UncaughtExceptionHandler { public class CrashHandler implements UncaughtExceptionHandler {
public static final String TAG = "CrashHandler"; private static final String TAG = "CrashHandler";
//系统默认的UncaughtException处理类 //系统默认的UncaughtException处理类
private Thread.UncaughtExceptionHandler mDefaultHandler; private Thread.UncaughtExceptionHandler mDefaultHandler;
//CrashHandler实例 //CrashHandler实例

View File

@ -0,0 +1,28 @@
package com.idormy.sms.forwarder.model.vo;
import java.io.Serializable;
import lombok.Data;
@Data
public class CloneInfoVo implements Serializable {
private int versionCode;
private String versionName;
private boolean enableSms;
private boolean enablePhone;
private boolean callType1;
private boolean callType2;
private boolean callType3;
private boolean enableAppNotify;
private boolean cancelAppNotify;
private String smsHubApiUrl;
private int batteryLevelAlarmMin;
private int batteryLevelAlarmMax;
private boolean batteryLevelAlarmOnce;
private int retryTimes;
private int delayTime;
private boolean enableSmsTemplate;
private String smsTemplate;
}

View File

@ -6,8 +6,9 @@ import android.util.Log;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.util.IOUtils; 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.model.vo.SmsHubVo;
import com.idormy.sms.forwarder.utils.BackupDbTask;
import com.idormy.sms.forwarder.utils.SettingUtil;
import com.idormy.sms.forwarder.utils.SmsHubActionHandler; import com.idormy.sms.forwarder.utils.SmsHubActionHandler;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
@ -21,7 +22,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.Reader; import java.io.Reader;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.servlet.MultipartConfigElement; import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
@ -36,7 +39,7 @@ import javax.servlet.http.HttpServletResponse;
public class BaseServlet extends HttpServlet { public class BaseServlet extends HttpServlet {
public static final int BUFFER_SIZE = 1 << 12; public static final int BUFFER_SIZE = 1 << 12;
public static final String CLONE_PATH = "/"; public static final String CLONE_PATH = "/clone";
public static final String SMSHUB_PATH = "/send_api"; public static final String SMSHUB_PATH = "/send_api";
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final String TAG = "BaseServlet"; private static final String TAG = "BaseServlet";
@ -87,12 +90,27 @@ public class BaseServlet extends HttpServlet {
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String msg = "HTTP method POST is not supported by this URL";
if (CLONE_PATH.equals(path)) { if (CLONE_PATH.equals(path)) {
clone(req, resp); clone_api(req, resp);
} else if (SMSHUB_PATH.equals(path)) { } else if (SMSHUB_PATH.equals(path)) {
send_api(req, resp); send_api(req, resp);
} else if ("1.1".endsWith(req.getProtocol())) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else { } else {
notFound(req, resp); resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String msg = "HTTP method GET is not supported by this URL";
if (CLONE_PATH.equals(path)) {
clone(req, resp);
} else if ("1.1".endsWith(req.getProtocol())) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
} }
} }
@ -155,9 +173,48 @@ public class BaseServlet extends HttpServlet {
writer.println(text); writer.println(text);
} }
//一键克隆查询接口
@SuppressWarnings({"unchecked", "rawtypes"})
private void clone_api(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setCharacterEncoding("utf-8");
PrintWriter writer = resp.getWriter();
BufferedReader reader = req.getReader();
try {
Map msgMap = new HashMap();
msgMap.put("versionCode", SettingUtil.getVersionCode());
msgMap.put("versionName", SettingUtil.getVersionName());
msgMap.put("enableSms", SettingUtil.getSwitchEnableSms());
msgMap.put("enablePhone", SettingUtil.getSwitchEnablePhone());
msgMap.put("callType1", SettingUtil.getSwitchCallType1());
msgMap.put("callType2", SettingUtil.getSwitchCallType2());
msgMap.put("callType3", SettingUtil.getSwitchCallType3());
msgMap.put("enableAppNotify", SettingUtil.getSwitchEnableAppNotify());
msgMap.put("cancelAppNotify", SettingUtil.getSwitchCancelAppNotify());
msgMap.put("smsHubApiUrl", SettingUtil.getSmsHubApiUrl());
msgMap.put("batteryLevelAlarmMin", SettingUtil.getBatteryLevelAlarmMin());
msgMap.put("batteryLevelAlarmMax", SettingUtil.getBatteryLevelAlarmMax());
msgMap.put("batteryLevelAlarmOnce", SettingUtil.getBatteryLevelAlarmOnce());
msgMap.put("retryTimes", SettingUtil.getRetryTimes());
msgMap.put("delayTime", SettingUtil.getDelayTime());
msgMap.put("enableSmsTemplate", SettingUtil.getSwitchSmsTemplate());
msgMap.put("smsTemplate", SettingUtil.getSmsTemplate());
resp.setContentType("application/json;charset=utf-8");
String text = JSON.toJSONString(msgMap);
writer.println(text);
} catch (Exception e) {
e.printStackTrace();
printErrMsg(resp, writer, e);
} finally {
IOUtils.close(reader);
IOUtils.close(writer);
}
}
//一键克隆下载接口
private void clone(HttpServletRequest req, HttpServletResponse resp) throws IOException { private void clone(HttpServletRequest req, HttpServletResponse resp) throws IOException {
File file = context.getDatabasePath(CloneActivity.DATABASE_NAME); File file = new File(context.getCacheDir().getPath() + File.separator + BackupDbTask.BACKUP_FILE);
resp.addHeader("Content-Disposition", "attachment;filename=" + CloneActivity.DATABASE_NAME); resp.addHeader("Content-Disposition", "attachment;filename=" + BackupDbTask.BACKUP_FILE);
ServletOutputStream outputStream = resp.getOutputStream(); ServletOutputStream outputStream = resp.getOutputStream();
InputStream inputStream = new FileInputStream(file); InputStream inputStream = new FileInputStream(file);
try { try {

View File

@ -8,6 +8,7 @@ import android.widget.Toast;
import com.idormy.sms.forwarder.R; import com.idormy.sms.forwarder.R;
import com.idormy.sms.forwarder.model.vo.SmsHubVo; import com.idormy.sms.forwarder.model.vo.SmsHubVo;
import com.idormy.sms.forwarder.receiver.BaseServlet; import com.idormy.sms.forwarder.receiver.BaseServlet;
import com.idormy.sms.forwarder.utils.Define;
import com.idormy.sms.forwarder.utils.NetUtil; import com.idormy.sms.forwarder.utils.NetUtil;
import com.idormy.sms.forwarder.utils.SettingUtil; import com.idormy.sms.forwarder.utils.SettingUtil;
import com.idormy.sms.forwarder.utils.SmsHubActionHandler; import com.idormy.sms.forwarder.utils.SmsHubActionHandler;
@ -16,7 +17,6 @@ import org.eclipse.jetty.server.Server;
public class HttpServer { public class HttpServer {
private static final int port = 5000;
private static Boolean hasInit = false; private static Boolean hasInit = false;
private static Server jettyServer; private static Server jettyServer;
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
@ -33,7 +33,7 @@ public class HttpServer {
hasInit = true; hasInit = true;
HttpServer.context = context; HttpServer.context = context;
SmsHubActionHandler.init(context); SmsHubActionHandler.init(context);
jettyServer = new Server(port); jettyServer = new Server(Define.HTTP_SERVER_PORT);
BaseServlet.addServlet(jettyServer, context); BaseServlet.addServlet(jettyServer, context);
} }
} }
@ -102,13 +102,12 @@ public class HttpServer {
//}).start(); //}).start();
} }
private static void stop() { private static void stop() {
if (Boolean.FALSE.equals(asStopp())) { if (Boolean.FALSE.equals(asStopp())) {
try { try {
if (jettyServer != null) { if (jettyServer != null) {
jettyServer.stop(); jettyServer.stop();
// jettyServer = new Server(port); //jettyServer = new Server(port);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -0,0 +1,118 @@
package com.idormy.sms.forwarder.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.channels.FileChannel;
@SuppressWarnings("ResultOfMethodCallIgnored")
public class BackupDbTask {
private static final String TAG = "BackupDbTask";
public static final String COMMAND_BACKUP = "backupDatabase";
public static final String COMMAND_RESTORE = "restoreDatabase";
public final static String BACKUP_FOLDER = "SmsForwarder";
public final static String BACKUP_FILE = "SmsForwarder.zip";
public String backup_version;
@SuppressLint("StaticFieldLeak")
private static Context mContext;
public BackupDbTask(Context context) {
mContext = context;
}
private static File getExternalStoragePublicDir() {
// /sdcard/SmsForwarder/
String path = Environment.getExternalStorageDirectory() + File.separator + BACKUP_FOLDER + File.separator;
File dir = new File(path);
if (!dir.exists()) dir.mkdirs();
return dir;
}
public String doInBackground(String command) {
File dbFile = mContext.getDatabasePath(DbHelper.DATABASE_NAME);// 默认路径是 /data/data/(包名)/databases/*
File dbFile_shm = mContext.getDatabasePath(DbHelper.DATABASE_NAME + "-journal");// 默认路径是 /data/data/(包名)/databases/*
//File dbFile_wal = mContext.getDatabasePath("event_database-wal");// 默认路径是 /data/data/(包名)/databases/*
String bakFolder = mContext.getCacheDir().getPath() + File.separator + BACKUP_FOLDER;
String zipFile = mContext.getCacheDir().getPath() + File.separator + BACKUP_FILE;
Log.d(TAG, "备份目录名:" + bakFolder + ",备份文件名:" + zipFile);
File exportDir = new File(mContext.getCacheDir().getPath(), BACKUP_FOLDER);//直接丢在 cache 目录可以在在关于目录下清除缓存
if (!exportDir.exists()) exportDir.mkdirs();
File backup = new File(bakFolder, dbFile.getName());//备份文件与原数据库文件名一致
File backup_shm = new File(bakFolder, dbFile_shm.getName());//备份文件与原数据库文件名一致
//File backup_wal = new File(bakFolder, dbFile_wal.getName());//备份文件与原数据库文件名一致
if (command.equals(COMMAND_BACKUP)) {
try {
//备份文件
backup.createNewFile();
backup_shm.createNewFile();
//backup_wal.createNewFile();
fileCopy(dbFile, backup);//数据库文件拷贝至备份文件
fileCopy(dbFile_shm, backup_shm);//数据库文件拷贝至备份文件
//fileCopy(dbFile_wal, backup_wal);//数据库文件拷贝至备份文件
//backup.setLastModified(MyTimeUtils.getTimeLong());
backup_version = TimeUtil.getTimeString("yyyy.MM.dd_HH:mm:ss");
Log.d(TAG, "backup ok! 备份目录:" + backup.getName() + "\t" + backup_version);
//打包文件
ZipUtils.ZipFolder(bakFolder, zipFile);
Log.d(TAG, "备份Zip包" + zipFile);
return backup_version;
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "backup fail! 备份文件名:" + backup.getName());
return null;
}
} else if (command.equals(COMMAND_RESTORE)) {
try {
//解压文件
ZipUtils.UnZipFolder(zipFile, bakFolder);
Log.d(TAG, "解压Zip包" + zipFile);
//还原文件
fileCopy(backup, dbFile);//备份文件拷贝至数据库文件
fileCopy(backup_shm, dbFile_shm);//备份文件拷贝至数据库文件
//fileCopy(backup_wal, dbFile_wal);//备份文件拷贝至数据库文件
backup_version = TimeUtil.getTimeString(backup.lastModified(), "yyyy.MM.dd_HH:mm:ss");
Log.d(TAG, "restore success! 数据库文件名:" + dbFile.getName() + "\t" + backup_version);
try {
//noinspection BusyWait
Thread.sleep(1000);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new InterruptedIOException(e.getMessage());
}
LogUtil.delLog(null, null);
return backup_version;
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "restore fail! 数据库文件名:" + dbFile.getName());
return null;
}
} else {
return null;
}
}
private void fileCopy(File dbFile, File backup) {
try (FileChannel inChannel = new FileInputStream(dbFile).getChannel(); FileChannel outChannel = new FileOutputStream(backup).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -36,4 +36,6 @@ public class Define {
//OkHttp 请求超时时间 //OkHttp 请求超时时间
public static final int REQUEST_TIMEOUT_SECONDS = 5; public static final int REQUEST_TIMEOUT_SECONDS = 5;
//HttpServer 服务端口
public static final int HTTP_SERVER_PORT = 5000;
} }

View File

@ -0,0 +1,103 @@
package com.idormy.sms.forwarder.utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class DownloadUtil {
private static DownloadUtil downloadUtil;
private final OkHttpClient okHttpClient;
public static DownloadUtil get() {
if (downloadUtil == null) {
downloadUtil = new DownloadUtil();
}
return downloadUtil;
}
private DownloadUtil() {
okHttpClient = new OkHttpClient();
}
/**
* @param url 下载连接
* @param destFileDir 下载的文件储存目录
* @param destFileName 下载文件名称
* @param listener 下载监听
*/
public void download(final String url, final String destFileDir, final String destFileName, final OnDownloadListener listener) {
Request request = new Request.Builder().url(url).addHeader("Connection", "close").build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 下载失败监听回调
listener.onDownloadFailed(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
// 储存下载文件的目录
File dir = new File(destFileDir);
if (!dir.exists()) dir.mkdirs();
File file = new File(dir, destFileName);
if (file.exists()) file.delete();
try {
is = response.body().byteStream();
long total = response.body().contentLength();
fos = new FileOutputStream(file);
long sum = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
int progress = (int) (sum * 1.0f / total * 100);
// 下载中更新进度条
listener.onDownloading(progress);
}
fos.flush();
// 下载完成
listener.onDownloadSuccess(file);
} catch (Exception e) {
listener.onDownloadFailed(e);
} finally {
try {
if (is != null) is.close();
if (fos != null) fos.close();
} catch (IOException e) {
}
}
}
});
}
public interface OnDownloadListener {
/**
* @param file 下载成功后的文件
*/
void onDownloadSuccess(File file);
/**
* @param progress 下载进度
*/
void onDownloading(int progress);
/**
* @param e 下载异常信息
*/
void onDownloadFailed(Exception e);
}
}

View File

@ -22,7 +22,6 @@ public class NetUtil {
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
static Context context; static Context context;
public static void init(Context context1) { public static void init(Context context1) {
//noinspection SynchronizeOnNonFinalField //noinspection SynchronizeOnNonFinalField
synchronized (hasInit) { synchronized (hasInit) {

View File

@ -281,6 +281,19 @@ public class SettingUtil {
} }
} }
//获取应用的版本号
public static int getVersionCode() {
PackageManager manager = context.getPackageManager();
int code = 0;
try {
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
code = info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return code;
}
private static String getString(int resId) { private static String getString(int resId) {
return MyApplication.getContext().getString(resId); return MyApplication.getContext().getString(resId);
} }

View File

@ -77,4 +77,13 @@ public class TimeUtil {
return localFormatter.format(utcDate.getTime()); return localFormatter.format(utcDate.getTime());
} }
public static String getTimeString(String pattern) {
return new SimpleDateFormat(pattern, Locale.CHINESE).format(new Date());
}
public static String getTimeString(long time, String pattern) {
SimpleDateFormat df = new SimpleDateFormat(pattern, Locale.CHINESE);
return df.format(new Date(time));
}
} }

View File

@ -0,0 +1,210 @@
package com.idormy.sms.forwarder.utils;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Created by YuShuangPing on 2018/11/11.
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
public class ZipUtils {
public static final String TAG = "ZIP";
public ZipUtils() {
}
/**
* 解压zip到指定的路径
*
* @param zipFileString ZIP的名称
* @param outPathString 要解压缩路径
* @throws Exception 异常抛出
*/
public static void UnZipFolder(String zipFileString, String outPathString) throws Exception {
ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString));
ZipEntry zipEntry;
String szName;
while ((zipEntry = inZip.getNextEntry()) != null) {
szName = zipEntry.getName();
if (zipEntry.isDirectory()) {
//获取部件的文件夹名
szName = szName.substring(0, szName.length() - 1);
File folder = new File(outPathString + File.separator + szName);
folder.mkdirs();
} else {
Log.e(TAG, outPathString + File.separator + szName);
File file = new File(outPathString + File.separator + szName);
if (!file.exists()) {
Log.e(TAG, "Create the file:" + outPathString + File.separator + szName);
Objects.requireNonNull(file.getParentFile()).mkdirs();
file.createNewFile();
}
// 获取文件的输出流
FileOutputStream out = new FileOutputStream(file);
int len;
byte[] buffer = new byte[1024];
// 读取字节字节到缓冲区
while ((len = inZip.read(buffer)) != -1) {
// 从缓冲区0位置写入字节字节
out.write(buffer, 0, len);
out.flush();
}
out.close();
}
}
inZip.close();
}
public static void UnZipFolder(String zipFileString, String outPathString, String szName) throws Exception {
ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString));
ZipEntry zipEntry;
while ((zipEntry = inZip.getNextEntry()) != null) {
//szName = zipEntry.getName();
if (zipEntry.isDirectory()) {
//获取部件的文件夹名
szName = szName.substring(0, szName.length() - 1);
File folder = new File(outPathString + File.separator + szName);
folder.mkdirs();
} else {
Log.e(TAG, outPathString + File.separator + szName);
File file = new File(outPathString + File.separator + szName);
if (!file.exists()) {
Log.e(TAG, "Create the file:" + outPathString + File.separator + szName);
Objects.requireNonNull(file.getParentFile()).mkdirs();
file.createNewFile();
}
// 获取文件的输出流
FileOutputStream out = new FileOutputStream(file);
int len;
byte[] buffer = new byte[1024];
// 读取字节字节到缓冲区
while ((len = inZip.read(buffer)) != -1) {
// 从缓冲区0位置写入字节字节
out.write(buffer, 0, len);
out.flush();
}
out.close();
}
}
inZip.close();
}
/**
* 压缩文件和文件夹
*
* @param srcFileString 要压缩的文件或文件夹
* @param zipFileString 解压完成的Zip路径
* @throws Exception 异常抛出
*/
public static void ZipFolder(String srcFileString, String zipFileString) throws Exception {
//创建ZIP
ZipOutputStream outZip = new ZipOutputStream(new FileOutputStream(zipFileString));
//创建文件
File file = new File(srcFileString);
//压缩
Log.d(TAG, "---->" + file.getParent() + "===" + file.getAbsolutePath());
ZipFiles(file.getParent() + File.separator, file.getName(), outZip);
//完成和关闭
outZip.finish();
outZip.close();
}
/**
* 压缩文件
*
* @param folderString 文件夹
* @param fileString 文件
* @param zipOutputSteam zip输出流
* @throws Exception 异常抛出
*/
private static void ZipFiles(String folderString, String fileString, ZipOutputStream zipOutputSteam) throws Exception {
Log.d(TAG, "folderString:" + folderString + "\n" +
"fileString:" + fileString + "\n==========================");
if (zipOutputSteam == null)
return;
File file = new File(folderString + fileString);
if (file.isFile()) {
ZipEntry zipEntry = new ZipEntry(fileString);
FileInputStream inputStream = new FileInputStream(file);
zipOutputSteam.putNextEntry(zipEntry);
int len;
byte[] buffer = new byte[4096];
while ((len = inputStream.read(buffer)) != -1) {
zipOutputSteam.write(buffer, 0, len);
}
zipOutputSteam.closeEntry();
} else {
//文件夹
String[] fileList = file.list();
//没有子文件和压缩
if (Objects.requireNonNull(fileList).length <= 0) {
ZipEntry zipEntry = new ZipEntry(fileString + File.separator);
zipOutputSteam.putNextEntry(zipEntry);
zipOutputSteam.closeEntry();
}
//子文件和递归
for (String s : fileList) {
ZipFiles(folderString + fileString + "/", s, zipOutputSteam);
}
}
}
/**
* 返回zip的文件输入流
*
* @param zipFileString zip的名称
* @param fileString ZIP的文件名
* @return InputStream 输出流
* @throws Exception 异常抛出
*/
public static InputStream UpZip(String zipFileString, String fileString) throws Exception {
ZipFile zipFile = new ZipFile(zipFileString);
ZipEntry zipEntry = zipFile.getEntry(fileString);
return zipFile.getInputStream(zipEntry);
}
/**
* 返回ZIP中的文件列表文件和文件夹
*
* @param zipFileString ZIP的名称
* @param bContainFolder 是否包含文件夹
* @param bContainFile 是否包含文件
* @throws Exception 异常抛出
*/
public static List<File> GetFileList(String zipFileString, boolean bContainFolder, boolean bContainFile) throws Exception {
List<File> fileList = new ArrayList<>();
ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFileString));
ZipEntry zipEntry;
String szName;
while ((zipEntry = inZip.getNextEntry()) != null) {
szName = zipEntry.getName();
if (zipEntry.isDirectory()) {
// 获取部件的文件夹名
szName = szName.substring(0, szName.length() - 1);
File folder = new File(szName);
if (bContainFolder) {
fileList.add(folder);
}
} else {
File file = new File(szName);
if (bContainFile) {
fileList.add(file);
}
}
}
inZip.close();
return fileList;
}
}

View File

@ -73,9 +73,7 @@ public class StepBar extends LinearLayout {
mTypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.StepBar); mTypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.StepBar);
if (mTypedArray != null) { if (mTypedArray != null) {
current_step = mTypedArray.getString(R.styleable.StepBar_current_step); current_step = mTypedArray.getString(R.styleable.StepBar_current_step);
System.out.println("current_step = " + current_step);
help_tip = mTypedArray.getString(R.styleable.StepBar_help_tip); help_tip = mTypedArray.getString(R.styleable.StepBar_help_tip);
System.out.println("help_tip = " + help_tip);
mTypedArray.recycle(); mTypedArray.recycle();
} }
} }

View File

@ -132,7 +132,7 @@
android:id="@+id/cbCallType3" android:id="@+id/cbCallType3"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="-5dp" android:layout_marginStart="-6dp"
android:scaleX="0.7" android:scaleX="0.7"
android:scaleY="0.7" android:scaleY="0.7"
android:text="@string/missed_call" /> android:text="@string/missed_call" />
@ -141,7 +141,7 @@
android:id="@+id/cbCallType1" android:id="@+id/cbCallType1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="-15dp" android:layout_marginStart="-16dp"
android:scaleX="0.7" android:scaleX="0.7"
android:scaleY="0.7" android:scaleY="0.7"
android:text="@string/received_call" /> android:text="@string/received_call" />
@ -150,7 +150,7 @@
android:id="@+id/cbCallType2" android:id="@+id/cbCallType2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="-15dp" android:layout_marginStart="-16dp"
android:scaleX="0.7" android:scaleX="0.7"
android:scaleY="0.7" android:scaleY="0.7"
android:text="@string/local_outgoing_call" /> android:text="@string/local_outgoing_call" />
@ -217,7 +217,7 @@
android:id="@+id/checkbox_cancel_app_notify" android:id="@+id/checkbox_cancel_app_notify"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="-5dp" android:layout_marginStart="-6dp"
android:scaleX="0.7" android:scaleX="0.7"
android:scaleY="0.7" android:scaleY="0.7"
android:text="@string/cancel_app_notify" /> android:text="@string/cancel_app_notify" />

View File

@ -218,9 +218,9 @@
<string name="get">GET</string> <string name="get">GET</string>
<!--CloneActivity--> <!--CloneActivity-->
<string name="local_ip">Local IP</string> <string name="local_ip">Local IP</string>
<string name="operating_instruction">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!</string> <!-- 原文是“新旧手机”,英文翻译中处理为“源”手机和“目标”手机,因为担心“新旧”的表述引起混淆(有没一种可能就是用户就是用从新手机的设备复制到旧手机上去呢?)。 --> <string name="operating_instruction">Instructions: \n[Note] The APP version of the sender and receiver must be the same!\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!</string> <!-- 原文是“新旧手机”,英文翻译中处理为“源”手机和“目标”手机,因为担心“新旧”的表述引起混淆(有没一种可能就是用户就是用从新手机的设备复制到旧手机上去呢?)。 -->
<string name="send">Start Server</string> <string name="send">Start</string>
<string name="stop">Stop Server</string> <string name="stop">Stop</string>
<string name="old_mobile_phone">I\'m the SCOURCE phone</string> <string name="old_mobile_phone">I\'m the SCOURCE phone</string>
<string name="receive">Receive</string> <string name="receive">Receive</string>
<string name="new_mobile_phone">I\'m the DESTINATION phone</string> <string name="new_mobile_phone">I\'m the DESTINATION phone</string>

View File

@ -16,7 +16,7 @@
<string name="rule_setting">转发规则</string> <string name="rule_setting">转发规则</string>
<string name="sender_setting">发送通道</string> <string name="sender_setting">发送通道</string>
<string name="app_list">应用列表</string> <string name="app_list">应用列表</string>
<string name="setting_tips">提示:首次使用请按照以下步骤顺序设置,该步骤点亮表示已设置!</string> <string name="setting_tips">提示:首次使用按照1234步骤顺序设置数字点亮即步骤已设置</string>
<string name="log_tips">提示:置顶下拉刷新,长按删除单条,选项卡切换日志类型</string> <string name="log_tips">提示:置顶下拉刷新,长按删除单条,选项卡切换日志类型</string>
<string name="rule_tips">提示:新建规则点击“添加”,长按删除/克隆,点击编辑已有</string> <string name="rule_tips">提示:新建规则点击“添加”,长按删除/克隆,点击编辑已有</string>
<string name="sender_tips">提示:新建发送通道点击“添加”,长按删除/克隆,点击编辑已有</string> <string name="sender_tips">提示:新建发送通道点击“添加”,长按删除/克隆,点击编辑已有</string>
@ -217,9 +217,9 @@
<string name="post">POST</string> <string name="post">POST</string>
<string name="get">GET</string> <string name="get">GET</string>
<string name="local_ip">本机IP</string> <string name="local_ip">本机IP</string>
<string name="operating_instruction">操作说明:\n1.新旧手机连接同一个WiFi网络(禁用AP隔离)\n2.旧手机直接点【发送】按钮获取到【服务端IP】\n3.新手机填写【服务端IP】后点【接收】按钮\n【注意】新手机接收后发送通道、转发规则将完全被覆盖清空历史记录</string> <string name="operating_instruction">操作说明:\n【注意】发送方与接收方的APP版本号必须一致!\n1.新旧手机连接同一个WiFi网络(禁用AP隔离)\n2.旧手机直接点【发送】按钮获取到【服务端IP】\n3.新手机填写【服务端IP】后点【接收】按钮\n【注意】新手机接收后发送通道、转发规则将完全被覆盖清空历史记录</string>
<string name="send">启动服务</string> <string name="send">启动</string>
<string name="stop">停止服务</string> <string name="stop">停止</string>
<string name="old_mobile_phone">我是旧手机</string> <string name="old_mobile_phone">我是旧手机</string>
<string name="receive">接收</string> <string name="receive">接收</string>
<string name="new_mobile_phone">我是新手机</string> <string name="new_mobile_phone">我是新手机</string>
@ -229,7 +229,7 @@
<string name="server_has_started">服务端已启动</string> <string name="server_has_started">服务端已启动</string>
<string name="server_has_stopped">服务端已停止</string> <string name="server_has_stopped">服务端已停止</string>
<string name="sender_cannot_receive">本手机是发送端,不可接收文件,请先停止服务端!</string> <string name="sender_cannot_receive">本手机是发送端,不可接收文件,请先停止服务端!</string>
<string name="no_wifi_network">未接入Wifi网络不可使用 HttpServer!</string> <string name="no_wifi_network">未接入Wifi网络不可使用 HttpServer 功能!</string>
<string name="invalid_server_ip">请输入服务端IP</string> <string name="invalid_server_ip">请输入服务端IP</string>
<string name="download_success">下载成功</string> <string name="download_success">下载成功</string>
<string name="on_wireless_network">当前处于无线网络</string> <string name="on_wireless_network">当前处于无线网络</string>
@ -280,7 +280,7 @@
<string name="privacy_policy">隐私政策</string> <string name="privacy_policy">隐私政策</string>
<string name="agree">同意</string> <string name="agree">同意</string>
<string name="disagree">不同意</string> <string name="disagree">不同意</string>
<string name="privacy_policy_text">&#12288;&#12288;SmsForwarder-短信转发器(下称本软件) 100% 免费开源Github 在线编译发版,绝不会收集您的任何隐私数据! \n\n以下情形会上报本软件的版本信息 \n&#12288;&#12288;1、启动本软件时发送版本信息发送到《友盟+·U-App移动统计》用于分析本软件的用户版本留存与软件奔溃统计 \n&#12288;&#12288;2、手动检查更新时发送版本号用于检查新版本 \n&#12288;&#12288;除此之外,没有任何数据!!! \n\n&#12288;&#12288;本软件会遵循《隐私政策》收集、使用版本信息,但不会因为您同意了《隐私政策》而进行强制捆绑式的信息收集。</string> <string name="privacy_policy_text">&#160;&#160;&#160;&#160;SmsForwarder-短信转发器(下称本软件) 100% 免费开源Github 在线编译发版,绝不会收集您的任何隐私数据! \n\n以下情形会上报本软件的版本信息 \n&#160;&#160;&#160;&#160;1、启动本软件时发送版本信息发送到《友盟+·U-App移动统计》用于分析本软件的用户版本留存与软件奔溃统计 \n&#160;&#160;&#160;&#160;2、手动检查更新时发送版本号用于检查新版本 \n&#160;&#160;&#160;&#160;除此之外,没有任何数据!!! \n\n&#160;&#160;&#160;&#160;本软件会遵循《隐私政策》收集、使用版本信息,但不会因为您同意了《隐私政策》而进行强制捆绑式的信息收集。\n\n&#160;&#160;&#160;&#160;提示首次使用请按照1234步骤顺序设置数字点亮表示该步骤已设置</string>
<string name="GotifyWebServer">WebServer</string> <string name="GotifyWebServer">WebServer</string>
<string name="GotifyWebServerTips"><![CDATA[例https://push.ppps.cn/message?token=<apptoken>]]></string> <string name="GotifyWebServerTips"><![CDATA[例https://push.ppps.cn/message?token=<apptoken>]]></string>
<string name="title_template">标题模板</string> <string name="title_template">标题模板</string>