Compare commits

..

11 Commits

Author SHA1 Message Date
ZhanGSKen
3535df8b3e <contacts>APK 1.0.9 release Publish. 2025-03-05 17:21:57 +08:00
ZhanGSKen
bd4ba7b291 连接规则记录操作接口完成 2025-03-05 17:20:00 +08:00
ZhanGSKen
9ff8b1ed80 1629 2025-03-05 16:29:55 +08:00
ZhanGSKen
859238a173 1852 2025-03-04 18:52:16 +08:00
ZhanGSKen
53eced104e <contacts>APK 1.0.8 release Publish. 2025-03-04 01:25:27 +08:00
ZhanGSKen
b45119b487 添加调试信息 2025-03-04 01:24:16 +08:00
ZhanGSKen
fdddde33b5 <contacts>APK 1.0.7 release Publish. 2025-03-03 20:51:18 +08:00
ZhanGSKen
f263733609 UI界面优化 2025-03-03 20:42:00 +08:00
ZhanGSKen
51a1cf1e26 重构为Java7语法 2025-03-03 20:23:00 +08:00
ZhanGSKen
093772c824 <contacts>APK 1.0.6 release Publish. 2025-03-03 17:39:16 +08:00
ZhanGSKen
bf3e9bdc91 云盾防御体系成型 2025-03-03 17:37:42 +08:00
14 changed files with 1097 additions and 197 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #Created by .winboll/winboll_app_build.gradle
#Sun Mar 02 18:48:57 HKT 2025 #Wed Mar 05 17:21:57 HKT 2025
stageCount=6 stageCount=10
libraryProject= libraryProject=
baseVersion=1.0 baseVersion=1.0
publishVersion=1.0.5 publishVersion=1.0.9
buildCount=0 buildCount=0
baseBetaVersion=1.0.6 baseBetaVersion=1.0.10

View File

@@ -37,6 +37,9 @@ import cc.winboll.studio.libappbase.bean.APPInfo;
import com.hjq.toast.ToastUtils; import com.hjq.toast.ToastUtils;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List; import java.util.List;
import cc.winboll.studio.contacts.beans.SettingsModel;
import cc.winboll.studio.contacts.views.DuInfoTextView;
import cc.winboll.studio.libappbase.LogUtils;
public class SettingsActivity extends AppCompatActivity implements IWinBollActivity { public class SettingsActivity extends AppCompatActivity implements IWinBollActivity {
@@ -49,11 +52,21 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv
int mnStreamMaxVolume; int mnStreamMaxVolume;
int mnStreamVolume; int mnStreamVolume;
Switch mswMainService; Switch mswMainService;
static DuInfoTextView _DuInfoTextView;
// 云盾防御层数量
EditText etDunTotalCount;
// 防御层恢复时间间隔(秒钟)
EditText etDunResumeSecondCount;
// 每次恢复防御层数
EditText etDunResumeCount;
// 是否启用云盾
Switch swIsEnableDun;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private PhoneConnectRuleAdapter adapter; private PhoneConnectRuleAdapter adapter;
private List<PhoneConnectRuleModel> ruleList; private List<PhoneConnectRuleModel> ruleList;
@Override @Override
public APPInfo getAppInfo() { public APPInfo getAppInfo() {
return null; return null;
@@ -104,13 +117,14 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv
mswMainService.setOnClickListener(new View.OnClickListener(){ mswMainService.setOnClickListener(new View.OnClickListener(){
@Override @Override
public void onClick(View arg0) { public void onClick(View arg0) {
LogUtils.d(TAG, "mswMainService onClick");
// TODO: Implement this method // TODO: Implement this method
if (mswMainService.isChecked()) { if (mswMainService.isChecked()) {
//ToastUtils.show("Is Checked"); //ToastUtils.show("Is Checked");
MainService.startMainService(SettingsActivity.this); MainService.startMainServiceAndSaveStatus(SettingsActivity.this);
} else { } else {
//ToastUtils.show("Not Checked"); //ToastUtils.show("Not Checked");
MainService.stopMainService(SettingsActivity.this); MainService.stopMainServiceAndSaveStatus(SettingsActivity.this);
} }
} }
}); });
@@ -156,15 +170,58 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv
// 当停止拖动SeekBar时的操作 // 当停止拖动SeekBar时的操作
} }
}); });
recyclerView = findViewById(R.id.recycler_view); recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setLayoutManager(new LinearLayoutManager(this));
ruleList = Rules.getInstance(this).getPhoneBlacRuleBeanList(); ruleList = Rules.getInstance(this).getPhoneBlacRuleBeanList();
adapter = new PhoneConnectRuleAdapter(this, ruleList); adapter = new PhoneConnectRuleAdapter(this, ruleList);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
// 设置参数云盾
_DuInfoTextView = findViewById(R.id.tv_DunInfo);
etDunTotalCount = findViewById(R.id.et_DunTotalCount);
etDunResumeSecondCount = findViewById(R.id.et_DunResumeSecondCount);
etDunResumeCount = findViewById(R.id.et_DunResumeCount);
swIsEnableDun = findViewById(R.id.sw_IsEnableDun);
SettingsModel settingsModel = Rules.getInstance(this).getSettingsModel();
etDunTotalCount.setText(Integer.toString(settingsModel.getDunTotalCount()));
etDunResumeSecondCount.setText(Integer.toString(settingsModel.getDunResumeSecondCount()));
etDunResumeCount.setText(Integer.toString(settingsModel.getDunResumeCount()));
swIsEnableDun.setChecked(settingsModel.isEnableDun());
boolean isEnableDun = settingsModel.isEnableDun();
etDunTotalCount.setEnabled(!isEnableDun);
etDunResumeSecondCount.setEnabled(!isEnableDun);
etDunResumeCount.setEnabled(!isEnableDun);
}
public static void notifyDunInfoUpdate() {
if (_DuInfoTextView != null) {
_DuInfoTextView.notifyInfoUpdate();
}
}
public void onSW_IsEnableDun(View view) {
LogUtils.d(TAG, "onSW_IsEnableDun");
boolean isEnableDun = swIsEnableDun.isChecked();
etDunTotalCount.setEnabled(!isEnableDun);
etDunResumeSecondCount.setEnabled(!isEnableDun);
etDunResumeCount.setEnabled(!isEnableDun);
SettingsModel settingsModel = Rules.getInstance(this).getSettingsModel();
if (isEnableDun) {
settingsModel.setDunTotalCount(Integer.parseInt(etDunTotalCount.getText().toString()));
settingsModel.setDunResumeSecondCount(Integer.parseInt(etDunResumeSecondCount.getText().toString()));
settingsModel.setDunResumeCount(Integer.parseInt(etDunResumeCount.getText().toString()));
}
settingsModel.setIsEnableDun(isEnableDun);
Rules.getInstance(this).saveDun();
Rules.getInstance(this).reload();
} }
void updateStreamVolumeTextView() { void updateStreamVolumeTextView() {
@@ -175,6 +232,12 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv
Intent intent = new Intent(this, UnitTestActivity.class); Intent intent = new Intent(this, UnitTestActivity.class);
startActivity(intent); startActivity(intent);
} }
public void onAddNewConnectionRule(View view) {
Rules.getInstance(this).getPhoneBlacRuleBeanList().add(new PhoneConnectRuleModel());
Rules.getInstance(this).saveRules();
adapter.notifyDataSetChanged();
}
public void onDefaultPhone(View view) { public void onDefaultPhone(View view) {
Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS); Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS);
@@ -198,10 +261,14 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv
public void run() { public void run() {
if (tomCat.downloadBoBullToon()) { if (tomCat.downloadBoBullToon()) {
ToastUtils.show("BoBullToon downlaod OK!"); ToastUtils.show("BoBullToon downlaod OK!");
MainService.restartMainService(SettingsActivity.this);
Rules.getInstance(SettingsActivity.this).reload();
} }
} }
}).start(); }).start();
} }
public void onSearchBoBullToonPhone(View view) { public void onSearchBoBullToonPhone(View view) {
TomCat tomCat = TomCat.getInstance(this); TomCat tomCat = TomCat.getInstance(this);

View File

@@ -19,7 +19,12 @@ import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R; import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.dun.Rules; import cc.winboll.studio.contacts.dun.Rules;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.widget.LinearLayout;
import android.view.MotionEvent;
import android.widget.HorizontalScrollView;
import cc.winboll.studio.contacts.views.LeftScrollView;
public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@@ -42,10 +47,10 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V
LayoutInflater inflater = LayoutInflater.from(context); LayoutInflater inflater = LayoutInflater.from(context);
if (viewType == VIEW_TYPE_SIMPLE) { if (viewType == VIEW_TYPE_SIMPLE) {
View view = inflater.inflate(R.layout.view_phone_connect_rule_simple, parent, false); View view = inflater.inflate(R.layout.view_phone_connect_rule_simple, parent, false);
return new SimpleViewHolder(view); return new SimpleViewHolder(parent, view);
} else { } else {
View view = inflater.inflate(R.layout.view_phone_connect_rule, parent, false); View view = inflater.inflate(R.layout.view_phone_connect_rule, parent, false);
return new EditViewHolder(view); return new EditViewHolder(parent, view);
} }
} }
@@ -53,15 +58,80 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
final PhoneConnectRuleModel model = ruleList.get(position); final PhoneConnectRuleModel model = ruleList.get(position);
if (holder instanceof SimpleViewHolder) { if (holder instanceof SimpleViewHolder) {
SimpleViewHolder simpleViewHolder = (SimpleViewHolder) holder; final SimpleViewHolder simpleViewHolder = (SimpleViewHolder) holder;
simpleViewHolder.textView.setText(model.getRuleText()); String szView = model.getRuleText().trim().equals("")?"[NULL]":model.getRuleText();
simpleViewHolder.button.setOnClickListener(new View.OnClickListener() { simpleViewHolder.tvRuleText.setText(szView);
simpleViewHolder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener(){
@Override @Override
public void onClick(View v) { public void onEdit() {
simpleViewHolder.scrollView.smoothScrollTo(0, 0);
model.setIsSimpleView(false); model.setIsSimpleView(false);
notifyItemChanged(position); notifyDataSetChanged();
//notifyItemChanged(position);
}
@Override
public void onDelete() {
simpleViewHolder.scrollView.smoothScrollTo(0, 0);
model.setIsSimpleView(true);
ArrayList<PhoneConnectRuleModel> list = Rules.getInstance(context).getPhoneBlacRuleBeanList();
list.remove(position);
Rules.getInstance(context).saveRules();
notifyDataSetChanged();
//notifyItemChanged(position);
} }
}); });
// simpleViewHolder.editButton.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// model.setIsSimpleView(false);
// notifyItemChanged(position);
// }
// });
// simpleViewHolder.deleteButton.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// model.setIsSimpleView(false);
// ArrayList<PhoneConnectRuleModel> list = Rules.getInstance(context).getPhoneBlacRuleBeanList();
// list.remove(position);
// Rules.getInstance(context).saveRules();
// notifyItemChanged(position);
// }
// });
// // 触摸事件处理
// simpleViewHolder.contentLayout.setOnTouchListener(new View.OnTouchListener() {
// @Override
// public boolean onTouch(View v, MotionEvent event) {
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// simpleViewHolder.startX = event.getX();
// simpleViewHolder.isSwiping = true;
// break;
// case MotionEvent.ACTION_MOVE:
// if (simpleViewHolder.isSwiping) {
// float deltaX = simpleViewHolder.startX - event.getX();
// if (deltaX > 0) { // 左滑
// float translationX = Math.max(-simpleViewHolder.actionLayout.getWidth(), -deltaX);
// simpleViewHolder.contentLayout.setTranslationX(translationX);
// simpleViewHolder.actionLayout.setVisibility(View.VISIBLE);
// }
// }
// break;
// case MotionEvent.ACTION_UP:
// simpleViewHolder.isSwiping = false;
// if (simpleViewHolder.contentLayout.getTranslationX() < -simpleViewHolder.actionLayout.getWidth() / 2) {
// // 保持按钮显示
// simpleViewHolder.contentLayout.setTranslationX(-actionLayout.getWidth());
// } else {
// // 恢复原状
// simpleViewHolder.contentLayout.animate().translationX(0).setDuration(200).start();
// simpleViewHolder.actionLayout.setVisibility(View.INVISIBLE);
// }
// break;
// }
// return true;
// }
// });
} else if (holder instanceof EditViewHolder) { } else if (holder instanceof EditViewHolder) {
final EditViewHolder editViewHolder = (EditViewHolder) holder; final EditViewHolder editViewHolder = (EditViewHolder) holder;
editViewHolder.editText.setText(model.getRuleText()); editViewHolder.editText.setText(model.getRuleText());
@@ -95,14 +165,21 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V
} }
static class SimpleViewHolder extends RecyclerView.ViewHolder { static class SimpleViewHolder extends RecyclerView.ViewHolder {
TextView textView;
Button button;
public SimpleViewHolder(@NonNull View itemView) { private final LeftScrollView scrollView;
private final TextView tvRuleText;
public SimpleViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) {
super(itemView); super(itemView);
textView = itemView.findViewById(R.id.text_view); scrollView = itemView.findViewById(R.id.scrollView);
button = itemView.findViewById(R.id.button); //tvRuleText = itemView.findViewById(R.id.ruletext_tv);
tvRuleText = new TextView(itemView.getContext());
scrollView.setContentWidth(parent.getWidth());
//scrollView.setContentWidth(600);
scrollView.addContentLayout(tvRuleText);
} }
} }
static class EditViewHolder extends RecyclerView.ViewHolder { static class EditViewHolder extends RecyclerView.ViewHolder {
@@ -111,7 +188,7 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V
CheckBox checkBoxEnable; CheckBox checkBoxEnable;
Button buttonConfirm; Button buttonConfirm;
public EditViewHolder(@NonNull View itemView) { public EditViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) {
super(itemView); super(itemView);
editText = itemView.findViewById(R.id.edit_text); editText = itemView.findViewById(R.id.edit_text);
checkBoxAllow = itemView.findViewById(R.id.checkbox_allow); checkBoxAllow = itemView.findViewById(R.id.checkbox_allow);

View File

@@ -0,0 +1,135 @@
package cc.winboll.studio.contacts.beans;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/02 19:51:40
* @Describe SettingsModel
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class SettingsModel extends BaseBean {
public static final String TAG = "SettingsModel";
// 云盾防御层数量
int dunTotalCount;
// 当前云盾防御层
int dunCurrentCount;
// 防御层恢复时间间隔(秒钟)
int dunResumeSecondCount;
// 每次恢复防御层数
int dunResumeCount;
// 是否启用云盾
boolean isEnableDun;
public SettingsModel() {
this.dunTotalCount = 6;
this.dunCurrentCount = 6;
this.dunResumeSecondCount = 60;
this.dunResumeCount = 1;
this.isEnableDun = false;
}
public SettingsModel(int dunTotalCount, int dunCurrentCount, int dunResumeSecondCount, int dunResumeCount, boolean isEnableDun) {
this.dunTotalCount = dunTotalCount;
this.dunCurrentCount = dunCurrentCount;
this.dunResumeSecondCount = dunResumeSecondCount;
this.dunResumeCount = dunResumeCount;
this.isEnableDun = isEnableDun;
}
public void setDunTotalCount(int dunTotalCount) {
this.dunTotalCount = dunTotalCount;
}
public int getDunTotalCount() {
return dunTotalCount;
}
public void setDunCurrentCount(int dunCurrentCount) {
this.dunCurrentCount = dunCurrentCount;
}
public int getDunCurrentCount() {
return dunCurrentCount;
}
public void setDunResumeSecondCount(int dunResumeSecondCount) {
this.dunResumeSecondCount = dunResumeSecondCount;
}
public int getDunResumeSecondCount() {
return dunResumeSecondCount;
}
public void setDunResumeCount(int dunResumeCount) {
this.dunResumeCount = dunResumeCount;
}
public int getDunResumeCount() {
return dunResumeCount;
}
public void setIsEnableDun(boolean isEnableDun) {
this.isEnableDun = isEnableDun;
}
public boolean isEnableDun() {
return isEnableDun;
}
@Override
public String getName() {
return SettingsModel.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("dunTotalCount").value(getDunTotalCount());
jsonWriter.name("dunCurrentCount").value(getDunCurrentCount());
jsonWriter.name("dunResumeSecondCount").value(getDunResumeSecondCount());
jsonWriter.name("dunResumeCount").value(getDunResumeCount());
jsonWriter.name("isEnableDun").value(isEnableDun());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("dunTotalCount")) {
setDunTotalCount(jsonReader.nextInt());
} else if (name.equals("dunCurrentCount")) {
setDunCurrentCount(jsonReader.nextInt());
} else if (name.equals("dunResumeSecondCount")) {
setDunResumeSecondCount(jsonReader.nextInt());
} else if (name.equals("dunResumeCount")) {
setDunResumeCount(jsonReader.nextInt());
} else if (name.equals("isEnableDun")) {
setIsEnableDun(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@@ -48,24 +48,29 @@ public class TomCat {
.url(zipUrl) .url(zipUrl)
.build(); .build();
try (Response response = client.newCall(request).execute()) { try {
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response); throw new IOException("Unexpected code " + response);
} }
// 下载 ZIP 文件到临时位置 // 下载 ZIP 文件到临时位置
File tempZipFile = File.createTempFile("temp", ".zip"); File tempZipFile = File.createTempFile("temp", ".zip");
try (InputStream inputStream = response.body().byteStream(); try {
FileOutputStream outputStream = new FileOutputStream(tempZipFile)) { InputStream inputStream = response.body().byteStream();
FileOutputStream outputStream = new FileOutputStream(tempZipFile);
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int length; int length;
while ((length = inputStream.read(buffer)) > 0) { while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length); outputStream.write(buffer, 0, length);
} }
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
} }
// 解压 ZIP 文件到指定文件夹 // 解压 ZIP 文件到指定文件夹
try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(tempZipFile.toPath()))) { try {
ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(tempZipFile.toPath()));
ZipEntry zipEntry; ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) { while ((zipEntry = zipInputStream.getNextEntry()) != null) {
Path targetFilePath = Paths.get(destinationFolder, zipEntry.getName()); Path targetFilePath = Paths.get(destinationFolder, zipEntry.getName());
@@ -83,10 +88,15 @@ public class TomCat {
} }
zipInputStream.closeEntry(); zipInputStream.closeEntry();
} }
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
} }
// 删除临时 ZIP 文件 // 删除临时 ZIP 文件
tempZipFile.delete(); tempZipFile.delete();
LogUtils.d(TAG, "已更新 BoBullToon 数据");
} catch (Exception e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
} }
} }
@@ -94,6 +104,15 @@ public class TomCat {
String zipUrl = "https://gitea.winboll.cc//Studio/BoBullToon/archive/main.zip"; // 替换为实际的 ZIP 文件 URL String zipUrl = "https://gitea.winboll.cc//Studio/BoBullToon/archive/main.zip"; // 替换为实际的 ZIP 文件 URL
String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径 String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径
try { try {
// 删除旧文件
File fOldFolder = new File(destinationFolder);
if (fOldFolder.exists()) {
deleteFolderRecursive(fOldFolder);
fOldFolder.mkdirs();
LogUtils.d(TAG, "已清空 BoBullToon 数据");
}
// 更新新文件
downloadAndExtractZip(zipUrl, destinationFolder); downloadAndExtractZip(zipUrl, destinationFolder);
LogUtils.d(TAG, "ZIP 文件下载并解压成功。"); LogUtils.d(TAG, "ZIP 文件下载并解压成功。");
return true; return true;
@@ -103,6 +122,23 @@ public class TomCat {
return false; return false;
} }
// 递归删除文件夹及其内容的方法
public static void deleteFolderRecursive(File file) {
// 判断是否为文件夹
if (file.isDirectory()) {
// 列出文件夹中的所有文件和子文件夹
File[] files = file.listFiles();
if (files != null) {
// 遍历并递归删除每个文件和子文件夹
for (File f : files) {
deleteFolderRecursive(f);
}
}
}
// 删除文件或空文件夹
file.delete();
}
File getWorkingFolder() { File getWorkingFolder() {
return mContext.getExternalFilesDir(TAG); return mContext.getExternalFilesDir(TAG);
} }

View File

@@ -6,11 +6,17 @@ package cc.winboll.studio.contacts.dun;
* @Describe 云盾防御规则 * @Describe 云盾防御规则
*/ */
import android.content.Context; import android.content.Context;
import android.media.AudioManager;
import cc.winboll.studio.contacts.activities.SettingsActivity;
import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.beans.RingTongBean;
import cc.winboll.studio.contacts.beans.SettingsModel;
import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.utils.RegexPPiUtils; import cc.winboll.studio.contacts.utils.RegexPPiUtils;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class Rules { public class Rules {
@@ -20,11 +26,13 @@ public class Rules {
ArrayList<PhoneConnectRuleModel> _PhoneConnectRuleModelList; ArrayList<PhoneConnectRuleModel> _PhoneConnectRuleModelList;
static volatile Rules _Rules; static volatile Rules _Rules;
Context mContext; Context mContext;
SettingsModel mSettingsModel;
Timer mDunResumeTimer;
Rules(Context context) { Rules(Context context) {
mContext = context; mContext = context;
_PhoneConnectRuleModelList = new ArrayList<PhoneConnectRuleModel>(); _PhoneConnectRuleModelList = new ArrayList<PhoneConnectRuleModel>();
loadRules(); reload();
} }
public static synchronized Rules getInstance(Context context) { public static synchronized Rules getInstance(Context context) {
@@ -34,40 +42,143 @@ public class Rules {
return _Rules; return _Rules;
} }
public void reload() {
LogUtils.d(TAG, "reload()");
loadRules();
loadDun();
setDunResumTimer();
}
public void setDunResumTimer() {
if (mDunResumeTimer != null) {
mDunResumeTimer.cancel();
}
// 盾牌恢复定时器
mDunResumeTimer = new Timer();
mDunResumeTimer.schedule(new TimerTask() {
@Override
public void run() {
if (mSettingsModel.getDunCurrentCount() != mSettingsModel.getDunTotalCount()) {
LogUtils.d(TAG, String.format("当前防御值为%d最大防御值为%d", mSettingsModel.getDunCurrentCount(), mSettingsModel.getDunTotalCount()));
int newDunCount = mSettingsModel.getDunCurrentCount() + mSettingsModel.getDunResumeCount();
// 设置盾值在[0DunTotalCount]之内其他值一律重置为 DunTotalCount。
newDunCount = (newDunCount > mSettingsModel.getDunTotalCount()) ?mSettingsModel.getDunTotalCount(): newDunCount;
mSettingsModel.setDunCurrentCount(newDunCount);
LogUtils.d(TAG, String.format("设置防御值为%d", newDunCount));
saveDun();
SettingsActivity.notifyDunInfoUpdate();
}
}
}, 1000, mSettingsModel.getDunResumeSecondCount() * 1000);
}
public void loadRules() { public void loadRules() {
_PhoneConnectRuleModelList.clear(); _PhoneConnectRuleModelList.clear();
PhoneConnectRuleModel.loadBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class); PhoneConnectRuleModel.loadBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class);
} }
public void saveRules() { public void saveRules() {
LogUtils.d(TAG, String.format("saveRules()"));
PhoneConnectRuleModel.saveBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class); PhoneConnectRuleModel.saveBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class);
} }
public boolean isAllowed(String phoneNumber) { public void loadDun() {
// 正则运算预防针 mSettingsModel = SettingsModel.loadBean(mContext, SettingsModel.class);
if (!RegexPPiUtils.isPPiOK(phoneNumber)) { if (mSettingsModel == null) {
LogUtils.d(TAG, "RegexPPiUtils.isPPiOK return false."); mSettingsModel = new SettingsModel();
return false; SettingsModel.saveBean(mContext, mSettingsModel);
} }
}
public void saveDun() {
LogUtils.d(TAG, String.format("saveDun()"));
SettingsModel.saveBean(mContext, mSettingsModel);
}
public boolean isAllowed(String phoneNumber) {
// 没有启用云盾,默认允许接通任何电话
if (!mSettingsModel.isEnableDun()) {
LogUtils.d(TAG, String.format("没有启用云盾默认允许接通任何电话。isAllowed(...) return true"));
return true;
}
//
// 以下是云盾防御体系
boolean isDefend = false; // 盾牌是否生效
boolean isConnect = true; // 防御结果是否连接
// 如果盾值小于1则解除防御
if (!isDefend && mSettingsModel.getDunCurrentCount() < 1) {
// 盾层为1以下防御解除
LogUtils.d(TAG, "盾层为1以下防御解除");
isDefend = true;
isConnect = true;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
}
// 正则运算预防针
if (!isDefend && !RegexPPiUtils.isPPiOK(phoneNumber)) {
LogUtils.d(TAG, "正则运算预防针生效。");
isDefend = true;
isConnect = false;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
}
// 检验拨不通号码群 // 检验拨不通号码群
if (MainService.isPhoneInBoBullToon(phoneNumber)) { if (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) {
LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber)); LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber));
return false; isDefend = true;
isConnect = false;
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
} }
// 正则匹配规则名单校验 // 正则匹配规则名单校验
for (int i = 0; i < _PhoneConnectRuleModelList.size(); i++) { if (!isDefend) {
if (_PhoneConnectRuleModelList.get(i).isEnable()) { for (int i = 0; i < _PhoneConnectRuleModelList.size(); i++) {
String regex = _PhoneConnectRuleModelList.get(i).getRuleText(); if (_PhoneConnectRuleModelList.get(i).isEnable()) {
if (Pattern.matches(regex, phoneNumber)) { String regex = _PhoneConnectRuleModelList.get(i).getRuleText();
LogUtils.d(TAG, String.format("phoneNumber :%s \nisAllowConnection %s By Rule : %s", phoneNumber, _PhoneConnectRuleModelList.get(i).isAllowConnection(), _PhoneConnectRuleModelList.get(i))); if (Pattern.matches(regex, phoneNumber)) {
return _PhoneConnectRuleModelList.get(i).isAllowConnection(); LogUtils.d(TAG, String.format("Phone Number [%s] is matched by rule : %s", phoneNumber, _PhoneConnectRuleModelList.get(i)));
isDefend = true;
isConnect = _PhoneConnectRuleModelList.get(i).isAllowConnection();
LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect));
break;
}
} }
} }
} }
// 其他默认接收 if (isConnect) {
return true; // 如果防御结果为连接,则恢复防御盾牌最大值层数
mSettingsModel.setDunCurrentCount(mSettingsModel.getDunTotalCount());
LogUtils.d(TAG, String.format("防御结果为连接,恢复防御盾牌最大值层数 %d", mSettingsModel.getDunTotalCount()));
saveDun();
SettingsActivity.notifyDunInfoUpdate();
} else if (isDefend) {
// 如果触发了以上某个防御模块,
// 就减少防御盾牌层数。
// 每校验一次规则云盾防御层数减1
// 当云盾防御层数为0时再次进行以下程序段则恢复满值防御。
int newDunCount = mSettingsModel.getDunCurrentCount() - 1;
LogUtils.d(TAG, String.format("新的防御层数预计为 %d", newDunCount));
// 保证盾值在[0DunTotalCount]之内其他值一律重置为 DunTotalCount。
if (newDunCount < 0 || newDunCount > mSettingsModel.getDunTotalCount()) {
mSettingsModel.setDunCurrentCount(mSettingsModel.getDunTotalCount());
LogUtils.d(TAG, String.format("盾值不在[0%d]区间,恢复防御最大值%d", mSettingsModel.getDunTotalCount(), mSettingsModel.getDunTotalCount()));
} else {
mSettingsModel.setDunCurrentCount(newDunCount);
LogUtils.d(TAG, String.format("设置防御层数为 %d", newDunCount));
}
saveDun();
SettingsActivity.notifyDunInfoUpdate();
}
// 返回校验结果
LogUtils.d(TAG, String.format("返回校验结果 isConnect == %s", isConnect));
return isConnect;
} }
public void add(String szPhoneConnectRule, boolean isAllowConnection, boolean isEnable) { public void add(String szPhoneConnectRule, boolean isAllowConnection, boolean isEnable) {
@@ -77,4 +188,8 @@ public class Rules {
public ArrayList<PhoneConnectRuleModel> getPhoneBlacRuleBeanList() { public ArrayList<PhoneConnectRuleModel> getPhoneBlacRuleBeanList() {
return _PhoneConnectRuleModelList; return _PhoneConnectRuleModelList;
} }
public SettingsModel getSettingsModel() {
return mSettingsModel;
}
} }

View File

@@ -142,7 +142,7 @@ public class MainService extends Service {
mMainReceiver.registerAction(this); mMainReceiver.registerAction(this);
} }
Rules.getInstance(this); Rules.getInstance(this).loadRules();
startPhoneCallListener(); startPhoneCallListener();
@@ -273,14 +273,40 @@ public class MainService extends Service {
public static void stopMainService(Context context) { public static void stopMainService(Context context) {
LogUtils.d(TAG, "stopMainService"); LogUtils.d(TAG, "stopMainService");
context.stopService(new Intent(context, MainService.class));
}
public static void startMainService(Context context) {
LogUtils.d(TAG, "startMainService");
context.startService(new Intent(context, MainService.class));
}
public static void restartMainService(Context context) {
LogUtils.d(TAG, "restartMainService");
MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class);
if (bean != null && bean.isEnable()) {
context.stopService(new Intent(context, MainService.class));
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
// }
context.startService(new Intent(context, MainService.class));
LogUtils.d(TAG, "已重启 MainService");
}
}
public static void stopMainServiceAndSaveStatus(Context context) {
LogUtils.d(TAG, "stopMainServiceAndSaveStatus");
MainServiceBean bean = new MainServiceBean(); MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(false); bean.setIsEnable(false);
MainServiceBean.saveBean(context, bean); MainServiceBean.saveBean(context, bean);
context.stopService(new Intent(context, MainService.class)); context.stopService(new Intent(context, MainService.class));
} }
public static void startMainService(Context context) { public static void startMainServiceAndSaveStatus(Context context) {
LogUtils.d(TAG, "startMainService"); LogUtils.d(TAG, "startMainServiceAndSaveStatus");
MainServiceBean bean = new MainServiceBean(); MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(true); bean.setIsEnable(true);
MainServiceBean.saveBean(context, bean); MainServiceBean.saveBean(context, bean);

View File

@@ -0,0 +1,68 @@
package cc.winboll.studio.contacts.views;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/02 21:11:03
* @Describe 云盾防御信息
*/
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
import cc.winboll.studio.contacts.beans.SettingsModel;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.libappbase.LogUtils;
public class DuInfoTextView extends TextView {
public static final String TAG = "DuInfoTextView";
public static final int MSG_NOTIFY_INFO_UPDATE = 0;
Context mContext;
public DuInfoTextView(android.content.Context context) {
super(context);
}
public DuInfoTextView(android.content.Context context, android.util.AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public DuInfoTextView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public DuInfoTextView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
void initView(android.content.Context context) {
mContext = context;
updateInfo();
}
void updateInfo() {
LogUtils.d(TAG, "updateInfo()");
SettingsModel settingsModel = Rules.getInstance(mContext).getSettingsModel();
String info = String.format("(云盾防御值【%d/%d】)", settingsModel.getDunCurrentCount(), settingsModel.getDunTotalCount());
setText(info);
}
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == MSG_NOTIFY_INFO_UPDATE) {
updateInfo();
}
}
};
public void notifyInfoUpdate() {
LogUtils.d(TAG, "notifyInfoUpdate()");
mHandler.sendMessage(mHandler.obtainMessage(MSG_NOTIFY_INFO_UPDATE));
}
}

View File

@@ -0,0 +1,170 @@
package cc.winboll.studio.contacts.views;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/03/04 10:51:50
* @Describe CustomHorizontalScrollView
*/
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
import android.util.TypedValue;
public class LeftScrollView extends HorizontalScrollView {
public static final String TAG = "LeftScrollView";
private LinearLayout contentLayout;
private LinearLayout toolLayout;
private TextView textView;
private Button editButton;
private Button deleteButton;
private float mLastX;
private boolean isScrolling = false;
public LeftScrollView(Context context) {
super(context);
init();
}
public LeftScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LeftScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void addContentLayout(TextView textView) {
contentLayout.addView(textView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
}
public void setContentWidth(int contentWidth) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) contentLayout.getLayoutParams();
layoutParams.width = contentWidth;
contentLayout.setLayoutParams(layoutParams);
}
private void init() {
View viewMain = inflate(getContext(), R.layout.view_left_scroll, null);
// 创建内容布局
contentLayout = viewMain.findViewById(R.id.content_layout);
toolLayout = viewMain.findViewById(R.id.action_layout);
//LogUtils.d(TAG, String.format("getWidth() %d", getWidth()));
addView(viewMain);
// 创建编辑按钮
editButton = viewMain.findViewById(R.id.edit_btn);
// 创建删除按钮
deleteButton = viewMain.findViewById(R.id.delete_btn);
// 编辑按钮点击事件
editButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onActionListener != null) {
onActionListener.onEdit();
}
}
});
// 删除按钮点击事件
deleteButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onActionListener != null) {
onActionListener.onDelete();
}
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
LogUtils.d(TAG, "ACTION_DOWN");
mLastX = event.getX();
isScrolling = false;
break;
case MotionEvent.ACTION_MOVE:
//LogUtils.d(TAG, "ACTION_MOVE");
float currentX = event.getX();
float deltaX = mLastX - currentX;
mLastX = currentX;
if (Math.abs(deltaX) > 0) {
isScrolling = true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
LogUtils.d(TAG, "ACTION_UP");
if (isScrolling) {
LogUtils.d(TAG, String.format("isScrolling \ngetScrollX() %d\neditButton.getWidth() %d", getScrollX(), editButton.getWidth()));
int scrollX = getScrollX();
if (scrollX > editButton.getWidth()) {
// 获取HorizontalScrollView的子视图
View childView = getChildAt(0);
if (childView != null) {
// 计算需要滑动到最右边的距离
int scrollToX = childView.getWidth() - getWidth();
// 确保滑动距离不小于0
final int scrollToX2 = Math.max(0, scrollToX);
// 平滑滑动到最右边
post(new Runnable() {
@Override
public void run() {
smoothScrollTo(scrollToX2, 0);
LogUtils.d(TAG, "smoothScrollTo(0, 0);");
}
});
LogUtils.d(TAG, "smoothScrollTo(scrollToX, 0);");
}
} else {
// 恢复原状
// 在手指抬起时,使用 post 方法调用 smoothScrollTo(0, 0)
post(new Runnable() {
@Override
public void run() {
smoothScrollTo(0, 0);
LogUtils.d(TAG, "smoothScrollTo(0, 0);");
}
});
//toolLayout.setTranslationX(0);
}
}
break;
}
return super.onTouchEvent(event);
}
// 设置文本内容
public void setText(CharSequence text) {
textView.setText(text);
}
// 定义回调接口
public interface OnActionListener {
void onEdit();
void onDelete();
}
private OnActionListener onActionListener;
public void setOnActionListener(OnActionListener listener) {
this.onActionListener = listener;
}
}

View File

@@ -1,176 +1,290 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <ScrollView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activitymainToolbar1"/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content">
android:layout_weight="1.0">
<TextView <androidx.appcompat.widget.Toolbar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="服务设置:"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="主要服务"
android:id="@+id/sw_mainservice"
android:layout_margin="10dp"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="应用权限设置:"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="right"> android:id="@+id/activitymainToolbar1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="悬浮窗设置"
android:id="@+id/activitysettingsButton2"
android:onClick="onCanDrawOverlays"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="默认拨号设置"
android:id="@+id/activitysettingsButton1"
android:onClick="onDefaultPhone"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="音量设置:"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:id="@+id/tv_volume"/>
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:max="100"
android:id="@+id/bellvolume"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拨不通电话记录查询:"/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:gravity="right" android:layout_weight="1.0">
android:layout_margin="10dp">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载 BoBullToon"
android:onClick="onDownloadBoBullToon"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right|center_vertical"
android:layout_margin="10dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="查询电话"/> android:text="服务设置"/>
<EditText <LinearLayout
android:layout_width="0dp" android:orientation="horizontal"
android:inputType="phone" android:layout_width="match_parent"
android:layout_height="wrap_content">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="主要服务"
android:id="@+id/sw_mainservice"
android:layout_margin="5dp"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="云盾设置:"/>
<cc.winboll.studio.contacts.views.DuInfoTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_DunInfo"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="是否启用云盾防御"
android:layout_margin="5dp"
android:id="@+id/sw_IsEnableDun"
android:onClick="onSW_IsEnableDun"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ems="10" android:gravity="center_vertical">
android:layout_weight="1.0"
android:id="@+id/activitysettingsEditText1"/>
<Button <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="初始防御层的叠加数量:"/>
<EditText
android:layout_width="0dp"
android:inputType="number"
android:layout_height="wrap_content"
android:ems="10"
android:layout_weight="1.0"
android:id="@+id/et_DunTotalCount"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="防御层每次恢复的时间间隔(秒钟)"/>
<EditText
android:layout_width="0dp"
android:inputType="number"
android:layout_height="wrap_content"
android:ems="10"
android:layout_weight="1.0"
android:id="@+id/et_DunResumeSecondCount"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="防御层每次恢复的叠加数量:"/>
<EditText
android:layout_width="0dp"
android:inputType="number"
android:layout_height="wrap_content"
android:ems="10"
android:layout_weight="1.0"
android:id="@+id/et_DunResumeCount"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="查询记录" android:text="应用权限设置:"/>
android:onClick="onSearchBoBullToonPhone"/>
</LinearLayout> <LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView <Button
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Phone Connect Rule :"/> android:text="悬浮窗设置"
android:id="@+id/activitysettingsButton2"
android:onClick="onCanDrawOverlays"/>
<androidx.recyclerview.widget.RecyclerView <Button
android:id="@+id/recycler_view" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="0dp" android:text="默认拨号设置"
android:layout_weight="1.0" android:id="@+id/activitysettingsButton1"
android:background="@drawable/recycler_view_border" android:onClick="onDefaultPhone"/>
android:layout_margin="5dp"/>
<TextView </LinearLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="单元测试:"/>
<LinearLayout <TextView
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Unit Test" android:text="音量设置:"/>
android:onClick="onUnitTest"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:id="@+id/tv_volume"/>
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:max="100"
android:id="@+id/bellvolume"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拨不通电话记录查询:"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_margin="10dp">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载 BoBullToon"
android:onClick="onDownloadBoBullToon"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right|center_vertical"
android:layout_margin="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查询电话:"/>
<EditText
android:layout_width="0dp"
android:inputType="phone"
android:layout_height="wrap_content"
android:ems="10"
android:layout_weight="1.0"
android:id="@+id/activitysettingsEditText1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查询记录"
android:onClick="onSearchBoBullToonPhone"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Phone Connect Rule :"
android:layout_weight="1.0"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加新规则"
android:onClick="onAddNewConnectionRule"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@drawable/recycler_view_border"
android:layout_margin="5dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="单元测试:"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Unit Test"
android:onClick="onUnitTest"/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </ScrollView>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- 内容区域 -->
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@color/white">
<!-- 这里放置你的列表项内容 -->
</LinearLayout>
<!-- 操作按钮 -->
<LinearLayout
android:id="@+id/action_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/lightgray">
<Button
android:id="@+id/edit_btn"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="编辑"
android:background="@color/blue" />
<Button
android:id="@+id/delete_btn"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="删除"
android:background="@color/red" />
</LinearLayout>
</LinearLayout>

View File

@@ -2,24 +2,15 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp" android:orientation="horizontal"
android:gravity="center_vertical"> android:padding="2dp">
<TextView <cc.winboll.studio.contacts.views.LeftScrollView
android:id="@+id/text_view" android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:id="@+id/scrollView"/>
android:textSize="16sp"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="编辑"/>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:id="@+id/scrollView">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- 内容区域 -->
<LinearLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@color/white">
<!-- 这里放置你的列表项内容 -->
<TextView
android:id="@+id/text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"/>
</LinearLayout>
<!-- 操作按钮 -->
<LinearLayout
android:id="@+id/action_layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="@color/lightgray">
<Button
android:id="@+id/edit_btn"
android:layout_width="80dp"
android:layout_height="match_parent"
android:text="编辑"
android:background="@color/blue" />
<Button
android:id="@+id/delete_btn"
android:layout_width="80dp"
android:layout_height="match_parent"
android:text="删除"
android:background="@color/red" />
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>

View File

@@ -4,4 +4,9 @@
<color name="colorPrimary">#FF196ABC</color> <color name="colorPrimary">#FF196ABC</color>
<color name="colorPrimaryDark">#FF002B57</color> <color name="colorPrimaryDark">#FF002B57</color>
<color name="colorAccent">#FF80BFFF</color> <color name="colorAccent">#FF80BFFF</color>
<color name="blue">#FF379AFF</color>
<color name="red">#FFE55151</color>
<color name="white">#FFFFFFFF</color>
<color name="lightgray">#FFE0E0E0</color>
</resources> </resources>