新增:一键克隆增加离线模式(导出备份json文件到Download目录,其他机器读取文件导入)

优化:一键克隆机制优化(替换db文件→操作现有db)
pull/153/head v2.4.4
pppscn 3 years ago
parent 60dde070b5
commit 31017609f9

@ -1,28 +1,32 @@
package com.idormy.sms.forwarder;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.alibaba.fastjson.JSON;
import com.hjq.permissions.OnPermissionCallback;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import com.hjq.toast.ToastUtils;
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.sender.HttpServer;
import com.idormy.sms.forwarder.utils.BackupDbTask;
import com.idormy.sms.forwarder.utils.CloneUtils;
import com.idormy.sms.forwarder.utils.Define;
import com.idormy.sms.forwarder.utils.DownloadUtil;
import com.idormy.sms.forwarder.utils.FileUtils;
import com.idormy.sms.forwarder.utils.HttpUtil;
import com.idormy.sms.forwarder.utils.NetUtil;
import com.idormy.sms.forwarder.utils.SettingUtil;
@ -31,6 +35,7 @@ import com.idormy.sms.forwarder.view.IPEditText;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@ -47,37 +52,19 @@ public class CloneActivity extends AppCompatActivity {
private final String TAG = "CloneActivity";
private Context context;
private String serverIp;
public static final String DATABASE_NAME = "sms_forwarder.db";
private String backupPath;
private final String backupFile = "SmsForwarder.json";
private IPEditText textServerIp;
private TextView sendTxt;
private TextView receiveTxt;
private TextView backupPathTxt;
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) {
ToastUtils.delayedShow(msg.getData().getString("DATA"), 3000);
} 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, msg.getData().getString("INFO"));
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
context = CloneActivity.this;
setContentView(R.layout.activity_clone);
Log.d(TAG, "onCreate: " + RebootBroadcastReceiver.class.getName());
@ -92,6 +79,41 @@ public class CloneActivity extends AppCompatActivity {
super.onStart();
Log.d(TAG, "onStart");
backupPathTxt = findViewById(R.id.backupPathTxt);
// 申请储存权限
XXPermissions.with(this).permission(Permission.Group.STORAGE).request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
backupPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
backupPathTxt.setText(backupPath + File.separator + backupFile);
}
@Override
public void onDenied(List<String> permissions, boolean never) {
if (never) {
ToastUtils.show(R.string.toast_denied_never);
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(CloneActivity.this, permissions);
} else {
ToastUtils.show(R.string.toast_denied);
}
backupPathTxt.setText("未授权储存权限,该功能无法使用!");
}
});
LinearLayout layoutNetwork = findViewById(R.id.layoutNetwork);
LinearLayout layoutOffline = findViewById(R.id.layoutOffline);
final RadioGroup radioGroupTypeCheck = findViewById(R.id.radioGroupTypeCheck);
radioGroupTypeCheck.setOnCheckedChangeListener((group, checkedId) -> {
if (checkedId == R.id.btnTypeOffline) {
layoutNetwork.setVisibility(View.GONE);
layoutOffline.setVisibility(View.VISIBLE);
} else {
layoutNetwork.setVisibility(View.VISIBLE);
layoutOffline.setVisibility(View.GONE);
}
});
sendBtn = findViewById(R.id.sendBtn);
sendTxt = findViewById(R.id.sendTxt);
TextView ipText = findViewById(R.id.ipText);
@ -110,18 +132,14 @@ public class CloneActivity extends AppCompatActivity {
sendBtn.setText(R.string.send);
sendTxt.setText(R.string.server_has_stopped);
}
//noinspection CommentedOutCode
//发送
sendBtn.setOnClickListener(v -> {
if (!HttpServer.asRunning() && NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) {
Toast(handError, TAG, getString(R.string.no_wifi_network));
ToastUtils.show(getString(R.string.no_wifi_network));
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());
if (!HttpServer.update()) {
SettingUtil.switchEnableHttpServer(!SettingUtil.getSwitchEnableHttpServer());
@ -138,23 +156,24 @@ public class CloneActivity extends AppCompatActivity {
}
});
//接收
receiveBtn.setOnClickListener(v -> {
if (HttpServer.asRunning()) {
receiveTxt.setText(R.string.sender_cannot_receive);
Toast(handError, TAG, getString(R.string.sender_cannot_receive));
ToastUtils.show(getString(R.string.sender_cannot_receive));
return;
}
if (NetUtil.NETWORK_WIFI != NetUtil.getNetWorkStatus()) {
receiveTxt.setText(R.string.no_wifi_network);
Toast(handError, TAG, getString(R.string.no_wifi_network));
ToastUtils.show(getString(R.string.no_wifi_network));
return;
}
serverIp = textServerIp.getIP();
if (serverIp == null || serverIp.isEmpty()) {
receiveTxt.setText(R.string.invalid_server_ip);
Toast(handError, TAG, getString(R.string.invalid_server_ip));
ToastUtils.show(getString(R.string.invalid_server_ip));
return;
}
@ -187,7 +206,7 @@ public class CloneActivity extends AppCompatActivity {
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull final IOException e) {
Toast(handError, TAG, getString(R.string.tips_get_info_failed));
ToastUtils.show(getString(R.string.tips_get_info_failed));
}
@Override
@ -196,7 +215,7 @@ public class CloneActivity extends AppCompatActivity {
Log.d(TAG, "Response" + response.code() + "" + responseStr);
if (TextUtils.isEmpty(responseStr)) {
Toast(handError, TAG, getString(R.string.tips_get_info_failed));
ToastUtils.show(getString(R.string.tips_get_info_failed));
return;
}
@ -205,27 +224,68 @@ public class CloneActivity extends AppCompatActivity {
Log.d(TAG, cloneInfoVo.toString());
if (!SettingUtil.getVersionName().equals(cloneInfoVo.getVersionName())) {
Toast(handError, TAG, getString(R.string.tips_versions_inconsistent));
ToastUtils.show(getString(R.string.tips_versions_inconsistent));
return;
}
//下载备份文件
Message msg = new Message();
msg.what = DOWNLOAD;
Bundle bundle = new Bundle();
bundle.putString("URL", requestUrl);
bundle.putString("INFO", responseStr);
msg.setData(bundle);
handError.sendMessage(msg);
if (CloneUtils.restoreSettings(cloneInfoVo)) {
ToastUtils.show(getString(R.string.tips_clone_done));
} else {
ToastUtils.show(getString(R.string.tips_clone_failed));
}
} catch (Exception e) {
Toast(handError, TAG, getString(R.string.tips_clone_failed) + e.getMessage());
ToastUtils.show(getString(R.string.tips_clone_failed) + e.getMessage());
}
}
});
});
Button exportBtn = findViewById(R.id.exportBtn);
TextView exportTxt = findViewById(R.id.exportTxt);
Button importBtn = findViewById(R.id.importBtn);
TextView importTxt = findViewById(R.id.importTxt);
//导出
exportBtn.setOnClickListener(v -> {
if (FileUtils.writeFileR(CloneUtils.exportSettings(), backupPath, backupFile, true)) {
ToastUtils.show("导出配置成功!");
} else {
exportTxt.setText("导出失败,请检查写入权限!");
ToastUtils.show("导出失败,请检查写入权限!");
}
});
//导入
importBtn.setOnClickListener(v -> {
try {
String responseStr = FileUtils.readFileI(backupPath, backupFile);
if (TextUtils.isEmpty(responseStr)) {
ToastUtils.show(getString(R.string.tips_get_info_failed));
return;
}
CloneInfoVo cloneInfoVo = JSON.parseObject(responseStr, CloneInfoVo.class);
Log.d(TAG, Objects.requireNonNull(cloneInfoVo).toString());
if (!SettingUtil.getVersionName().equals(cloneInfoVo.getVersionName())) {
ToastUtils.show(getString(R.string.tips_versions_inconsistent));
return;
}
if (CloneUtils.restoreSettings(cloneInfoVo)) {
ToastUtils.show(getString(R.string.tips_clone_done));
} else {
ToastUtils.show(getString(R.string.tips_clone_failed));
}
} catch (Exception e) {
e.printStackTrace();
importTxt.setText("还原失败:" + e.getMessage());
}
});
}
@SuppressLint("SetTextI18n")
@ -238,82 +298,4 @@ public class CloneActivity extends AppCompatActivity {
ipText.setText(getString(R.string.local_ip) + serverIp);
}
/**
*
*
* @param url
*/
public void downloadFile(String url, final String destFileDir, final String destFileName, final String cloneInfo) {
ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setTitle(getString(R.string.tips_downloading));
progressDialog.setMessage(getString(R.string.tips_please_wait));
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, getString(R.string.tips_download_done));
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);
//应用配置
CloneInfoVo cloneInfoVo = JSON.parseObject(cloneInfo, CloneInfoVo.class);
System.out.println(cloneInfoVo.toString());
SettingUtil.init(context);
SettingUtil.switchEnableSms(cloneInfoVo.isEnableSms());
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());
Toast(handError, TAG, getString(R.string.tips_clone_done));
}
@Override
public void onDownloading(int progress) {
progressDialog.setProgress(progress);
}
@SuppressLint("SetTextI18n")
@Override
public void onDownloadFailed(Exception e) {
//下载异常进行相关提示操作
Log.e(TAG, getString(R.string.tips_download_failed) + e.getMessage());
Toast(handError, TAG, getString(R.string.tips_download_failed) + 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);
}
}
}

@ -2,7 +2,11 @@ package com.idormy.sms.forwarder.model.vo;
import androidx.annotation.NonNull;
import com.idormy.sms.forwarder.model.RuleModel;
import com.idormy.sms.forwarder.model.SenderModel;
import java.io.Serializable;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
@ -28,7 +32,8 @@ public class CloneInfoVo implements Serializable {
private int delayTime;
private boolean enableSmsTemplate;
private String smsTemplate;
private String backupVersion;
private List<SenderModel> senderList;
private List<RuleModel> ruleList;
@NonNull
@Override
@ -51,7 +56,8 @@ public class CloneInfoVo implements Serializable {
", delayTime=" + delayTime +
", enableSmsTemplate=" + enableSmsTemplate +
", smsTemplate=" + smsTemplate +
", backupVersion=" + backupVersion +
", senderList=" + senderList.toString() +
", ruleList=" + ruleList.toString() +
'}';
}
}

@ -9,7 +9,7 @@ import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.util.IOUtils;
import com.idormy.sms.forwarder.model.vo.ResVo;
import com.idormy.sms.forwarder.model.vo.SmsHubVo;
import com.idormy.sms.forwarder.utils.BackupDbTask;
import com.idormy.sms.forwarder.utils.CloneUtils;
import com.idormy.sms.forwarder.utils.SettingUtil;
import com.idormy.sms.forwarder.utils.SmsHubActionHandler;
@ -20,14 +20,11 @@ import org.eclipse.jetty.util.StringUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.HashMap;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletOutputStream;
@ -118,7 +115,7 @@ public class BaseServlet extends HttpServlet {
}
}
//发送短信api
private void send_api(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setCharacterEncoding("utf-8");
PrintWriter writer = resp.getWriter();
@ -170,40 +167,15 @@ public class BaseServlet extends HttpServlet {
}
//一键克隆——查询接口
@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 {
//备份文件
BackupDbTask task = new BackupDbTask(context);
String backup_version = task.doInBackground(BackupDbTask.COMMAND_BACKUP);
Log.d(TAG, "backup_version = " + backup_version);
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());
msgMap.put("backupVersion", backup_version);
try {
resp.setContentType("application/json;charset=utf-8");
String text = JSON.toJSONString(msgMap);
writer.println(text);
String json = CloneUtils.exportSettings();
writer.println(json);
} catch (Exception e) {
e.printStackTrace();
printErrMsg(resp, writer, e);
@ -215,23 +187,17 @@ public class BaseServlet extends HttpServlet {
//一键克隆——下载接口
private void clone(HttpServletRequest req, HttpServletResponse resp) throws IOException {
File file = new File(context.getCacheDir().getPath() + File.separator + BackupDbTask.BACKUP_FILE);
resp.addHeader("Content-Disposition", "attachment;filename=" + BackupDbTask.BACKUP_FILE);
resp.addHeader("Content-Disposition", "attachment;filename=" + "SmsForwarder.json");
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);
}
String json = CloneUtils.exportSettings();
outputStream.write(json.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
String text = "Internal server error: " + e.getMessage();
Log.e(TAG, text);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
IOUtils.close(inputStream);
IOUtils.close(outputStream);
}
}

@ -28,6 +28,7 @@ public class SenderBarkMsg extends SenderBaseMsg {
static final String TAG = "SenderBarkMsg";
@SuppressWarnings("RedundantThrows")
public static void sendMsg(final long logId, final Handler handError, final RetryIntercepter retryInterceptor, BarkSettingVo barkSettingVo, String title, String content, String groupName) throws Exception {
Log.i(TAG, "sendMsg barkServer:" + barkSettingVo.toString() + " title:" + title + " content:" + content);

@ -22,6 +22,7 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@SuppressWarnings("RedundantThrows")
public class SenderGotifyMsg extends SenderBaseMsg {
static final String TAG = "SenderGotifyMsg";

@ -24,7 +24,7 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
@SuppressWarnings({"rawtypes", "unchecked", "deprecation", "RedundantThrows"})
public class SenderPushPlusMsg extends SenderBaseMsg {
static final String TAG = "SenderPushPlusMsg";

@ -26,7 +26,7 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
@SuppressWarnings({"rawtypes", "unchecked", "deprecation", "RedundantThrows"})
public class SenderQyWxAppMsg extends SenderBaseMsg {
static final String TAG = "SenderQyWxAppMsg";

@ -23,7 +23,7 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
@SuppressWarnings({"rawtypes", "unchecked", "deprecation", "RedundantThrows"})
public class SenderQyWxGroupRobotMsg extends SenderBaseMsg {
static final String TAG = "SenderQyWxGroupRobotMsg";

@ -20,6 +20,7 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@SuppressWarnings("RedundantThrows")
public class SenderServerChanMsg extends SenderBaseMsg {
static final String TAG = "SenderServerChanMsg";

@ -8,6 +8,7 @@ import com.idormy.sms.forwarder.utils.LogUtil;
import com.idormy.sms.forwarder.utils.SimUtil;
import com.idormy.sms.forwarder.utils.SmsUtil;
@SuppressWarnings("RedundantThrows")
public class SenderSmsMsg extends SenderBaseMsg {
static final String TAG = "SenderSmsMsg";

@ -46,9 +46,13 @@ public class SenderUtil {
values.put(SenderTable.SenderEntry.COLUMN_NAME_STATUS, senderModel.getStatus());
values.put(SenderTable.SenderEntry.COLUMN_NAME_JSON_SETTING, senderModel.getJsonSetting());
// Insert the new row, returning the primary key value of the new row
return db.insert(SenderTable.SenderEntry.TABLE_NAME, null, values);
if (null != senderModel.getId()) {
values.put(BaseColumns._ID, senderModel.getId());
return db.replace(SenderTable.SenderEntry.TABLE_NAME, null, values);
} else {
// Insert the new row, returning the primary key value of the new row
return db.insert(SenderTable.SenderEntry.TABLE_NAME, null, values);
}
}
public static long updateSender(SenderModel senderModel) {

@ -1,117 +0,0 @@
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", "CommentedOutCode"})
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 {
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();
}
}
}

@ -0,0 +1,88 @@
package com.idormy.sms.forwarder.utils;
import com.alibaba.fastjson.JSON;
import com.idormy.sms.forwarder.model.RuleModel;
import com.idormy.sms.forwarder.model.SenderModel;
import com.idormy.sms.forwarder.model.vo.CloneInfoVo;
import com.idormy.sms.forwarder.sender.SenderUtil;
import java.util.List;
/**
*
*/
public class CloneUtils {
//导出设置
public static String exportSettings() {
CloneInfoVo cloneInfo = new CloneInfoVo();
try {
cloneInfo.setVersionCode(SettingUtil.getVersionCode());
cloneInfo.setVersionName(SettingUtil.getVersionName());
cloneInfo.setEnableSms(SettingUtil.getSwitchEnableSms());
cloneInfo.setEnablePhone(SettingUtil.getSwitchEnablePhone());
cloneInfo.setCallType1(SettingUtil.getSwitchCallType1());
cloneInfo.setCallType2(SettingUtil.getSwitchCallType2());
cloneInfo.setCallType3(SettingUtil.getSwitchCallType3());
cloneInfo.setEnableAppNotify(SettingUtil.getSwitchEnableAppNotify());
cloneInfo.setCancelAppNotify(SettingUtil.getSwitchCancelAppNotify());
cloneInfo.setSmsHubApiUrl(SettingUtil.getSmsHubApiUrl());
cloneInfo.setBatteryLevelAlarmMin(SettingUtil.getBatteryLevelAlarmMin());
cloneInfo.setBatteryLevelAlarmMax(SettingUtil.getBatteryLevelAlarmMax());
cloneInfo.setBatteryLevelAlarmOnce(SettingUtil.getBatteryLevelAlarmOnce());
cloneInfo.setRetryTimes(SettingUtil.getRetryTimes());
cloneInfo.setDelayTime(SettingUtil.getDelayTime());
cloneInfo.setEnableSmsTemplate(SettingUtil.getSwitchSmsTemplate());
cloneInfo.setSmsTemplate(SettingUtil.getSmsTemplate());
cloneInfo.setSenderList(SenderUtil.getSender(null, null));
cloneInfo.setRuleList(RuleUtil.getRule(null, null));
} catch (Exception e) {
e.printStackTrace();
}
return JSON.toJSONString(cloneInfo);
}
//还原设置
public static boolean restoreSettings(CloneInfoVo cloneInfoVo) {
try {
//应用配置
//SettingUtil.init(context);
SettingUtil.switchEnableSms(cloneInfoVo.isEnableSms());
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());
SenderUtil.delSender(null);
List<SenderModel> senderList = cloneInfoVo.getSenderList();
for (SenderModel senderModel : senderList) {
SenderUtil.addSender(senderModel);
}
RuleUtil.delRule(null);
List<RuleModel> ruleList = cloneInfoVo.getRuleList();
for (RuleModel ruleModel : ruleList) {
RuleUtil.addRule(ruleModel);
}
LogUtil.delLog(null, null);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}

@ -1,108 +0,0 @@
package com.idormy.sms.forwarder.utils;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
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(@NonNull Call call, @NonNull IOException e) {
// 下载失败监听回调
listener.onDownloadFailed(e);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[2048];
int len;
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 = Objects.requireNonNull(response.body()).byteStream();
long total = Objects.requireNonNull(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) {
e.printStackTrace();
}
}
}
});
}
public interface OnDownloadListener {
/**
* @param file
*/
void onDownloadSuccess(File file);
/**
* @param progress
*/
void onDownloading(int progress);
/**
* @param e
*/
void onDownloadFailed(Exception e);
}
}

@ -0,0 +1,253 @@
package com.idormy.sms.forwarder.utils;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
/*
*
* external storage
Environment.getExternalStorageDirectory() SD:/mnt/sdcard/ (6.0)
context.getExternalFilesDir(dir) :/mnt/sdcard/Android/data/< package name >/files/
context.getExternalCacheDir() :/mnt/sdcard//Android/data/< package name >/cach/…
*
internal storage
context.getFilesDir() :/data/data/< package name >/files/
context.getCacheDir() :/data/data/< package name >/cach/
*/
@SuppressWarnings({"UnusedReturnValue", "ResultOfMethodCallIgnored"})
public class FileUtils {
private static final String TAG = FileUtils.class.getSimpleName();
/**
* @return .sd
*/
public static String getRootPath() {
// /storage/emulated/0
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
/**
* @param context .
* @return . sd :/mnt/sdcard/Android/data/< package name >/files/
*/
public static String getAppRootPth(Context context) {
// /storage/emulated/0/Android/data/pack_name/files
return context.getExternalFilesDir("").getAbsolutePath();
}
/**
* @return .
*/
public static String getInternalPath() {
// /data
return Environment.getDataDirectory().getAbsolutePath();
}
/**
* @param context .
* @return .:/data/data/< package name >/files/
*/
public static String getInternalAppPath(Context context) {
return context.getFilesDir().getAbsolutePath();
}
/**
* @param path
* @param fileName
* @return .
*/
public static boolean createFile(String path, String fileName) {
File file = new File(path + File.separator + fileName);
//先创建文件夹 保证文件创建成功
createDirs(path);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
return true;
} else {
//
return false;
}
}
/**
* @param folder
* @return .
*/
public static boolean createDirs(String folder) {
File file = new File(folder);
if (!file.exists()) {
boolean mkdirs = file.mkdirs();
Log.i(TAG, "createDirs: 不存在文件夹 开始创建" + mkdirs + "--" + folder);
return true;
} else {
Log.i(TAG, "createDirs: 文件夹已存在");
}
return false;
}
/**
* ====================================================================================
*
* @param content
* @param path .
* @param fileName .
* @param isRewrite
*/
//1.RandomAccessFile 读写
public static boolean writeFileR(String content, String path, String fileName, boolean isRewrite) {
//路径非斜杆结尾
if (!path.endsWith("/")) path += File.separator;
File file = new File(path + fileName);
//文件目录不存在,先创建
if (!file.exists()) createFile(path, fileName);
RandomAccessFile randomAccessFile;
try {
randomAccessFile = new RandomAccessFile(file, "rw");
if (isRewrite) {
randomAccessFile.setLength(content.length());
randomAccessFile.seek(0);
} else {
randomAccessFile.seek(randomAccessFile.length());
}
randomAccessFile.write(content.getBytes());
randomAccessFile.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static boolean writeFileR(String content, String path, String fileName) {
return writeFileR(content, path, fileName, false);
}
/**
*
*
* @param path .
* @param fileName .
* @return .
*/
public static String readFileR(String path, String fileName) {
//路径非斜杆结尾
if (!path.endsWith("/")) path += File.separator;
File file = new File(path + fileName);
if (!file.exists()) {
Log.i(TAG, "readFileR: return null");
return null;
}
StringBuilder buffer = new StringBuilder();
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(0);
byte[] buf = new byte[(int) randomAccessFile.length()];
if (randomAccessFile.read(buf) != -1) {
buffer.append(new String(buf));
Log.i(TAG, "readFileR: length" + randomAccessFile.length());
//buffer.append(new String(buf, StandardCharsets.UTF_8));
}
randomAccessFile.close();
} catch (IOException e) {
Log.i(TAG, "readFileR: " + e.getMessage());
e.printStackTrace();
}
return buffer.toString();
}
/**
*
*
* @param path .
* @param name .
* @return .
*/
public static String readFileI(String path, String name) {
//路径非斜杆结尾
if (!path.endsWith("/")) path += File.separator;
//默认编码格式 StandardCharsets.UTF_8;
File file = new File(path, name);
if (!file.exists()) {
return null;
}
StringBuilder builder = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append("\n");
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
return builder.toString();
}
/**
*
*
* @param content .
* @param path .
* @param fileName .
*/
public static void writeFileO(String content, String path, String fileName) {
writeFileO(content, path, fileName, false);
}
/**
* @param content .
* @param path .
* @param fileName .
* @param isReWrite .
*/
public static void writeFileO(String content, String path, String fileName, boolean isReWrite) {
//路径非斜杆结尾
if (!path.endsWith("/")) path += File.separator;
File file = new File(path + fileName);
//文件目录不存在,先创建
if (!file.exists()) createFile(path, fileName);
try {
FileOutputStream ops = new FileOutputStream(file, isReWrite);
OutputStreamWriter opsw = new OutputStreamWriter(ops, StandardCharsets.UTF_8);
// byte[] bytes = content.getBytes();
opsw.write(content);
opsw.close();
ops.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -147,6 +147,7 @@ public class HttpUtil {
return null;
}
@SuppressWarnings("RedundantThrows")
public static class Callback0 implements Callback {
public Callback0(String tag, Lamda.Consumer<Response> onResponse, Lamda.Consumer<Exception> onFailure) {
this.tag = tag;

@ -48,9 +48,13 @@ public class RuleUtil {
values.put(RuleTable.RuleEntry.COLUMN_REGEX_REPLACE, ruleModel.getRegexReplace());
values.put(RuleTable.RuleEntry.COLUMN_NAME_STATUS, ruleModel.getStatus());
// Insert the new row, returning the primary key value of the new row
return db.insert(RuleTable.RuleEntry.TABLE_NAME, null, values);
if (null != ruleModel.getId()) {
values.put(BaseColumns._ID, ruleModel.getId());
return db.replace(RuleTable.RuleEntry.TABLE_NAME, null, values);
} else {
// Insert the new row, returning the primary key value of the new row
return db.insert(RuleTable.RuleEntry.TABLE_NAME, null, values);
}
}
public static long updateRule(RuleModel ruleModel) {
@ -91,6 +95,10 @@ public class RuleUtil {
}
public static List<RuleModel> getRule(Long id, String key) {
return getRule(id, key, null, null);
}
public static List<RuleModel> getRule(Long id, String key, String type) {
return getRule(id, key, type, null);
}

@ -1,112 +1,265 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="16dp"
android:weightSum="1">
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="0.3"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/sendBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/send_btn"
android:text="@string/send"
android:textColor="@android:color/white"
android:textSize="30sp" />
<TextView
android:id="@+id/sendTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10sp"
android:text="@string/old_mobile_phone" />
<RadioGroup
android:id="@+id/radioGroupTypeCheck"
style="@style/rg_style"
android:layout_width="match_parent"
android:layout_marginTop="5dip"
android:gravity="center"
android:orientation="horizontal">
</LinearLayout>
<RadioButton
android:id="@+id/btnTypeNetwork"
style="@style/select_style"
android:checked="true"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingStart="10dp"
android:paddingTop="5dp"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:tag="network"
android:text="@string/network_model" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.1"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="15dp">
<RadioButton
android:id="@+id/btnTypeOffline"
style="@style/select_style"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingStart="10dp"
android:paddingTop="5dp"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:tag="offline"
android:text="@string/offline_mode" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_ip"
android:textStyle="bold" />
</RadioGroup>
<com.idormy.sms.forwarder.view.IPEditText
android:id="@+id/textServerIp"
<View
android:id="@+id/radioGroupLine"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="1dp"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="25dp"
android:background="#aadcdcdc" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNetwork"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.3"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/sendBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/send_btn"
android:text="@string/send"
android:textColor="@android:color/white"
android:textSize="30sp" />
<Button
android:id="@+id/receiveBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/receive_btn"
android:text="@string/receive"
android:textColor="@android:color/white"
android:textSize="30sp" />
<TextView
android:id="@+id/receiveTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10sp"
android:text="@string/new_mobile_phone" />
<TextView
android:id="@+id/sendTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10sp"
android:text="@string/old_mobile_phone" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.2"
android:gravity="start"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="15dp">
<TextView
android:id="@+id/ipText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/local_ip"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_ip"
android:textStyle="bold" />
<com.idormy.sms.forwarder.view.IPEditText
android:id="@+id/textServerIp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/receiveBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/receive_btn"
android:text="@string/receive"
android:textColor="@android:color/white"
android:textSize="30sp" />
<TextView
android:id="@+id/receiveTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10sp"
android:text="@string/new_mobile_phone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:orientation="vertical"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/ipText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/local_ip"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/operating_instruction"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutOffline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/operating_instruction"
android:textSize="10sp"
tools:ignore="SmallSp" />
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="15dp">
<Button
android:id="@+id/exportBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/send_btn"
android:text="@string/export"
android:textColor="@android:color/white"
android:textSize="30sp" />
<TextView
android:id="@+id/exportTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10sp"
android:text="@string/old_mobile_phone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:padding="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tips_backup_path"
android:textStyle="bold" />
<TextView
android:id="@+id/backupPathTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/importBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@drawable/receive_btn"
android:text="@string/imports"
android:textColor="@android:color/white"
android:textSize="30sp" />
<TextView
android:id="@+id/importTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10sp"
android:text="@string/new_mobile_phone" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:orientation="vertical"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/operating_instruction_offline"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -227,8 +227,11 @@
<!--CloneActivity-->
<string name="local_ip">Local IP</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="operating_instruction_offline">[NOTE:] sender(s), forwarding rule(s) and log(s) will be overwritten after cloning!</string> <!-- 原文是“新旧手机”,英文翻译中处理为“源”手机和“目标”手机,因为担心“新旧”的表述引起混淆(有没一种可能就是用户就是用从新手机的设备复制到旧手机上去呢?)。 -->
<string name="send">Start</string>
<string name="stop">Stop</string>
<string name="export">Export</string>
<string name="imports">Import</string>
<string name="old_mobile_phone">I\'m the SCOURCE phone</string>
<string name="receive">Receive</string>
<string name="new_mobile_phone">I\'m the DESTINATION phone</string>
@ -393,10 +396,6 @@
<string name="tips_compatible_solution">Compatible solution</string>
<string name="tips_wait_3_seconds">Please wait 3 seconds after clicking start</string>
<string name="tips_clone_done">One-click clone operation is complete! \nPlease check whether the general settings and switches are turned on!</string>
<string name="tips_download_done">Download complete, preparing to restore data…</string>
<string name="tips_download_failed">Download failed:</string>
<string name="tips_downloading">Downloading</string>
<string name="tips_please_wait">Please wait…</string>
<string name="tips_clone_failed">One-click clone failed:</string>
<string name="tips_versions_inconsistent">The APP versions of the sender and the receiver are inconsistent, and cannot be cloned with one click!</string>
<string name="tips_get_info_failed">Failed to get one-click clone information from sender</string>
@ -452,4 +451,7 @@
<string name="tips_other_mail_type">For other email addresses, please fill in the complete email address and manually fill in the SMTP server information</string>
<string name="optional">Optional</string>
<string name="TelegramChatIdTips">Follow the steps in the wiki to obtain it</string>
<string name="network_model">网络模式</string>
<string name="offline_mode">离线模式</string>
<string name="tips_backup_path">Backup Path:</string>
</resources>

@ -225,9 +225,12 @@
<string name="post">POST</string>
<string name="get">GET</string>
<string name="local_ip">本机IP</string>
<string name="operating_instruction">操作说明:\n1.新旧手机连接同一个WiFi网络(禁用AP隔离)\n2.旧手机直接点【发送】按钮获取到【服务端IP】\n3.新手机填写【服务端IP】后点【接收】按钮\n\n注意事项\n1.发送方与接收方的APP版本必须一致才能克隆!\n2.新手机接收后,发送通道、转发规则将完全被覆盖,清空历史记录!\n3.主动请求、保活措施、个性设置不在克隆范围</string>
<string name="operating_instruction">严正声明:\n该功能仅限个人新旧手机切换使用用于非法用途后果自负\n\n操作说明\n1.新旧手机连接同一个WiFi网络(禁用AP隔离)\n2.旧手机直接点【发送】按钮获取到【服务端IP】\n3.新手机填写【服务端IP】后点【接收】按钮\n\n注意事项\n1.发送方与接收方的APP版本必须一致才能克隆!\n2.新手机接收后,发送通道、转发规则将完全被覆盖,清空历史记录!\n3.主动请求、保活措施、个性设置不在克隆范围</string>
<string name="operating_instruction_offline">严正声明:\n该功能仅限个人新旧手机切换使用用于非法用途后果自负\n\n注意事项\n1.发送方与接收方的APP版本必须一致才能克隆!\n2.新手机接收后,发送通道、转发规则将完全被覆盖,清空历史记录!\n3.主动请求、保活措施、个性设置不在克隆范围</string>
<string name="send">启动</string>
<string name="stop">停止</string>
<string name="export">导出</string>
<string name="imports">导入</string>
<string name="old_mobile_phone">我是旧手机</string>
<string name="receive">接收</string>
<string name="new_mobile_phone">我是新手机</string>
@ -392,13 +395,9 @@
<string name="tips_compatible_solution">兼容方案</string>
<string name="tips_wait_3_seconds">点击启动后请等待3秒</string>
<string name="tips_clone_done">一键克隆操作完成!\n请检查·通用设置·各项开关是否已开启</string>
<string name="tips_download_done">下载完成,正准备还原数据…</string>
<string name="tips_download_failed">下载失败:</string>
<string name="tips_downloading">正在下载</string>
<string name="tips_please_wait">请稍后…</string>
<string name="tips_clone_failed">一键克隆失败:</string>
<string name="tips_versions_inconsistent">发送端与接收端的APP版本不一致无法一键克隆</string>
<string name="tips_get_info_failed">从发送端获取一键克隆信息失败</string>
<string name="tips_versions_inconsistent">APP版本不一致无法一键克隆</string>
<string name="tips_get_info_failed">获取一键克隆信息失败</string>
<string name="linkman">联 系 人:</string>
<string name="via_number">来源号码:</string>
<string name="bark_sound">消息声音</string>
@ -451,4 +450,7 @@
<string name="tips_other_mail_type">其他邮箱请填写完整的邮箱地址并手动填写SMTP服务器信息</string>
<string name="optional">可选</string>
<string name="TelegramChatIdTips">请按照wiki中的步骤获取</string>
<string name="network_model">网络模式</string>
<string name="offline_mode">离线模式</string>
<string name="tips_backup_path">备份文件存放路径:</string>
</resources>

@ -1,3 +1,3 @@
#Fri Jul 16 10:33:23 CST 2021
versionName=2.4.3
versionCode=36
versionName=2.4.4
versionCode=37

Loading…
Cancel
Save