From 3bcfc5a5daf0af7a92771682825547b45951a489 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sat, 22 Feb 2025 01:46:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Positions=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- positions/README.md | 38 ++ positions/app_update_description.txt | 1 + positions/build.gradle | 71 ++++ positions/build.properties | 8 + positions/proguard-rules.pro | 17 + positions/src/beta/AndroidManifest.xml | 13 + positions/src/beta/res/values/strings.xml | 6 + positions/src/main/AndroidManifest.xml | 62 +++ .../java/cc/winboll/studio/positions/App.java | 28 ++ .../studio/positions/MainActivity.java | 387 ++++++++++++++++++ .../activities/SettingsActivity.java | 138 +++++++ .../positions/adapters/MyPagerAdapter.java | 42 ++ .../positions/beans/MainServiceBean.java | 68 +++ .../positions/fragments/CallFragment.java | 51 +++ .../positions/fragments/ContactsFragment.java | 50 +++ .../positions/fragments/LogFragment.java | 50 +++ .../handlers/MainServiceHandler.java | 38 ++ .../positions/receivers/MainReceiver.java | 49 +++ .../positions/services/AssistantService.java | 139 +++++++ .../positions/services/MainService.java | 228 +++++++++++ .../positions/threads/MainServiceThread.java | 77 ++++ .../src/main/res/drawable/ic_launcher.xml | 11 + .../res/drawable/ic_launcher_background.xml | 170 ++++++++ .../main/res/drawable/ic_launcher_disable.xml | 11 + .../res/drawable/ic_launcher_foreground.xml | 10 + .../ic_launcher_foreground_disable.xml | 10 + .../src/main/res/drawable/shape_gradient.xml | 10 + .../src/main/res/layout/activity_main.xml | 27 ++ .../src/main/res/layout/activity_settings.xml | 46 +++ .../src/main/res/layout/fragment_call.xml | 29 ++ .../src/main/res/layout/fragment_contacts.xml | 15 + .../src/main/res/layout/fragment_log.xml | 12 + positions/src/main/res/layout/view_toast.xml | 32 ++ positions/src/main/res/menu/toolbar_main.xml | 8 + positions/src/main/res/values/colors.xml | 7 + positions/src/main/res/values/strings.xml | 6 + positions/src/main/res/values/styles.xml | 17 + .../src/main/res/xml/studio_provider.xml | 25 ++ positions/src/stage/AndroidManifest.xml | 12 + positions/src/stage/res/values/strings.xml | 6 + settings.gradle-demo | 5 + winboll-shared/build.properties | 12 +- 42 files changed, 2036 insertions(+), 6 deletions(-) create mode 100644 positions/README.md create mode 100644 positions/app_update_description.txt create mode 100644 positions/build.gradle create mode 100644 positions/build.properties create mode 100644 positions/proguard-rules.pro create mode 100644 positions/src/beta/AndroidManifest.xml create mode 100644 positions/src/beta/res/values/strings.xml create mode 100644 positions/src/main/AndroidManifest.xml create mode 100644 positions/src/main/java/cc/winboll/studio/positions/App.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/MainActivity.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/activities/SettingsActivity.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/adapters/MyPagerAdapter.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/beans/MainServiceBean.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/fragments/CallFragment.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/fragments/ContactsFragment.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/fragments/LogFragment.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/handlers/MainServiceHandler.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/receivers/MainReceiver.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/services/AssistantService.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/services/MainService.java create mode 100644 positions/src/main/java/cc/winboll/studio/positions/threads/MainServiceThread.java create mode 100644 positions/src/main/res/drawable/ic_launcher.xml create mode 100644 positions/src/main/res/drawable/ic_launcher_background.xml create mode 100644 positions/src/main/res/drawable/ic_launcher_disable.xml create mode 100644 positions/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 positions/src/main/res/drawable/ic_launcher_foreground_disable.xml create mode 100644 positions/src/main/res/drawable/shape_gradient.xml create mode 100644 positions/src/main/res/layout/activity_main.xml create mode 100644 positions/src/main/res/layout/activity_settings.xml create mode 100644 positions/src/main/res/layout/fragment_call.xml create mode 100644 positions/src/main/res/layout/fragment_contacts.xml create mode 100644 positions/src/main/res/layout/fragment_log.xml create mode 100644 positions/src/main/res/layout/view_toast.xml create mode 100644 positions/src/main/res/menu/toolbar_main.xml create mode 100644 positions/src/main/res/values/colors.xml create mode 100644 positions/src/main/res/values/strings.xml create mode 100644 positions/src/main/res/values/styles.xml create mode 100644 positions/src/main/res/xml/studio_provider.xml create mode 100644 positions/src/stage/AndroidManifest.xml create mode 100644 positions/src/stage/res/values/strings.xml diff --git a/positions/README.md b/positions/README.md new file mode 100644 index 0000000..6acd7f1 --- /dev/null +++ b/positions/README.md @@ -0,0 +1,38 @@ +# Positions + +#### 介绍 +位置应用,与卫星定位有关的应用。可以根据设定的位置与时间条件判断,来发送通知的应用。 + +#### 软件架构 +适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。 +也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。 + + +#### Gradle 编译说明 +调试版编译命令 :gradle assembleBetaDebug +阶段版编译命令 :gradle assembleStageRelease + +#### 使用说明 + +在安卓系统中需要设置两个权限允许。 +1.自启动权限允许。 +2.省电策略-无限制权限允许。 + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 : ZhanGSKen(ZhanGSKen@AliYun.Com) +4. 新建 Pull Request + + +#### 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) + +#### 参考文档 diff --git a/positions/app_update_description.txt b/positions/app_update_description.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/positions/app_update_description.txt @@ -0,0 +1 @@ + diff --git a/positions/build.gradle b/positions/build.gradle new file mode 100644 index 0000000..6d015af --- /dev/null +++ b/positions/build.gradle @@ -0,0 +1,71 @@ +apply plugin: 'com.android.application' +apply from: '../.winboll/winboll_app_build.gradle' +apply from: '../.winboll/winboll_lint_build.gradle' + +def genVersionName(def versionName){ + // 检查编译标志位配置 + assert (winbollBuildProps['stageCount'] != null) + assert (winbollBuildProps['baseVersion'] != null) + // 保存基础版本号 + winbollBuildProps.setProperty("baseVersion", "${versionName}"); + //保存编译标志配置 + FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile) + winbollBuildProps.store(fos, "${winbollBuildPropsDesc}"); + fos.close(); + + // 返回编译版本号 + return "${versionName}." + winbollBuildProps['stageCount'] +} + +android { + compileSdkVersion 32 + buildToolsVersion "33.0.3" + + defaultConfig { + applicationId "cc.winboll.studio.positions" + minSdkVersion 21 + targetSdkVersion 30 + versionCode 1 + // versionName 更新后需要手动设置 + // 项目模块目录的 build.gradle 文件的 stageCount=0 + // Gradle编译环境下合起来的 versionName 就是 "${versionName}.0" + versionName "1.0" + if(true) { + versionName = genVersionName("${versionName}") + } + } + + buildTypes { + release { + minifyEnabled false + 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']) +} diff --git a/positions/build.properties b/positions/build.properties new file mode 100644 index 0000000..c76c4df --- /dev/null +++ b/positions/build.properties @@ -0,0 +1,8 @@ +#Created by .winboll/winboll_app_build.gradle +#Fri Feb 21 17:45:58 GMT 2025 +stageCount=0 +libraryProject= +baseVersion=1.0 +publishVersion=1.0.0 +buildCount=1 +baseBetaVersion=1.0.1 diff --git a/positions/proguard-rules.pro b/positions/proguard-rules.pro new file mode 100644 index 0000000..233bad2 --- /dev/null +++ b/positions/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/positions/src/beta/AndroidManifest.xml b/positions/src/beta/AndroidManifest.xml new file mode 100644 index 0000000..c598f4f --- /dev/null +++ b/positions/src/beta/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/positions/src/beta/res/values/strings.xml b/positions/src/beta/res/values/strings.xml new file mode 100644 index 0000000..7ef2ccf --- /dev/null +++ b/positions/src/beta/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + Positions+ + + diff --git a/positions/src/main/AndroidManifest.xml b/positions/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cbe3d34 --- /dev/null +++ b/positions/src/main/AndroidManifest.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/positions/src/main/java/cc/winboll/studio/positions/App.java b/positions/src/main/java/cc/winboll/studio/positions/App.java new file mode 100644 index 0000000..0782b21 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/App.java @@ -0,0 +1,28 @@ +package cc.winboll.studio.positions; + +/** + * @Author ZhanGSKen@QQ.COM + * @Date 2024/12/08 15:10:51 + * @Describe 全局应用类 + */ +import cc.winboll.studio.libappbase.GlobalApplication; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libapputils.app.WinBollActivityManager; + +public class App extends GlobalApplication { + + public static final String TAG = "App"; + + @Override + public void onCreate() { + // 必须在调用基类前设置应用调试标志, + // 这样可以预先设置日志与数据的存储根目录。 + setIsDebuging(this, BuildConfig.DEBUG); + super.onCreate(); + // 设置 WinBoll 应用 UI 类型 + WinBollActivityManager.getInstance(this).setWinBollUI_TYPE(WinBollActivityManager.WinBollUI_TYPE.Aplication); + + LogUtils.d(TAG, "onCreate"); + } + +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java b/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java new file mode 100644 index 0000000..0732632 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/MainActivity.java @@ -0,0 +1,387 @@ +package cc.winboll.studio.positions; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.telecom.TelecomManager; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.viewpager.widget.ViewPager; +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.positions.R; +import cc.winboll.studio.positions.activities.SettingsActivity; +import cc.winboll.studio.positions.adapters.MyPagerAdapter; +import cc.winboll.studio.positions.beans.MainServiceBean; +import cc.winboll.studio.positions.services.MainService; +import com.google.android.material.tabs.TabLayout; +import java.util.ArrayList; +import java.util.List; + +final public class MainActivity extends AppCompatActivity implements IWinBollActivity, ViewPager.OnPageChangeListener, View.OnClickListener { + + public static final String TAG = "MainActivity"; + + public static final int REQUEST_HOME_ACTIVITY = 0; + public static final int REQUEST_ABOUT_ACTIVITY = 1; + + public static final String ACTION_SOS = "cc.winboll.studio.libappbase.WinBoll.ACTION_SOS"; + + LogView mLogView; + Toolbar mToolbar; + CheckBox cbMainService; + MainServiceBean mMainServiceBean; + ViewPager viewPager; + private List views; //用来存放放进ViewPager里面的布局 + //实例化存储imageView(导航原点)的集合 + ImageView[] imageViews; + //MyPagerAdapter adapter;//适配器 + MyPagerAdapter pagerAdapter; + LinearLayout linearLayout;//下标所在在LinearLayout布局里 + int currentPoint = 0;//当前被选中中页面的下标 + + private static final int DIALER_REQUEST_CODE = 1; + + @Override + public AppCompatActivity getActivity() { + return this; + } + + @Override + public APPInfo getAppInfo() { +// String szBranchName = "contacts"; +// +// APPInfo appInfo = AboutActivityFactory.buildDefaultAPPInfo(); +// appInfo.setAppName("Contacts"); +// appInfo.setAppIcon(cc.winboll.studio.libapputils.R.drawable.ic_winboll); +// appInfo.setAppDescription("Contacts Description"); +// appInfo.setAppGitName("APP"); +// appInfo.setAppGitOwner("Studio"); +// appInfo.setAppGitAPPBranch(szBranchName); +// appInfo.setAppGitAPPSubProjectFolder(szBranchName); +// appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=Contacts"); +// appInfo.setAppAPKName("Contacts"); +// appInfo.setAppAPKFolderName("Contacts"); +// return appInfo; + return null; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + // 接收并处理 Intent 数据,函数 Intent 处理接收就直接返回 + //if (prosessIntents(getIntent())) return; + // 以下正常创建主窗口 + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // 初始化工具栏 + mToolbar = findViewById(R.id.activitymainToolbar1); + setSupportActionBar(mToolbar); + if (isEnableDisplayHomeAsUp()) { + // 显示后退按钮 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + getSupportActionBar().setSubtitle(getTag()); + + 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) { +// mMainServiceBean = new MainServiceBean(); +// } +// cbMainService = findViewById(R.id.activitymainCheckBox1); +// cbMainService.setChecked(mMainServiceBean.isEnable()); +// cbMainService.setOnClickListener(new View.OnClickListener(){ +// @Override +// public void onClick(View view) { +// if (cbMainService.isChecked()) { +// MainService.startMainService(MainActivity.this); +// } else { +// MainService.stopMainService(MainActivity.this); +// } +// } +// }); + MainService.startMainService(MainActivity.this); + } + + //初始化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 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 initPoint() { +// imageViews = new ImageView[5];//实例化5个图片 +// for (int i = 0; i < linearLayout.getChildCount(); i++) { +// imageViews[i] = (ImageView) linearLayout.getChildAt(i); +// imageViews[i].setImageResource(R.drawable.ic_launcher); +// imageViews[i].setOnClickListener(this);//点击导航点,即可跳转 +// imageViews[i].setTag(i);//重复利用实例化的对象 +// } +// currentPoint = 0;//默认第一个坐标 +// imageViews[currentPoint].setImageResource(R.drawable.ic_launcher); +// } + + //OnPageChangeListener接口要实现的三个方法 + /* onPageScrollStateChanged(int state) + 此方法是在状态改变的时候调用,其中state这个参数有三种状态: + SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态 + (手指按下但是还没有拖动的时候还不是这个状态,只有按下并且手指开始拖动后log才打出。) + SCROLL_STATE_IDLE(0)滑动动画做完的状态。 + SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。*/ + @Override + public void onPageScrollStateChanged(int state) { + + } + /* onPageScrolled(int position, float positionOffset, int positionOffsetPixels) + 当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为: + + position :当前页面,即你点击滑动的页面(从A滑B,则是A页面的position。 + positionOffset:当前页面偏移的百分比 + positionOffsetPixels:当前页面偏移的像素位置*/ + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + /* onPageSelected(int position) + 此方法是页面滑动完后得到调用,position是你当前选中的页面的Position(位置编号) + (从A滑动到B,就是B的position)*/ + public void onPageSelected(int position) { + +// ImageView preView = imageViews[currentPoint]; +// preView.setImageResource(R.drawable.ic_launcher); +// ImageView currView = imageViews[position]; +// currView.setImageResource(R.drawable.ic_launcher); +// currentPoint = position; + } + + //小圆点点击事件 + @Override + public void onClick(View v) { + // TODO Auto-generated method stub + //通过getTag(),可以判断是哪个控件 +// int i = (Integer) v.getTag(); +// viewPager.setCurrentItem(i);//直接跳转到某一个页面的情况 + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + //setSubTitle(""); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + LogUtils.d(TAG, "onDestroy() SOS"); + } + + + + // + // 处理传入的 Intent 数据 + // +// boolean prosessIntents(Intent intent) { +// if (intent == null +// || intent.getAction() == null +// || intent.getAction().equals("")) +// return false; +// +// if (intent.getAction().equals(StringToQrCodeView.ACTION_UNITTEST_QRCODE)) { +// try { +// WinBollActivity clazzActivity = UnitTestActivity.class.newInstance(); +// String tag = clazzActivity.getTag(); +// LogUtils.d(TAG, "String tag = clazzActivity.getTag(); tag " + tag); +// Intent subIntent = new Intent(this, UnitTestActivity.class); +// subIntent.setAction(intent.getAction()); +// File file = new File(getCacheDir(), UUID.randomUUID().toString()); +// //取出文件uri +// Uri uri = intent.getData(); +// if (uri == null) { +// uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); +// } +// //获取文件真实地址 +// String szSrcPath = UriUtils.getFileFromUri(getApplication(), uri); +// if (TextUtils.isEmpty(szSrcPath)) { +// return false; +// } +// +// Files.copy(Paths.get(szSrcPath), Paths.get(file.getPath())); +// //startWinBollActivity(subIntent, tag); +// WinBollActivityManager.getInstance(this).startWinBollActivity(this, subIntent, UnitTestActivity.class); +// } catch (IllegalAccessException | InstantiationException | IOException e) { +// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); +// // 函数处理异常返回失败 +// return false; +// } +// } else { +// LogUtils.d(TAG, "prosessIntents|" + intent.getAction() + "|yet"); +// return false; +// } +// return true; +// } + + @Override + public String getTag() { + return TAG; + } + + @Override + public Toolbar initToolBar() { + return findViewById(R.id.activitymainToolbar1); + } + + @Override + public boolean isAddWinBollToolBar() { + return true; + } + + @Override + public boolean isEnableDisplayHomeAsUp() { + 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 boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.toolbar_main, menu); + return super.onCreateOptionsMenu(menu); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.item_settings) { + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); + //WinBollActivityManager.getInstance(this).startWinBollActivity(this, CallActivity.class); + } +// } else +// if (item.getItemId() == R.id.item_exit) { +// exit(); +// return true; +// } + return super.onOptionsItemSelected(item); + } + + + @Override + protected void onResume() { + super.onResume(); + } + + /** + * Android M 及以上检查是否是系统默认电话应用 + */ + public boolean isDefaultPhoneCallApp() { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + TelecomManager manger = (TelecomManager) getSystemService(TELECOM_SERVICE); + if (manger != null && manger.getDefaultDialerPackage() != null) { + return manger.getDefaultDialerPackage().equals(getPackageName()); + } + } + return false; + } + + public boolean isServiceRunning(Class serviceClass) { + ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + if (manager == null) return false; + + for (ActivityManager.RunningServiceInfo service : manager.getRunningServices( + Integer.MAX_VALUE)) { + if (serviceClass.getName().equals(service.service.getClassName())) { + return true; + } + } + + return false; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { +// switch (resultCode) { +// case REQUEST_HOME_ACTIVITY : { +// LogUtils.d(TAG, "REQUEST_HOME_ACTIVITY"); +// break; +// } +// case REQUEST_ABOUT_ACTIVITY : { +// LogUtils.d(TAG, "REQUEST_ABOUT_ACTIVITY"); +// break; +// } +// default : { +// super.onActivityResult(requestCode, resultCode, data); +// } +// } + if (requestCode == DIALER_REQUEST_CODE) { + if (resultCode == Activity.RESULT_OK) { + Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用", + Toast.LENGTH_SHORT).show(); + } + } + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/activities/SettingsActivity.java b/positions/src/main/java/cc/winboll/studio/positions/activities/SettingsActivity.java new file mode 100644 index 0000000..5639791 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/activities/SettingsActivity.java @@ -0,0 +1,138 @@ +package cc.winboll.studio.positions.activities; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/21 05:37:42 + */ +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.view.View; +import android.view.WindowManager; +import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import cc.winboll.studio.positions.R; +import com.hjq.toast.ToastUtils; +import java.lang.reflect.Field; +import androidx.appcompat.widget.Toolbar; +import cc.winboll.studio.libappbase.IWinBollActivity; +import cc.winboll.studio.libappbase.bean.APPInfo; + +public class SettingsActivity extends AppCompatActivity implements IWinBollActivity { + + public static final String TAG = "SettingsActivity"; + + Toolbar mToolbar; + + @Override + public APPInfo getAppInfo() { + return null; + } + + @Override + public AppCompatActivity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + + @Override + public Toolbar initToolBar() { + return findViewById(R.id.activitymainToolbar1); + } + + @Override + public boolean isAddWinBollToolBar() { + return true; + } + + @Override + public boolean isEnableDisplayHomeAsUp() { + return false; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings); + + // 初始化工具栏 + mToolbar = findViewById(R.id.activitymainToolbar1); + setSupportActionBar(mToolbar); + if (isEnableDisplayHomeAsUp()) { + // 显示后退按钮 + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + getSupportActionBar().setSubtitle(getTag()); + + } + + public void onDefaultPhone(View view) { + Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS); + startActivity(intent); + } + public void onCanDrawOverlays(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && !Settings.canDrawOverlays(this)) { + // 请求 悬浮框 权限 + askForDrawOverlay(); + } else { + ToastUtils.show("悬浮窗已开启"); + } + } + + private void askForDrawOverlay() { + AlertDialog alertDialog = new AlertDialog.Builder(this) + .setTitle("允许显示悬浮框") + .setMessage("为了使电话监听服务正常工作,请允许这项权限") + .setPositiveButton("去设置", new DialogInterface.OnClickListener(){ + @Override + public void onClick(DialogInterface dialog, int which) { + openDrawOverlaySettings(); + dialog.dismiss(); + } + }) + .setNegativeButton("稍后再说", new DialogInterface.OnClickListener(){ + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .create(); + + //noinspection ConstantConditions + alertDialog.getWindow().setFlags( + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + alertDialog.show(); + } + + /** + * 跳转悬浮窗管理设置界面 + */ + private void openDrawOverlaySettings() { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // Android M 以上引导用户去系统设置中打开允许悬浮窗 + // 使用反射是为了用尽可能少的代码保证在大部分机型上都可用 + try { + Context context = this; + Class clazz = Settings.class; + Field field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION"); + Intent intent = new Intent(field.get(null).toString()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setData(Uri.parse("package:" + context.getPackageName())); + context.startActivity(intent); + } catch (Exception e) { + Toast.makeText(this, "请在悬浮窗管理中打开权限", Toast.LENGTH_LONG).show(); + } + } + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/adapters/MyPagerAdapter.java b/positions/src/main/java/cc/winboll/studio/positions/adapters/MyPagerAdapter.java new file mode 100644 index 0000000..f668734 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/adapters/MyPagerAdapter.java @@ -0,0 +1,42 @@ +package cc.winboll.studio.positions.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.positions.fragments.CallFragment; +import cc.winboll.studio.positions.fragments.ContactsFragment; +import cc.winboll.studio.positions.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/positions/src/main/java/cc/winboll/studio/positions/beans/MainServiceBean.java b/positions/src/main/java/cc/winboll/studio/positions/beans/MainServiceBean.java new file mode 100644 index 0000000..7c40d6f --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/beans/MainServiceBean.java @@ -0,0 +1,68 @@ +package cc.winboll.studio.positions.beans; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/13 07:06:13 + */ +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class MainServiceBean extends BaseBean { + + public static final String TAG = "MainServiceBean"; + + boolean isEnable; + + public MainServiceBean() { + this.isEnable = false; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + @Override + public String getName() { + return MainServiceBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + MainServiceBean bean = this; + jsonWriter.name("isEnable").value(bean.isEnable()); + + } + + @Override + public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException { + if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else { + if (name.equals("isEnable")) { + setIsEnable(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/positions/src/main/java/cc/winboll/studio/positions/fragments/CallFragment.java b/positions/src/main/java/cc/winboll/studio/positions/fragments/CallFragment.java new file mode 100644 index 0000000..72f98b3 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/fragments/CallFragment.java @@ -0,0 +1,51 @@ +package cc.winboll.studio.positions.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.positions.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/positions/src/main/java/cc/winboll/studio/positions/fragments/ContactsFragment.java b/positions/src/main/java/cc/winboll/studio/positions/fragments/ContactsFragment.java new file mode 100644 index 0000000..16b3880 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/fragments/ContactsFragment.java @@ -0,0 +1,50 @@ +package cc.winboll.studio.positions.fragments; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/20 12:57:50 + * @Describe 联系人 + */ +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import cc.winboll.studio.positions.R; + +public class ContactsFragment extends Fragment { + + public static final String TAG = "ContactsFragment"; + + private static final String ARG_PAGE = "ARG_PAGE"; + private int mPage; + + public static ContactsFragment newInstance(int page) { + Bundle args = new Bundle(); + args.putInt(ARG_PAGE, page); + ContactsFragment fragment = new ContactsFragment(); + 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_contacts, container, false); + TextView textView = view.findViewById(R.id.page_text); + textView.setText("这是第 " + mPage + " 页"); + return view; + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/fragments/LogFragment.java b/positions/src/main/java/cc/winboll/studio/positions/fragments/LogFragment.java new file mode 100644 index 0000000..491cc40 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/fragments/LogFragment.java @@ -0,0 +1,50 @@ +package cc.winboll.studio.positions.fragments; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/20 12:58:15 + * @Describe 应用日志 + */ +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import cc.winboll.studio.positions.R; +import cc.winboll.studio.libappbase.LogView; + +public class LogFragment extends Fragment { + + public static final String TAG = "LogFragment"; + + private static final String ARG_PAGE = "ARG_PAGE"; + private int mPage; + + public static LogFragment newInstance(int page) { + Bundle args = new Bundle(); + args.putInt(ARG_PAGE, page); + LogFragment fragment = new LogFragment(); + 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_log, container, false); + LogView logView = view.findViewById(R.id.logview); + logView.start(); + return view; + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/handlers/MainServiceHandler.java b/positions/src/main/java/cc/winboll/studio/positions/handlers/MainServiceHandler.java new file mode 100644 index 0000000..d4c6030 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/handlers/MainServiceHandler.java @@ -0,0 +1,38 @@ +package cc.winboll.studio.positions.handlers; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/14 03:51:40 + */ +import android.os.Handler; +import android.os.Message; +import cc.winboll.studio.positions.services.MainService; +import java.lang.ref.WeakReference; + +public class MainServiceHandler extends Handler { + public static final String TAG = "MainServiceHandler"; + + public static final int MSG_REMINDTHREAD = 0; + + WeakReference serviceWeakReference; + public MainServiceHandler(MainService service) { + serviceWeakReference = new WeakReference(service); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REMINDTHREAD: // 处理下载完成消息,更新UI + { + // 显示提醒消息 + // + //LogUtils.d(TAG, "显示提醒消息"); + MainService mainService = serviceWeakReference.get(); + if (mainService != null) { + mainService.appenMessage((String)msg.obj); + } + break; + } + } + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/receivers/MainReceiver.java b/positions/src/main/java/cc/winboll/studio/positions/receivers/MainReceiver.java new file mode 100644 index 0000000..d3daaa5 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/receivers/MainReceiver.java @@ -0,0 +1,49 @@ +package cc.winboll.studio.positions.receivers; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/13 06:58:04 + * @Describe 主要广播接收器 + */ +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import cc.winboll.studio.positions.services.MainService; +import com.hjq.toast.ToastUtils; +import java.lang.ref.WeakReference; + +public class MainReceiver extends BroadcastReceiver { + + public static final String TAG = "MainReceiver"; + public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; + WeakReference mwrService; + // 存储电量指示值, + // 用于校验电量消息时的电量变化 + static volatile int _mnTheQuantityOfElectricityOld = -1; + static volatile boolean _mIsCharging = false; + + public MainReceiver(MainService service) { + mwrService = new WeakReference(service); + } + + @Override + public void onReceive(Context context, Intent intent) { + String szAction = intent.getAction(); + if (szAction.equals(ACTION_BOOT_COMPLETED)) { + ToastUtils.show("ACTION_BOOT_COMPLETED"); + MainService.startMainService(context); + } else { + ToastUtils.show(szAction); + } + } + + // 注册 Receiver + // + public void registerAction(Context context) { + IntentFilter filter=new IntentFilter(); + filter.addAction(ACTION_BOOT_COMPLETED); + //filter.addAction(Intent.ACTION_BATTERY_CHANGED); + context.registerReceiver(this, filter); + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/services/AssistantService.java b/positions/src/main/java/cc/winboll/studio/positions/services/AssistantService.java new file mode 100644 index 0000000..192d8c2 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/services/AssistantService.java @@ -0,0 +1,139 @@ +package cc.winboll.studio.positions.services; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/14 03:38:31 + * @Describe 守护进程服务 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import cc.winboll.studio.positions.beans.MainServiceBean; +import cc.winboll.studio.positions.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 { + + public static final String TAG = "AssistantService"; + + MainServiceBean mMainServiceBean; + MyServiceConnection mMyServiceConnection; + MainService mMainService; + boolean isBound = false; + volatile boolean isThreadAlive = false; + + public synchronized void setIsThreadAlive(boolean isThreadAlive) { + LogUtils.d(TAG, "setIsThreadAlive(...)"); + LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive)); + this.isThreadAlive = isThreadAlive; + } + + public boolean isThreadAlive() { + return isThreadAlive; + } + + @Override + public IBinder onBind(Intent intent) { + return new MyBinder(); + } + + @Override + public void onCreate() { + LogUtils.d(TAG, "onCreate"); + super.onCreate(); + + //mMyBinder = new MyBinder(); + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + // 设置运行参数 + setIsThreadAlive(false); + assistantService(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + LogUtils.d(TAG, "call onStartCommand(...)"); + assistantService(); + return START_STICKY; + } + + @Override + public void onDestroy() { + //LogUtils.d(TAG, "onDestroy"); + setIsThreadAlive(false); + // 解除绑定 + if (isBound) { + unbindService(mMyServiceConnection); + isBound = false; + } + super.onDestroy(); + } + + // 运行服务内容 + // + void assistantService() { + LogUtils.d(TAG, "assistantService()"); + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable())); + if (mMainServiceBean.isEnable()) { + LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive())); + if (isThreadAlive() == false) { + // 设置运行状态 + setIsThreadAlive(true); + // 唤醒和绑定主进程 + wakeupAndBindMain(); + } + } + } + + // 唤醒和绑定主进程 + // + void wakeupAndBindMain() { + LogUtils.d(TAG, "wakeupAndBindMain()"); + // 绑定服务的Intent + Intent intent = new Intent(this, MainService.class); + startService(new Intent(this, MainService.class)); + bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); + +// startService(new Intent(this, MainService.class)); +// bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + + // 主进程与守护进程连接时需要用到此类 + // + class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + LogUtils.d(TAG, "onServiceConnected(...)"); + MainService.MyBinder binder = (MainService.MyBinder) service; + mMainService = binder.getService(); + isBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + LogUtils.d(TAG, "onServiceDisconnected(...)"); + mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class); + if (mMainServiceBean.isEnable()) { + wakeupAndBindMain(); + } + isBound = false; + mMainService = null; + } + } + + // 用于返回服务实例的Binder + public class MyBinder extends Binder { + AssistantService getService() { + LogUtils.d(TAG, "AssistantService MyBinder getService()"); + return AssistantService.this; + } + } +} diff --git a/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java new file mode 100644 index 0000000..1288821 --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/services/MainService.java @@ -0,0 +1,228 @@ +package cc.winboll.studio.positions.services; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/13 06:56:41 + * @Describe 拨号主服务 + * 参考: + * 进程保活-双进程守护的正确姿势 + * https://blog.csdn.net/sinat_35159441/article/details/75267380 + * Android Service之onStartCommand方法研究 + * https://blog.csdn.net/cyp331203/article/details/38920491 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.SOS; +import cc.winboll.studio.libappbase.bean.APPSOSBean; +import cc.winboll.studio.positions.beans.MainServiceBean; +import cc.winboll.studio.positions.handlers.MainServiceHandler; +import cc.winboll.studio.positions.receivers.MainReceiver; +import cc.winboll.studio.positions.services.MainService; +import cc.winboll.studio.positions.threads.MainServiceThread; + +public class MainService extends Service { + + public static final String TAG = "MainService"; + + public static final int MSG_UPDATE_STATUS = 0; + + static MainService _mControlCenterService; + + volatile boolean isServiceRunning; + + MainServiceBean mMainServiceBean; + MainServiceThread mMainServiceThread; + MainServiceHandler mMainServiceHandler; + MyServiceConnection mMyServiceConnection; + AssistantService mAssistantService; + boolean isBound = false; + MainReceiver mMainReceiver; + + @Override + public IBinder onBind(Intent intent) { + return new MyBinder(); + } + + public MainServiceThread getRemindThread() { + return mMainServiceThread; + } + + @Override + public void onCreate() { + super.onCreate(); + LogUtils.d(TAG, "onCreate()"); + _mControlCenterService = MainService.this; + isServiceRunning = false; + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + mMainServiceHandler = new MainServiceHandler(this); + + // 运行服务内容 + mainService(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + LogUtils.d(TAG, "onStartCommand(...)"); + // 运行服务内容 + mainService(); + return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId); + } + + // 运行服务内容 + // + void mainService() { + LogUtils.d(TAG, "mainService()"); + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + if (mMainServiceBean.isEnable() && isServiceRunning == false) { + LogUtils.d(TAG, "mainService() start running"); + isServiceRunning = true; + // 唤醒守护进程 + wakeupAndBindAssistant(); + // 召唤 WinBoll APP 绑定本服务 + SOS.bindToAPPService(this, new APPSOSBean(getPackageName(), MainService.class.getName())); + + if (mMainReceiver == null) { + // 注册广播接收器 + mMainReceiver = new MainReceiver(this); + mMainReceiver.registerAction(this); + } + + + MainServiceThread.getInstance(this, mMainServiceHandler).start(); + + LogUtils.i(TAG, "Main Service Is Start."); + } + } + + // 唤醒和绑定守护进程 + // + void wakeupAndBindAssistant() { + LogUtils.d(TAG, "wakeupAndBindAssistant()"); +// if (ServiceUtils.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) { +// startService(new Intent(MainService.this, AssistantService.class)); +// //LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService"); +// bindService(new Intent(MainService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); +// } + Intent intent = new Intent(this, AssistantService.class); + startService(intent); + // 绑定服务的Intent + //Intent intent = new Intent(this, AssistantService.class); + bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT); + +// Intent intent = new Intent(this, AssistantService.class); +// startService(intent); +// LogUtils.d(TAG, "startService(intent)"); +// bindService(new Intent(this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + + @Override + public void onDestroy() { + //LogUtils.d(TAG, "onDestroy"); + mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class); + //LogUtils.d(TAG, "onDestroy done"); + if (mMainServiceBean.isEnable() == false) { + // 设置运行状态 + isServiceRunning = false;// 解除绑定 + if (isBound) { + unbindService(mMyServiceConnection); + isBound = false; + } + // 停止守护进程 + Intent intent = new Intent(this, AssistantService.class); + stopService(intent); + // 停止Receiver + if (mMainReceiver != null) { + unregisterReceiver(mMainReceiver); + mMainReceiver = null; + } + // 停止前台通知栏 + stopForeground(true); + + // 停止主要进程 + MainServiceThread.getInstance(this, mMainServiceHandler).setIsExit(true); + } + + super.onDestroy(); + } + + // 主进程与守护进程连接时需要用到此类 + // + private class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + LogUtils.d(TAG, "onServiceConnected(...)"); + AssistantService.MyBinder binder = (AssistantService.MyBinder) service; + mAssistantService = binder.getService(); + isBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + LogUtils.d(TAG, "onServiceDisconnected(...)"); + if (mMainServiceBean.isEnable()) { + // 唤醒守护进程 + wakeupAndBindAssistant(); + SOS.sosWinBollService(getApplicationContext(), new APPSOSBean(getPackageName(), MainService.class.getName())); + } + isBound = false; + mAssistantService = null; + } + + } + + + // 用于返回服务实例的Binder + public class MyBinder extends Binder { + MainService getService() { + LogUtils.d(TAG, "MainService MyBinder getService()"); + return MainService.this; + } + } + +// // +// // 启动服务 +// // +// public static void startControlCenterService(Context context) { +// Intent intent = new Intent(context, MainService.class); +// context.startForegroundService(intent); +// } +// +// // +// // 停止服务 +// // +// public static void stopControlCenterService(Context context) { +// Intent intent = new Intent(context, MainService.class); +// context.stopService(intent); +// } + + public void appenMessage(String message) { + LogUtils.d(TAG, String.format("Message : %s", message)); + } + + public static void stopMainService(Context context) { + LogUtils.d(TAG, "stopMainService"); + 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"); + MainServiceBean bean = new MainServiceBean(); + bean.setIsEnable(true); + MainServiceBean.saveBean(context, bean); + context.startService(new Intent(context, MainService.class)); + } +} + diff --git a/positions/src/main/java/cc/winboll/studio/positions/threads/MainServiceThread.java b/positions/src/main/java/cc/winboll/studio/positions/threads/MainServiceThread.java new file mode 100644 index 0000000..c0eab8c --- /dev/null +++ b/positions/src/main/java/cc/winboll/studio/positions/threads/MainServiceThread.java @@ -0,0 +1,77 @@ +package cc.winboll.studio.positions.threads; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/02/14 03:46:44 + */ +import android.content.Context; +import cc.winboll.studio.positions.handlers.MainServiceHandler; +import cc.winboll.studio.positions.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 { + + public static final String TAG = "MainServiceThread"; + + volatile static MainServiceThread _MainServiceThread; + // 控制线程是否退出的标志 + volatile boolean isExit = false; + volatile boolean isStarted = false; + Context mContext; + // 服务Handler, 用于线程发送消息使用 + WeakReference mwrMainServiceHandler; + + MainServiceThread(Context context, MainServiceHandler handler) { + mContext = context; + mwrMainServiceHandler = new WeakReference(handler); + } + + public void setIsExit(boolean isExit) { + this.isExit = isExit; + } + + public boolean isExit() { + return isExit; + } + + public void setIsStarted(boolean isStarted) { + this.isStarted = isStarted; + } + + public boolean isStarted() { + return isStarted; + } + + public static MainServiceThread getInstance(Context context, MainServiceHandler handler) { + if (_MainServiceThread != null) { + _MainServiceThread.setIsExit(true); + } + _MainServiceThread = new MainServiceThread(context, handler); + return _MainServiceThread; + } + + @Override + public void run() { + if (isStarted == false) { + isStarted = true; + LogUtils.d(TAG, "run()"); + + while (!isExit()) { + //ToastUtils.show("run"); + //LogUtils.d(TAG, "run()"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + _MainServiceThread = null; + LogUtils.d(TAG, "run() exit"); + } + } + +} diff --git a/positions/src/main/res/drawable/ic_launcher.xml b/positions/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000..d4d1eaf --- /dev/null +++ b/positions/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/positions/src/main/res/drawable/ic_launcher_background.xml b/positions/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..9486190 --- /dev/null +++ b/positions/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/positions/src/main/res/drawable/ic_launcher_disable.xml b/positions/src/main/res/drawable/ic_launcher_disable.xml new file mode 100644 index 0000000..9a31905 --- /dev/null +++ b/positions/src/main/res/drawable/ic_launcher_disable.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/positions/src/main/res/drawable/ic_launcher_foreground.xml b/positions/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..872b04e --- /dev/null +++ b/positions/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,10 @@ + + + + diff --git a/positions/src/main/res/drawable/ic_launcher_foreground_disable.xml b/positions/src/main/res/drawable/ic_launcher_foreground_disable.xml new file mode 100644 index 0000000..763b72c --- /dev/null +++ b/positions/src/main/res/drawable/ic_launcher_foreground_disable.xml @@ -0,0 +1,10 @@ + + + + diff --git a/positions/src/main/res/drawable/shape_gradient.xml b/positions/src/main/res/drawable/shape_gradient.xml new file mode 100644 index 0000000..c164fe9 --- /dev/null +++ b/positions/src/main/res/drawable/shape_gradient.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/positions/src/main/res/layout/activity_main.xml b/positions/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..5376c86 --- /dev/null +++ b/positions/src/main/res/layout/activity_main.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/positions/src/main/res/layout/activity_settings.xml b/positions/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..b77a699 --- /dev/null +++ b/positions/src/main/res/layout/activity_settings.xml @@ -0,0 +1,46 @@ + + + + + + + + + +