diff --git a/contacts/build.gradle b/contacts/build.gradle index 552a42a..9f7186f 100644 --- a/contacts/build.gradle +++ b/contacts/build.gradle @@ -18,13 +18,13 @@ def genVersionName(def versionName){ } android { - compileSdkVersion 32 - buildToolsVersion "33.0.3" + compileSdkVersion 30 + buildToolsVersion "30.0.3" defaultConfig { applicationId "cc.winboll.studio.contacts" - minSdkVersion 21 - targetSdkVersion 30 + minSdkVersion 26 + targetSdkVersion 29 versionCode 1 // versionName 更新后需要手动设置 // 项目模块目录的 build.gradle 文件的 stageCount=0 @@ -41,31 +41,41 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } } dependencies { - // 二维码使用的类库 - api 'com.google.zxing:core:3.4.1' - api 'com.journeyapps:zxing-android-embedded:3.6.0' - - api 'io.github.medyo:android-about-page:2.0.0' - api 'com.github.getActivity:ToastUtils:10.5' - api 'com.jcraft:jsch:0.1.55' - api 'org.jsoup:jsoup:1.13.1' - api 'com.squareup.okhttp3:okhttp:4.4.1' - - api 'androidx.appcompat:appcompat:1.1.0' - api 'androidx.viewpager:viewpager:1.0.0' - api 'androidx.fragment:fragment:1.1.0' - api 'com.google.android.material:material:1.4.0' - - api 'cc.winboll.studio:libapputils:9.3.2' - api 'cc.winboll.studio:libappbase:1.5.6' - api fileTree(dir: 'libs', include: ['*.jar']) + + // 权限请求框架:https://github.com/getActivity/XXPermissions + implementation 'com.github.getActivity:XXPermissions:18.63' + // 下拉控件 + implementation 'com.baoyz.pullrefreshlayout:library:1.2.0' + // 拼音搜索 + // https://mvnrepository.com/artifact/com.github.open-android/pinyin4j + implementation 'com.github.open-android:pinyin4j:2.5.0' + // SSH + implementation 'com.jcraft:jsch:0.1.55' + // Html 解析 + implementation 'org.jsoup:jsoup:1.13.1' + // 二维码类库 + implementation 'com.google.zxing:core:3.4.1' + implementation 'com.journeyapps:zxing-android-embedded:3.6.0' + // 应用介绍页类库 + implementation 'io.github.medyo:android-about-page:2.0.0' + // 吐司类库 + implementation 'com.github.getActivity:ToastUtils:10.5' + // 网络连接类库 + implementation 'com.squareup.okhttp3:okhttp:4.4.1' + + // Android 类库 + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.viewpager:viewpager:1.0.0' + implementation 'androidx.vectordrawable:vectordrawable:1.1.0' + implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0' + implementation 'androidx.fragment:fragment:1.1.0' + implementation 'com.google.android.material:material:1.4.0' + + implementation 'cc.winboll.studio:libappbase:2.1.5' + implementation 'cc.winboll.studio:libapputils:9.4.4' + implementation 'cc.winboll.studio:libaes:7.6.8' } diff --git a/contacts/build.properties b/contacts/build.properties index 86e2b57..7976126 100644 --- a/contacts/build.properties +++ b/contacts/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Tue Feb 25 00:17:29 HKT 2025 -stageCount=2 +#Sun Mar 09 03:40:58 GMT 2025 +stageCount=16 libraryProject= baseVersion=1.0 -publishVersion=1.0.1 -buildCount=0 -baseBetaVersion=1.0.2 +publishVersion=1.0.15 +buildCount=7 +baseBetaVersion=1.0.16 diff --git a/contacts/src/main/AndroidManifest.xml b/contacts/src/main/AndroidManifest.xml index 36ac1ee..f92009a 100644 --- a/contacts/src/main/AndroidManifest.xml +++ b/contacts/src/main/AndroidManifest.xml @@ -29,9 +29,10 @@ - - - + + + + @@ -182,6 +183,8 @@ + + - + \ No newline at end of file diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/App.java b/contacts/src/main/java/cc/winboll/studio/contacts/App.java index 6f482d5..9c2044a 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/App.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/App.java @@ -8,6 +8,7 @@ package cc.winboll.studio.contacts; import cc.winboll.studio.libappbase.GlobalApplication; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libapputils.app.WinBollActivityManager; +import com.hjq.toast.ToastUtils; public class App extends GlobalApplication { @@ -23,6 +24,8 @@ public class App extends GlobalApplication { WinBollActivityManager.getInstance(this).setWinBollUI_TYPE(WinBollActivityManager.WinBollUI_TYPE.Aplication); LogUtils.d(TAG, "onCreate"); + + ToastUtils.init(this); } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java index 24f9f4f..3b67c4b 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/MainActivity.java @@ -1,52 +1,44 @@ package cc.winboll.studio.contacts; import android.Manifest; -import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; -import android.app.role.RoleManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.provider.Settings; import android.telecom.TelecomManager; -import android.view.LayoutInflater; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.WindowManager; import android.widget.CheckBox; -import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.Switch; import android.widget.Toast; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; import cc.winboll.studio.contacts.R; -import cc.winboll.studio.contacts.activities.CallActivity; -import cc.winboll.studio.contacts.adapters.MyPagerAdapter; +import cc.winboll.studio.contacts.activities.SettingsActivity; import cc.winboll.studio.contacts.beans.MainServiceBean; +import cc.winboll.studio.contacts.fragments.CallLogFragment; +import cc.winboll.studio.contacts.fragments.ContactsFragment; +import cc.winboll.studio.contacts.fragments.LogFragment; import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogView; import cc.winboll.studio.libapputils.app.IWinBollActivity; -import cc.winboll.studio.libapputils.app.WinBollActivityManager; import cc.winboll.studio.libapputils.bean.APPInfo; -import cc.winboll.studio.libapputils.view.YesNoAlertDialog; -import cc.winboll.studio.contacts.listenphonecall.CallListenerService; import com.google.android.material.tabs.TabLayout; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; -import android.content.DialogInterface; -import cc.winboll.studio.contacts.activities.SettingsActivity; final public class MainActivity extends AppCompatActivity implements IWinBollActivity, ViewPager.OnPageChangeListener, View.OnClickListener { @@ -57,11 +49,13 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct public static final String ACTION_SOS = "cc.winboll.studio.libappbase.WinBoll.ACTION_SOS"; + static MainActivity _MainActivity; LogView mLogView; Toolbar mToolbar; CheckBox cbMainService; MainServiceBean mMainServiceBean; - ViewPager viewPager; + private TabLayout tabLayout; + private ViewPager viewPager; private List views; //用来存放放进ViewPager里面的布局 //实例化存储imageView(导航原点)的集合 ImageView[] imageViews; @@ -70,6 +64,9 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct LinearLayout linearLayout;//下标所在在LinearLayout布局里 int currentPoint = 0;//当前被选中中页面的下标 + private TelephonyManager telephonyManager; + private MyPhoneStateListener phoneStateListener; + private static final int DIALER_REQUEST_CODE = 1; @Override @@ -102,6 +99,7 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct //if (prosessIntents(getIntent())) return; // 以下正常创建主窗口 super.onCreate(savedInstanceState); + _MainActivity = this; setContentView(R.layout.activity_main); // 初始化工具栏 @@ -112,17 +110,39 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct getSupportActionBar().setDisplayHomeAsUpEnabled(true); } getSupportActionBar().setSubtitle(getTag()); + + tabLayout = findViewById(R.id.tabLayout); + viewPager = findViewById(R.id.viewPager); - initData(); - initView(); - //initPoint();//调用初始化导航原点的方法 - viewPager.addOnPageChangeListener(this);//滑动事件 + // 创建Fragment列表和标题列表 + List fragmentList = new ArrayList<>(); + List tabTitleList = new ArrayList<>(); + fragmentList.add(CallLogFragment.newInstance(0)); + fragmentList.add(ContactsFragment.newInstance(1)); + fragmentList.add(LogFragment.newInstance(2)); + tabTitleList.add("通话记录"); + tabTitleList.add("联系人"); + tabTitleList.add("应用日志"); - ViewPager viewPager = findViewById(R.id.activitymainViewPager1); - MyPagerAdapter pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); - viewPager.setAdapter(pagerAdapter); - TabLayout tabLayout = findViewById(R.id.activitymainTabLayout1); + // 设置ViewPager的适配器 + MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList); + viewPager.setAdapter(adapter); + + // 关联TabLayout和ViewPager tabLayout.setupWithViewPager(viewPager); + + + +// initData(); +// initView(); +// //initPoint();//调用初始化导航原点的方法 +// viewPager.addOnPageChangeListener(this);//滑动事件 + + //ViewPager viewPager = findViewById(R.id.activitymainViewPager1); + //MyPagerAdapter pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); + //viewPager.setAdapter(pagerAdapter); + //TabLayout tabLayout = findViewById(R.id.activitymainTabLayout1); + //tabLayout.setupWithViewPager(viewPager); // mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); // if (mMainServiceBean == null) { @@ -140,36 +160,86 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct // } // } // }); - MainService.startMainService(MainActivity.this); + + MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + if (mMainServiceBean == null) { + mMainServiceBean = new MainServiceBean(); + MainServiceBean.saveBean(this, mMainServiceBean); + } + if (mMainServiceBean.isEnable()) { + MainService.startMainService(this); + } + + // 初始化TelephonyManager和PhoneStateListener + telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + phoneStateListener = new MyPhoneStateListener(); + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + } + + + // ViewPager的适配器 + private class MyPagerAdapter extends FragmentPagerAdapter { + + private List fragmentList; + private List tabTitleList; + + public MyPagerAdapter(FragmentManager fm, List fragmentList, List tabTitleList) { + super(fm); + this.fragmentList = fragmentList; + this.tabTitleList = tabTitleList; + } + + @Override + public Fragment getItem(int position) { + return fragmentList.get(position); + } + + @Override + public int getCount() { + return fragmentList.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return tabTitleList.get(position); + } + } + + public static void dialPhoneNumber(String phoneNumber) { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + if (ActivityCompat.checkSelfPermission(_MainActivity, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + return; + } + _MainActivity.startActivity(intent); } //初始化view,即显示的图片 - void initView() { - viewPager = findViewById(R.id.activitymainViewPager1); - pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); - viewPager.setAdapter(pagerAdapter); - //adapter = new MyPagerAdapter(views); - //viewPager = findViewById(R.id.activitymainViewPager1); - //viewPager.setAdapter(adapter); - //linearLayout = findViewById(R.id.activitymainLinearLayout1); - //initPoint();//初始化页面下方的点 - viewPager.setOnPageChangeListener(this); - - } +// void initView() { +// viewPager = findViewById(R.id.activitymainViewPager1); +// pagerAdapter = new MyPagerAdapter(getSupportFragmentManager()); +// viewPager.setAdapter(pagerAdapter); +// //adapter = new MyPagerAdapter(views); +// //viewPager = findViewById(R.id.activitymainViewPager1); +// //viewPager.setAdapter(adapter); +// //linearLayout = findViewById(R.id.activitymainLinearLayout1); +// //initPoint();//初始化页面下方的点 +// viewPager.setOnPageChangeListener(this); +// +// } //初始化所要显示的布局 - void initData() { - ViewPager viewPager = findViewById(R.id.activitymainViewPager1); - LayoutInflater inflater = LayoutInflater.from(getActivity()); - View view1 = inflater.inflate(R.layout.fragment_call, viewPager, false); - View view2 = inflater.inflate(R.layout.fragment_contacts, viewPager, false); - View view3 = inflater.inflate(R.layout.fragment_log, viewPager, false); - - views = new ArrayList<>(); - views.add(view1); - views.add(view2); - views.add(view3); - } +// void initData() { +// LayoutInflater inflater = LayoutInflater.from(getActivity()); +// View view1 = inflater.inflate(R.layout.fragment_call_log, viewPager, false); +// View view2 = inflater.inflate(R.layout.fragment_contacts, viewPager, false); +// View view3 = inflater.inflate(R.layout.fragment_log, viewPager, false); +// +// views = new ArrayList<>(); +// views.add(view1); +// views.add(view2); +// views.add(view3); +// } // void initPoint() { // imageViews = new ImageView[5];//实例化5个图片 @@ -231,6 +301,23 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct //setSubTitle(""); } + private class MyPhoneStateListener extends PhoneStateListener { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + LogUtils.d(TAG, "电话已挂断"); + break; + case TelephonyManager.CALL_STATE_OFFHOOK: + LogUtils.d(TAG, "正在通话中"); + break; + case TelephonyManager.CALL_STATE_RINGING: + LogUtils.d(TAG, "来电: " + incomingNumber); + break; + } + } + } + @Override protected void onDestroy() { super.onDestroy(); @@ -302,25 +389,25 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct return false; } - @Override - public void onBackPressed() { - exit(); - } - - void exit() { - YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){ - - @Override - public void onYes() { - WinBollActivityManager.getInstance(getApplicationContext()).finishAll(); - } - - @Override - public void onNo() { - } - }; - YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener); - } +// @Override +// public void onBackPressed() { +// exit(); +// } +// +// void exit() { +// YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){ +// +// @Override +// public void onYes() { +// WinBollActivityManager.getInstance(getApplicationContext()).finishAll(); +// } +// +// @Override +// public void onNo() { +// } +// }; +// YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener); +// } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -331,11 +418,7 @@ final public class MainActivity extends AppCompatActivity implements IWinBollAct @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.item_call) { - Intent intent = new Intent(this, CallActivity.class); - startActivity(intent); - //WinBollActivityManager.getInstance(this).startWinBollActivity(this, CallActivity.class); - } else if (item.getItemId() == R.id.item_settings) { + if (item.getItemId() == R.id.item_settings) { Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); //WinBollActivityManager.getInstance(this).startWinBollActivity(this, CallActivity.class); diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/PhoneCallManager.java b/contacts/src/main/java/cc/winboll/studio/contacts/PhoneCallManager.java deleted file mode 100644 index 7f7292f..0000000 --- a/contacts/src/main/java/cc/winboll/studio/contacts/PhoneCallManager.java +++ /dev/null @@ -1,35 +0,0 @@ -package cc.winboll.studio.contacts; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/02/20 21:14:52 - * @Describe PhoneCallManager - */ - -import android.telecom.Call; -import android.telecom.VideoProfile; - -public class PhoneCallManager { - - public static final String TAG = "PhoneCallManager"; - - public static Call call; - - /** - * 接听电话 - */ - public void answer() { - if (call != null) { - call.answer(VideoProfile.STATE_AUDIO_ONLY); - } - } - - /** - * 断开电话,包括来电时的拒接以及接听后的挂断 - */ - public void disconnect() { - if (call != null) { - call.disconnect(); - } - } -} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java index e8f8e3e..ebff426 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/CallActivity.java @@ -34,6 +34,7 @@ public class CallActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); setContentView(R.layout.activity_call); diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java index bc54d95..5bbabad 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/SettingsActivity.java @@ -4,7 +4,6 @@ package cc.winboll.studio.contacts.activities; * @Author ZhanGSKen@AliYun.Com * @Date 2025/02/21 05:37:42 */ -import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -15,17 +14,32 @@ import android.os.Bundle; import android.provider.Settings; import android.view.View; import android.view.WindowManager; +import android.widget.EditText; +import android.widget.SeekBar; import android.widget.Switch; +import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.adapters.PhoneConnectRuleAdapter; +import cc.winboll.studio.contacts.beans.MainServiceBean; +import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; import cc.winboll.studio.contacts.beans.RingTongBean; -import cc.winboll.studio.libappbase.IWinBollActivity; -import cc.winboll.studio.libappbase.bean.APPInfo; +import cc.winboll.studio.contacts.beans.SettingsModel; +import cc.winboll.studio.contacts.bobulltoon.TomCat; +import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.contacts.services.MainService; +import cc.winboll.studio.contacts.views.DuInfoTextView; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libapputils.app.IWinBollActivity; +import cc.winboll.studio.libapputils.bean.APPInfo; import com.hjq.toast.ToastUtils; import java.lang.reflect.Field; +import java.util.List; public class SettingsActivity extends AppCompatActivity implements IWinBollActivity { @@ -33,6 +47,25 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv Toolbar mToolbar; Switch swSilent; + SeekBar msbVolume; + TextView mtvVolume; + int mnStreamMaxVolume; + int mnStreamVolume; + Switch mswMainService; + static DuInfoTextView _DuInfoTextView; + + // 云盾防御层数量 + EditText etDunTotalCount; + // 防御层恢复时间间隔(秒钟) + EditText etDunResumeSecondCount; + // 每次恢复防御层数 + EditText etDunResumeCount; + // 是否启用云盾 + Switch swIsEnableDun; + + private RecyclerView recyclerView; + private PhoneConnectRuleAdapter adapter; + private List ruleList; @Override public APPInfo getAppInfo() { @@ -77,6 +110,133 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv getSupportActionBar().setDisplayHomeAsUpEnabled(true); } getSupportActionBar().setSubtitle(getTag()); + + mswMainService = findViewById(R.id.sw_mainservice); + MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + mswMainService.setChecked(mMainServiceBean.isEnable()); + mswMainService.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View arg0) { + LogUtils.d(TAG, "mswMainService onClick"); + // TODO: Implement this method + if (mswMainService.isChecked()) { + //ToastUtils.show("Is Checked"); + MainService.startMainServiceAndSaveStatus(SettingsActivity.this); + } else { + //ToastUtils.show("Not Checked"); + MainService.stopMainServiceAndSaveStatus(SettingsActivity.this); + } + } + }); + + msbVolume = findViewById(R.id.bellvolume); + mtvVolume = findViewById(R.id.tv_volume); + final AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + + // 设置SeekBar的最大值为系统铃声音量的最大刻度 + mnStreamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING); + msbVolume.setMax(mnStreamMaxVolume); + // 获取当前铃声音量并设置为SeekBar的初始进度 + mnStreamVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + msbVolume.setProgress(mnStreamVolume); + + updateStreamVolumeTextView(); + + msbVolume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + // 设置铃声音量 + audioManager.setStreamVolume(AudioManager.STREAM_RING, progress, 0); + RingTongBean bean = RingTongBean.loadBean(SettingsActivity.this, RingTongBean.class); + if (bean == null) { + bean = new RingTongBean(); + } + bean.setStreamVolume(progress); + RingTongBean.saveBean(SettingsActivity.this, bean); + mnStreamVolume = progress; + updateStreamVolumeTextView(); + //Toast.makeText(SettingsActivity.this, "音量设置为: " + progress, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // 当开始拖动SeekBar时的操作 + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // 当停止拖动SeekBar时的操作 + } + }); + + + recyclerView = findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + ruleList = Rules.getInstance(this).getPhoneBlacRuleBeanList(); + + adapter = new PhoneConnectRuleAdapter(this, ruleList); + 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() { + mtvVolume.setText(String.format("%d/%d", mnStreamVolume, mnStreamMaxVolume)); + } + + public void onUnitTest(View view) { + Intent intent = new Intent(this, UnitTestActivity.class); + 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) { @@ -94,6 +254,38 @@ public class SettingsActivity extends AppCompatActivity implements IWinBollActiv } } + public void onDownloadBoBullToon(View view) { + final TomCat tomCat = TomCat.getInstance(this); + new Thread(new Runnable() { + @Override + public void run() { + if (tomCat.downloadBoBullToon()) { + ToastUtils.show("BoBullToon downlaod OK!"); + MainService.restartMainService(SettingsActivity.this); + Rules.getInstance(SettingsActivity.this).reload(); + } + } + }).start(); + } + + + + public void onSearchBoBullToonPhone(View view) { + TomCat tomCat = TomCat.getInstance(this); + EditText etPhone = findViewById(R.id.activitysettingsEditText1); + String phone = etPhone.getText().toString().trim(); + if (tomCat.loadPhoneBoBullToon()) { + if (tomCat.isPhoneBoBullToon(phone)) { + ToastUtils.show("It is a BoBullToon Phone!"); + } else { + ToastUtils.show("Not in BoBullToon."); + } + } else { + ToastUtils.show("没有下载 BoBullToon。"); + } + + } + private void askForDrawOverlay() { AlertDialog alertDialog = new AlertDialog.Builder(this) .setTitle("允许显示悬浮框") diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/activities/UnitTestActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/activities/UnitTestActivity.java new file mode 100644 index 0000000..97e2f8f --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/activities/UnitTestActivity.java @@ -0,0 +1,91 @@ +package cc.winboll.studio.contacts.activities; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.dun.Rules; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.LogView; +import android.widget.EditText; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 16:07:04 + */ +public class UnitTestActivity extends Activity { + + public static final String TAG = "UnitTestActivity"; + + LogView logView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_unittest); + logView = findViewById(R.id.logview); + logView.start(); + } + + public void onTestPhone(View view) { + // 开始测试数据 + EditText etPhone = findViewById(R.id.phone_et); + Rules rules = Rules.getInstance(this); + String phone = etPhone.getText().toString().trim(); + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + } + + public void onTestMain(View view) { + Rules rules = Rules.getInstance(this); + + // 如果没有规则就添加测试规则 + if (rules.getPhoneBlacRuleBeanList().size() == 0) { + // 手机号码允许 + // 中国手机号码正则表达式,以1开头,第二位可以是3、4、5、6、7、8、9,后面跟9位数字 + String regex = "^1[3-9]\\d{9}$"; + rules.add(regex, true, true); + + // 指定区号号码允许 + regex = "^0660\\d+$"; + rules.add(regex, true, true); + + // 指定区号号码允许 + regex = "^020\\d+$"; + rules.add(regex, true, true); + + // 添加默认拒接规则 + regex = ".*"; + rules.add(regex, false, true); + + // 保存规则到文件 + rules.saveRules(); + } + + // 开始测试数据 + String phone = "16769764848"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "16856582777"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "17519703124"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "0205658955"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "0108965253"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "+8616769764848"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "4005816769764848"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + phone = "95566"; + LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone))); + + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/CallLogAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/CallLogAdapter.java new file mode 100644 index 0000000..7ebc7a8 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/CallLogAdapter.java @@ -0,0 +1,93 @@ +package cc.winboll.studio.contacts.adapters; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:09:32 + * @Describe CallLogAdapter + */ +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.beans.CallLogModel; +import cc.winboll.studio.contacts.utils.ContactUtils; +import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; +import com.hjq.toast.ToastUtils; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Locale; + +public class CallLogAdapter extends RecyclerView.Adapter { + public static final String TAG = "CallLogAdapter"; + + private List callLogList; + ContactUtils mContactUtils; + Context mContext; + + public CallLogAdapter(Context context, List callLogList) { + mContext = context; + this.mContactUtils = ContactUtils.getInstance(mContext); + this.callLogList = callLogList; + } + + @NonNull + @Override + public CallLogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_call_log, parent, false); + return new CallLogViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull CallLogViewHolder holder, int position) { + final CallLogModel callLog = callLogList.get(position); + holder.phoneNumber.setText(callLog.getPhoneNumber() + " ☎ " + mContactUtils.getContactsName(callLog.getPhoneNumber())); + holder.callStatus.setText(callLog.getCallStatus()); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + holder.callDate.setText(dateFormat.format(callLog.getCallDate())); + + // 初始化拉动后拨号控件 + holder.dialAOHPCTCSeekBar.setThumb(holder.itemView.getContext().getDrawable(R.drawable.ic_call)); + holder.dialAOHPCTCSeekBar.setBlurRightDP(80); + //holder.dialAOHPCTCSeekBar.setThumbOffset(10); + holder.dialAOHPCTCSeekBar.setOnOHPCListener( + new AOHPCTCSeekBar.OnOHPCListener(){ + @Override + public void onOHPCommit() { + String phoneNumber = callLog.getPhoneNumber().replaceAll("\\s", ""); + ToastUtils.show(phoneNumber); + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + // 添加 FLAG_ACTIVITY_NEW_TASK 标志 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + holder.itemView.getContext().startActivity(intent); + } + }); + } + + @Override + public int getItemCount() { + return callLogList.size(); + } + + public class CallLogViewHolder extends RecyclerView.ViewHolder { + TextView phoneNumber, callStatus, callDate; + Button dialButton; + AOHPCTCSeekBar dialAOHPCTCSeekBar; + + public CallLogViewHolder(@NonNull View itemView) { + super(itemView); + phoneNumber = itemView.findViewById(R.id.phone_number); + callStatus = itemView.findViewById(R.id.call_status); + callDate = itemView.findViewById(R.id.call_date); + dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial); + } + } + +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/ContactAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/ContactAdapter.java new file mode 100644 index 0000000..cc09491 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/ContactAdapter.java @@ -0,0 +1,83 @@ +package cc.winboll.studio.contacts.adapters; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:35:44 + * @Describe ContactAdapter + */ +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.beans.ContactModel; +import com.hjq.toast.ToastUtils; +import java.util.List; +import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; + +public class ContactAdapter extends RecyclerView.Adapter { + + public static final String TAG = "ContactAdapter"; + + private static final int REQUEST_CALL_PHONE = 1; + + private List contactList; + + public ContactAdapter(List contactList) { + this.contactList = contactList; + } + + @NonNull + @Override + public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false); + return new ContactViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) { + final ContactModel contact = contactList.get(position); + holder.contactName.setText(contact.getName()); + holder.contactNumber.setText(contact.getNumber()); + + // 初始化拉动后拨号控件 + holder.dialAOHPCTCSeekBar.setThumb(holder.itemView.getContext().getDrawable(R.drawable.ic_call)); + holder.dialAOHPCTCSeekBar.setBlurRightDP(80); + holder.dialAOHPCTCSeekBar.setOnOHPCListener( + new AOHPCTCSeekBar.OnOHPCListener(){ + @Override + public void onOHPCommit() { + String phoneNumber = contact.getNumber().replaceAll("\\s", ""); + ToastUtils.show(phoneNumber); + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + // 添加 FLAG_ACTIVITY_NEW_TASK 标志 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + holder.itemView.getContext().startActivity(intent); + } + }); + } + + @Override + public int getItemCount() { + return contactList.size(); + } + + public class ContactViewHolder extends RecyclerView.ViewHolder { + TextView contactName; + TextView contactNumber; + AOHPCTCSeekBar dialAOHPCTCSeekBar; + + public ContactViewHolder(@NonNull View itemView) { + super(itemView); + contactName = itemView.findViewById(R.id.contact_name); + contactNumber = itemView.findViewById(R.id.contact_number); + dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial); + } + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/MyPagerAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/MyPagerAdapter.java deleted file mode 100644 index 3212231..0000000 --- a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/MyPagerAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package cc.winboll.studio.contacts.adapters; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/02/20 13:33:04 - * @Describe MyPagerAdapter - */ -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import cc.winboll.studio.contacts.fragments.CallFragment; -import cc.winboll.studio.contacts.fragments.ContactsFragment; -import cc.winboll.studio.contacts.fragments.LogFragment; - -public class MyPagerAdapter extends FragmentPagerAdapter { - public static final String TAG = "MyPagerAdapter"; - - private static final int PAGE_COUNT = 3; - - public MyPagerAdapter(@NonNull FragmentManager fm) { - super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - } - - @NonNull - @Override - public Fragment getItem(int position) { - if(position == 1) { - return ContactsFragment.newInstance(position); - } else if(position == 2) { - return LogFragment.newInstance(position); - } else { - return CallFragment.newInstance(position); - } - } - - @Override - public int getCount() { - return PAGE_COUNT; - } -} - diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/adapters/PhoneConnectRuleAdapter.java b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/PhoneConnectRuleAdapter.java new file mode 100644 index 0000000..f225c36 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/adapters/PhoneConnectRuleAdapter.java @@ -0,0 +1,250 @@ +package cc.winboll.studio.contacts.adapters; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 17:27:41 + * @Describe PhoneConnectRuleAdapter + */ +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel; +import cc.winboll.studio.contacts.dun.Rules; +import java.util.ArrayList; +import java.util.List; +import android.widget.LinearLayout; +import android.view.MotionEvent; +import android.widget.HorizontalScrollView; +import cc.winboll.studio.contacts.views.LeftScrollView; +import com.hjq.toast.ToastUtils; +import cc.winboll.studio.libapputils.view.YesNoAlertDialog; + +public class PhoneConnectRuleAdapter extends RecyclerView.Adapter { + + public static final String TAG = "PhoneConnectRuleAdapter"; + + private static final int VIEW_TYPE_SIMPLE = 0; + private static final int VIEW_TYPE_EDIT = 1; + + private Context context; + private List ruleList; + + public PhoneConnectRuleAdapter(Context context, List ruleList) { + this.context = context; + this.ruleList = ruleList; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(context); + if (viewType == VIEW_TYPE_SIMPLE) { + View view = inflater.inflate(R.layout.view_phone_connect_rule_simple, parent, false); + return new SimpleViewHolder(parent, view); + } else { + View view = inflater.inflate(R.layout.view_phone_connect_rule, parent, false); + return new EditViewHolder(parent, view); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) { + final PhoneConnectRuleModel model = ruleList.get(position); + if (holder instanceof SimpleViewHolder) { + final SimpleViewHolder simpleViewHolder = (SimpleViewHolder) holder; + String szView = model.getRuleText().trim().equals("") ?"[NULL]": model.getRuleText(); + simpleViewHolder.tvRuleText.setText(szView); + simpleViewHolder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener(){ + + @Override + public void onUp() { + ArrayList list = Rules.getInstance(context).getPhoneBlacRuleBeanList(); + if (position > 0) { + ToastUtils.show("onUp"); + simpleViewHolder.scrollView.smoothScrollTo(0, 0); +// PhoneConnectRuleModel newBean = new PhoneConnectRuleModel(); +// newBean.setRuleText(list.get(position).getRuleText()); +// newBean.setIsAllowConnection(list.get(position).isAllowConnection()); +// newBean.setIsEnable(list.get(position).isEnable()); +// newBean.setIsSimpleView(list.get(position).isSimpleView()); + list.add(position - 1, list.get(position)); + list.remove(position + 1); + Rules.getInstance(context).saveRules(); + notifyDataSetChanged(); + } + } + + @Override + public void onDown() { + ArrayList list = Rules.getInstance(context).getPhoneBlacRuleBeanList(); + if (position < list.size() - 1) { + ToastUtils.show("onDown"); + simpleViewHolder.scrollView.smoothScrollTo(0, 0); +// PhoneConnectRuleModel newBean = new PhoneConnectRuleModel(); +// newBean.setRuleText(list.get(position).getRuleText()); +// newBean.setIsAllowConnection(list.get(position).isAllowConnection()); +// newBean.setIsEnable(list.get(position).isEnable()); +// newBean.setIsSimpleView(list.get(position).isSimpleView()); + list.add(position + 2, list.get(position)); + list.remove(position); + Rules.getInstance(context).saveRules(); + notifyDataSetChanged(); + } + } + + @Override + public void onEdit() { + simpleViewHolder.scrollView.smoothScrollTo(0, 0); + model.setIsSimpleView(false); + notifyDataSetChanged(); + //notifyItemChanged(position); + } + + @Override + public void onDelete() { + YesNoAlertDialog.show(simpleViewHolder.scrollView.getContext(), "删除确认", "是否删除该通话规则?", new YesNoAlertDialog.OnDialogResultListener(){ + + @Override + public void onYes() { + simpleViewHolder.scrollView.smoothScrollTo(0, 0); + model.setIsSimpleView(true); + ArrayList list = Rules.getInstance(context).getPhoneBlacRuleBeanList(); + list.remove(position); + Rules.getInstance(context).saveRules(); + notifyDataSetChanged(); + //notifyItemChanged(position); + } + + @Override + public void onNo() { + } + }); + + } + }); +// 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 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) { + final EditViewHolder editViewHolder = (EditViewHolder) holder; + editViewHolder.editText.setText(model.getRuleText()); + editViewHolder.checkBoxAllow.setChecked(model.isAllowConnection()); + editViewHolder.checkBoxEnable.setChecked(model.isEnable()); + editViewHolder.buttonConfirm.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + model.setRuleText(editViewHolder.editText.getText().toString()); + model.setIsAllowConnection(editViewHolder.checkBoxAllow.isChecked()); + model.setIsEnable(editViewHolder.checkBoxEnable.isChecked()); + model.setIsSimpleView(true); + Rules.getInstance(context).saveRules(); + notifyItemChanged(position); + Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show(); + } + }); + } + } + + @Override + public int getItemCount() { + return ruleList.size(); + } + + @Override + public int getItemViewType(int position) { + PhoneConnectRuleModel model = ruleList.get(position); + // 这里可以根据模型的状态来决定视图类型,简单起见,假设点击按钮后进入编辑视图 + return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT; + } + + static class SimpleViewHolder extends RecyclerView.ViewHolder { + + private final LeftScrollView scrollView; + private final TextView tvRuleText; + + + public SimpleViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) { + super(itemView); + scrollView = itemView.findViewById(R.id.scrollView); + //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 { + EditText editText; + CheckBox checkBoxAllow; + CheckBox checkBoxEnable; + Button buttonConfirm; + + public EditViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) { + super(itemView); + editText = itemView.findViewById(R.id.edit_text); + checkBoxAllow = itemView.findViewById(R.id.checkbox_allow); + checkBoxEnable = itemView.findViewById(R.id.checkbox_enable); + buttonConfirm = itemView.findViewById(R.id.button_confirm); + } + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/CallLogModel.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/CallLogModel.java new file mode 100644 index 0000000..32f7d58 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/CallLogModel.java @@ -0,0 +1,36 @@ +package cc.winboll.studio.contacts.beans; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:10:57 + * @Describe CallLogModel + */ + +import java.util.Date; + +public class CallLogModel { + public static final String TAG = "CallLogModel"; + + private String phoneNumber; + private String callStatus; + private Date callDate; + + public CallLogModel(String phoneNumber, String callStatus, Date callDate) { + this.phoneNumber = phoneNumber.replaceAll("\\s", ""); + this.callStatus = callStatus; + this.callDate = callDate; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public String getCallStatus() { + return callStatus; + } + + public Date getCallDate() { + return callDate; + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/ContactModel.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/ContactModel.java new file mode 100644 index 0000000..cf3c559 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/ContactModel.java @@ -0,0 +1,64 @@ +package cc.winboll.studio.contacts.beans; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 13:37:00 + * @Describe ContactModel + */ +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; + +public class ContactModel { + + public static final String TAG = "ContactModel"; + + private String name; + private String number; + private String pinyin; + + public ContactModel(String name, String number) { + this.name = name; + this.number = number.replaceAll("\\s", ""); + this.pinyin = convertToPinyin(name); + } + + private String convertToPinyin(String chinese) { + HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); + format.setCaseType(HanyuPinyinCaseType.LOWERCASE); + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + + StringBuilder pinyin = new StringBuilder(); + for (int i = 0; i < chinese.length(); i++) { + char ch = chinese.charAt(i); + if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { + try { + String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format); + if (pinyinArray != null) { + pinyin.append(pinyinArray[0]); + } + } catch (BadHanyuPinyinOutputFormatCombination e) { + e.printStackTrace(); + } + } else { + pinyin.append(ch); + } + } + return pinyin.toString(); + } + + public String getName() { + return name; + } + + public String getNumber() { + return number; + } + + public String getPinyin() { + return pinyin; + } +} + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneBlackRuleBean.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneConnectRuleModel.java similarity index 64% rename from contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneBlackRuleBean.java rename to contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneConnectRuleModel.java index 69550cc..3975902 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneBlackRuleBean.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/PhoneConnectRuleModel.java @@ -10,21 +10,35 @@ import android.util.JsonWriter; import cc.winboll.studio.libappbase.BaseBean; import java.io.IOException; -public class PhoneBlackRuleBean extends BaseBean { - - public static final String TAG = "PhoneBlackRuleBean"; - +public class PhoneConnectRuleModel extends BaseBean { + + public static final String TAG = "PhoneConnectRuleModel"; + String ruleText; + boolean isAllowConnection; boolean isEnable; - - public PhoneBlackRuleBean() { + boolean isSimpleView; + + public PhoneConnectRuleModel() { this.ruleText = ""; + this.isAllowConnection = false; this.isEnable = false; + this.isSimpleView = true; } - public PhoneBlackRuleBean(String ruleText, boolean isEnable) { + public PhoneConnectRuleModel(String ruleText, boolean isAllowConnection, boolean isEnable) { this.ruleText = ruleText; + this.isAllowConnection = isAllowConnection; this.isEnable = isEnable; + this.isSimpleView = true; + } + + public void setIsSimpleView(boolean isSimpleView) { + this.isSimpleView = isSimpleView; + } + + public boolean isSimpleView() { + return isSimpleView; } public void setRuleText(String ruleText) { @@ -35,6 +49,14 @@ public class PhoneBlackRuleBean extends BaseBean { return ruleText; } + public void setIsAllowConnection(boolean isAllowConnection) { + this.isAllowConnection = isAllowConnection; + } + + public boolean isAllowConnection() { + return isAllowConnection; + } + public void setIsEnable(boolean isEnable) { this.isEnable = isEnable; } @@ -43,17 +65,19 @@ public class PhoneBlackRuleBean extends BaseBean { return isEnable; } + + @Override public String getName() { - return PhoneBlackRuleBean.class.getName(); + return PhoneConnectRuleModel.class.getName(); } @Override public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { super.writeThisToJsonWriter(jsonWriter); jsonWriter.name("ruleText").value(getRuleText()); + jsonWriter.name("isAllowConnection").value(isAllowConnection()); jsonWriter.name("isEnable").value(isEnable()); - } @Override @@ -61,6 +85,8 @@ public class PhoneBlackRuleBean extends BaseBean { if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { if (name.equals("ruleText")) { setRuleText(jsonReader.nextString()); + } else if (name.equals("isAllowConnection")) { + setIsAllowConnection(jsonReader.nextBoolean()); } else if (name.equals("isEnable")) { setIsEnable(jsonReader.nextBoolean()); } else { @@ -83,6 +109,6 @@ public class PhoneBlackRuleBean extends BaseBean { jsonReader.endObject(); return this; } - - + + } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java index 792bce4..5db7262 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/RingTongBean.java @@ -12,26 +12,26 @@ import android.media.AudioManager; import android.util.JsonReader; public class RingTongBean extends BaseBean { - + public static final String TAG = "AudioRingTongBean"; - - // 模式 - int ringerMode; + + // 铃声音量 + int streamVolume; public RingTongBean() { - this.ringerMode = AudioManager.RINGER_MODE_NORMAL; + this.streamVolume = 100; } - public RingTongBean(int ringerMode) { - this.ringerMode = ringerMode; + public RingTongBean(int streamVolume) { + this.streamVolume = streamVolume; } - public void setRingerMode(int ringerMode) { - this.ringerMode = ringerMode; + public void setStreamVolume(int streamVolume) { + this.streamVolume = streamVolume; } - public int getRingerMode() { - return ringerMode; + public int getStreamVolume() { + return streamVolume; } @Override @@ -42,15 +42,15 @@ public class RingTongBean extends BaseBean { @Override public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { super.writeThisToJsonWriter(jsonWriter); - jsonWriter.name("ringerMode").value(getRingerMode()); + jsonWriter.name("streamVolume").value(getStreamVolume()); } @Override public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { - if (name.equals("ringerMode")) { - setRingerMode(jsonReader.nextInt()); + if (name.equals("streamVolume")) { + setStreamVolume(jsonReader.nextInt()); } else { return false; } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/beans/SettingsModel.java b/contacts/src/main/java/cc/winboll/studio/contacts/beans/SettingsModel.java new file mode 100644 index 0000000..2f0fc1e --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/beans/SettingsModel.java @@ -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; + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/bobulltoon/TomCat.java b/contacts/src/main/java/cc/winboll/studio/contacts/bobulltoon/TomCat.java new file mode 100644 index 0000000..d7cd44c --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/bobulltoon/TomCat.java @@ -0,0 +1,178 @@ +package cc.winboll.studio.contacts.bobulltoon; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/02 13:47:48 + * @Describe 汤姆猫管家 :使用 BoBullToon 项目,对通讯地址进行筛选判断的好朋友。 + */ +import android.content.Context; +import cc.winboll.studio.libappbase.LogUtils; +import com.hjq.toast.ToastUtils; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class TomCat { + + public static final String TAG = "TomCat"; + + List listPhoneBoBullToon = new ArrayList(); + + static volatile TomCat _TomCat; + Context mContext; + TomCat(Context context) { + mContext = context; + } + + public static synchronized TomCat getInstance(Context context) { + if (_TomCat == null) { + _TomCat = new TomCat(context); + } + return _TomCat; + } + + void downloadAndExtractZip(String zipUrl, String destinationFolder) throws IOException { + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() + .url(zipUrl) + .build(); + + try { + Response response = client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + // 下载 ZIP 文件到临时位置 + File tempZipFile = File.createTempFile("temp", ".zip"); + try { + InputStream inputStream = response.body().byteStream(); + FileOutputStream outputStream = new FileOutputStream(tempZipFile); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + + // 解压 ZIP 文件到指定文件夹 + try { + ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(tempZipFile.toPath())); + ZipEntry zipEntry; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + Path targetFilePath = Paths.get(destinationFolder, zipEntry.getName()); + if (zipEntry.isDirectory()) { + Files.createDirectories(targetFilePath); + } else { + Files.createDirectories(targetFilePath.getParent()); + try (FileOutputStream fos = new FileOutputStream(targetFilePath.toFile())) { + byte[] buffer = new byte[1024]; + int len; + while ((len = zipInputStream.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + } + zipInputStream.closeEntry(); + } + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + + // 删除临时 ZIP 文件 + tempZipFile.delete(); + LogUtils.d(TAG, "已更新 BoBullToon 数据"); + } catch (Exception e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + public boolean downloadBoBullToon() { + String zipUrl = "https://gitea.winboll.cc//Studio/BoBullToon/archive/main.zip"; // 替换为实际的 ZIP 文件 URL + String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径 + try { + // 删除旧文件 + File fOldFolder = new File(destinationFolder); + if (fOldFolder.exists()) { + deleteFolderRecursive(fOldFolder); + fOldFolder.mkdirs(); + LogUtils.d(TAG, "已清空 BoBullToon 数据"); + } + + // 更新新文件 + downloadAndExtractZip(zipUrl, destinationFolder); + LogUtils.d(TAG, "ZIP 文件下载并解压成功。"); + return true; + } catch (IOException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + 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() { + return mContext.getExternalFilesDir(TAG); + } + + public boolean loadPhoneBoBullToon() { + listPhoneBoBullToon.clear(); + File fBoBullToon = new File(getWorkingFolder(), "bobulltoon"); + if (fBoBullToon.exists()) { + LogUtils.d(TAG, String.format("getWorkingFolder() %s", getWorkingFolder())); + for (File userFolder : fBoBullToon.listFiles()) { + if (userFolder.isDirectory()) { + for (File recordFile : userFolder.listFiles()) { + listPhoneBoBullToon.add(recordFile.getName()); + } + } + } + + for (int i = 0; i < listPhoneBoBullToon.size(); i++) { + LogUtils.d(TAG, String.format("listPhoneBoBullToon add : %s", listPhoneBoBullToon.get(i))); + } + return true; + } else { + LogUtils.d(TAG, "fBoBullToon not exists。"); + } + return false; + } + + public boolean isPhoneBoBullToon(String phone) { + for (int i = 0; i < listPhoneBoBullToon.size(); i++) { + LogUtils.d(TAG, String.format("isPhoneBoBullToon(...) get(i) phone : %s", listPhoneBoBullToon.get(i))); + if (listPhoneBoBullToon.get(i).equals(phone)) { + return true; + } + } + return false; + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java b/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java index a4f9a1e..b3b9788 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/dun/Rules.java @@ -5,25 +5,36 @@ package cc.winboll.studio.contacts.dun; * @Date 2025/02/21 06:15:10 * @Describe 云盾防御规则 */ -import cc.winboll.studio.contacts.beans.PhoneBlackRuleBean; -import java.util.ArrayList; -import java.util.regex.Pattern; 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.RingTongBean; +import cc.winboll.studio.contacts.beans.SettingsModel; +import cc.winboll.studio.contacts.services.MainService; +import cc.winboll.studio.contacts.utils.RegexPPiUtils; +import cc.winboll.studio.libappbase.LogUtils; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Pattern; public class Rules { public static final String TAG = "Rules"; - ArrayList _PhoneBlacRuleBeanList; + ArrayList _PhoneConnectRuleModelList; static volatile Rules _Rules; Context mContext; + SettingsModel mSettingsModel; + Timer mDunResumeTimer; Rules(Context context) { mContext = context; - _PhoneBlacRuleBeanList = new ArrayList(); - PhoneBlackRuleBean.loadBeanList(mContext, _PhoneBlacRuleBeanList, PhoneBlackRuleBean.class); - + _PhoneConnectRuleModelList = new ArrayList(); + reload(); } + public static synchronized Rules getInstance(Context context) { if (_Rules == null) { _Rules = new Rules(context); @@ -31,46 +42,154 @@ public class 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(); + // 设置盾值在[0,DunTotalCount]之内其他值一律重置为 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() { + _PhoneConnectRuleModelList.clear(); + PhoneConnectRuleModel.loadBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class); + } + + public void saveRules() { + LogUtils.d(TAG, String.format("saveRules()")); + PhoneConnectRuleModel.saveBeanList(mContext, _PhoneConnectRuleModelList, PhoneConnectRuleModel.class); + } + + public void loadDun() { + mSettingsModel = SettingsModel.loadBean(mContext, SettingsModel.class); + if (mSettingsModel == null) { + mSettingsModel = new SettingsModel(); + SettingsModel.saveBean(mContext, mSettingsModel); + } + } + + public void saveDun() { + LogUtils.d(TAG, String.format("saveDun()")); + SettingsModel.saveBean(mContext, mSettingsModel); + } + public boolean isAllowed(String phoneNumber) { - // 黑名单拒接 - for (int i = 0; i < _PhoneBlacRuleBeanList.size(); i++) { - if (_PhoneBlacRuleBeanList.get(i).isEnable()) { - String regex = _PhoneBlacRuleBeanList.get(i).getRuleText(); - if (Pattern.matches(regex, phoneNumber)) { - return false; + // 没有启用云盾,默认允许接通任何电话 + 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 (!isDefend && MainService.isPhoneInBoBullToon(phoneNumber)) { + LogUtils.d(TAG, String.format("PhoneNumber %s\n Is In BoBullToon", phoneNumber)); + isDefend = true; + isConnect = false; + LogUtils.d(TAG, String.format("isDefend == %s\nisConnect == %s", isDefend, isConnect)); + } + + // 正则匹配规则名单校验 + if (!isDefend) { + for (int i = 0; i < _PhoneConnectRuleModelList.size(); i++) { + if (_PhoneConnectRuleModelList.get(i).isEnable()) { + String regex = _PhoneConnectRuleModelList.get(i).getRuleText(); + if (Pattern.matches(regex, phoneNumber)) { + 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; + } } } } - // 手机号码允许 - // 中国手机号码正则表达式,以1开头,第二位可以是3、4、5、6、7、8、9,后面跟9位数字 - String regex = "^1[3-9]\\d{9}$"; - if (Pattern.matches(regex, phoneNumber)) { - return true; - } - - // 指定区号号码允许 - regex = "^0660\\d+$"; - if (Pattern.matches(regex, phoneNumber)) { - return true; - } - - // 指定区号号码允许 - regex = "^020\\d+$"; - if (Pattern.matches(regex, phoneNumber)) { - return true; + if (isConnect) { + // 如果防御结果为连接,则恢复防御盾牌最大值层数 + 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)); + + // 保证盾值在[0,DunTotalCount]之内其他值一律重置为 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(); } - // 其他拒接 - return false; + // 返回校验结果 + LogUtils.d(TAG, String.format("返回校验结果 isConnect == %s", isConnect)); + return isConnect; } - public void add(String phoneRuleBlack, boolean isEnable) { - _PhoneBlacRuleBeanList.add(new PhoneBlackRuleBean(phoneRuleBlack, isEnable)); - PhoneBlackRuleBean.saveBeanList(mContext, _PhoneBlacRuleBeanList, PhoneBlackRuleBean.class); + public void add(String szPhoneConnectRule, boolean isAllowConnection, boolean isEnable) { + _PhoneConnectRuleModelList.add(new PhoneConnectRuleModel(szPhoneConnectRule, isAllowConnection, isEnable)); } - public ArrayList getPhoneBlacRuleBeanList() { - return _PhoneBlacRuleBeanList; + public ArrayList getPhoneBlacRuleBeanList() { + return _PhoneConnectRuleModelList; + } + + public SettingsModel getSettingsModel() { + return mSettingsModel; } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallFragment.java b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallFragment.java deleted file mode 100644 index 6acb172..0000000 --- a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallFragment.java +++ /dev/null @@ -1,51 +0,0 @@ -package cc.winboll.studio.contacts.fragments; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/02/20 12:57:00 - * @Describe 拨号 - */ -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import androidx.fragment.app.Fragment; -import cc.winboll.studio.contacts.R; -import cc.winboll.studio.libappbase.LogView; -import androidx.annotation.Nullable; -import androidx.annotation.NonNull; -import android.widget.TextView; - -public class CallFragment extends Fragment { - - public static final String TAG = "CallFragment"; - - private static final String ARG_PAGE = "ARG_PAGE"; - private int mPage; - - public static CallFragment newInstance(int page) { - Bundle args = new Bundle(); - args.putInt(ARG_PAGE, page); - CallFragment fragment = new CallFragment(); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments()!= null) { - mPage = getArguments().getInt(ARG_PAGE); - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_call, container, false); - TextView textView = view.findViewById(R.id.page_text); - textView.setText("这是第 " + mPage + " 页"); - return view; - } -} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallLogFragment.java b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallLogFragment.java new file mode 100644 index 0000000..4f594c2 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/CallLogFragment.java @@ -0,0 +1,125 @@ +package cc.winboll.studio.contacts.fragments; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/20 12:57:00 + * @Describe 拨号 + */ +import android.Manifest; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.CallLog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.adapters.CallLogAdapter; +import cc.winboll.studio.contacts.beans.CallLogModel; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class CallLogFragment extends Fragment { + + public static final String TAG = "CallFragment"; + + private static final String ARG_PAGE = "ARG_PAGE"; + private int mPage; + + private static final int REQUEST_READ_CALL_LOG = 1; + private RecyclerView recyclerView; + private CallLogAdapter callLogAdapter; + private List callLogList = new ArrayList<>(); + + public static CallLogFragment newInstance(int page) { + Bundle args = new Bundle(); + args.putInt(ARG_PAGE, page); + CallLogFragment fragment = new CallLogFragment(); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_call_log, container, false); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments()!= null) { + mPage = getArguments().getInt(ARG_PAGE); + } + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recyclerView = view.findViewById(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + callLogAdapter = new CallLogAdapter(getContext(), callLogList); + recyclerView.setAdapter(callLogAdapter); + + if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALL_LOG)!= PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CALL_LOG}, REQUEST_READ_CALL_LOG); + } else { + readCallLog(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_READ_CALL_LOG) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + readCallLog(); + } + } + } + + private void readCallLog() { + Cursor cursor = requireContext().getContentResolver().query( + CallLog.Calls.CONTENT_URI, + null, + null, + null, + CallLog.Calls.DATE + " DESC"); + + if (cursor!= null) { + while (cursor.moveToNext()) { + String phoneNumber = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER)); + int callType = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE)); + long callDateLong = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE)); + Date callDate = new Date(callDateLong); + + String callStatus = getCallStatus(callType); + + callLogList.add(new CallLogModel(phoneNumber, callStatus, callDate)); + } + cursor.close(); + callLogAdapter.notifyDataSetChanged(); + } + } + + private String getCallStatus(int callType) { + switch (callType) { + case CallLog.Calls.OUTGOING_TYPE: + return "Outgoing"; + case CallLog.Calls.INCOMING_TYPE: + return "Incoming"; + case CallLog.Calls.MISSED_TYPE: + return "Missed"; + default: + return "Unknown"; + } + } + +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java index 2f7dad6..356201e 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/fragments/ContactsFragment.java @@ -5,23 +5,47 @@ package cc.winboll.studio.contacts.fragments; * @Date 2025/02/20 12:57:50 * @Describe 联系人 */ +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; import android.os.Bundle; +import android.provider.ContactsContract; +import android.text.Editable; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; +import android.widget.Button; +import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.adapters.ContactAdapter; +import cc.winboll.studio.contacts.beans.ContactModel; +import com.hjq.toast.ToastUtils; +import java.util.ArrayList; +import java.util.List; + public class ContactsFragment extends Fragment { - + public static final String TAG = "ContactsFragment"; - + private static final String ARG_PAGE = "ARG_PAGE"; private int mPage; + private static final int REQUEST_READ_CONTACTS = 1; + private RecyclerView recyclerView; + private ContactAdapter contactAdapter; + private List contactList = new ArrayList<>(); + private List originalContactList = new ArrayList<>(); + private EditText searchEditText; + public static ContactsFragment newInstance(int page) { Bundle args = new Bundle(); args.putInt(ARG_PAGE, page); @@ -33,18 +57,111 @@ public class ContactsFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (getArguments()!= null) { + if (getArguments() != null) { mPage = getArguments().getInt(ARG_PAGE); } } @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_contacts, container, false); - TextView textView = view.findViewById(R.id.page_text); - textView.setText("这是第 " + mPage + " 页"); - return view; + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_contacts, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recyclerView = view.findViewById(R.id.contacts_recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + contactAdapter = new ContactAdapter(contactList); + recyclerView.setAdapter(contactAdapter); + + searchEditText = view.findViewById(R.id.search_edit_text); + searchEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + filterContacts(s.toString()); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + + if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS); + } else { + readContacts(); + } + + Button btnDial = view.findViewById(R.id.btn_dial); + btnDial.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View p1) { + + String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", ""); + //phoneNumber = "+8616769764848"; + ToastUtils.show(phoneNumber); + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + // 添加 FLAG_ACTIVITY_NEW_TASK 标志 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_READ_CONTACTS) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + readContacts(); + } + } + } + + private void readContacts() { + contactList.clear(); + originalContactList.clear(); + Cursor cursor = requireContext().getContentResolver().query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + null, + null, + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"); + + if (cursor != null) { + while (cursor.moveToNext()) { + String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); + ContactModel contact = new ContactModel(name, number); + contactList.add(contact); + originalContactList.add(contact); + } + cursor.close(); + contactAdapter.notifyDataSetChanged(); + } + } + + private void filterContacts(String query) { + contactList.clear(); + if (query.isEmpty()) { + contactList.addAll(originalContactList); + } else { + for (ContactModel contact : originalContactList) { + if (contact.getName().toLowerCase().contains(query.toLowerCase()) || + contact.getPinyin().toLowerCase().contains(query.toLowerCase()) || + contact.getNumber().toLowerCase().contains(query.toLowerCase())) { + contactList.add(contact); + } + } + } + contactAdapter.notifyDataSetChanged(); } } + diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java b/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java index f555114..42e98aa 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/listenphonecall/CallListenerService.java @@ -21,6 +21,8 @@ import android.widget.TextView; import androidx.annotation.Nullable; import cc.winboll.studio.contacts.MainActivity; import cc.winboll.studio.contacts.R; +import cc.winboll.studio.contacts.phonecallui.PhoneCallActivity; +import cc.winboll.studio.contacts.phonecallui.PhoneCallService; public class CallListenerService extends Service { @@ -152,9 +154,12 @@ public class CallListenerService extends Service { @Override public void onClick(View view) { - Intent intent = new Intent(getApplicationContext(), MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - CallListenerService.this.startActivity(intent); +// Intent intent = new Intent(getApplicationContext(), MainActivity.class); +// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); +// CallListenerService.this.startActivity(intent); + + PhoneCallService.CallType callType = isCallingIn ? PhoneCallService.CallType.CALL_IN: PhoneCallService.CallType.CALL_OUT; + PhoneCallActivity.actionStart(CallListenerService.this, callNumber, callType); } }); } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java index eec1ab7..95d99c5 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallActivity.java @@ -57,10 +57,9 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick setContentView(R.layout.activity_phone_call); ActivityStack.getInstance().addActivity(this); - initData(); - initView(); + } private void initData() { @@ -74,9 +73,9 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick private void initView() { int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //hide navigationBar - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //hide navigationBar + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; getWindow().getDecorView().setSystemUiVisibility(uiOptions); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); @@ -94,9 +93,7 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick if (callType == PhoneCallService.CallType.CALL_IN) { tvCallNumberLabel.setText("来电号码"); tvPickUp.setVisibility(View.VISIBLE); - } - // 打出的电话 - else if (callType == PhoneCallService.CallType.CALL_OUT) { + } else if (callType == PhoneCallService.CallType.CALL_OUT) { tvCallNumberLabel.setText("呼叫号码"); tvPickUp.setVisibility(View.GONE); phoneCallManager.openSpeaker(); @@ -107,13 +104,13 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick public void showOnLockScreen() { this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | - WindowManager.LayoutParams.FLAG_FULLSCREEN | - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON, - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | - WindowManager.LayoutParams.FLAG_FULLSCREEN | - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | - WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + WindowManager.LayoutParams.FLAG_FULLSCREEN | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON, + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | + WindowManager.LayoutParams.FLAG_FULLSCREEN | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); } @Override @@ -123,18 +120,18 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick tvPickUp.setVisibility(View.GONE); tvCallingTime.setVisibility(View.VISIBLE); onGoingCallTimer.schedule(new TimerTask() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @SuppressLint("SetTextI18n") - @Override - public void run() { - callingTime++; - tvCallingTime.setText("通话中:" + getCallingTime()); - } - }); - } - }, 0, 1000); + @Override + public void run() { + runOnUiThread(new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + callingTime++; + tvCallingTime.setText("通话中:" + getCallingTime()); + } + }); + } + }, 0, 1000); } else if (v.getId() == R.id.tv_phone_hang_up) { phoneCallManager.disconnect(); stopTimer(); @@ -145,8 +142,8 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick int minute = callingTime / 60; int second = callingTime % 60; return (minute < 10 ? "0" + minute : minute) + - ":" + - (second < 10 ? "0" + second : second); + ":" + + (second < 10 ? "0" + second : second); } private void stopTimer() { @@ -160,7 +157,6 @@ public class PhoneCallActivity extends AppCompatActivity implements View.OnClick @Override protected void onDestroy() { super.onDestroy(); - phoneCallManager.destroy(); } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java index 88eb69e..3786899 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/phonecallui/PhoneCallService.java @@ -21,8 +21,6 @@ import cc.winboll.studio.libappbase.LogUtils; public class PhoneCallService extends InCallService { public static final String TAG = "PhoneCallService"; - - private volatile int originalRingVolume; private final Call.Callback callback = new Call.Callback() { @Override @@ -61,25 +59,58 @@ public class PhoneCallService extends InCallService { String phoneNumber = details.getHandle().getSchemeSpecificPart(); // 记录原始铃声音量 + // AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - originalRingVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + int ringerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + // 恢复铃声音量,预防其他意外条件导致的音量变化问题 + // + + // 读取应用配置,未配置就初始化配置文件 + RingTongBean bean = RingTongBean.loadBean(this, RingTongBean.class); + if (bean == null) { + // 初始化配置 + bean = new RingTongBean(); + RingTongBean.saveBean(this, bean); + } + // 如果当前音量和应用保存的不一致就恢复为应用设定值 + // 恢复铃声音量 + try { + if (ringerVolume != bean.getStreamVolume()) { + audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0); + //audioManager.setMode(AudioManager.RINGER_MODE_NORMAL); + } + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + // 检查电话接收规则 if (!Rules.getInstance(this).isAllowed(phoneNumber)) { - // 预先静音 - audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0); + // 调低音量 + try { + audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0); + //audioManager.setMode(AudioManager.RINGER_MODE_SILENT); + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } // 断开电话 call.disconnect(); // 停顿1秒,预防第一声铃声响动 try { - Thread.sleep(1000); + Thread.sleep(500); } catch (InterruptedException e) { LogUtils.d(TAG, ""); } // 恢复铃声音量 - audioManager.setStreamVolume(AudioManager.STREAM_RING, originalRingVolume, 0); + try { + audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0); + //audioManager.setMode(AudioManager.RINGER_MODE_NORMAL); + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } // 屏蔽电话结束 return; } + // 正常接听电话 PhoneCallActivity.actionStart(this, phoneNumber, callType); } @@ -88,12 +119,8 @@ public class PhoneCallService extends InCallService { @Override public void onCallRemoved(Call call) { super.onCallRemoved(call); - call.unregisterCallback(callback); PhoneCallManager.call = null; - AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - // 恢复铃声音量 - audioManager.setStreamVolume(AudioManager.STREAM_RING, originalRingVolume, 0); } public enum CallType { diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java b/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java index d1dfb52..924ddab 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/receivers/MainReceiver.java @@ -9,13 +9,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.media.RingtoneManager; -import android.net.Uri; -import android.util.Log; import cc.winboll.studio.contacts.services.MainService; import com.hjq.toast.ToastUtils; import java.lang.ref.WeakReference; -import cc.winboll.studio.libappbase.LogUtils; public class MainReceiver extends BroadcastReceiver { @@ -43,7 +39,7 @@ public class MainReceiver extends BroadcastReceiver { public void registerAction(Context context) { IntentFilter filter=new IntentFilter(); filter.addAction(ACTION_BOOT_COMPLETED); - //filter.addAction(Intent.ACTION_BATTERY_CHANGED); + //filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); context.registerReceiver(this, filter); } } diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java b/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java index a031f4b..6fd8673 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/services/AssistantService.java @@ -15,8 +15,6 @@ import android.os.IBinder; import cc.winboll.studio.contacts.beans.MainServiceBean; import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.SOS; -import cc.winboll.studio.libappbase.bean.APPSOSBean; public class AssistantService extends Service { diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java b/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java index 3eef1a4..f61cde5 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/services/MainService.java @@ -11,25 +11,29 @@ package cc.winboll.studio.contacts.services; * https://blog.csdn.net/cyp331203/article/details/38920491 */ import android.app.Service; -import cc.winboll.studio.contacts.listenphonecall.CallListenerService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.media.AudioManager; import android.os.Binder; import android.os.IBinder; import cc.winboll.studio.contacts.beans.MainServiceBean; +import cc.winboll.studio.contacts.beans.RingTongBean; +import cc.winboll.studio.contacts.bobulltoon.TomCat; +import cc.winboll.studio.contacts.dun.Rules; import cc.winboll.studio.contacts.handlers.MainServiceHandler; +import cc.winboll.studio.contacts.listenphonecall.CallListenerService; import cc.winboll.studio.contacts.receivers.MainReceiver; import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.contacts.threads.MainServiceThread; -import cc.winboll.studio.contacts.widgets.APPStatusWidget; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.SOS; -import cc.winboll.studio.libappbase.bean.APPSOSBean; -import cc.winboll.studio.contacts.dun.Rules; -import android.media.AudioManager; -import com.hjq.toast.ToastUtils; +import cc.winboll.studio.libappbase.sos.SOS; +import java.util.Timer; +import java.util.TimerTask; +import cc.winboll.studio.libappbase.sos.WinBoll; +import cc.winboll.studio.contacts.App; +import cc.winboll.studio.libappbase.sos.APPModel; public class MainService extends Service { @@ -48,8 +52,9 @@ public class MainService extends Service { AssistantService mAssistantService; boolean isBound = false; MainReceiver mMainReceiver; - - + Timer mStreamVolumeCheckTimer; + static volatile TomCat _TomCat; + @Override public IBinder onBind(Intent intent) { return new MyBinder(); @@ -71,9 +76,37 @@ public class MainService extends Service { mMyServiceConnection = new MyServiceConnection(); } mMainServiceHandler = new MainServiceHandler(this); - - - + + // 铃声检查定时器 + mStreamVolumeCheckTimer = new Timer(); + mStreamVolumeCheckTimer.schedule(new TimerTask() { + @Override + public void run() { + AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); + int ringerVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); + // 恢复铃声音量,预防其他意外条件导致的音量变化问题 + // + + // 读取应用配置,未配置就初始化配置文件 + RingTongBean bean = RingTongBean.loadBean(MainService.this, RingTongBean.class); + if (bean == null) { + // 初始化配置 + bean = new RingTongBean(); + RingTongBean.saveBean(MainService.this, bean); + } + // 如果当前音量和应用保存的不一致就恢复为应用设定值 + // 恢复铃声音量 + try { + if (ringerVolume != bean.getStreamVolume()) { + audioManager.setStreamVolume(AudioManager.STREAM_RING, bean.getStreamVolume(), 0); + //audioManager.setMode(AudioManager.RINGER_MODE_NORMAL); + } + } catch (java.lang.SecurityException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + }, 1000, 60000); + // 运行服务内容 mainService(); } @@ -97,18 +130,26 @@ public class MainService extends Service { // 唤醒守护进程 wakeupAndBindAssistant(); // 召唤 WinBoll APP 绑定本服务 - SOS.bindToAPPService(this, new APPSOSBean(getPackageName(), MainService.class.getName())); + if (App.isDebuging()) { + WinBoll.bindToAPPBaseBeta(this, MainService.class.getName()); + } else { + WinBoll.bindToAPPBase(this, MainService.class.getName()); + } + + // 初始化服务运行参数 + _TomCat = TomCat.getInstance(this); + if (!_TomCat.loadPhoneBoBullToon()) { + LogUtils.d(TAG, "没有下载 BoBullToon 数据。BoBullToon 参数无法加载。"); + } if (mMainReceiver == null) { // 注册广播接收器 mMainReceiver = new MainReceiver(this); mMainReceiver.registerAction(this); } - - Rules.getInstance(this); - //Rules.getInstance(this).add("18888888888", true); - //Rules.getInstance(this).add("16769764848", true); - + + Rules.getInstance(this).loadRules(); + startPhoneCallListener(); MainServiceThread.getInstance(this, mMainServiceHandler).start(); @@ -117,6 +158,14 @@ public class MainService extends Service { } } + public static boolean isPhoneInBoBullToon(String phone) { + if (_TomCat != null) { + return _TomCat.isPhoneBoBullToon(phone); + } + return false; + } + + // 唤醒和绑定守护进程 // void wakeupAndBindAssistant() { @@ -137,7 +186,7 @@ public class MainService extends Service { // LogUtils.d(TAG, "startService(intent)"); // bindService(new Intent(this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); } - + void startPhoneCallListener() { Intent callListener = new Intent(this, CallListenerService.class); startService(callListener); @@ -168,7 +217,7 @@ public class MainService extends Service { // 停止主要进程 MainServiceThread.getInstance(this, mMainServiceHandler).setIsExit(true); - + } super.onDestroy(); @@ -191,7 +240,11 @@ public class MainService extends Service { if (mMainServiceBean.isEnable()) { // 唤醒守护进程 wakeupAndBindAssistant(); - SOS.sosWinBollService(getApplicationContext(), new APPSOSBean(getPackageName(), MainService.class.getName())); + if (App.isDebuging()) { + SOS.sosToAppBase(getApplicationContext(), MainService.class.getName()); + } else { + SOS.sosToAppBaseBeta(getApplicationContext(), MainService.class.getName()); + } } isBound = false; mAssistantService = null; @@ -230,14 +283,40 @@ public class MainService extends Service { public static void stopMainService(Context context) { 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(); bean.setIsEnable(false); MainServiceBean.saveBean(context, bean); context.stopService(new Intent(context, MainService.class)); } - public static void startMainService(Context context) { - LogUtils.d(TAG, "startMainService"); + public static void startMainServiceAndSaveStatus(Context context) { + LogUtils.d(TAG, "startMainServiceAndSaveStatus"); MainServiceBean bean = new MainServiceBean(); bean.setIsEnable(true); MainServiceBean.saveBean(context, bean); diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java b/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java index 0dfd918..c703d83 100644 --- a/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java +++ b/contacts/src/main/java/cc/winboll/studio/contacts/threads/MainServiceThread.java @@ -6,11 +6,7 @@ package cc.winboll.studio.contacts.threads; */ import android.content.Context; import cc.winboll.studio.contacts.handlers.MainServiceHandler; -import cc.winboll.studio.contacts.services.MainService; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.SOS; -import cc.winboll.studio.libappbase.bean.APPSOSBean; -import com.hjq.toast.ToastUtils; import java.lang.ref.WeakReference; public class MainServiceThread extends Thread { diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/ContactUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/ContactUtils.java new file mode 100644 index 0000000..e94a647 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/ContactUtils.java @@ -0,0 +1,93 @@ +package cc.winboll.studio.contacts.utils; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract; +import java.util.HashMap; +import java.util.Map; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/06 21:08:16 + * @Describe ContactUtils + */ +public class ContactUtils { + + public static final String TAG = "ContactUtils"; + + Map contactMap = new HashMap<>(); + + static volatile ContactUtils _ContactUtils; + Context mContext; + ContactUtils(Context context) { + mContext = context; + relaodContacts(); + } + public synchronized static ContactUtils getInstance(Context context) { + if (_ContactUtils == null) { + _ContactUtils = new ContactUtils(context); + } + return _ContactUtils; + } + + public void relaodContacts() { + readContacts(); + } + + private void readContacts() { + contactMap.clear(); + ContentResolver contentResolver = mContext.getContentResolver(); + Cursor cursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, null, null, null); + if (cursor != null) { + while (cursor.moveToNext()) { + String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); + //Map contactMap = new HashMap<>(); + contactMap.put(formatToSimplePhoneNumber(phoneNumber), displayName); + } + cursor.close(); + } + // 此时 contactList 就是存储联系人信息的 Map 列表 + } + + public String getContactsName(String phone) { + String result = contactMap.get(formatToSimplePhoneNumber(phone)); + return result == null ? "[NoInContacts]" : result; + } + +// static String getSimplePhone(String phone) { +// return phone.replaceAll("[+\\s]", ""); +// } + + public static String formatToSimplePhoneNumber(String number) { + // 去除所有空格和非数字字符 + return number.replaceAll("[^0-9]", ""); + } + + public static String getDisplayNameByPhone(Context context, String phoneNumber) { + String displayName = null; + ContentResolver resolver = context.getContentResolver(); + String[] projection = {ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}; + Cursor cursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, ContactsContract.CommonDataKinds.Phone.NUMBER + "=?", new String[]{formatToSimplePhoneNumber(phoneNumber)}, null); + if (cursor!= null && cursor.moveToFirst()) { + displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + cursor.close(); + } + return displayName; + } + + public static String formatToSpacePhoneNumber(String simpleNumber) { + // 去除所有空格和非数字字符 + StringBuilder sbSpaceNumber = new StringBuilder(); + String regex = "^1[0-9]{10}$"; + if(simpleNumber.matches(regex)) { + sbSpaceNumber.append(simpleNumber.substring(0,2)); + sbSpaceNumber.append(" "); + sbSpaceNumber.append(simpleNumber.substring(3,6)); + sbSpaceNumber.append(" "); + sbSpaceNumber.append(simpleNumber.substring(7,10)); + } + return sbSpaceNumber.toString(); + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/PhoneUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/PhoneUtils.java new file mode 100644 index 0000000..9fa3e17 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/PhoneUtils.java @@ -0,0 +1,27 @@ +package cc.winboll.studio.contacts.utils; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/26 15:21:48 + * @Describe PhoneUtils + */ +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import androidx.core.app.ActivityCompat; + +public class PhoneUtils { + + public static final String TAG = "PhoneUtils"; + + public static void call(Context context, String phoneNumber) { + Intent intent = new Intent(Intent.ACTION_CALL); + intent.setData(android.net.Uri.parse("tel:" + phoneNumber)); + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + return; + } + context.startActivity(intent); + } + +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/utils/RegexPPiUtils.java b/contacts/src/main/java/cc/winboll/studio/contacts/utils/RegexPPiUtils.java new file mode 100644 index 0000000..c00a9f2 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/utils/RegexPPiUtils.java @@ -0,0 +1,32 @@ +package cc.winboll.studio.contacts.utils; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2024/12/09 19:00:21 + * @Describe .* 前置预防针 + regex pointer preventive injection + 简称 RegexPPi + */ +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexPPiUtils { + + public static final String TAG = "RegexPPiUtils"; + + // + // 检验文本是否满足适合正则表达式模式计算 + // + public static boolean isPPiOK(String text) { + //String text = "这里是一些任意的文本内容"; + String regex = ".*"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(text); + /*if (matcher.matches()) { + System.out.println("文本满足该正则表达式模式"); + } else { + System.out.println("文本不满足该正则表达式模式"); + }*/ + return matcher.matches(); + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/views/DuInfoTextView.java b/contacts/src/main/java/cc/winboll/studio/contacts/views/DuInfoTextView.java new file mode 100644 index 0000000..62d968c --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/views/DuInfoTextView.java @@ -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)); + } +} diff --git a/contacts/src/main/java/cc/winboll/studio/contacts/views/LeftScrollView.java b/contacts/src/main/java/cc/winboll/studio/contacts/views/LeftScrollView.java new file mode 100644 index 0000000..2bebd41 --- /dev/null +++ b/contacts/src/main/java/cc/winboll/studio/contacts/views/LeftScrollView.java @@ -0,0 +1,220 @@ +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; + +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 Button upButton; + private Button downButton; + private float mStartX; + private float mEndX; + private boolean isScrolling = false; + private int nScrollAcceptSize; + + 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); + // 向上按钮 + upButton = viewMain.findViewById(R.id.up_btn); + // 向下按钮 + downButton = viewMain.findViewById(R.id.down_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(); + } + } + }); + // 编辑按钮点击事件 + upButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (onActionListener != null) { + onActionListener.onUp(); + } + } + }); + + // 删除按钮点击事件 + downButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (onActionListener != null) { + onActionListener.onDown(); + } + } + }); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + LogUtils.d(TAG, "ACTION_DOWN"); + mStartX = event.getX(); +// isScrolling = false; + break; + case MotionEvent.ACTION_MOVE: + //LogUtils.d(TAG, "ACTION_MOVE"); +// float currentX = event.getX(); +// float deltaX = mStartX - currentX; +// //mLastX = currentX; +// if (Math.abs(deltaX) > 0) { +// isScrolling = true; +// } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (getScrollX() > 0) { + LogUtils.d(TAG, "ACTION_UP"); + mEndX = event.getX(); + LogUtils.d(TAG, String.format("mStartX %f, mEndX %f", mStartX, mEndX)); + if (mEndX < mStartX) { + LogUtils.d(TAG, String.format("mEndX >= mStartX \ngetScrollX() %d", getScrollX())); + //if (getScrollX() > editButton.getWidth()) { + if (Math.abs(mStartX - mEndX) > editButton.getWidth()) { + smoothScrollToRight(); + } else { + smoothScrollToLeft(); + } + } else { + LogUtils.d(TAG, String.format("mEndX >= mStartX \ngetScrollX() %d", getScrollX())); + //if (getScrollX() > deleteButton.getWidth()) { + if (Math.abs(mEndX - mStartX) > deleteButton.getWidth()) { + smoothScrollToLeft(); + } else { + smoothScrollToRight(); + } + } + } + break; + } + return super.onTouchEvent(event); + } + + void smoothScrollToRight() { + mEndX = 0; + mStartX = 0; + 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);"); + } + } + + void smoothScrollToLeft() { + mEndX = 0; + mStartX = 0; + // 在手指抬起时,使用 post 方法调用 smoothScrollTo(0, 0) + post(new Runnable() { + @Override + public void run() { + smoothScrollTo(0, 0); + LogUtils.d(TAG, "smoothScrollTo(0, 0);"); + } + }); + } + + // 设置文本内容 + public void setText(CharSequence text) { + textView.setText(text); + } + + // 定义回调接口 + public interface OnActionListener { + void onEdit(); + void onDelete(); + void onUp(); + void onDown(); + } + + private OnActionListener onActionListener; + + public void setOnActionListener(OnActionListener listener) { + this.onActionListener = listener; + } +} + diff --git a/contacts/src/main/res/drawable/ic_call.xml b/contacts/src/main/res/drawable/ic_call.xml new file mode 100644 index 0000000..c5802bb --- /dev/null +++ b/contacts/src/main/res/drawable/ic_call.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/contacts/src/main/res/drawable/recycler_view_border.xml b/contacts/src/main/res/drawable/recycler_view_border.xml new file mode 100644 index 0000000..d538cab --- /dev/null +++ b/contacts/src/main/res/drawable/recycler_view_border.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/contacts/src/main/res/layout/activity_main.xml b/contacts/src/main/res/layout/activity_main.xml index 5376c86..27af1a4 100644 --- a/contacts/src/main/res/layout/activity_main.xml +++ b/contacts/src/main/res/layout/activity_main.xml @@ -16,12 +16,12 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1.0" - android:id="@+id/activitymainViewPager1"/> + android:id="@+id/viewPager"/> + android:id="@+id/tabLayout"/> diff --git a/contacts/src/main/res/layout/activity_phone_call.xml b/contacts/src/main/res/layout/activity_phone_call.xml index 9768e9f..327130c 100644 --- a/contacts/src/main/res/layout/activity_phone_call.xml +++ b/contacts/src/main/res/layout/activity_phone_call.xml @@ -1,95 +1,98 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".phonecallui.PhoneCallActivity"> - + - + + - + - + - + - + - + - + - - + + + + + - - \ No newline at end of file diff --git a/contacts/src/main/res/layout/activity_settings.xml b/contacts/src/main/res/layout/activity_settings.xml index 2122230..3bd69f2 100644 --- a/contacts/src/main/res/layout/activity_settings.xml +++ b/contacts/src/main/res/layout/activity_settings.xml @@ -1,51 +1,290 @@ - - - + android:layout_height="wrap_content"> - - - + android:id="@+id/activitymainToolbar1"/> -