Compare commits
17 Commits
appbase-v1
...
winboll-v1
| Author | SHA1 | Date | |
|---|---|---|---|
| 25f4c79860 | |||
| 5440c1ad39 | |||
| 47dd4698b8 | |||
| 861ccb5832 | |||
| 8b1f119e44 | |||
| c6495d1859 | |||
| 15197b123c | |||
| df2e91d390 | |||
| 61ca0d2672 | |||
| 8c18710e36 | |||
| 224bd243e2 | |||
| b30bdc6802 | |||
| 8f0973cb6c | |||
| b9fab2d737 | |||
| 156af54eaa | |||
| fb9dd93162 | |||
| 7e757a456a |
@@ -13,6 +13,9 @@ WinBoLL 网站浏览器。
|
||||
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh winboll
|
||||
|
||||
#### 使用说明
|
||||
3. Termux应用配置:
|
||||
- 已安装Termux(包名 com.termux );
|
||||
- 执行 echo "allow-external-apps = true" > ~/.termux/termux.properties
|
||||
|
||||
#### 参与贡献
|
||||
|
||||
|
||||
@@ -73,12 +73,13 @@ dependencies {
|
||||
implementation 'com.alibaba:fastjson:1.2.76'
|
||||
|
||||
// AndroidX 类库
|
||||
api 'androidx.appcompat:appcompat:1.1.0'
|
||||
/*api 'androidx.appcompat:appcompat:1.1.0'
|
||||
//api 'com.google.android.material:material:1.4.0'
|
||||
//api 'androidx.viewpager:viewpager:1.0.0'
|
||||
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'*/
|
||||
|
||||
|
||||
// 米盟
|
||||
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||
@@ -88,14 +89,31 @@ dependencies {
|
||||
api 'com.google.code.gson:gson:2.8.5'
|
||||
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.core:core:1.6.0"
|
||||
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
implementation "com.google.guava:guava:24.1-jre"
|
||||
/*
|
||||
implementation "io.noties.markwon:core:$markwonVersion"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||
*/
|
||||
implementation 'com.termux:terminal-emulator:0.118.0'
|
||||
implementation 'com.termux:terminal-view:0.118.0'
|
||||
implementation 'com.termux:termux-shared:0.118.0'
|
||||
|
||||
// WinBoLL库 nexus.winboll.cc 地址
|
||||
api 'cc.winboll.studio:libaes:15.12.13'
|
||||
api 'cc.winboll.studio:libappbase:15.14.2'
|
||||
api 'cc.winboll.studio:libaes:15.15.2'
|
||||
api 'cc.winboll.studio:libappbase:15.15.7'
|
||||
|
||||
// WinBoLL备用库 jitpack.io 地址
|
||||
//api 'com.github.ZhanGSKen:AES:aes-v15.12.9'
|
||||
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
|
||||
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
|
||||
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.4'
|
||||
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Jan 12 05:14:13 GMT 2026
|
||||
stageCount=9
|
||||
#Fri Jan 23 06:00:53 HKT 2026
|
||||
stageCount=12
|
||||
libraryProject=
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.8
|
||||
buildCount=15
|
||||
baseBetaVersion=15.11.9
|
||||
publishVersion=15.11.11
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.12
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.winboll">
|
||||
package="cc.winboll.studio.winboll"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:sharedUserId="com.termux">
|
||||
|
||||
<!-- 拥有完全的网络访问权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
@@ -11,7 +13,12 @@
|
||||
|
||||
<!-- 对正在运行的应用重新排序 -->
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS"/>
|
||||
|
||||
<!-- Android 11+ 查询已安装应用权限 -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<!-- 可选:兼容低版本系统 -->
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
@@ -282,6 +289,8 @@
|
||||
|
||||
<activity android:name="cc.winboll.studio.winboll.activities.WXPayActivity"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package cc.winboll.studio.winboll;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@188.com>
|
||||
* @Date 2025/05/22 13:08
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
public class CustomToolbar extends Toolbar {
|
||||
|
||||
private View viewMain;
|
||||
|
||||
public CustomToolbar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CustomToolbar(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CustomToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initView(context, attrs);
|
||||
}
|
||||
|
||||
private void initView(Context context, AttributeSet attrs) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomToolbar);
|
||||
|
||||
// 获取属性值
|
||||
String toolbarTitle = typedArray.getString(R.styleable.CustomToolbar_toolbarTitle);
|
||||
int toolbarTitleColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarTitleColor, android.graphics.Color.WHITE);
|
||||
int toolbarBackgroundColor = typedArray.getColor(R.styleable.CustomToolbar_toolbarBackgroundColor, android.graphics.Color.BLUE);
|
||||
|
||||
// 加载布局
|
||||
viewMain = LayoutInflater.from(context).inflate(R.layout.view_toolbar, this, true);
|
||||
|
||||
// 应用属性值
|
||||
TextView toolbarTitleTextView = viewMain.findViewById(R.id.toolbar_title);
|
||||
toolbarTitleTextView.setText(toolbarTitle);
|
||||
toolbarTitleTextView.setTextColor(toolbarTitleColor);
|
||||
viewMain.setBackgroundColor(toolbarBackgroundColor);
|
||||
|
||||
// 释放 TypedArray
|
||||
typedArray.recycle();
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,15 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import cc.winboll.studio.libaes.activitys.AboutActivity;
|
||||
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
|
||||
import cc.winboll.studio.libaes.models.APPInfo;
|
||||
import cc.winboll.studio.libaes.models.DrawerMenuBean;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.activities.AboutActivity;
|
||||
import cc.winboll.studio.winboll.activities.SettingsActivity;
|
||||
import cc.winboll.studio.winboll.activities.WXPayActivity;
|
||||
import cc.winboll.studio.winboll.fragments.BrowserFragment;
|
||||
import cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MainActivity extends DrawerFragmentActivity {
|
||||
@@ -123,6 +122,9 @@ public class MainActivity extends DrawerFragmentActivity {
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
if (!App.isDebugging()) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_test, menu);
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -149,12 +151,13 @@ public class MainActivity extends DrawerFragmentActivity {
|
||||
}
|
||||
} else if (nItemId == R.id.item_settings) {
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), SettingsActivity.class);
|
||||
} else if (nItemId == R.id.item_wxpayactivity) {
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), WXPayActivity.class);
|
||||
} else if (nItemId == cc.winboll.studio.libaes.R.id.item_about) {
|
||||
} else if (nItemId == R.id.item_about) {
|
||||
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
|
||||
APPInfo appInfo = genDefaultAPPInfo();
|
||||
intent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
|
||||
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
|
||||
} else if (nItemId == R.id.item_termux_env_test) {
|
||||
Intent intent = new Intent(getApplicationContext(), TermuxEnvTestActivity.class);
|
||||
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -163,23 +166,6 @@ public class MainActivity extends DrawerFragmentActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
APPInfo genDefaultAPPInfo() {
|
||||
String szBranchName = "winboll";
|
||||
APPInfo appInfo = new APPInfo();
|
||||
appInfo.setAppName("WinBoLL");
|
||||
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
|
||||
appInfo.setAppDescription("WinBoLL 网站浏览器。");
|
||||
appInfo.setAppGitName("WinBoLL");
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(szBranchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=WinBoLL");
|
||||
appInfo.setAppAPKName("WinBoLL");
|
||||
appInfo.setAppAPKFolderName("WinBoLL");
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
// ------------------- 新增:对外提供Handler(供其他组件发送消息,如BrowserFragment) -------------------
|
||||
public Handler getMainHandler() {
|
||||
return _mMainHandler;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.models.APPInfo;
|
||||
import cc.winboll.studio.libappbase.views.AboutView;
|
||||
import cc.winboll.studio.winboll.MainActivity;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/13 11:54
|
||||
* @Describe 应用介绍窗口
|
||||
*/
|
||||
public class AboutActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static final String TAG = "AboutActivity";
|
||||
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
// 设置工具栏
|
||||
initToolbar();
|
||||
|
||||
AboutView aboutView = getActivity().findViewById(R.id.aboutview);
|
||||
aboutView.setAPPInfo(genDefaultAppInfo());
|
||||
}
|
||||
|
||||
private void initToolbar() {
|
||||
LogUtils.d(TAG, "initToolbar() 开始初始化");
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
if (mToolbar == null) {
|
||||
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
|
||||
return;
|
||||
}
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "导航栏 点击返回按钮");
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "initToolbar() 配置完成");
|
||||
}
|
||||
|
||||
private APPInfo genDefaultAppInfo() {
|
||||
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
|
||||
String branchName = "winboll";
|
||||
APPInfo appInfo = new APPInfo();
|
||||
appInfo.setAppName(getActivity().getString(R.string.app_name));
|
||||
appInfo.setAppIcon(R.drawable.ic_winboll);
|
||||
appInfo.setAppDescription(getActivity().getString(R.string.app_description));
|
||||
appInfo.setAppGitName("WinBoLL");
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(branchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(branchName);
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=WinBoLL");
|
||||
appInfo.setAppAPKName("WinBoLL");
|
||||
appInfo.setAppAPKFolderName("WinBoLL");
|
||||
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
|
||||
return appInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/13 16:33
|
||||
* BaseWinBollActivity 【继承AppCompatActivity,保留核心能力,不额外暴露方法】
|
||||
* 继承链路:BaseWinBoLLActivity → AppCompatActivity → FragmentActivity,AppCompat能力天然继承可用
|
||||
*/
|
||||
public abstract class BaseWinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||
public static final String TAG = "BaseWinBoLLActivity";
|
||||
|
||||
protected volatile AESThemeBean.ThemeType mThemeType;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mThemeType = getThemeType();
|
||||
setThemeStyle();
|
||||
super.onCreate(savedInstanceState);
|
||||
WinBoLLActivityManager.getInstance().add(this);
|
||||
//ToastUtils.show(getTag() + ": onCreate");
|
||||
}
|
||||
|
||||
AESThemeBean.ThemeType getThemeType() {
|
||||
/*SharedPreferences sharedPreferences = getSharedPreferences(
|
||||
SHAREDPREFERENCES_NAME, MODE_PRIVATE);
|
||||
return AESThemeBean.ThemeType.values()[((sharedPreferences.getInt(DRAWER_THEME_TYPE, AESThemeBean.ThemeType.DEFAULT.ordinal())))];
|
||||
*/
|
||||
return AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
|
||||
}
|
||||
|
||||
void setThemeStyle() {
|
||||
//setTheme(AESThemeBean.getThemeStyle(getThemeType()));
|
||||
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// 子类必须实现getTag(),确保唯一标识
|
||||
@Override
|
||||
public abstract String getTag();
|
||||
|
||||
public abstract Activity getActivity();
|
||||
}
|
||||
|
||||
@@ -5,20 +5,32 @@ package cc.winboll.studio.winboll.activities;
|
||||
* @Date 2025/03/25 11:46:40
|
||||
* @Describe 测试窗口2
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import android.app.Activity;
|
||||
|
||||
public class New2Activity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public class New2Activity extends WinBoLLActivity {
|
||||
|
||||
public static final String TAG = "New2Activity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
//LogView mLogView;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -28,15 +40,10 @@ public class New2Activity extends WinBoLLActivity {
|
||||
// mLogView = findViewById(R.id.logview);
|
||||
// mLogView.start();
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setActionBar(mToolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//mLogView.start();
|
||||
}
|
||||
|
||||
public void onCloseThisActivity(View view) {
|
||||
//WinBoLLActivityManager.getInstance().finish(this);
|
||||
@@ -49,17 +56,4 @@ public class New2Activity extends WinBoLLActivity {
|
||||
public void onNewActivity(View view) {
|
||||
//WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, NewActivity.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,26 @@ package cc.winboll.studio.winboll.activities;
|
||||
* @Date 2025/03/25 05:04:22
|
||||
* @Describe
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.App;
|
||||
import android.app.Activity;
|
||||
|
||||
public class NewActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "NewActivity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
//LogView mLogView;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
@@ -40,15 +37,10 @@ public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
// mLogView = findViewById(R.id.logview);
|
||||
// mLogView.start();
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setActionBar(mToolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//mLogView.start();
|
||||
}
|
||||
|
||||
public void onCloseThisActivity(View view) {
|
||||
WinBoLLActivityManager.getInstance().finish(this);
|
||||
@@ -61,17 +53,4 @@ public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
public void onNew2Activity(View view) {
|
||||
// WinBoLLActivityManager.getInstance().startWinBoLLActivity(App.getInstance(), New2Activity.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
//getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/05 18:48
|
||||
* @Describe Settings Activity
|
||||
*/
|
||||
public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "SettingsActivity";
|
||||
public class SettingsActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public static final String TAG = "SettingsActivity";
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.WxPayConfig;
|
||||
import cc.winboll.studio.winboll.utils.SpecUtil;
|
||||
import cc.winboll.studio.winboll.utils.WxPayApi;
|
||||
import cc.winboll.studio.winboll.utils.ZXingUtils;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 主界面:生成二维码 + 轮询查询支付结果
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/07
|
||||
*/
|
||||
public class WXPayActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
private static final String TAG = "WXPayActivity";
|
||||
|
||||
// Handler消息标识
|
||||
private static final int MSG_POLL_TIMEOUT = 1001;
|
||||
private static final int MSG_POLL_SUCCESS = 1002;
|
||||
private static final int MSG_POLL_FAILED = 1003;
|
||||
|
||||
private ImageView mIvQrCode;
|
||||
private TextView mTvOrderNo;
|
||||
private TextView mTvPayStatus;
|
||||
private Button mBtnCreateOrder;
|
||||
|
||||
private String mOutTradeNo; // 商户订单号
|
||||
private Timer mPollTimer; // 轮询定时器
|
||||
private long mPollStartTime; // 轮询开始时间
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private Handler mPollHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
switch (msg.what) {
|
||||
case MSG_POLL_TIMEOUT:
|
||||
stopPoll();
|
||||
mTvPayStatus.setText("支付状态:轮询超时");
|
||||
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.darker_gray));
|
||||
Toast.makeText(WXPayActivity.this, "轮询超时,请手动查询", Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case MSG_POLL_SUCCESS:
|
||||
boolean isPaySuccess = (boolean) msg.obj;
|
||||
String tradeState = (String) msg.getData().getString("tradeState");
|
||||
stopPoll();
|
||||
if (isPaySuccess) {
|
||||
mTvPayStatus.setText("支付状态:支付成功 ✅");
|
||||
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
|
||||
Toast.makeText(WXPayActivity.this, "支付成功!", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
mTvPayStatus.setText("支付状态:" + tradeState);
|
||||
mTvPayStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
|
||||
}
|
||||
break;
|
||||
case MSG_POLL_FAILED:
|
||||
String errorMsg = (String) msg.obj;
|
||||
mTvPayStatus.setText("查询失败:" + errorMsg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_wxpay);
|
||||
|
||||
initView();
|
||||
initListener();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
mIvQrCode = findViewById(R.id.iv_qrcode);
|
||||
mTvOrderNo = findViewById(R.id.tv_order_no);
|
||||
mTvPayStatus = findViewById(R.id.tv_pay_status);
|
||||
mBtnCreateOrder = findViewById(R.id.btn_create_order);
|
||||
}
|
||||
|
||||
private void initListener() {
|
||||
mBtnCreateOrder.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createOrder();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一下单,生成二维码
|
||||
*/
|
||||
private void createOrder() {
|
||||
mBtnCreateOrder.setEnabled(false);
|
||||
mTvPayStatus.setText("支付状态:生成订单中...");
|
||||
mIvQrCode.setImageBitmap(null);
|
||||
|
||||
WxPayApi.createOrder(new WxPayApi.OnCreateOrderCallback() {
|
||||
@Override
|
||||
public void onSuccess(String outTradeNo, String codeUrl) {
|
||||
mOutTradeNo = outTradeNo;
|
||||
mTvOrderNo.setText("商户订单号:" + outTradeNo);
|
||||
mTvPayStatus.setText("支付状态:未支付,请扫码");
|
||||
|
||||
// 生成二维码
|
||||
Bitmap qrCodeBitmap = ZXingUtils.createQRCodeBitmap(codeUrl, 250, 250);
|
||||
if (qrCodeBitmap != null) {
|
||||
mIvQrCode.setImageBitmap(qrCodeBitmap);
|
||||
// 开始轮询查询支付结果
|
||||
startPoll();
|
||||
} else {
|
||||
mTvPayStatus.setText("支付状态:生成二维码失败");
|
||||
mBtnCreateOrder.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String errorMsg) {
|
||||
SpecUtil.WWSpecLogError(TAG, "统一下单失败:" + errorMsg);
|
||||
mTvPayStatus.setText("生成订单失败:" + errorMsg);
|
||||
mBtnCreateOrder.setEnabled(true);
|
||||
Toast.makeText(WXPayActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始轮询查询支付结果
|
||||
*/
|
||||
private void startPoll() {
|
||||
stopPoll(); // 先停止之前的轮询
|
||||
mPollStartTime = System.currentTimeMillis();
|
||||
mPollTimer = new Timer();
|
||||
mPollTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 检查是否超时
|
||||
long elapsedTime = System.currentTimeMillis() - mPollStartTime;
|
||||
if (elapsedTime >= WxPayConfig.POLL_TIMEOUT) {
|
||||
mPollHandler.sendEmptyMessage(MSG_POLL_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询订单状态
|
||||
WxPayApi.queryOrder(mOutTradeNo, new WxPayApi.OnQueryOrderCallback() {
|
||||
@Override
|
||||
public void onSuccess(boolean isPaySuccess, String tradeState) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = MSG_POLL_SUCCESS;
|
||||
msg.obj = isPaySuccess;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("tradeState", tradeState);
|
||||
msg.setData(bundle);
|
||||
mPollHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String errorMsg) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = MSG_POLL_FAILED;
|
||||
msg.obj = errorMsg;
|
||||
mPollHandler.sendMessage(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 0, WxPayConfig.POLL_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止轮询
|
||||
*/
|
||||
private void stopPoll() {
|
||||
if (mPollTimer != null) {
|
||||
mPollTimer.cancel();
|
||||
mPollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopPoll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package cc.winboll.studio.winboll.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/05/10 09:48
|
||||
* @Describe WinBoLL 窗口基础类
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class WinBoLLActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "WinBoLLActivity";
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
/*if (item.getItemId() == R.id.item_log) {
|
||||
WinBoLLActivityManager.getInstance().startLogActivity(this);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.item_home) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
}*/
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
//WinBoLLActivityManager.getInstance().add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
//WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package cc.winboll.studio.winboll.termux;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import cc.winboll.studio.libappbase.LogUtils; // 替换 Log 为 LogUtils(与 Activity 一致)
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.shell.command.ExecutionCommand.Runner;
|
||||
|
||||
/**
|
||||
* Termux 命令调用工具类(基于 RunCommandService 原型封装)
|
||||
* 用于向 Termux 发送命令执行请求,兼容 Termux RUN_COMMAND Intent 规范
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/19 16:30:00
|
||||
* @LastEditTime 2026/01/20 10:15:00
|
||||
*/
|
||||
public class TermuxCommandExecutor {
|
||||
private static final String TAG = "TermuxCommandExecutor";
|
||||
// 核心修复:Termux 官方包名(无 .app 后缀)
|
||||
private static final String TERMUX_PACKAGE_NAME = "com.termux";
|
||||
// Termux RunCommandService 完整类名(包名+类名)
|
||||
private static final String TERMUX_RUN_CMD_SERVICE_CLASS = "com.termux.app.RunCommandService";
|
||||
private static final String TERMUX_RUN_CMD_ACTION = TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND;
|
||||
|
||||
/**
|
||||
* 执行 Termux 命令(核心方法)
|
||||
* @param context 上下文(如 Activity、Service)
|
||||
* @param command 要执行的命令路径(如 "/bin/ls"、"/data/data/com.termux/files/usr/bin/bash")
|
||||
* @param args 命令参数(如 ["-l", "/data/data/com.termux/files/home"])
|
||||
* @param workDir 工作目录(可为 null,默认 Termux 主目录)
|
||||
* @param isBackground 是否后台执行(true=后台,false=终端会话执行)
|
||||
* @param resultDir 命令结果输出目录(可为 null,不输出到文件)
|
||||
* @return 是否成功发送命令请求
|
||||
*/
|
||||
public static boolean executeCommand(Context context, String command, String[] args, String workDir, boolean isBackground, String resultDir) {
|
||||
// 1. 校验上下文和命令合法性
|
||||
if (context == null || command == null || command.isEmpty()) {
|
||||
LogUtils.e(TAG, "执行命令失败:上下文或命令为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 校验 Termux 是否安装(新增:提前校验,避免白跑流程)
|
||||
if (!isTermuxInstalled(context)) {
|
||||
LogUtils.e(TAG, "执行命令失败:Termux 未安装");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 创建 Intent 并设置目标 Service
|
||||
Intent intent = new Intent(TERMUX_RUN_CMD_ACTION);
|
||||
intent.setClassName(TERMUX_PACKAGE_NAME, TERMUX_RUN_CMD_SERVICE_CLASS); // 用正确包名
|
||||
intent.setPackage(TERMUX_PACKAGE_NAME); // 明确包名,避免歧义
|
||||
|
||||
// 4. 设置核心命令参数(遵循 Termux RunCommandService 规范)
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH, command);
|
||||
if (args != null && args.length > 0) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS, args);
|
||||
LogUtils.d(TAG, "命令参数:" + String.join(",", args));
|
||||
}
|
||||
if (workDir != null && !workDir.isEmpty()) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_WORKDIR, workDir);
|
||||
LogUtils.d(TAG, "工作目录:" + workDir);
|
||||
}
|
||||
|
||||
// 5. 设置执行模式(后台/终端会话)
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, isBackground);
|
||||
String runner = isBackground ? Runner.APP_SHELL.getName() : Runner.TERMINAL_SESSION.getName();
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RUNNER, runner);
|
||||
LogUtils.d(TAG, "执行模式:" + (isBackground ? "后台" : "终端会话") + ",Runner:" + runner);
|
||||
|
||||
// 6. 设置命令结果输出(可选,输出到文件)
|
||||
if (resultDir != null && !resultDir.isEmpty()) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_DIRECTORY, resultDir);
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_SINGLE_FILE, true);
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_BASENAME, "authcenter_cmd_result");
|
||||
LogUtils.d(TAG, "结果输出目录:" + resultDir);
|
||||
}
|
||||
|
||||
// 7. 允许替换参数中的逗号替代字符
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS, true);
|
||||
|
||||
// 8. 发送请求(区分 Android O 及以上的前台服务)
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
LogUtils.d(TAG, "Android O+ 启动前台服务发送命令");
|
||||
} else {
|
||||
context.startService(intent);
|
||||
LogUtils.d(TAG, "启动普通服务发送命令");
|
||||
}
|
||||
LogUtils.i(TAG, "命令发送成功:command=" + command);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "命令发送失败:" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化方法:执行 Termux 终端命令(默认工作目录,终端会话执行)
|
||||
* @param context 上下文
|
||||
* @param command 命令(如 "ls -l /home"、"echo 'hello termux'")
|
||||
* @return 是否成功发送
|
||||
*/
|
||||
public static boolean executeTerminalCommand(Context context, String command) {
|
||||
LogUtils.d(TAG, "调用 executeTerminalCommand,命令:" + command);
|
||||
if (command == null || command.isEmpty()) {
|
||||
LogUtils.e(TAG, "命令为空,执行失败");
|
||||
return false;
|
||||
}
|
||||
// 通过 bash 执行任意终端命令
|
||||
String[] args = {"-c", command};
|
||||
return executeCommand(
|
||||
context,
|
||||
"/data/data/com.termux/files/usr/bin/bash", // Termux 默认 bash 路径(正确)
|
||||
args,
|
||||
"/data/data/com.termux/files/home", // 默认工作目录
|
||||
false, // 终端会话执行(可见)
|
||||
null // 不输出到文件
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化方法:后台执行 Termux 命令(无输出文件)
|
||||
* @param context 上下文
|
||||
* @param command 命令路径
|
||||
* @param args 命令参数
|
||||
* @return 是否成功发送
|
||||
*/
|
||||
public static boolean executeBackgroundCommand(Context context, String command, String[] args) {
|
||||
LogUtils.d(TAG, "调用 executeBackgroundCommand,command=" + command);
|
||||
return executeCommand(
|
||||
context,
|
||||
command,
|
||||
args,
|
||||
null,
|
||||
true, // 后台执行
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Termux 是否安装(修复核心错误)
|
||||
* @param context 上下文
|
||||
* @return Termux 是否已安装
|
||||
*/
|
||||
public static boolean isTermuxInstalled(Context context) {
|
||||
LogUtils.d(TAG, "校验 Termux 是否安装,包名:" + TERMUX_PACKAGE_NAME);
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "校验失败:上下文为空");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 用正确的 Termux 包名查询(com.termux)
|
||||
context.getPackageManager().getPackageInfo(TERMUX_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
||||
LogUtils.d(TAG, "Termux 已安装");
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.w(TAG, "Termux 未安装:" + e.getMessage());
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "校验 Termux 安装状态异常:" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Termux 是否允许外部应用调用
|
||||
* @return 校验提示信息
|
||||
*/
|
||||
public static String checkTermuxExternalAppPermission() {
|
||||
String tip = "请确保 Termux 已开启「允许外部应用调用」权限:\n1. 打开 Termux 输入:termux-setup-storage\n2. 编辑配置文件:echo \"allow-external-apps = true\" > ~/.termux/termux.properties\n3. 重启 Termux 生效";
|
||||
LogUtils.d(TAG, "外部应用调用权限提示:" + tip);
|
||||
return tip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
package cc.winboll.studio.winboll.unittest;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.winboll.MainActivity;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.activities.BaseWinBoLLActivity;
|
||||
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/19 11:11:00
|
||||
* @LastEditTime 2026/01/21 17:45:00
|
||||
* @Describe Termux环境测试工具(跨包+sharedUserId模式)
|
||||
* 适配不同应用包名(当前包:cc.winboll.studio.winboll.beta / Termux包:com.termux)
|
||||
* 支持Termux目录读取、Gradle命令实时输出执行(唤起窗口),基于sharedUserId实现跨包权限适配
|
||||
*/
|
||||
public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 常量属性(置顶排列)
|
||||
public static final String TAG = "TermuxEnvTestActivity";
|
||||
private static final String TERMUX_HOME_PATH = "/sdcard/WinBoLLStudio";
|
||||
private static final String CMD_RESULT_FILE = TERMUX_HOME_PATH + "/.authcenter_cmd_result.tmp";
|
||||
|
||||
// 成员属性(常量后排列)
|
||||
private Toolbar mToolbar;
|
||||
private TextView tvMessage;
|
||||
private Handler mainHandler;
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "onCreate() 调用,初始化Activity");
|
||||
setContentView(R.layout.activity_termux_env_test);
|
||||
initView();
|
||||
initToolbar();
|
||||
initTermuxDirectory();
|
||||
LogUtils.d(TAG, "onCreate() 执行完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图组件
|
||||
*/
|
||||
private void initView() {
|
||||
LogUtils.d(TAG, "initView() 开始初始化视图");
|
||||
tvMessage = (TextView) findViewById(R.id.tv_message);
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// 初始化提示信息
|
||||
StringBuilder initMsg = new StringBuilder();
|
||||
initMsg.append("Termux 测试工具(跨包+sharedUserId模式)\n");
|
||||
initMsg.append("-------------------------\n");
|
||||
initMsg.append("当前应用包名:");
|
||||
initMsg.append(getPackageName());
|
||||
initMsg.append("\n");
|
||||
initMsg.append("Termux应用包名:com.termux\n");
|
||||
initMsg.append("sharedUserId:com.termux(需一致)\n");
|
||||
initMsg.append("支持功能:目录读取、Gradle命令实时输出(唤起Termux窗口)\n");
|
||||
initMsg.append("-------------------------\n");
|
||||
tvMessage.setText(initMsg.toString());
|
||||
|
||||
LogUtils.d(TAG, "initView() 初始化完成,tvMessage初始值:" + initMsg.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Toolbar组件
|
||||
*/
|
||||
private void initToolbar() {
|
||||
LogUtils.d(TAG, "initToolbar() 开始初始化Toolbar");
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
|
||||
if (mToolbar == null) {
|
||||
LogUtils.e(TAG, "initToolbar() 错误:未找到Toolbar组件");
|
||||
return;
|
||||
}
|
||||
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "initToolbar() 导航栏返回按钮点击");
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
|
||||
LogUtils.d(TAG, "initToolbar() 初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Termux目标目录(跨包+sharedUserId模式)
|
||||
*/
|
||||
private void initTermuxDirectory() {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() 开始初始化Termux目录,路径:" + TERMUX_HOME_PATH);
|
||||
File termuxDir = new File(TERMUX_HOME_PATH);
|
||||
|
||||
if (termuxDir.exists()) {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() Termux目录已存在,无需创建");
|
||||
return;
|
||||
}
|
||||
|
||||
tvMessage.append("正在创建Termux目标目录...\n");
|
||||
boolean createSuccess = termuxDir.mkdirs();
|
||||
if (createSuccess) {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() Termux目录创建成功:" + TERMUX_HOME_PATH);
|
||||
tvMessage.append("Termux目录创建成功:");
|
||||
tvMessage.append(TERMUX_HOME_PATH);
|
||||
tvMessage.append("\n");
|
||||
} else {
|
||||
LogUtils.e(TAG, "initTermuxDirectory() 错误:Termux目录创建失败");
|
||||
tvMessage.append("警告:Termux目录创建失败!\n");
|
||||
tvMessage.append("请检查:\n");
|
||||
tvMessage.append("1.AndroidManifest.xml中sharedUserId是否为com.termux\n");
|
||||
tvMessage.append("2.设备是否已root(部分机型需root才能跨包写私有目录)\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试读取Termux目录(按钮点击事件)
|
||||
*/
|
||||
public void onTestTermuxEnv(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxEnv() 按钮点击,开始读取Termux目录");
|
||||
tvMessage.append("\n【测试:读取Termux目录】\n");
|
||||
|
||||
String fileListStr = readTermuxHomeFileList();
|
||||
tvMessage.append(fileListStr);
|
||||
tvMessage.append("\n-------------------------\n");
|
||||
|
||||
LogUtils.d(TAG, "onTestTermuxEnv() 执行完成,读取结果长度:" + fileListStr.length() + "字符");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试执行Gradle命令(实时输出版,唤起Termux窗口)
|
||||
*/
|
||||
public void onTestTermuxCMD(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击,执行Gradle命令(实时输出)");
|
||||
tvMessage.append("\n【测试:执行Gradle命令(实时输出)】\n");
|
||||
|
||||
// 1. 校验Termux是否安装
|
||||
if (!TermuxCommandExecutor.isTermuxInstalled(this)) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:未安装Termux应用");
|
||||
tvMessage.append("错误:未安装Termux应用(包名:com.termux)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 定义核心路径(确保路径与Termux中一致)
|
||||
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
|
||||
String projectPath = TERMUX_HOME_PATH + "/Sources/WinBoLL"; // 项目目录
|
||||
|
||||
// 3. 构造命令(核心:用stdbuf禁用缓冲,实现实时输出)
|
||||
String targetCmd = "";
|
||||
// 步骤1:进入项目目录(不存在则创建)
|
||||
//targetCmd += "cd " + projectPath + " || (mkdir -p " + projectPath + " && cd " + projectPath + ") && ";
|
||||
targetCmd += "cd " + projectPath + " && ";
|
||||
// 步骤2:加载环境变量
|
||||
targetCmd += "source ~/.bashrc && ";
|
||||
// 步骤3:显式配置PATH
|
||||
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin:$PATH && ";
|
||||
// 步骤4:用stdbuf禁用stdout/stderr缓冲(关键!),执行Gradle命令
|
||||
// -o0:stdout无缓冲;-e0:stderr无缓冲;-i0:stdin无缓冲
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
|
||||
targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
|
||||
// 步骤5:执行成功提示
|
||||
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 项目目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
|
||||
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + targetCmd);
|
||||
|
||||
// 4. 执行命令(终端会话模式,唤起Termux窗口)
|
||||
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd);
|
||||
if (!cmdSuccess) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败");
|
||||
tvMessage.append("命令发送失败!\n");
|
||||
tvMessage.append("可能原因:\n");
|
||||
tvMessage.append("1.Termux未开启外部应用调用权限(执行termux-setup-storage后配置)\n");
|
||||
tvMessage.append("2.sharedUserId配置不一致\n");
|
||||
tvMessage.append("3.Termux未安装stdbuf(执行pkg install coreutils)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 应用内提示(说明实时输出特性)
|
||||
tvMessage.append("已唤起Termux窗口,执行说明:\n");
|
||||
tvMessage.append("1. 自动创建/进入项目目录:");
|
||||
tvMessage.append(projectPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("2. 禁用输出缓冲(stdbuf),实现Gradle实时输出\n");
|
||||
tvMessage.append("3. 筛选assemble相关任务(grep assemble)\n");
|
||||
tvMessage.append("⚙️ Gradle路径:");
|
||||
tvMessage.append(gradleFullPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("💡 若未实时输出,请在Termux中执行:pkg install coreutils(安装stdbuf)\n");
|
||||
tvMessage.append("-------------------------\n");
|
||||
}
|
||||
|
||||
public void onOpenTermuxBash(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击,执行Gradle命令(实时输出)");
|
||||
tvMessage.append("\n【测试:执行Gradle命令(实时输出)】\n");
|
||||
|
||||
// 1. 校验Termux是否安装
|
||||
if (!TermuxCommandExecutor.isTermuxInstalled(this)) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:未安装Termux应用");
|
||||
tvMessage.append("错误:未安装Termux应用(包名:com.termux)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 定义核心路径(确保路径与Termux中一致)
|
||||
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
|
||||
String projectPath = TERMUX_HOME_PATH + "/Sources/WinBoLL"; // 项目目录
|
||||
|
||||
// 3. 构造命令(核心:用stdbuf禁用缓冲,实现实时输出)
|
||||
String targetCmd = "";
|
||||
// 步骤1:进入项目目录(不存在则创建)
|
||||
targetCmd += "cd " + projectPath + " && ";
|
||||
// 步骤2:加载环境变量
|
||||
targetCmd += "source ~/.bashrc && ";
|
||||
// 步骤3:显式配置PATH
|
||||
targetCmd += "export PATH=/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin:$PATH && ";
|
||||
// 步骤4:用stdbuf禁用stdout/stderr缓冲(关键!),执行Gradle命令
|
||||
// -o0:stdout无缓冲;-e0:stderr无缓冲;-i0:stdin无缓冲
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " task --all | grep assemble && ";
|
||||
//targetCmd += "stdbuf -o0 -e0 -i0 " + gradleFullPath + " -Pandroid.aapt2FromMavenOverride=/data/data/com.termux/files/home/android-sdk/build-tools/34.0.4/aapt2 assembleBetaDebug && ";
|
||||
targetCmd += "stdbuf -o0 -e0 -i0 bash && ";
|
||||
// 步骤5:执行成功提示
|
||||
targetCmd += "echo '\n✅ 命令执行完成!' && echo '\n📌 项目目录:" + projectPath + "' && read -p '按回车键关闭终端...'";
|
||||
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + targetCmd);
|
||||
|
||||
// 4. 执行命令(终端会话模式,唤起Termux窗口)
|
||||
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd);
|
||||
if (!cmdSuccess) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败");
|
||||
tvMessage.append("命令发送失败!\n");
|
||||
tvMessage.append("可能原因:\n");
|
||||
tvMessage.append("1.Termux未开启外部应用调用权限(执行termux-setup-storage后配置)\n");
|
||||
tvMessage.append("2.sharedUserId配置不一致\n");
|
||||
tvMessage.append("3.Termux未安装stdbuf(执行pkg install coreutils)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 应用内提示(说明实时输出特性)
|
||||
tvMessage.append("已唤起Termux窗口,执行说明:\n");
|
||||
tvMessage.append("1. 自动创建/进入项目目录:");
|
||||
tvMessage.append(projectPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("2. 禁用输出缓冲(stdbuf),实现Gradle实时输出\n");
|
||||
tvMessage.append("3. 筛选assemble相关任务(grep assemble)\n");
|
||||
tvMessage.append("⚙️ Gradle路径:");
|
||||
tvMessage.append(gradleFullPath);
|
||||
tvMessage.append("\n");
|
||||
tvMessage.append("💡 若未实时输出,请在Termux中执行:pkg install coreutils(安装stdbuf)\n");
|
||||
tvMessage.append("-------------------------\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨包读取Termux命令结果文件(保留原功能,兼容其他场景)
|
||||
*/
|
||||
private String readCmdResultDirectly() {
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() 开始读取命令结果,文件路径:" + CMD_RESULT_FILE);
|
||||
File resultFile = new File(CMD_RESULT_FILE);
|
||||
|
||||
// 校验文件存在性
|
||||
if (!resultFile.exists()) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:结果文件不存在");
|
||||
return "错误:结果文件不存在\n可能原因:\n1.命令执行失败\n2.延迟时间不足\n3.跨包写权限被拒绝(需root)";
|
||||
}
|
||||
|
||||
// 校验文件可读性
|
||||
if (!resultFile.canRead()) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:无结果文件读取权限");
|
||||
return "错误:无结果文件读取权限(sharedUserId配置错误或未root)";
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
StringBuilder result = new StringBuilder();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(new FileInputStream(resultFile), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
result.append(line);
|
||||
result.append("\n");
|
||||
}
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() 文件读取完成,结果长度:" + result.length() + "字符");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:文件读取异常", e);
|
||||
return "错误:读取文件异常 → " + e.getMessage();
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() BufferedReader资源已关闭");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:BufferedReader关闭异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String resultStr = result.toString().trim();
|
||||
return resultStr.isEmpty() ? "命令执行成功,但无输出" : resultStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除命令结果临时文件(保留原功能)
|
||||
*/
|
||||
private void deleteCmdResultFile() {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 开始删除临时文件,路径:" + CMD_RESULT_FILE);
|
||||
File resultFile = new File(CMD_RESULT_FILE);
|
||||
|
||||
if (!resultFile.exists()) {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件不存在,无需删除");
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultFile.canWrite()) {
|
||||
boolean deleteSuccess = resultFile.delete();
|
||||
if (deleteSuccess) {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件删除成功");
|
||||
} else {
|
||||
LogUtils.w(TAG, "deleteCmdResultFile() 警告:临时文件删除失败");
|
||||
tvMessage.append("警告:临时结果文件删除失败\n");
|
||||
}
|
||||
} else {
|
||||
LogUtils.w(TAG, "deleteCmdResultFile() 警告:无临时文件删除权限");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨包读取Termux目录文件列表(保留原功能)
|
||||
*/
|
||||
private String readTermuxHomeFileList() {
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 开始读取目录,路径:" + TERMUX_HOME_PATH);
|
||||
File termuxHomeDir = new File(TERMUX_HOME_PATH);
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
// 基础校验
|
||||
if (!termuxHomeDir.exists()) {
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:目录不存在");
|
||||
return "错误:Termux目录不存在 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
if (!termuxHomeDir.isDirectory()) {
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:指定路径不是目录");
|
||||
return "错误:指定路径不是目录 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
if (!termuxHomeDir.canRead()) {
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:无目录读取权限");
|
||||
return "错误:无目录读取权限(需满足:1.sharedUserId=com.termux 2.设备root)";
|
||||
}
|
||||
|
||||
// 获取文件列表
|
||||
File[] files = termuxHomeDir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 目录为空");
|
||||
return "Termux目录为空 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
|
||||
// 拼接结果字符串
|
||||
result.append("Termux主目录:");
|
||||
result.append(TERMUX_HOME_PATH);
|
||||
result.append("\n");
|
||||
result.append("文件/子目录总数:");
|
||||
result.append(files.length);
|
||||
result.append("\n");
|
||||
result.append("目录权限:");
|
||||
result.append("读:");
|
||||
result.append(termuxHomeDir.canRead());
|
||||
result.append(" | 写:");
|
||||
result.append(termuxHomeDir.canWrite());
|
||||
result.append("\n");
|
||||
result.append("-------------------------\n");
|
||||
|
||||
for (File file : files) {
|
||||
String fileType = file.isDirectory() ? "[目录]" : "[文件]";
|
||||
String fileName = file.getName();
|
||||
String filePath = file.getAbsolutePath();
|
||||
String fileSize = file.isFile() ? " | 大小:" + formatFileSize(file.length()) : "";
|
||||
String filePerm = " | 权限:r:" + file.canRead() + "/w:" + file.canWrite();
|
||||
|
||||
result.append(fileType);
|
||||
result.append(" ");
|
||||
result.append(fileName);
|
||||
result.append(fileSize);
|
||||
result.append(filePerm);
|
||||
result.append(" → ");
|
||||
result.append(filePath);
|
||||
result.append("\n");
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 读取完成,结果长度:" + result.length() + "字符");
|
||||
return result.toString().trim();
|
||||
}
|
||||
/**
|
||||
* 格式化文件大小(B→KB→MB)
|
||||
*/
|
||||
private String formatFileSize(long length) {
|
||||
LogUtils.d(TAG, "formatFileSize() 调用,文件长度:" + length + "B");
|
||||
String sizeStr;
|
||||
if (length < 1024) {
|
||||
sizeStr = length + "B";
|
||||
} else if (length < 1024 * 1024) {
|
||||
sizeStr = String.format("%.1fKB", length / 1024.0);
|
||||
} else {
|
||||
sizeStr = String.format("%.1fMB", length / (1024.0 * 1024));
|
||||
}
|
||||
LogUtils.d(TAG, "formatFileSize() 格式化结果:" + sizeStr);
|
||||
return sizeStr;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy() 调用,清理资源");
|
||||
deleteCmdResultFile();
|
||||
if (mainHandler != null) {
|
||||
mainHandler.removeCallbacksAndMessages(null);
|
||||
LogUtils.d(TAG, "onDestroy() Handler资源已释放");
|
||||
}
|
||||
LogUtils.d(TAG, "onDestroy() 执行完成");
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import android.os.Message;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.activities.WinBoLLActivity;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -18,7 +18,7 @@ import cc.winboll.studio.winboll.activities.WinBoLLActivity;
|
||||
* @Describe 企业微信SDK接口测试(基础调试版)
|
||||
* 包含:SDK初始化、基础接口调用、日志输出、主线程回调处理
|
||||
*/
|
||||
public class TestWeWorkSpecSDK extends WinBoLLActivity implements IWinBoLLActivity, View.OnClickListener {
|
||||
public class TestWeWorkSpecSDK extends AppCompatActivity implements IWinBoLLActivity, View.OnClickListener {
|
||||
|
||||
public static final String TAG = "TestWeWorkSpecSDK";
|
||||
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
package com.tencent.wework;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;;
|
||||
|
||||
/**
|
||||
* @warning: 1. 不要修改成员变量名,native方法内有反射调用
|
||||
* 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内
|
||||
* 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件
|
||||
*/
|
||||
public final class SpecCallbackSDK {
|
||||
|
||||
/**
|
||||
* @description 调用本地方法后实例化的对象指针
|
||||
*/
|
||||
private long specCallbackSDKptr = 0;
|
||||
|
||||
public long GetPtr() { return specCallbackSDKptr; }
|
||||
|
||||
/**
|
||||
* @description: 回包的headers
|
||||
*/
|
||||
private Map<String, String> responseHeaders;
|
||||
|
||||
public Map<String, String> GetResponseHeaders() { return responseHeaders; }
|
||||
|
||||
/**
|
||||
* @description: 回包的加密后的body
|
||||
*/
|
||||
private String responseBody;
|
||||
|
||||
public String GetResponseBody() { return responseBody; }
|
||||
|
||||
/**
|
||||
* @description: 每个请求构造一个SpecCallbackSDK示例,
|
||||
* SpecCallbackSDK仅持有headers和body的引用,
|
||||
* 因此需保证headers和body的生存期比SpecCallbackSDK长
|
||||
* @param method: 请求方法GET/POST
|
||||
* @param headers: 请求header
|
||||
* @param body: 请求body
|
||||
* @example:
|
||||
* SpecCallbackSDK sdk = new SpecCallbackSDK(method, headers, body);
|
||||
* if (sdk.IsOk()) {
|
||||
* String corpid = sdk.GetCorpId();
|
||||
* String agentid = sdk.GetAgentId();
|
||||
* String call_type = sdk.GetCallType();
|
||||
* String data = sdk.GetData();
|
||||
* //do something...
|
||||
* }
|
||||
* String response = ...;
|
||||
* sdk.BuildResponseHeaderBody(response);
|
||||
* Map<String, String> responseHeaders = sdk.GetResponseHeaders();
|
||||
* String body = sdk.GetResponseBody();
|
||||
* //do response
|
||||
*
|
||||
* @return errorcode 示例如下:
|
||||
* -920001: 未设置请求方法
|
||||
* -920002: 未设置请求header
|
||||
* -920003: 未设置请求body
|
||||
* */
|
||||
public SpecCallbackSDK(String method, Map<String, String> headers, String body) {
|
||||
try {
|
||||
specCallbackSDKptr = NewCallbackSDK(method, headers, body);
|
||||
} catch (Exception e) {
|
||||
SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private native long NewCallbackSDK(String method, Map<String, String> headers, String body);
|
||||
|
||||
/**
|
||||
* @usage 在Java对象的内存回收前析构C++对象
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
DeleteCPPInstance(specCallbackSDKptr);
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
private native void DeleteCPPInstance(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 判断构造函数中传入的请求是否解析成功
|
||||
* @return: 成功与否
|
||||
* */
|
||||
public boolean IsOk() {
|
||||
return IsOk(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native boolean IsOk(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 获取请求的企业
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: corpid
|
||||
* */
|
||||
public String GetCorpId() {
|
||||
return GetCorpId(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native String GetCorpId(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 获取请求的应用
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: agentid
|
||||
* */
|
||||
public long GetAgentId() {
|
||||
return GetAgentId(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native long GetAgentId(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 获取请求的类型
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: 1 - 来自[应用调用专区]的请求
|
||||
* 2 - 来自企业微信的回调事件
|
||||
* */
|
||||
public long GetCallType() {
|
||||
return GetCallType(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native long GetCallType(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 获取请求数据
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: 请求数据,根据call_type可能是:
|
||||
* - 企业微信回调事件
|
||||
* - [应用调用专区]接口中的request_data
|
||||
* */
|
||||
public String GetData() {
|
||||
return GetData(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native String GetData(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 是否异步请求
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: 是否异步请求
|
||||
* */
|
||||
public boolean GetIsAsync() {
|
||||
return GetIsAsync(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native boolean GetIsAsync(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 获取请求的job_info,
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: job_info,无需理解内容,
|
||||
* 在同一个请求上下文中使用SpecSDK的时候传入
|
||||
* */
|
||||
public String GetJobInfo() {
|
||||
return GetJobInfo(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native String GetJobInfo(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 获取请求的ability_id,[应用调用专区]接口时指定
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: ability_id
|
||||
* */
|
||||
public String GetAbilityId() {
|
||||
return GetAbilityId(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native String GetAbilityId(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 获取请求的notify_id,用于[应用同步调用专区程序]接口
|
||||
* @require: 仅当IsOk() == true可调用
|
||||
* @return: notify_id
|
||||
* */
|
||||
public String GetNotifyId() {
|
||||
return GetNotifyId(specCallbackSDKptr);
|
||||
}
|
||||
|
||||
private native String GetNotifyId(long specCallbackSDKptr);
|
||||
|
||||
/**
|
||||
* @description: 对返回包计算签名&加密
|
||||
* @param response: 待加密的回包明文.如果IsOk()==false,传入空串即可
|
||||
* @note 本接口的执行问题可查看日志
|
||||
* */
|
||||
public void BuildResponseHeaderBody(String response) {
|
||||
try {
|
||||
responseHeaders = new HashMap<String, String>();
|
||||
responseBody = "";
|
||||
BuildResponseHeaderBody(specCallbackSDKptr, response);
|
||||
} catch (Exception e) {
|
||||
SpecUtil.WWSpecLogError("SpecCallbackSDK exception caught", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private native void BuildResponseHeaderBody(long specCallbackSDKptr, String response);
|
||||
|
||||
// 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询
|
||||
static {
|
||||
try {
|
||||
Class.forName("com.tencent.wework.SpecUtil");
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package com.tencent.wework;
|
||||
|
||||
/**
|
||||
* @warning: 1. 不要修改成员变量名,native方法内有反射调用
|
||||
* 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内
|
||||
* 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件
|
||||
*/
|
||||
public final class SpecSDK {
|
||||
|
||||
/**
|
||||
* @description 调用本地方法后实例化的对象指针
|
||||
*/
|
||||
private long specSDKptr = 0;
|
||||
|
||||
/**
|
||||
* @usage invoke的请求
|
||||
* @example "{\"limit\":1}
|
||||
*/
|
||||
private String request;
|
||||
|
||||
public void SetRequest(String request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @usage 访问上一次invoke的结果
|
||||
*/
|
||||
private String response;
|
||||
|
||||
public String GetResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param corpid: 企业corpid,必选参数
|
||||
* @param agentid: 应用id,必选参数
|
||||
* @param ability_id: 能力ID,可选参数
|
||||
* @param job_info: job_info,可选参数
|
||||
* */
|
||||
public SpecSDK(String corpId, long agentId) {
|
||||
specSDKptr = NewSDK1(corpId, agentId);
|
||||
}
|
||||
|
||||
private native long NewSDK1(String corpId, long agentId);
|
||||
|
||||
public SpecSDK(String corpId, long agentId, String abilityId) {
|
||||
specSDKptr = NewSDK2(corpId, agentId, abilityId);
|
||||
}
|
||||
|
||||
private native long NewSDK2(String corpId, long agentId, String abilityId);
|
||||
|
||||
public SpecSDK(String corpId, long agentId, String abilityId, String jobInfo) {
|
||||
specSDKptr = NewSDK3(corpId, agentId, abilityId, jobInfo);
|
||||
}
|
||||
|
||||
private native long NewSDK3(String corpId, long agentId, String abilityId, String jobInfo);
|
||||
|
||||
/**
|
||||
* @description 使用callback的请求来初始化
|
||||
* @param callback_sdk: 要求IsOk()==true
|
||||
* @return C++内部指针,创建失败时指针仍为0,并输出错误日志
|
||||
* */
|
||||
public SpecSDK(SpecCallbackSDK callbackSDK) {
|
||||
specSDKptr = NewSDK4(callbackSDK.GetPtr());
|
||||
}
|
||||
|
||||
private native long NewSDK4(long callbackSDK);
|
||||
|
||||
/**
|
||||
* @usage 在Java对象的内存回收前析构C++对象
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
DeleteCPPInstance(specSDKptr);
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
private native void DeleteCPPInstance(long specSDKptr);
|
||||
|
||||
/**
|
||||
* @description 用于在专区内调用企业微信接口
|
||||
* @param api_name 接口名
|
||||
* @param request json格式的请求数据
|
||||
* @param response json格式的返回数据
|
||||
* @return errorcode 参考如下:
|
||||
* 0: 成功
|
||||
* -910001: SDK没有初始化
|
||||
* -910002: 没有设置请求体
|
||||
* -910003: 没有设置请求的API
|
||||
* -910004: 在SDK成员内找不到成员"response",注意lib内有反射机制,不要修改成员变量名
|
||||
* -910005: 使用未初始化的callback初始化SDK
|
||||
* -910006: invoke调用失败,应检查日志查看具体原因
|
||||
* -910007: 响应体为空
|
||||
* @note 当返回0时,表示没有网络或请求协议层面或调用方法的失败,
|
||||
* 调用方需继续检查response中的errcode字段确保业务层面的成功
|
||||
*
|
||||
* @usage 当前版本sdk支持的接口列表,每个接口的具体协议请查看企业微信文档:
|
||||
* https://developer.work.weixin.qq.com/document/path/91201
|
||||
*
|
||||
* +--------------------------------+--------------------------------+
|
||||
* |接口名 |描述 |
|
||||
* |--------------------------------|--------------------------------|
|
||||
* |program_async_job_call_back |上报异步任务结果 |
|
||||
* |sync_msg |获取会话记录 |
|
||||
* |get_group_chat |获取内部群信息 |
|
||||
* |get_agree_status_single |获取单聊会话同意情况 |
|
||||
* |get_agree_status_room |获取群聊会话同意情况 |
|
||||
* |set_hide_sensitiveinfo_config |设置成员会话组件敏感信息隐藏配置|
|
||||
* |get_hide_sensitiveinfo_config |获取成员会话组件敏感信息隐藏配置|
|
||||
* |search_chat |会话名称搜索 |
|
||||
* |search_msg |会话消息搜索 |
|
||||
* |create_rule |新增关键词规则 |
|
||||
* |get_rule_list |获取关键词列表 |
|
||||
* |get_rule_detail |获取关键词规则详情 |
|
||||
* |update_rule |修改关键词规则 |
|
||||
* |delete_rule |删除关键词规则 |
|
||||
* |get_hit_msg_list |获取命中关键词规则的会话记录 |
|
||||
* |create_sentiment_task |创建情感分析任务 |
|
||||
* |get_sentiment_result |获取情感分析结果 |
|
||||
* |create_summary_task |创建摘要提取任务 |
|
||||
* |get_summary_result |获取摘要提取结果 |
|
||||
* |create_customer_tag_task |创建标签匹配任务 |
|
||||
* |get_customer_tag_result |获取标签任务结果 |
|
||||
* |create_recommend_dialog_task |创建话术推荐任务 |
|
||||
* |get_recommend_dialog_result |获取话术推荐结果 |
|
||||
* |create_private_task |创建自定义模型任务 |
|
||||
* |get_private_task_result |获取自定义模型结果 |
|
||||
* |(废弃)document_list |获取知识集列表 |
|
||||
* |create_spam_task |会话反垃圾创建分析任务 |
|
||||
* |get_spam_result |会话反垃圾获取任务结果 |
|
||||
* |create_chatdata_export_job |创建会话内容导出任务 |
|
||||
* |get_chatdata_export_job_status |获取会话内容导出任务结果 |
|
||||
* |spec_notify_app |专区通知应用 |
|
||||
* |create_program_task |创建自定义程序任务 |
|
||||
* |get_program_task_result |获取自定义程序结果 |
|
||||
* |knowledge_base_list |获取企业授权给应用的知识集列表 |
|
||||
* |knowledge_base_create |创建知识集 |
|
||||
* |knowledge_base_detail |获取知识集详情 |
|
||||
* |knowledge_base_add_doc |添加知识集內容 |
|
||||
* |knowledge_base_remove_doc |删除知识集內容 |
|
||||
* |knowledge_base_modify_name |修改知识集名称 |
|
||||
* |knowledge_base_delete |删除知识集 |
|
||||
* |search_contact_or_customer |员工或者客户名称搜索 |
|
||||
* |create_ww_model_task |创建企微通用模型任务 |
|
||||
* |get_ww_model_result |获取企微通用模型结果 |
|
||||
* |get_msg_list_by_page_id |page_id获取消息列表 |
|
||||
* +-----------------------------------------------------------------+
|
||||
* */
|
||||
public int Invoke(String apiName) {
|
||||
return Invoke(specSDKptr, apiName, request);
|
||||
}
|
||||
|
||||
private native int Invoke(long sdk, String apiName, String request);
|
||||
|
||||
// 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询
|
||||
static {
|
||||
try {
|
||||
Class.forName("com.tencent.wework.SpecUtil");
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
package com.tencent.wework;
|
||||
|
||||
//import java.lang.management.ManagementFactory;
|
||||
//import java.lang.management.RuntimeMXBean;
|
||||
|
||||
/**
|
||||
* @warning: 1. 不要修改成员变量名,native方法内有反射调用
|
||||
* 2. 调用本地方法需保持包结构,本工具需放在包com.tencent.wework内
|
||||
* 3. 不允许继承,类名和函数名均不可修改,会影响本地方法的引用,详见:javah生成本地方法头文件
|
||||
* 4. 使用其他工具打印的日志将无法被查询,如需使用SLF4j风格的日志或性能更好的日志框架,
|
||||
* 请自行封装SpecUtil.SpecLog或SpecUtil.SpecLogNative方法
|
||||
*
|
||||
* @usage: 1. 获取SDK的版本号
|
||||
* 2. 打印三个级别的日志
|
||||
* 3. 开启调试模式
|
||||
*/
|
||||
public final class SpecUtil {
|
||||
|
||||
/**
|
||||
* @description SDK版本号
|
||||
* @usage 可用于校对不同SDK版本,或后续针对不同的SDK版本添加业务逻辑
|
||||
*/
|
||||
private static final String SDK_VERSION = "1.4.0";
|
||||
|
||||
public static String GetSDKVersion() {
|
||||
return SDK_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 正确的包名,SDK必须存放在"com.tencent.wework"下,否则会影响本地方法的调用
|
||||
*/
|
||||
private static final String EXPECTED_PACKAGE_NAME = "com.tencent.wework";
|
||||
|
||||
public static String GetExpectedPackageName() {
|
||||
return EXPECTED_PACKAGE_NAME;
|
||||
}
|
||||
|
||||
private static final String LINE_SEPERATOR = System.getProperty("line.separator");
|
||||
|
||||
public static void WWSpecLogInfo(String... args) {
|
||||
SpecLog('I', args);
|
||||
}
|
||||
|
||||
public static void WWSpecLogError(String... args) {
|
||||
SpecLog('E', args);
|
||||
}
|
||||
|
||||
public static void WWSpecLogDebug(String... args) {
|
||||
SpecLog('D', args);
|
||||
}
|
||||
|
||||
public static void WWSpecLogInfoWithReqId(String reqId, String... args) {
|
||||
SpecLogWithReqId(reqId, 'I', args);
|
||||
}
|
||||
|
||||
public static void WWSpecLogErrorWithReqId(String reqId, String... args) {
|
||||
SpecLogWithReqId(reqId, 'E', args);
|
||||
}
|
||||
|
||||
public static void WWSpecLogDebugWithReqId(String reqId, String... args) {
|
||||
SpecLogWithReqId(reqId, 'D', args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @usage 打印标准日志
|
||||
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||
* @param args 自定义参数
|
||||
*/
|
||||
public static void SpecLog(char logLevel, String... args) {
|
||||
StackTraceElement element = Thread.currentThread().getStackTrace()[3];
|
||||
SpecLogNative(
|
||||
logLevel,
|
||||
element.getFileName(),
|
||||
element.getLineNumber(),
|
||||
String.join(",", args).replace(LINE_SEPERATOR, " ")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @usage 打印标准日志
|
||||
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||
* @param reqid 请求id
|
||||
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||
* @param args 自定义参数
|
||||
*/
|
||||
public static void SpecLogWithReqId(String reqId, char logLevel, String... args) {
|
||||
StackTraceElement element = Thread.currentThread().getStackTrace()[3];
|
||||
SpecLogNativeWithReqId(
|
||||
reqId,
|
||||
logLevel,
|
||||
element.getFileName(),
|
||||
element.getLineNumber(),
|
||||
String.join(",", args).replace(LINE_SEPERATOR, " ")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @usage 打印标准日志
|
||||
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||
* 如需SLF4J风格的接口或对日志性能有进一步需求,开发者可以自行封装该函数
|
||||
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||
* @param fileName 文件名(类名)
|
||||
* @param lineNumber 行号
|
||||
* @param argsString 自定义参数
|
||||
*/
|
||||
public static native void SpecLogNative(char logLevel, String fileName, int lineNumber, String argsString);
|
||||
|
||||
/**
|
||||
* @usage 打印标准日志
|
||||
* @note 只有使用SpecLog和SpecLogNative函数打印的日志能被调试平台查询,其他框架的日志仅能本地查看
|
||||
* 如需SLF4J风格的接口或对日志性能有进一步需求,开发者可以自行封装该函数
|
||||
* @param reqid 请求id
|
||||
* @param logLevel 日志级别,使用char传递,目前支持I——INFO、E——ERROR、D——DEBUG
|
||||
* @param fileName 文件名(类名)
|
||||
* @param lineNumber 行号
|
||||
* @param argsString 自定义参数
|
||||
*/
|
||||
public static native void SpecLogNativeWithReqId(String reqId, char logLevel, String fileName, int lineNumber, String argsString);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @usage 开启调试模式,进程级别开关
|
||||
* @param debugToken 调试凭证,在管理端获取
|
||||
* @param accessToken 应用access token
|
||||
* @return 是否开启成功
|
||||
*/
|
||||
public static boolean SpecOpenDebugMode(String debugToken, String accessToken) {
|
||||
return SpecOpenDebugModeNative(debugToken, accessToken);
|
||||
}
|
||||
|
||||
private static native boolean SpecOpenDebugModeNative(String debugToken, String accessToken);
|
||||
|
||||
/**
|
||||
* @usage 生成notify id。用户可调用本接口生成notify id,也可完全自定义生成
|
||||
* @return 新的notify id,支持纳秒级隔离,内部异常时会输出日志并返回空串
|
||||
* @note 1. 用户可先生成notify id,将其与回调数据关联存储后,再使用该notify id通知应用,
|
||||
* 从而保证回调数据被请求时已存储完毕
|
||||
*/
|
||||
public static String GenerateNotifyId() {
|
||||
return GenerateNotifyIdNative();
|
||||
}
|
||||
|
||||
private static native String GenerateNotifyIdNative();
|
||||
|
||||
static {
|
||||
// 检查包名
|
||||
String packageName = SpecUtil.class.getPackage().getName();
|
||||
if (!EXPECTED_PACKAGE_NAME.equals(packageName)) {
|
||||
// 静态代码块内还无法调用native日志函数,这里的日志在管理系统无法查询
|
||||
System.out.println("SpecUtil class must be in package com.tencent.wework");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// 加载so库
|
||||
try {
|
||||
System.loadLibrary("WeWorkSpecSDK");
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
System.out.println("libWeWorkSpecSDK.so not found in java.library.path");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
} catch (Exception e) {
|
||||
System.out.println("unexpected exception: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
SpecUtil.WWSpecLogInfo("SDK init done", "packageName=" + packageName, "SDK_VERSION=" + SDK_VERSION);
|
||||
}
|
||||
}
|
||||
20
winboll/src/main/res/layout/activity_about.xml
Normal file
20
winboll/src/main/res/layout/activity_about.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<cc.winboll.studio.libaes.views.ASupportToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<cc.winboll.studio.libappbase.views.AboutView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/aboutview"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -6,14 +6,6 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<cc.winboll.studio.winboll.CustomToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:toolbarTitle="@string/app_name"
|
||||
app:toolbarTitleColor="@color/colorAccent"
|
||||
app:toolbarBackgroundColor="@color/colorPrimary"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.widget.Toolbar
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.widget.Toolbar
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
55
winboll/src/main/res/layout/activity_termux_env_test.xml
Normal file
55
winboll/src/main/res/layout/activity_termux_env_test.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<cc.winboll.studio.libaes.views.ASupportToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="OpenTermuxBash"
|
||||
android:onClick="onOpenTermuxBash"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestTermuxCMD"
|
||||
android:onClick="onTestTermuxCMD"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestTermuxEnv"
|
||||
android:onClick="onTestTermuxEnv"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Text"
|
||||
android:id="@+id/tv_message"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
android:id="@+id/item_settings"
|
||||
android:title="Settings"/>
|
||||
<item
|
||||
android:id="@+id/item_wxpayactivity"
|
||||
android:title="WXPayActivity"/>
|
||||
android:id="@+id/item_about"
|
||||
android:title="About"/>
|
||||
</menu>
|
||||
|
||||
12
winboll/src/main/res/menu/toolbar_test.xml
Normal file
12
winboll/src/main/res/menu/toolbar_test.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:title="UnitTest">
|
||||
<menu >
|
||||
<item
|
||||
android:id="@+id/item_termux_env_test"
|
||||
android:title="TermuxEnvTestActivity"/>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
@@ -1,5 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">WinBoLL</string>
|
||||
<string name="app_description">WinBoLL 网站浏览器。</string>
|
||||
<string name="app_name_cn1">筋斗云</string>
|
||||
<string name="app_name_cn2">金抖云</string>
|
||||
<string name="switchto_en1">WinBoLL</string>
|
||||
|
||||
@@ -11,5 +11,11 @@
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<!-- 允许特定域名的明文请求(仅本地127.0.0.1) -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user