diff --git a/aes/build.gradle b/aes/build.gradle index d022e95..3bfd561 100644 --- a/aes/build.gradle +++ b/aes/build.gradle @@ -23,7 +23,7 @@ android { defaultConfig { applicationId "cc.winboll.studio.aes" - minSdkVersion 26 + minSdkVersion 24 targetSdkVersion 29 versionCode 1 // versionName 更新后需要手动设置 diff --git a/aes/build.properties b/aes/build.properties index 650a078..83da101 100644 --- a/aes/build.properties +++ b/aes/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Mar 24 19:50:52 GMT 2025 +#Sat Mar 29 00:26:50 GMT 2025 stageCount=8 libraryProject=libaes baseVersion=15.0 publishVersion=15.0.7 -buildCount=6 +buildCount=23 baseBetaVersion=15.0.8 diff --git a/aes/src/main/AndroidManifest.xml b/aes/src/main/AndroidManifest.xml index 2f397f0..bbd1308 100644 --- a/aes/src/main/AndroidManifest.xml +++ b/aes/src/main/AndroidManifest.xml @@ -34,4 +34,4 @@ - \ No newline at end of file + diff --git a/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java b/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java index d8b297c..80fc544 100644 --- a/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java +++ b/aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java @@ -5,26 +5,38 @@ package cc.winboll.studio.aes; * @Date 2025/03/24 23:52:29 * @Describe AES应用介绍窗口 */ -import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.ViewGroup; -import android.widget.Button; import android.widget.LinearLayout; +import androidx.appcompat.app.AppCompatActivity; import cc.winboll.studio.libaes.winboll.APPInfo; import cc.winboll.studio.libaes.winboll.AboutView; +import cc.winboll.studio.libappbase.GlobalApplication; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; +import android.app.Activity; -public class AboutActivity extends Activity { +public class AboutActivity extends AppCompatActivity implements IWinBollActivity { + + public static final String TAG = "AboutActivity"; Context mContext; - - public static final String TAG = "AboutActivity"; + + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = this; - + AboutView aboutView = CreateAboutView(); // 在 Activity 的 onCreate 或其他生命周期方法中调用 LinearLayout layout = new LinearLayout(this); @@ -35,7 +47,14 @@ public class AboutActivity extends Activity { ViewGroup.LayoutParams.MATCH_PARENT ); addContentView(aboutView, params); - + + GlobalApplication.getWinBollActivityManager().add(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + GlobalApplication.getWinBollActivityManager().registeRemove(this); } public AboutView CreateAboutView() { diff --git a/aes/src/main/java/cc/winboll/studio/aes/App.java b/aes/src/main/java/cc/winboll/studio/aes/App.java index df1a0a2..b8723d9 100644 --- a/aes/src/main/java/cc/winboll/studio/aes/App.java +++ b/aes/src/main/java/cc/winboll/studio/aes/App.java @@ -6,6 +6,8 @@ package cc.winboll.studio.aes; * @Describe AES应用类 */ import cc.winboll.studio.libappbase.GlobalApplication; +import com.hjq.toast.ToastUtils; + public class App extends GlobalApplication { @@ -14,6 +16,8 @@ public class App extends GlobalApplication { @Override public void onCreate() { super.onCreate(); + ToastUtils.init(this); + ToastUtils.show("App onCreate"); } } diff --git a/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java b/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java index 61ce96e..06ef438 100644 --- a/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java +++ b/aes/src/main/java/cc/winboll/studio/aes/MainActivity.java @@ -5,6 +5,7 @@ package cc.winboll.studio.aes; * @Date 2024/06/13 19:05:52 * @Describe 应用主窗口 */ +import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.Menu; @@ -12,8 +13,6 @@ import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.Toast; -import android.widget.Toolbar; -import androidx.appcompat.app.AppCompatActivity; import cc.winboll.studio.aes.R; import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity; import cc.winboll.studio.libaes.beans.DrawerMenuBean; @@ -25,9 +24,10 @@ import cc.winboll.studio.libaes.unittests.TestASupportToolbarActivity; import cc.winboll.studio.libaes.unittests.TestAToolbarActivity; import cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity; import cc.winboll.studio.libaes.unittests.TestViewPageFragment; -import cc.winboll.studio.libaes.winboll.IWinBollActivity; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; import com.a4455jkjh.colorpicker.ColorPickerDialog; +import com.hjq.toast.ToastUtils; import java.util.ArrayList; public class MainActivity extends DrawerFragmentActivity implements IWinBollActivity { @@ -39,7 +39,7 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBollActi TestViewPageFragment mTestViewPageFragment; @Override - public AppCompatActivity getActivity() { + public Activity getActivity() { return this; } @@ -48,21 +48,6 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBollActi return TAG; } - @Override - public Toolbar initToolBar() { - return null; - } - - @Override - public boolean isAddWinBollToolBar() { - return false; - } - - @Override - public boolean isEnableDisplayHomeAsUp() { - return false; - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -72,6 +57,7 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBollActi } showFragment(mTestAButtonFragment); //setSubtitle(TAG); + ToastUtils.show("onCreate"); } @Override diff --git a/libaes/build.gradle b/libaes/build.gradle index f994f15..0f46395 100644 --- a/libaes/build.gradle +++ b/libaes/build.gradle @@ -8,7 +8,7 @@ android { buildToolsVersion "32.0.0" defaultConfig { - minSdkVersion 26 + minSdkVersion 24 targetSdkVersion 29 } buildTypes { @@ -22,6 +22,9 @@ android { dependencies { api fileTree(dir: 'libs', include: ['*.jar']) + // 吐司类库 + api 'com.github.getActivity:ToastUtils:10.5' + // 权限请求框架:https://github.com/getActivity/XXPermissions api 'com.github.getActivity:XXPermissions:18.63' // 下拉控件 @@ -48,6 +51,6 @@ dependencies { //api 'androidx.vectordrawable:vectordrawable-animated:1.1.0' //api 'androidx.fragment:fragment:1.1.0' - api 'cc.winboll.studio:libappbase:15.0.10' - api 'cc.winboll.studio:libapputils:15.0.17' + api 'cc.winboll.studio:libappbase:15.1.4' + api 'cc.winboll.studio:libapputils:15.0.19' } diff --git a/libaes/build.properties b/libaes/build.properties index 650a078..83da101 100644 --- a/libaes/build.properties +++ b/libaes/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Mar 24 19:50:52 GMT 2025 +#Sat Mar 29 00:26:50 GMT 2025 stageCount=8 libraryProject=libaes baseVersion=15.0 publishVersion=15.0.7 -buildCount=6 +buildCount=23 baseBetaVersion=15.0.8 diff --git a/libaes/src/main/AndroidManifest.xml b/libaes/src/main/AndroidManifest.xml index 7e9ecb7..a1257eb 100644 --- a/libaes/src/main/AndroidManifest.xml +++ b/libaes/src/main/AndroidManifest.xml @@ -13,6 +13,12 @@ + + + + + + diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/activitys/DrawerFragmentActivity.java b/libaes/src/main/java/cc/winboll/studio/libaes/activitys/DrawerFragmentActivity.java index 058a22f..c2e6c56 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/activitys/DrawerFragmentActivity.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/activitys/DrawerFragmentActivity.java @@ -16,6 +16,7 @@ import android.view.View; import android.widget.AdapterView; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -27,10 +28,9 @@ import cc.winboll.studio.libaes.beans.DrawerMenuBean; import cc.winboll.studio.libaes.utils.AESThemeUtil; import cc.winboll.studio.libaes.views.ADrawerMenuListView; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; import com.baoyz.widget.PullRefreshLayout; import java.util.ArrayList; -import cc.winboll.studio.libaes.winboll.IWinBollActivity; -import androidx.appcompat.widget.Toolbar; public abstract class DrawerFragmentActivity extends AppCompatActivity implements IWinBollActivity,AdapterView.OnItemClickListener { diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/SecondaryLibraryActivity.java b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/SecondaryLibraryActivity.java index a4d4f8e..42cdef8 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/SecondaryLibraryActivity.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/SecondaryLibraryActivity.java @@ -1,15 +1,13 @@ package cc.winboll.studio.libaes.unittests; +import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; -import android.widget.Toolbar; -import androidx.appcompat.app.AppCompatActivity; import cc.winboll.studio.libaes.R; import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity; -import cc.winboll.studio.libaes.winboll.APPInfo; -import cc.winboll.studio.libaes.winboll.IWinBollActivity; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; /** * @Author ZhanGSKen@QQ.COM @@ -23,7 +21,7 @@ public class SecondaryLibraryActivity extends DrawerFragmentActivity implements SecondaryLibraryFragment mSecondaryLibraryFragment; @Override - public AppCompatActivity getActivity() { + public Activity getActivity() { return this; } @@ -32,21 +30,6 @@ public class SecondaryLibraryActivity extends DrawerFragmentActivity implements return null; } - @Override - public Toolbar initToolBar() { - return null; - } - - @Override - public boolean isAddWinBollToolBar() { - return false; - } - - @Override - public boolean isEnableDisplayHomeAsUp() { - return false; - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAButtonFragment.java b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAButtonFragment.java index d79c186..6dcf11d 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAButtonFragment.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAButtonFragment.java @@ -13,7 +13,7 @@ import androidx.fragment.app.Fragment; import cc.winboll.studio.libaes.R; import cc.winboll.studio.libaes.views.AButton; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.utils.ToastUtils; +import com.hjq.toast.ToastUtils; public class TestAButtonFragment extends Fragment { diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestASupportToolbarActivity.java b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestASupportToolbarActivity.java index c570bf6..1ba071d 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestASupportToolbarActivity.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestASupportToolbarActivity.java @@ -5,19 +5,27 @@ package cc.winboll.studio.libaes.unittests; * @Date 2024/07/16 01:14:00 * @Describe TestASupportToolbarActivity */ -import android.content.Context; -import android.content.SharedPreferences; +import android.app.Activity; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import cc.winboll.studio.libaes.R; -import cc.winboll.studio.libaes.beans.AESThemeBean; import cc.winboll.studio.libaes.utils.AESThemeUtil; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; -public class TestASupportToolbarActivity extends AppCompatActivity { +public class TestASupportToolbarActivity extends AppCompatActivity implements IWinBollActivity { public static final String TAG = "TestASupportToolbarActivity"; + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAToolbarActivity.java b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAToolbarActivity.java index bdd401c..2aa4f4d 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAToolbarActivity.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestAToolbarActivity.java @@ -10,11 +10,22 @@ import android.os.Bundle; import android.widget.Toolbar; import cc.winboll.studio.libaes.R; import cc.winboll.studio.libaes.utils.AESThemeUtil; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; -public class TestAToolbarActivity extends Activity { +public class TestAToolbarActivity extends Activity implements IWinBollActivity { public static final String TAG = "TestAToolbarActivity"; + @Override + public Activity getActivity() { + return this; + } + + @Override + public String getTag() { + return TAG; + } + @Override protected void onCreate(Bundle savedInstanceState) { AESThemeUtil.applyAppTheme(this); diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestDrawerFragmentActivity.java b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestDrawerFragmentActivity.java index 469b047..2646e1c 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestDrawerFragmentActivity.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestDrawerFragmentActivity.java @@ -4,6 +4,7 @@ package cc.winboll.studio.libaes.unittests; * @Author ZhanGSKen@QQ.COM * @Date 2024/06/30 15:00:51 */ +import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -11,19 +12,18 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; -import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import cc.winboll.studio.libaes.R; import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity; import cc.winboll.studio.libaes.beans.DrawerMenuBean; -import cc.winboll.studio.libaes.winboll.IWinBollActivity; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.winboll.IWinBollActivity; import java.util.ArrayList; public class TestDrawerFragmentActivity extends DrawerFragmentActivity implements IWinBollActivity { @Override - public AppCompatActivity getActivity() { + public Activity getActivity() { return this; } @@ -32,22 +32,6 @@ public class TestDrawerFragmentActivity extends DrawerFragmentActivity implement return null; } - @Override - public Toolbar initToolBar() { - return null; - } - - @Override - public boolean isAddWinBollToolBar() { - return false; - } - - @Override - public boolean isEnableDisplayHomeAsUp() { - return false; - } - - public static final String TAG = "TestDrawerFragmentActivity"; TestFragment1 mTestFragment1; diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestViewPageFragment.java b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestViewPageFragment.java index 6f17661..a7253c8 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestViewPageFragment.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/unittests/TestViewPageFragment.java @@ -19,7 +19,7 @@ import cc.winboll.studio.libaes.ImagePagerAdapter; import cc.winboll.studio.libaes.R; import cc.winboll.studio.libaes.views.AOHPCTCSeekBar; import cc.winboll.studio.libappbase.LogView; -import cc.winboll.studio.libappbase.utils.ToastUtils; +import com.hjq.toast.ToastUtils; import java.util.ArrayList; import java.util.List; diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/utils/AppVersionUtils.java b/libaes/src/main/java/cc/winboll/studio/libaes/utils/AppVersionUtils.java new file mode 100644 index 0000000..f684626 --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/utils/AppVersionUtils.java @@ -0,0 +1,162 @@ +package cc.winboll.studio.libaes.utils; + + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2024/08/12 14:45:35 + * @Describe 应用版本工具集 + */ +import cc.winboll.studio.libappbase.LogUtils; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AppVersionUtils { + + public static final String TAG = "AppVersionUtils"; + + // + // 检查新版本是否成立 + // szCurrentCode : 当前版本应用包名 + // szNextCode : 新版本应用包名 + // 返回 :情况1:当前版本是发布版 + // 返回 true (新版本 > 当前版本) + // 情况1:当前版本是Beta版 + // true 新版本 == 当前版本 + // + public static boolean isHasNewVersion2(String szCurrentName, String szNextName) { + LogUtils.d(TAG, String.format("isHasNewVersion2\nszCurrentName : %s\nszNextName : %s", szCurrentName, szNextName)); + //szCurrentName = "AES_6.2.0-beta0_3234.apk"; + //szNextName = "AES_6.1.12.apk"; + //szCurrentName = "AES_6.2.0-beta0_3234.apk"; + //szNextName = "AES_6.2.0.apk"; + //szCurrentName = "AES_6.2.0-beta0_3234.apk"; + //szNextName = "AES_6.2.2.apk"; + //szCurrentName = "AES_6.2.0-beta0_3234.apk"; + //szNextName = "AES_6.2.0.apk"; + //szCurrentName = "AES_6.1.0.apk"; + //szNextName = "AES_6.2.0.apk"; + //LogUtils.d(TAG, "szCurrentName : " + szCurrentName); + //LogUtils.d(TAG, "szNextName : " + szNextName); + + //boolean isVersionNewer = false; + //if(szCurrentName.equals(szNextName)) { + // isVersionNewer = false; + //} else { + //ToastUtils.show("szCurrent : " + szCurrent + "\nszNext : " + szNext); + //int nApk = szNextName.lastIndexOf(".apk"); + //ToastUtils.show("nApk : " + Integer.toString(nApk)); + //String szNextNoApkName = szNextName.substring(0, nApk); + //ToastUtils.show("szNextNoApkName : " + szNextNoApkName); + //String szCurrentNoApkName = szCurrentName.substring(0, szNextNoApkName.length()); + //ToastUtils.show("szCurrentNoApkName : " + szCurrentNoApkName); + //String str1 = "3.4.50"; + //String str2 = "3.3.60"; + //String str1 = getCodeInPackageName(szCurrentName); + //String str2 = getCodeInPackageName(szNextName); + //String str1 = getCodeInPackageName(szNextName); + //String str2 = getCodeInPackageName(szCurrentName); + //Boolean isVersionNewer2 = checkNewVersion(str1,str2); + //ToastUtils.show("isVersionNewer2 : " + Boolean.toString(isVersionNewer2)); + //ToastUtils.show(checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))); + //return checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName)); + //} + //return isVersionNewer; + if (checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))) { + // 比 AES_6.2.0.apk 版本大,如 AES_6.2.1.apk。 + // 比 AES_6.2.0-beta0_3234.apk 大,如 AES_6.2.1.apk。 + //LogUtils.d(TAG, "App newer stage version is released. Release name : " + szNextName); + return true; + } + if (szCurrentName.matches(".*_\\d+\\.\\d+\\.\\d+-beta.*\\.apk")) { + String szCurrentReleasePackageName = getReleasePackageName(szCurrentName); + //LogUtils.d(TAG, "szCurrentReleasePackageName : " + szCurrentReleasePackageName); + if (szCurrentReleasePackageName.equals(szNextName)) { + // 与 AES_6.2.0-beta0_3234.apk 版本相同,如 AES_6.2.0.apk。 + //LogUtils.d(TAG, "App stage version is released. Release name : " + szNextName); + return true; + } + } + //LogUtils.d(TAG, "App version is the newest. "); + return false; + } + + public static boolean isHasNewStageReleaseVersion(String szCurrentName, String szNextName) { + LogUtils.d(TAG, String.format("isHasNewStageReleaseVersion\nszCurrentName : %s\nszNextName : %s", szCurrentName, szNextName)); + //szCurrentName = "AES_6.2.12.apk"; + //szNextName = "AES_6.3.12.apk"; + if (checkNewVersion(getCodeInPackageName(szCurrentName), getCodeInPackageName(szNextName))) { + // 比 AES_6.2.0.apk 版本大,如 AES_6.2.1.apk。 + //LogUtils.d(TAG, "App newer stage version is released. Release name : " + szNextName); + return true; + } + return false; + } + + // + // 检查新版本是否成立 + // szCurrentCode : 当前版本 + // szNextCode : 新版本 + // 返回 :true 新版本 > 当前版本 + // + public static Boolean checkNewVersion(String szCurrentCode, String szNextCode) { + if (szCurrentCode == null || szCurrentCode.equals("") || szNextCode == null || szNextCode.equals("")) { + LogUtils.d(TAG, String.format("checkNewVersion unexpected parameters:\nszCurrentCode : %s\nszNextCode : %s", szCurrentCode, szNextCode)); + return false; + } + boolean isNew = false; + String[] appVersionCurrent = szCurrentCode.split("\\."); + String[] appVersionNext = szNextCode.split("\\."); + //根据位数最短的判断 + int lim = appVersionCurrent.length > appVersionNext.length ? appVersionNext.length : appVersionCurrent.length; + //根据位数循环判断各个版本 + for (int i = 0; i < lim; i++) { + if (Integer.parseInt(appVersionNext[i]) > Integer.parseInt(appVersionCurrent[i])) { + isNew = true; + return isNew; + } else if (Integer.parseInt(appVersionNext[i]) == Integer.parseInt(appVersionCurrent[i])) { + continue ; + } else { + isNew = false; + return isNew; + } + } + return isNew; + } + + // + // 截取应用包名称版本号信息 + // 如 :AppUtils_7.0.4-beta1_0120.apk 版本号为 7.0.4 + // 如 :AppUtils_7.0.4.apk 版本号为 7.0.4 + // + public static String getCodeInPackageName(String apkName) { + LogUtils.d(TAG, String.format("getCodeInPackageName apkName : %s", apkName)); + //String apkName = "AppUtils_7.0.0.apk"; + Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+"); + Matcher matcher = pattern.matcher(apkName); + if (matcher.find()) { + String version = matcher.group(); + LogUtils.d(TAG, String.format("version is %s", version)); + return version; + //System.out.println("Version number: " + version); // 输出:7.0.0 + } + LogUtils.d(TAG, String.format("No result.")); + return ""; + } + + // + // 根据Beta版名称生成发布版应用包名称 + // 如 AppUtils_7.0.4-beta1_0120.apk + // 发布版名称就为AppUtils_7.0.4.apk + // + public static String getReleasePackageName(String szBetaPackageName) { + //String szBetaPackageName = "AppUtils_7.0.4-beta1_0120.apk"; + Pattern pattern = Pattern.compile(".*\\d+\\.\\d+\\.\\d+"); + Matcher matcher = pattern.matcher(szBetaPackageName); + if (matcher.find()) { + String szReleasePackageName = matcher.group(); + return szReleasePackageName + ".apk"; + //System.out.println("Version number: " + version); // 输出:7.0.0 + } + return ""; + } +} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/AboutView.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/AboutView.java index ad894b9..3628113 100644 --- a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/AboutView.java +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/AboutView.java @@ -17,13 +17,12 @@ import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; import cc.winboll.studio.libaes.R; +import cc.winboll.studio.libaes.utils.AppVersionUtils; import cc.winboll.studio.libappbase.GlobalApplication; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.utils.ToastUtils; -import cc.winboll.studio.libapputils.app.AppVersionUtils; -import cc.winboll.studio.libapputils.util.PrefUtils; -import cc.winboll.studio.libapputils.view.WinBollServiceStatusView; -import cc.winboll.studio.libapputils.view.YesNoAlertDialog; +import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog; +import cc.winboll.studio.libapputils.utils.PrefUtils; +import com.hjq.toast.ToastUtils; import java.io.IOException; import mehdi.sakout.aboutpage.AboutPage; import mehdi.sakout.aboutpage.Element; @@ -284,7 +283,7 @@ public class AboutView extends LinearLayout { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); GlobalApplication.setIsDebuging(true); - WinBollActivityManager.getInstance(context).finishAll(); + GlobalApplication.getWinBollActivityManager().finishAll(); context.startActivity(intent); } } @@ -295,7 +294,7 @@ public class AboutView extends LinearLayout { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); GlobalApplication.setIsDebuging(false); - WinBollActivityManager.getInstance(context).finishAll(); + GlobalApplication.getWinBollActivityManager().finishAll(); context.startActivity(intent); } } diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/AssistantService.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/AssistantService.java new file mode 100644 index 0000000..9f1e48a --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/AssistantService.java @@ -0,0 +1,96 @@ +package cc.winboll.studio.libaes.winboll; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 19:12:12 + * @Describe 应用主要服务组件类守护进程服务组件类 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import cc.winboll.studio.libaes.winboll.WinBollClientService; +import cc.winboll.studio.libappbase.utils.ServiceUtils; + +public class AssistantService extends Service { + + public final static String TAG = "AssistantService"; + + WinBollClientServiceBean mWinBollServiceBean; + MyServiceConnection mMyServiceConnection; + volatile boolean mIsServiceRunning; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + mWinBollServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + // 设置运行参数 + mIsServiceRunning = false; + run(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + run(); + return START_STICKY; + } + + @Override + public void onDestroy() { + mIsServiceRunning = false; + super.onDestroy(); + } + + // + // 运行服务内容 + // + void run() { + mWinBollServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); + if (mWinBollServiceBean.isEnable()) { + if (mIsServiceRunning == false) { + // 设置运行状态 + mIsServiceRunning = true; + // 唤醒和绑定主进程 + wakeupAndBindMain(); + } + } + } + + // + // 唤醒和绑定主进程 + // + void wakeupAndBindMain() { + if (ServiceUtils.isServiceRunning(getApplicationContext(), WinBollClientService.class.getName()) == false) { + startForegroundService(new Intent(AssistantService.this, WinBollClientService.class)); + } + + bindService(new Intent(AssistantService.this, WinBollClientService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + + // + // 主进程与守护进程连接时需要用到此类 + // + class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mWinBollServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(AssistantService.this); + if (mWinBollServiceBean.isEnable()) { + wakeupAndBindMain(); + } + } + } +} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/EWUIStatusIconDrawable.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/EWUIStatusIconDrawable.java new file mode 100644 index 0000000..3d66468 --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/EWUIStatusIconDrawable.java @@ -0,0 +1,35 @@ +package cc.winboll.studio.libaes.winboll; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 19:11:27 + * @Describe WinBoll UI 状态图标枚举 + */ +import cc.winboll.studio.libaes.R; + +public enum EWUIStatusIconDrawable { + NORMAL(0), + NEWS(1) + ; + + static final String TAG = "WUIStatusIconDrawable"; + + static String[] _mlistCNName = { "正常", "新的消息" }; + + private int value = 0; + private EWUIStatusIconDrawable(int value) { //必须是private的,否则编译错误 + this.value = value; + } + + public static int getIconDrawableId(EWUIStatusIconDrawable drawableId) { + int res; + switch(drawableId){ + case NEWS : + res = R.drawable.ic_winbollbeta; + break; + default : + res = R.drawable.ic_winboll; + } + return res; + } +} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/IWinBollActivity.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/IWinBollActivity.java deleted file mode 100644 index d4cb61c..0000000 --- a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/IWinBollActivity.java +++ /dev/null @@ -1,21 +0,0 @@ -package cc.winboll.studio.libaes.winboll; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/03/24 08:23:40 - * @Describe WinBoll 活动窗口通用接口 - */ -import android.widget.Toolbar; -import androidx.appcompat.app.AppCompatActivity; - -public interface IWinBollActivity { - - public static final String TAG = "IWinBollActivity"; - - // 获取应用资源上下文 - abstract public AppCompatActivity getActivity(); - abstract public String getTag(); - abstract public Toolbar initToolBar(); - abstract public boolean isEnableDisplayHomeAsUp(); - abstract public boolean isAddWinBollToolBar(); -} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/IWinBollClientServiceBinder.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/IWinBollClientServiceBinder.java new file mode 100644 index 0000000..eaf40d5 --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/IWinBollClientServiceBinder.java @@ -0,0 +1,17 @@ +package cc.winboll.studio.libaes.winboll; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 19:08:45 + * @Describe WinBollService 服务 Binder。 + */ +import android.graphics.drawable.Drawable; + +public interface IWinBollClientServiceBinder { + + public static final String TAG = "IWinBollClientServiceBinder"; + + public WinBollClientService getService(); + + public Drawable getCurrentStatusIconDrawable(); +} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/MyActivityLifecycleCallbacks.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/MyActivityLifecycleCallbacks.java deleted file mode 100644 index 5e928a8..0000000 --- a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/MyActivityLifecycleCallbacks.java +++ /dev/null @@ -1,97 +0,0 @@ -package cc.winboll.studio.libaes.winboll; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/03/24 08:24:52 - */ -import android.app.Activity; -import android.app.Application; -import android.content.Intent; -import android.os.Bundle; -import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.libappbase.utils.ToastUtils; - -public class MyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { - - public static final String TAG = "MyActivityLifecycleCallbacks"; - - public String mInfo = ""; - - public MyActivityLifecycleCallbacks() { - } - - void createActivityeInfo(Activity activity) { - StringBuilder sb = new StringBuilder(); - Intent receivedIntent = activity.getIntent(); - sb.append("\nCallingActivity : \n"); - if (activity.getCallingActivity() != null) { - sb.append(activity.getCallingActivity().getPackageName()); - } - sb.append("\nReceived Intent Package : \n"); - sb.append(receivedIntent.getPackage()); - - Bundle extras = receivedIntent.getExtras(); - if (extras != null) { - for (String key : extras.keySet()) { - sb.append("\nIntentInfo"); - sb.append("\n键: "); - sb.append(key); - sb.append(", 值: "); - sb.append(extras.get(key)); - //Log.d("IntentInfo", "键: " + key + ", 值: " + extras.get(key)); - } - } - mInfo = sb.toString(); - //Log.d("IntentInfo", "发送Intent的应用包名: " + senderPackage); - } - - public void showActivityeInfo() { - ToastUtils.show("ActivityeInfo : " + mInfo); - LogUtils.d(TAG, "ActivityeInfo : " + mInfo); - } - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - // 在这里可以做一些初始化相关的操作,例如记录Activity的创建时间等 - //System.out.println(activity.getLocalClassName() + " was created"); - LogUtils.d(TAG, activity.getLocalClassName() + " was created"); - createActivityeInfo(activity); - } - - @Override - public void onActivityStarted(Activity activity) { - //System.out.println(activity.getLocalClassName() + " was started"); - LogUtils.d(TAG, activity.getLocalClassName() + " was started"); - //createActivityeInfo(activity); - } - - @Override - public void onActivityResumed(Activity activity) { - //System.out.println(activity.getLocalClassName() + " was resumed"); - LogUtils.d(TAG, activity.getLocalClassName() + " was resumed"); - //createActivityeInfo(activity); - } - - @Override - public void onActivityPaused(Activity activity) { - //System.out.println(activity.getLocalClassName() + " was paused"); - LogUtils.d(TAG, activity.getLocalClassName() + " was paused"); - } - - @Override - public void onActivityStopped(Activity activity) { - //System.out.println(activity.getLocalClassName() + " was stopped"); - LogUtils.d(TAG, activity.getLocalClassName() + " was stopped"); - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - // 可以在这里添加保存状态的自定义逻辑 - } - - @Override - public void onActivityDestroyed(Activity activity) { - //System.out.println(activity.getLocalClassName() + " was destroyed"); - LogUtils.d(TAG, activity.getLocalClassName() + " was destroyed"); - } -} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollActivityManager.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollActivityManager.java deleted file mode 100644 index a975aed..0000000 --- a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollActivityManager.java +++ /dev/null @@ -1,318 +0,0 @@ -package cc.winboll.studio.libaes.winboll; - -/** - * @Author ZhanGSKen@AliYun.Com - * @Date 2025/03/24 08:25:43 - * @Describe 应用活动窗口管理器 - * 参考 : - * android 类似微信小程序多任务窗口 及 设置 TaskDescription 修改 icon 和 label - * https://blog.csdn.net/qq_29364417/article/details/109379915?app_version=6.4.2&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22109379915%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app - */ -import android.app.ActivityManager; -import android.app.TaskStackBuilder; -import android.content.Context; -import android.content.Intent; -import cc.winboll.studio.libappbase.LogUtils; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -public class WinBollActivityManager { - - public static final String TAG = "WinBollActivityManager"; - public static final String EXTRA_TAG = "EXTRA_TAG"; - - public static enum WinBollUI_TYPE { - Aplication, // 退出应用后,保持最近任务栏任务记录主窗口 - Service // 退出应用后,清理所有最近任务栏任务记录窗口 - }; - - // 应用类型标志 - volatile static WinBollUI_TYPE _mWinBollUI_TYPE = WinBollUI_TYPE.Service; - - Context mContext; - MyActivityLifecycleCallbacks mMyActivityLifecycleCallbacks; - static WinBollActivityManager _mWinBollActivityManager; - static Map _mapIWinBollList; - IWinBollActivity firstIWinBollActivity; - - public WinBollActivityManager(Context context) { - mContext = context; - LogUtils.d(TAG, "WinBollActivityManager()"); - _mapIWinBollList = new HashMap(); - } - - public static synchronized WinBollActivityManager getInstance(Context context) { - LogUtils.d(TAG, "getInstance"); - if (_mWinBollActivityManager == null) { - LogUtils.d(TAG, "_mWinBollActivityManager == null"); - _mWinBollActivityManager = new WinBollActivityManager(context); - } - return _mWinBollActivityManager; - } - - // - // 设置 WinBoll 应用 UI 类型 - // - public synchronized static void setWinBollUI_TYPE(WinBollUI_TYPE mWinBollUI_TYPE) { - _mWinBollUI_TYPE = mWinBollUI_TYPE; - } - - // - // 获取 WinBoll 应用 UI 类型 - // - public synchronized static WinBollUI_TYPE getWinBollUI_TYPE() { - return _mWinBollUI_TYPE; - } - - // - // 把Activity添加到管理中 - // - public void add(T iWinBoll) { - if (isActive(iWinBoll.getTag())) { - LogUtils.d(TAG, String.format("add(...) %s is active.", iWinBoll.getTag())); - } else { - // 设置起始活动窗口,以便最后退出时提问 - if (firstIWinBollActivity == null && _mapIWinBollList.size() == 0) { - firstIWinBollActivity = iWinBoll; - } - - // 添加到活动窗口列表 - _mapIWinBollList.put(iWinBoll.getTag(), iWinBoll); - LogUtils.d(TAG, String.format("Add activity : %s\n_mapActivityList.size() : %d", iWinBoll.getTag(), _mapIWinBollList.size())); - } - } - - - // - // activity: 为 null 时, - // intent.putExtra 函数 EXTRA_TAG 参数为 tag - // activity: 不为 null 时, - // intent.putExtra 函数 "tag" 参数为 activity.getTag() - // - public void startWinBollActivity(Context context, Class clazz) { - try { - // 如果窗口已存在就重启窗口 - String tag = clazz.newInstance().getTag(); - if (isActive(tag)) { - resumeActivity(context, tag); - return; - } - - // 新建一个任务窗口 - Intent intent = new Intent(context, clazz); - //打开多任务窗口 flags - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(EXTRA_TAG, tag); - mContext.startActivity(intent); - } catch (InstantiationException | IllegalAccessException e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); - } - } - - public void startWinBollActivity(Context context, Intent intent, Class clazz) { - try { - // 如果窗口已存在就重启窗口 - String tag = clazz.newInstance().getTag(); - if (isActive(tag)) { - resumeActivity(context, tag); - return; - } - - // 新建一个任务窗口 - //Intent intent = new Intent(context, clazz); - //打开多任务窗口 flags - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(EXTRA_TAG, tag); - mContext.startActivity(intent); - } catch (InstantiationException | IllegalAccessException e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); - } - } - - public boolean isFirstIWinBollActivity(IWinBollActivity iWinBollActivity) { - return firstIWinBollActivity != null && firstIWinBollActivity == iWinBollActivity; - } - - // - // 判断 tag绑定的 MyActivity是否存在 - // - public boolean isActive(String tag) { - //printAvtivityListInfo(); - IWinBollActivity iWinBoll = getIWinBoll(tag); - if (iWinBoll != null) { - LogUtils.d(TAG, "isActive(...) activity != null tag " + tag); - //ToastUtils.show("activity != null tag " + tag); - //判断是否为 BaseActivity,如果已经销毁,则移除 - if (iWinBoll.getActivity().isFinishing() || iWinBoll.getActivity().isDestroyed()) { - _mapIWinBollList.remove(iWinBoll.getTag()); - //_mWinBollActivityList.remove(activity); - LogUtils.d(TAG, String.format("isActive(...) remove activity.\ntag : %s", tag)); - return false; - } else { - LogUtils.d(TAG, String.format("isActive(...) activity is exist.\ntag : %s", tag)); - return true; - } - } else { - LogUtils.d(TAG, String.format("isActive(...) activity == null\ntag : %s", tag)); - return false; - } - } - - static IWinBollActivity getIWinBoll(String tag) { - return _mapIWinBollList.get(tag); - } - - // - // 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台 - // - public void resumeActivity(Context context, String tag) { - LogUtils.d(TAG, "resumeActivty"); - T iWinBoll = (T)getIWinBoll(tag); - //LogUtils.d(TAG, "activity " + activity.getTag()); - if (iWinBoll != null && !iWinBoll.getActivity().isFinishing() && !iWinBoll.getActivity().isDestroyed()) { - resumeActivity(context, iWinBoll); - } - } - - // - // 找到tag 绑定的 BaseActivity ,通过 getTaskId() 移动到前台 - // - public void resumeActivity(Context context, T iWinBoll) { - ActivityManager am = (ActivityManager) iWinBoll.getActivity().getSystemService(Context.ACTIVITY_SERVICE); - //返回启动它的根任务(home 或者 MainActivity) - Intent intent = new Intent(context, iWinBoll.getClass()); - TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); - stackBuilder.addNextIntentWithParentStack(intent); - stackBuilder.startActivities(); - //moveTaskToFront(YourTaskId, 0); - LogUtils.d(TAG, "am.moveTaskToFront"); - //ToastUtils.show("resumeActivity am.moveTaskToFront"); - am.moveTaskToFront(iWinBoll.getActivity().getTaskId(), ActivityManager.MOVE_TASK_NO_USER_ACTION); - } - - - // - // 结束所有 Activity - // - public void finishAll() { - try { - for (String key : _mapIWinBollList.keySet()) { - //System.out.println("Key: " + key + ", Value: " + _mapActivityList.get(key)); - IWinBollActivity iWinBoll = _mapIWinBollList.get(key); - //ToastUtils.show("finishAll() activity"); - if (iWinBoll != null && !iWinBoll.getActivity().isFinishing() && !iWinBoll.getActivity().isDestroyed()) { - //ToastUtils.show("activity != null ..."); - if (getWinBollUI_TYPE() == WinBollUI_TYPE.Service) { - // 结束窗口和最近任务栏, 建议前台服务类应用使用,可以方便用户再次调用 UI 操作。 - iWinBoll.getActivity().finishAndRemoveTask(); - //ToastUtils.show("finishAll() activity.finishAndRemoveTask();"); - } else if (getWinBollUI_TYPE() == WinBollUI_TYPE.Aplication) { - // 结束窗口保留最近任务栏,建议前台服务类应用使用,可以保持应用的系统自觉性。 - iWinBoll.getActivity().finish(); - //ToastUtils.show("finishAll() activity.finish();"); - } else { - LogUtils.d(TAG, "WinBollApplication.WinBollUI_TYPE error."); - //ToastUtils.show("WinBollApplication.WinBollUI_TYPE error."); - } - } - } - } catch (Exception e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); - } - } - - // - // 结束指定Activity - // - public void finish(T iWinBoll) { - try { - if (iWinBoll != null && !iWinBoll.getActivity().isFinishing() && !iWinBoll.getActivity().isDestroyed()) { - //根据tag 移除 MyActivity - //String tag= activity.getTag(); - //_mWinBollActivityList.remove(tag); - //ToastUtils.show("remove"); - //ToastUtils.show("_mWinBollActivityArrayMap.size() " + Integer.toString(_mWinBollActivityArrayMap.size())); - - // 窗口回调规则: - // [] 当前窗口位置 >> 调度出的窗口位置 - // ★:[0] 1 2 3 4 >> 1 - // ★:0 1 [2] 3 4 >> 1 - // ★:0 1 2 [3] 4 >> 2 - // ★:0 1 2 3 [4] >> 3 - // ★:[0] >> 直接关闭当前窗口 - LogUtils.d(TAG, "finish no yet."); - IWinBollActivity preIWinBoll = getPreIWinBoll(iWinBoll); - iWinBoll.getActivity().finish(); - if (preIWinBoll != null) { - resumeActivity(mContext, preIWinBoll); - } - } - - } catch (Exception e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); - } - } - - // - // 获取窗口队列中的前一个窗口 - // - IWinBollActivity getPreIWinBoll(IWinBollActivity iWinBoll) { - try { - boolean bingo = false; - IWinBollActivity preIWinBoll = null; - for (Map.Entry entity : _mapIWinBollList.entrySet()) { - if (entity.getKey().equals(iWinBoll.getTag())) { - bingo = true; - LogUtils.d(TAG, "bingo"); - break; - } - preIWinBoll = entity.getValue(); - } - - if (bingo) { - return preIWinBoll; - } - } catch (Exception e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); - } - - return null; - } - - // - // 从管理列表中移除管理项 - // - public boolean registeRemove(T activity) { - IWinBollActivity iWinBollTest = _mapIWinBollList.get(activity.getTag()); - if (iWinBollTest != null) { - _mapIWinBollList.remove(activity.getTag()); - return true; - } - return false; - } - - // - // 打印管理列表项列表里的信息 - // - public static void printIWinBollListInfo() { - //LogUtils.d(TAG, "printAvtivityListInfo"); - if (!_mapIWinBollList.isEmpty()) { - StringBuilder sb = new StringBuilder("Map entries : " + Integer.toString(_mapIWinBollList.size())); - Iterator> iterator = _mapIWinBollList.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - sb.append("\nKey: " + entry.getKey() + ", \nValue: " + entry.getValue().getTag()); - //ToastUtils.show("\nKey: " + entry.getKey() + ", Value: " + entry.getValue().getTag()); - } - sb.append("\nMap entries end."); - LogUtils.d(TAG, sb.toString()); - } else { - LogUtils.d(TAG, "The map is empty."); - } - } -} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollClientService.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollClientService.java new file mode 100644 index 0000000..54192ca --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollClientService.java @@ -0,0 +1,190 @@ +package cc.winboll.studio.libaes.winboll; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 19:06:54 + * @Describe WinBoll 客户端服务 + */ +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.drawable.Drawable; +import android.os.IBinder; +import cc.winboll.studio.libaes.winboll.AssistantService; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.utils.ServiceUtils; +import com.hjq.toast.ToastUtils; + +public class WinBollClientService extends Service implements IWinBollClientServiceBinder { + + public static final String TAG = "WinBollClientService"; + + WinBollClientServiceBean mWinBollClientServiceBean; + MyServiceConnection mMyServiceConnection; + volatile boolean mIsWinBollClientThreadRunning; + volatile boolean mIsEnableService; + WinBollClientThread mWinBollClientThread; + + public boolean isWinBollClientThreadRunning() { + return mIsWinBollClientThreadRunning; + } + + @Override + public WinBollClientService getService() { + return WinBollClientService.this; + } + + @Override + public Drawable getCurrentStatusIconDrawable() { + return mIsWinBollClientThreadRunning ? + getDrawable(EWUIStatusIconDrawable.getIconDrawableId(EWUIStatusIconDrawable.NORMAL)) + : getDrawable(EWUIStatusIconDrawable.getIconDrawableId(EWUIStatusIconDrawable.NEWS)); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + ToastUtils.show("onCreate"); + super.onCreate(); + mWinBollClientThread = null; + mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); + mIsEnableService = mWinBollClientServiceBean.isEnable(); + + if (mMyServiceConnection == null) { + mMyServiceConnection = new MyServiceConnection(); + } + + // 由系统启动时,应用可以通过下面函数实例化实际服务进程。 + runMainThread(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + ToastUtils.show("onStartCommand"); + // 由应用 Intent 启动时,应用可以通过下面函数实例化实际服务进程。 + runMainThread(); + + // 返回运行参数持久化存储后,服务状态控制参数 + // 无论 Intent 传入如何,服务状态一直以持久化存储后的参数控制, + // PS: 另外当然可以通过 Intent 传入的指标来修改 mWinBollServiceBean, + // 不过本服务的应用方向会变得繁琐, + // 现阶段只要满足手机端启动与停止本服务,WinBoll 客户端实例运行在手机端就可以了。 + return mIsEnableService ? Service.START_STICKY: super.onStartCommand(intent, flags, startId); + } + + void runMainThread() { + if (mWinBollClientThread == null) { + ToastUtils.show("runMainThread()"); + mWinBollClientThread = new WinBollClientThread(); + mWinBollClientThread.start(); + } + } + + void syncWinBollClientThreadStatus() { + mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); + mIsEnableService = mWinBollClientServiceBean.isEnable(); + } + + + // 唤醒和绑定守护进程 + // + void wakeupAndBindAssistant() { + if (ServiceUtils.isServiceRunning(getApplicationContext(), AssistantService.class.getName()) == false) { + startService(new Intent(WinBollClientService.this, AssistantService.class)); + //LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService"); + bindService(new Intent(WinBollClientService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT); + } + } + + // 主进程与守护进程连接时需要用到此类 + // + private class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(WinBollClientService.this); + if (mWinBollClientServiceBean.isEnable()) { + // 唤醒守护进程 + wakeupAndBindAssistant(); + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + ToastUtils.show("onDestroy"); + } + + @Override + public void onStart(Intent intent, int startId) { + super.onStart(intent, startId); + } + + void setWinBollServiceEnableStatus(boolean isEnable) { + WinBollClientServiceBean bean = WinBollClientServiceBean.loadWinBollClientServiceBean(this); + bean.setIsEnable(isEnable); + WinBollClientServiceBean.saveWinBollServiceBean(this, bean); + } + + boolean getWinBollServiceEnableStatus(Context context) { + mWinBollClientServiceBean = WinBollClientServiceBean.loadWinBollClientServiceBean(context); + return mWinBollClientServiceBean.isEnable(); + } + + /*public interface OnServiceStatusChangeListener { + void onServerStatusChange(boolean isServiceAlive); + } + + public void setOnServerStatusChangeListener(OnServiceStatusChangeListener l) { + mOnServerStatusChangeListener = l; + }*/ + + class WinBollClientThread extends Thread { + @Override + public void run() { + //ToastUtils.show("WinBollClientThread"); + super.run(); + syncWinBollClientThreadStatus(); + if (mIsEnableService) { + if (mIsWinBollClientThreadRunning == false) { + // 设置运行状态 + mIsWinBollClientThreadRunning = true; + + //ToastUtils.show("run()"); + + // 唤醒守护进程 + //wakeupAndBindAssistant(); + + while (mIsEnableService) { + // 显示运行状态 + ToastUtils.show(TAG + " is running."); + try { + Thread.sleep(2 * 1000); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + syncWinBollClientThreadStatus(); + //ToastUtils.show("syncServiceThreadStatus OK."); + //ToastUtils.show("mIsExist : " + Boolean.toString(!mIsEnableService)); + //break; + } + + // 服务进程退出, 重置进程运行状态 + mIsWinBollClientThreadRunning = false; + mWinBollClientThread = null; + } + } + } + } +} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollClientServiceBean.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollClientServiceBean.java new file mode 100644 index 0000000..271f1e0 --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollClientServiceBean.java @@ -0,0 +1,78 @@ +package cc.winboll.studio.libaes.winboll; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 19:05:15 + * @Describe WinBollService 运行参数配置 + */ +import android.content.Context; +import android.util.JsonReader; +import android.util.JsonWriter; +import cc.winboll.studio.libappbase.BaseBean; +import java.io.IOException; + +public class WinBollClientServiceBean extends BaseBean { + + public static final String TAG = "WinBollClientServiceBean"; + + volatile boolean isEnable; + + public WinBollClientServiceBean() { + isEnable = false; + } + + public void setIsEnable(boolean isEnable) { + this.isEnable = isEnable; + } + + public boolean isEnable() { + return isEnable; + } + + @Override + public String getName() { + return WinBollClientServiceBean.class.getName(); + } + + @Override + public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { + super.writeThisToJsonWriter(jsonWriter); + WinBollClientServiceBean 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; + } + + public static WinBollClientServiceBean loadWinBollClientServiceBean(Context context) { + WinBollClientServiceBean bean = WinBollClientServiceBean.loadBean(context, WinBollClientServiceBean.class); + return bean == null ? new WinBollClientServiceBean() : bean; + } + + public static boolean saveWinBollServiceBean(WinBollClientService service, WinBollClientServiceBean bean) { + return WinBollClientServiceBean.saveBean(service, bean); + } +} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollMail.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollMail.java new file mode 100644 index 0000000..3806106 --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollMail.java @@ -0,0 +1,22 @@ +package cc.winboll.studio.libaes.winboll; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 19:13:20 + * @Describe WinBoll 邮件服务 + */ +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class WinBollMail extends Service { + + public static final String TAG = "WinBollMail"; + + @Override + public IBinder onBind(Intent intent) { + + return null; + } + +} diff --git a/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollServiceStatusView.java b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollServiceStatusView.java new file mode 100644 index 0000000..9ec3bf2 --- /dev/null +++ b/libaes/src/main/java/cc/winboll/studio/libaes/winboll/WinBollServiceStatusView.java @@ -0,0 +1,340 @@ +package cc.winboll.studio.libaes.winboll; + +/** + * @Author ZhanGSKen@AliYun.Com + * @Date 2025/03/28 17:41:55 + * @Describe WinBoll 服务主机连接状态视图 + */ +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import cc.winboll.studio.libappbase.LogUtils; +import com.hjq.toast.ToastUtils; +import cc.winboll.studio.libapputils.R; +//import okhttp3.Authenticator; +//import okhttp3.Credentials; +//import okhttp3.OkHttpClient; +//import okhttp3.Request; +//import okhttp3.Response; +//import okhttp3.Route; + +public class WinBollServiceStatusView extends LinearLayout { + + public static final String TAG = "WinBollServiceStatusView"; + + public static final int MSG_CONNECTION_INFO = 0; + public static final int MSG_UPDATE_CONNECTION_STATUS = 1; + + Context mContext; + //boolean mIsConnected; + ConnectionThread mConnectionThread; + + String mszServerHost; + WinBollClientService mWinBollService; + ImageView mImageView; + TextView mTextView; + WinBollServiceViewHandler mWinBollServiceViewHandler; + //WebView mWebView; + static volatile ConnectionStatus mConnectionStatus; + View.OnClickListener mViewOnClickListener; + static String _mUserName; + static String _mPassword; + + static enum ConnectionStatus { + DISCONNECTED, + START_CONNECT, + CONNECTING, + CONNECTED; + }; + + boolean isBound = false; + ServiceConnection connection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IWinBollClientServiceBinder binder = (IWinBollClientServiceBinder) service; + mWinBollService = binder.getService(); + isBound = true; + // 可以在这里调用Service的方法进行通信,比如获取数据 + mImageView.setBackgroundDrawable(mWinBollService.getCurrentStatusIconDrawable()); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + isBound = false; + } + }; + + public WinBollServiceStatusView(Context context) { + super(context); + mContext = context; + initView(); + } + + public WinBollServiceStatusView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + initView(); + } + + public WinBollServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mContext = context; + initView(); + } + + public WinBollServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mContext = context; + initView(); + } + + ConnectionStatus getConnectionStatus() { + return false ? + ConnectionStatus.CONNECTED + : ConnectionStatus.DISCONNECTED; + } + + void initView() { + mImageView = new ImageView(mContext); + setImageViewByConnection(mImageView, false); + mConnectionStatus = getConnectionStatus(); + //mIsConnected = false; + //mWinBollServerHostConnectionStatus = WinBollServerHostConnectionStatus.DISCONNECTED; + //ToastUtils.show("initView()"); + + mViewOnClickListener = new View.OnClickListener(){ + @Override + public void onClick(View v) { + //ToastUtils.show("onClick()"); + //ToastUtils.show("mWinBollServerHostConnectionStatus : " + mWinBollServerHostConnectionStatus); + //isConnected = !isConnected; + if (mConnectionStatus == ConnectionStatus.CONNECTED) { + ToastUtils.show("Click to stop service."); + WinBollClientServiceBean bean = WinBollClientServiceBean.loadWinBollClientServiceBean(mContext); + bean.setIsEnable(false); + WinBollClientServiceBean.saveBean(mContext, bean); + Intent intent = new Intent(mContext, WinBollClientService.class); + mContext.stopService(intent); + mConnectionStatus = ConnectionStatus.DISCONNECTED; +// + /*//ToastUtils.show("CONNECTED"); + setConnectionStatusView(false); + mWinBollServerHostConnectionStatusViewHandler.postMessageText(""); + if (mConnectionThread != null) { + mConnectionThread.mIsExist = true; + mConnectionThread = null; + mWinBollServerHostConnectionStatus = WinBollServerHostConnectionStatus.DISCONNECTED; + ToastUtils.show("WinBoll Server Disconnected."); + }*/ + } else if (mConnectionStatus == ConnectionStatus.DISCONNECTED) { + ToastUtils.show("Click to start service."); + WinBollClientServiceBean bean = WinBollClientServiceBean.loadWinBollClientServiceBean(mContext); + bean.setIsEnable(true); + WinBollClientServiceBean.saveBean(mContext, bean); + Intent intent = new Intent(mContext, WinBollClientService.class); + mContext.startService(intent); + mConnectionStatus = ConnectionStatus.CONNECTED; + ToastUtils.show("startService"); + /*//ToastUtils.show("DISCONNECTED"); + setConnectionStatusView(true); + + if (mConnectionThread == null) { + ToastUtils.show("mConnectionThread == null"); + mConnectionThread = new ConnectionThread(); + mWinBollServerHostConnectionStatus = WinBollServerHostConnectionStatus.START_CONNECT; + mConnectionThread.start(); + }*/ + } else { + ToastUtils.show("Other Click condition."); + } + + /*if (isConnected) { + mWebView.loadUrl("https://dev.winboll.cc"); + } else { + mWebView.stopLoading(); + }*/ + //ToastUtils.show(mDevelopHostConnectionStatus); + //LogUtils.d(TAG, "mDevelopHostConnectionStatus : " + mWinBollServerHostConnectionStatus); + } + }; + setOnClickListener(mViewOnClickListener); + addView(mImageView); + mTextView = new TextView(mContext); + mWinBollServiceViewHandler = new WinBollServiceViewHandler(this); + addView(mTextView); + /*mWebView = new WebView(mContext); + mWebView.setWebViewClient(new WebViewClient() { + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { + // 弹出系统基本HTTP验证窗口 + handler.proceed("username", "password"); + } + }); + addView(mWebView);*/ + } + + void checkWinBollServerStatusAndUpdateCurrentView() { + LogUtils.d(TAG, "checkWinBollServerStatusAndUpdateCurrentView()"); + /*if (getConnectionStatus() == ConnectionStatus.CONNECTED) { + mConnectionStatus = ConnectionStatus.CONNECTED; + } else { + mConnectionStatus = ConnectionStatus.DISCONNECTED; + }*/ + } + + public void setServerHost(String szWinBollServerHost) { + mszServerHost = szWinBollServerHost; + } + + public void setAuthInfo(String username, String password) { + _mUserName = username; + _mPassword = password; + } + + void setImageViewByConnection(ImageView imageView, boolean isConnected) { + //mIsConnected = isConnected; + // 获取vector drawable + Drawable drawable = mContext.getDrawable(isConnected ? R.drawable.ic_dev_connected : R.drawable.ic_dev_disconnected); + if (drawable != null) { + imageView.setImageDrawable(drawable); + } + } + + void requestWithBasicAuth(WinBollServiceViewHandler textViewHandler, String targetUrl, final String username, final String password) { + // 用户名和密码,替换为实际的认证信息 + //String username = "your_username"; + //String password = "your_password"; + +// OkHttpClient client = new OkHttpClient.Builder() +// .authenticator(new Authenticator() { +// @Override +// public Request authenticate(Route route, Response response) throws IOException { +// String credential = Credentials.basic(username, password); +// return response.request().newBuilder() +// .header("Authorization", credential) +// .build(); +// } +// }) +// .build(); +// +// Request request = new Request.Builder() +// .url(targetUrl) // 替换为实际要请求的网页地址 +// .build(); +// +// try { +// Response response = client.newCall(request).execute(); +// if (response.isSuccessful()) { +// //System.out.println(response.body().string()); +// //ToastUtils.show("Develop Host Connection IP is : " + response.body().string()); +// // 获取当前时间 +// LocalDateTime now = LocalDateTime.now(); +// +// // 定义时间格式 +// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); +// // 按照指定格式格式化时间并输出 +// String formattedDateTime = now.format(formatter); +// //System.out.println(formattedDateTime); +// textViewHandler.postMessageText("ClientIP<" + formattedDateTime + ">: " + response.body().string()); +// textViewHandler.postMessageConnectionStatus(true); +// } else { +// String sz = "请求失败,状态码: " + response.code(); +// setImageViewByConnection(mImageView, false); +// textViewHandler.postMessageText(sz); +// textViewHandler.postMessageConnectionStatus(false); +// LogUtils.d(TAG, sz); +// } +// } catch (IOException e) { +// textViewHandler.postMessageText(e.getMessage()); +// textViewHandler.postMessageConnectionStatus(false); +// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); +// } + } + + class WinBollServiceViewHandler extends Handler { + WinBollServiceStatusView mDevelopHostConnectionStatusView; + + public WinBollServiceViewHandler(WinBollServiceStatusView view) { + mDevelopHostConnectionStatusView = view; + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_CONNECTION_INFO) { + mDevelopHostConnectionStatusView.mTextView.setText((String)msg.obj); + } else if (msg.what == MSG_UPDATE_CONNECTION_STATUS) { + mDevelopHostConnectionStatusView.setImageViewByConnection(mImageView, (boolean)msg.obj); + mDevelopHostConnectionStatusView.mConnectionStatus = ((boolean)msg.obj) ? ConnectionStatus.CONNECTED : ConnectionStatus.DISCONNECTED; + } + super.handleMessage(msg); + } + + void postMessageText(String szMSG) { + Message msg = new Message(); + msg.what = MSG_CONNECTION_INFO; + msg.obj = szMSG; + sendMessage(msg); + } + + void postMessageConnectionStatus(boolean isConnected) { + Message msg = new Message(); + msg.what = MSG_UPDATE_CONNECTION_STATUS; + msg.obj = isConnected; + sendMessage(msg); + } + } + + class ConnectionThread extends Thread { + + public volatile boolean mIsExist; + + //DevelopHostConnectionStatusViewHandler mDevelopHostConnectionStatusViewHandler; + + //public ConnectionThread(DevelopHostConnectionStatusViewHandler developHostConnectionStatusViewHandler) { + //mDevelopHostConnectionStatusViewHandler = developHostConnectionStatusViewHandler; + //} + public ConnectionThread() { + mIsExist = false; + } + + @Override + public void run() { + super.run(); + while (mIsExist == false) { + if (mConnectionStatus == ConnectionStatus.START_CONNECT) { + mConnectionStatus = ConnectionStatus.CONNECTING; + ToastUtils.show("WinBoll Server Connection Start."); + //LogUtils.d(TAG, "Develop Host Connection Start."); + String targetUrl = "https://" + mszServerHost + "/cip/?simple=true"; // 这里替换成你实际要访问的网址 + requestWithBasicAuth(mWinBollServiceViewHandler, targetUrl, _mUserName, _mPassword); + } else if (mConnectionStatus == ConnectionStatus.CONNECTED + && mConnectionStatus == ConnectionStatus.DISCONNECTED) { + ToastUtils.show("mWinBollServerHostConnectionStatus " + mConnectionStatus); + } + + try { + Thread.sleep(5 * 1000); + } catch (InterruptedException e) { + LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + //ToastUtils.show("ConnectionThread exit."); + LogUtils.d(TAG, "ConnectionThread exit."); + } + } + + /*WinBollService.OnServiceStatusChangeListener mOnServerStatusChangeListener = new WinBollService.OnServiceStatusChangeListener(){ + @Override + public void onServerStatusChange(boolean isServiceAlive) { + } + };*/ +} diff --git a/libaes/src/main/res/layout/view_about_dev.xml b/libaes/src/main/res/layout/view_about_dev.xml index 1f19c03..629585f 100644 --- a/libaes/src/main/res/layout/view_about_dev.xml +++ b/libaes/src/main/res/layout/view_about_dev.xml @@ -53,7 +53,7 @@ android:layout_height="wrap_content" android:gravity="center_horizontal"> - diff --git a/libaes/src/main/res/layout/view_about_www.xml b/libaes/src/main/res/layout/view_about_www.xml index 69c02d9..6cd4e5c 100644 --- a/libaes/src/main/res/layout/view_about_www.xml +++ b/libaes/src/main/res/layout/view_about_www.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" android:gravity="center_horizontal"> -