mirror of
https://github.com/pppscn/SmsForwarder
synced 2024-11-02 03:40:26 +00:00
优化:一键克隆机制优化,提高成功率
This commit is contained in:
parent
18b1efaf93
commit
f37ce20fcb
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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实例
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
210
app/src/main/java/com/idormy/sms/forwarder/utils/ZipUtils.java
Normal file
210
app/src/main/java/com/idormy/sms/forwarder/utils/ZipUtils.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
||||||
|
@ -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">  SmsForwarder-短信转发器(下称本软件) 100% 免费开源,Github 在线编译发版,绝不会收集您的任何隐私数据! \n\n以下情形会上报本软件的版本信息: \n  1、启动本软件时,发送版本信息发送到《友盟+·U-App移动统计》,用于分析本软件的用户版本留存与软件奔溃统计; \n  2、手动检查更新时,发送版本号用于检查新版本; \n  除此之外,没有任何数据!!! \n\n  本软件会遵循《隐私政策》收集、使用版本信息,但不会因为您同意了《隐私政策》而进行强制捆绑式的信息收集。</string>
|
<string name="privacy_policy_text">    SmsForwarder-短信转发器(下称本软件) 100% 免费开源,Github 在线编译发版,绝不会收集您的任何隐私数据! \n\n以下情形会上报本软件的版本信息: \n    1、启动本软件时,发送版本信息发送到《友盟+·U-App移动统计》,用于分析本软件的用户版本留存与软件奔溃统计; \n    2、手动检查更新时,发送版本号用于检查新版本; \n    除此之外,没有任何数据!!! \n\n    本软件会遵循《隐私政策》收集、使用版本信息,但不会因为您同意了《隐私政策》而进行强制捆绑式的信息收集。\n\n    提示:首次使用请按照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>
|
||||||
|
Loading…
Reference in New Issue
Block a user