Compare commits
43 Commits
appbase-v1
...
appbase-v1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5099d00050 | ||
![]() |
515d14e896 | ||
![]() |
f630e27ed8 | ||
![]() |
cd7ed01216 | ||
![]() |
bb24bbfbd1 | ||
![]() |
2ba2f88510 | ||
![]() |
db3a3644a8 | ||
![]() |
556bfa7024 | ||
![]() |
4842a1ec30 | ||
![]() |
b0388a2972 | ||
![]() |
bd5a1f18ce | ||
![]() |
99798b4816 | ||
![]() |
f93b6047a8 | ||
![]() |
daa3f858a0 | ||
![]() |
3fded32426 | ||
![]() |
8f85006040 | ||
![]() |
e28b0bd75e | ||
![]() |
af1d6d3439 | ||
![]() |
470d1ffa1f | ||
![]() |
49ae869df1 | ||
![]() |
77e98bafe4 | ||
![]() |
ff14d0c0c3 | ||
![]() |
950be3a182 | ||
![]() |
1f20fca9be | ||
![]() |
8d29d11078 | ||
![]() |
7534881f50 | ||
![]() |
8d1872a893 | ||
![]() |
9a0ee889ba | ||
![]() |
c40066ca4d | ||
![]() |
5348d1ef6d | ||
![]() |
063c997bbb | ||
![]() |
1376ca7ebb | ||
![]() |
92e271b569 | ||
![]() |
a5083cc52f | ||
![]() |
6cce9c4d3f | ||
![]() |
df18c34976 | ||
![]() |
22ca83b5b7 | ||
![]() |
98233ce148 | ||
![]() |
b61c63c426 | ||
![]() |
f02dc215ca | ||
![]() |
1c27d0ccdc | ||
![]() |
803745d12e | ||
![]() |
a66be9cd37 |
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Jun 01 08:26:46 GMT 2025
|
||||
stageCount=1
|
||||
#Tue Jun 03 19:17:05 HKT 2025
|
||||
stageCount=2
|
||||
libraryProject=libaes
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.0
|
||||
buildCount=2
|
||||
baseBetaVersion=15.8.1
|
||||
publishVersion=15.8.1
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.2
|
||||
|
@@ -83,7 +83,7 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(szBranchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=AES");
|
||||
appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=2&fromuid=1");
|
||||
appInfo.setAppAPKName("AES");
|
||||
appInfo.setAppAPKFolderName("AES");
|
||||
//appInfo.setIsAddDebugTools(false);
|
||||
|
@@ -67,6 +67,6 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/com.android.support/recyclerview-v7
|
||||
api 'com.android.support:recyclerview-v7:28.0.0'
|
||||
|
||||
api 'cc.winboll.studio:libapputils:15.8.1'
|
||||
api 'cc.winboll.studio:libappbase:15.8.1'
|
||||
api 'cc.winboll.studio:libapputils:15.8.2'
|
||||
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon May 19 21:45:28 GMT 2025
|
||||
#Sun Jun 01 08:02:46 GMT 2025
|
||||
stageCount=0
|
||||
libraryProject=
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.0
|
||||
buildCount=25
|
||||
buildCount=27
|
||||
baseBetaVersion=15.0.1
|
||||
|
@@ -68,6 +68,6 @@ dependencies {
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
|
||||
api 'cc.winboll.studio:libaes:15.8.0'
|
||||
api 'cc.winboll.studio:libapputils:15.8.1'
|
||||
api 'cc.winboll.studio:libappbase:15.8.1'
|
||||
api 'cc.winboll.studio:libapputils:15.8.2'
|
||||
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon May 19 21:43:40 GMT 2025
|
||||
#Sun Jun 01 08:03:56 GMT 2025
|
||||
stageCount=0
|
||||
libraryProject=
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.0
|
||||
buildCount=22
|
||||
buildCount=24
|
||||
baseBetaVersion=15.0.1
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Jun 03 13:40:01 HKT 2025
|
||||
stageCount=5
|
||||
#Wed Jun 04 15:04:39 HKT 2025
|
||||
stageCount=8
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.4
|
||||
publishVersion=15.8.7
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.5
|
||||
baseBetaVersion=15.8.8
|
||||
|
@@ -9,7 +9,8 @@
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/MyAPPBaseTheme"
|
||||
android:resizeableActivity="true"
|
||||
android:process=":App">
|
||||
android:process=":App"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@@ -39,7 +40,8 @@
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
||||
|
||||
<activity android:name=".activities.New2Activity"
|
||||
<activity
|
||||
android:name=".activities.New2Activity"
|
||||
android:label="New2Activity"
|
||||
android:exported="true"
|
||||
android:resizeableActivity="true"
|
||||
@@ -74,7 +76,8 @@
|
||||
|
||||
<service android:name=".services.AssistantService"/>
|
||||
|
||||
<receiver android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
|
||||
<receiver
|
||||
android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
@@ -105,7 +108,8 @@
|
||||
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".receivers.APPNewsWidgetClickListener"
|
||||
<receiver
|
||||
android:name=".receivers.APPNewsWidgetClickListener"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
@@ -122,7 +126,6 @@
|
||||
android:name="android.max_aspect"
|
||||
android:value="4.0"/>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@@ -62,6 +62,11 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(item.getItemId() == R.id.item_yun) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.YunActivity.class);
|
||||
} else if(item.getItemId() == R.id.item_logon) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.LogonActivity.class);
|
||||
}
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@@ -5,6 +5,14 @@
|
||||
android:id="@+id/item_home"
|
||||
android:title="HOME"
|
||||
android:icon="@drawable/ic_winboll"/>
|
||||
<item
|
||||
android:id="@+id/item_yun"
|
||||
android:title="YUN"
|
||||
android:icon="@drawable/ic_winboll"/>
|
||||
<item
|
||||
android:id="@+id/item_logon"
|
||||
android:title="Logon"
|
||||
android:icon="@drawable/ic_winboll"/>
|
||||
<item
|
||||
android:id="@+id/item_log"
|
||||
android:title="LOG"
|
||||
|
12
appbase/src/main/res/xml/network_security_config.xml
Normal file
12
appbase/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<!-- 允许访问 winboll.cc 及其子域名(原配置) -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">winboll.cc</domain>
|
||||
</domain-config>
|
||||
|
||||
<!-- **新增:允许访问 IP 地址 10.8.0.250** -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">10.8.0.250</domain> <!-- 不包含子域名 -->
|
||||
</domain-config>
|
||||
</network-security-config>
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue May 13 11:18:09 HKT 2025
|
||||
stageCount=2
|
||||
#Tue Jun 03 15:05:48 HKT 2025
|
||||
stageCount=5
|
||||
libraryProject=libapputils
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.1
|
||||
publishVersion=15.8.4
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.2
|
||||
baseBetaVersion=15.8.5
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Jun 01 08:26:46 GMT 2025
|
||||
stageCount=1
|
||||
#Tue Jun 03 19:16:41 HKT 2025
|
||||
stageCount=2
|
||||
libraryProject=libaes
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.0
|
||||
buildCount=2
|
||||
baseBetaVersion=15.8.1
|
||||
publishVersion=15.8.1
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.2
|
||||
|
@@ -115,7 +115,8 @@ public class AboutView extends LinearLayout {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
mszCurrentAppPackageName = mszAppAPKName + "_" + mszAppVersionName + ".apk";
|
||||
mszHomePage = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
|
||||
mszHomePage = mAPPInfo.getAppHomePage();
|
||||
//mszHomePage = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
|
||||
if (mAPPInfo.getAppGitAPPBranch().equals("")) {
|
||||
mszGitea = "https://gitea.winboll.cc/" + mAPPInfo.getAppGitOwner() + "/" + mszAppGitName;
|
||||
} else {
|
||||
|
@@ -22,4 +22,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// 网络连接类库
|
||||
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Jun 03 13:40:01 HKT 2025
|
||||
stageCount=5
|
||||
#Wed Jun 04 15:04:39 HKT 2025
|
||||
stageCount=8
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.4
|
||||
publishVersion=15.8.7
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.5
|
||||
baseBetaVersion=15.8.8
|
||||
|
@@ -103,6 +103,10 @@
|
||||
|
||||
</receiver>
|
||||
|
||||
<activity android:name="cc.winboll.studio.libappbase.activities.YunActivity"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.libappbase.activities.LogonActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@@ -0,0 +1,109 @@
|
||||
package cc.winboll.studio.libappbase.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
import cc.winboll.studio.libappbase.utils.RSAUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@188.com>
|
||||
* @Date 2025/06/04 13:29
|
||||
* @Describe 用户登录框
|
||||
*/
|
||||
public class LogonActivity extends Activity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "LogonActivity";
|
||||
|
||||
LogView mLogView;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_logon);
|
||||
mLogView = findViewById(R.id.logview);
|
||||
mLogView.start();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mLogView.start();
|
||||
}
|
||||
|
||||
public void onTestRSA(View view) {
|
||||
LogUtils.d(TAG, "onTestRSA");
|
||||
String keyPath = getFilesDir() + "/home/keys/"; // 密钥保存路径
|
||||
LogUtils.d(TAG, String.format("keyPath %s", keyPath));
|
||||
RSAUtils utils = RSAUtils.getInstance();
|
||||
|
||||
try {
|
||||
// 测试 1:首次生成密钥对
|
||||
LogUtils.d(TAG, "==== 首次生成密钥对 ====");
|
||||
if (utils.keysExist(keyPath)) {
|
||||
LogUtils.d(TAG, "密钥对已生成");
|
||||
} else {
|
||||
utils.generateAndSaveKeys(keyPath);
|
||||
LogUtils.d(TAG, "密钥对生成成功,保存至:" + keyPath);
|
||||
}
|
||||
|
||||
// 测试 2:获取密钥对(自动读取已生成的文件)
|
||||
KeyPair keyPair = utils.getOrGenerateKeys(keyPath);
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
// 打印密钥信息
|
||||
LogUtils.d(TAG, "\n==== 密钥信息 ====");
|
||||
LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm());
|
||||
LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节");
|
||||
LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm());
|
||||
LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节");
|
||||
|
||||
// 测试 3:重复调用时检查是否复用文件
|
||||
LogUtils.d(TAG, "\n==== 二次调用 ====");
|
||||
KeyPair reusedPair = utils.getOrGenerateKeys(keyPath);
|
||||
LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用)
|
||||
LogUtils.d(TAG, "操作完成");
|
||||
|
||||
String testMessage = "Hello, RSA Encryption!";
|
||||
|
||||
// 1. 获取或生成密钥对
|
||||
PublicKey publicKeyReused = reusedPair.getPublic();
|
||||
PrivateKey privateKeyReused = reusedPair.getPrivate();
|
||||
|
||||
// 2. 公钥加密
|
||||
byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused);
|
||||
LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length);
|
||||
|
||||
// 3. 私钥解密
|
||||
String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused);
|
||||
LogUtils.d(TAG, "解密结果:" + decryptedMessage);
|
||||
|
||||
// 4. 验证解密是否成功
|
||||
if (testMessage.equals(decryptedMessage)) {
|
||||
LogUtils.d(TAG, "加密解密测试通过!");
|
||||
} else {
|
||||
LogUtils.d(TAG, "测试失败:内容不一致");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
package cc.winboll.studio.libappbase.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import cc.winboll.studio.libappbase.BuildConfig;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import java.io.IOException;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import android.widget.RadioButton;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@188.com>
|
||||
* @Date 2025/06/04 11:06
|
||||
* @Describe 云宝云
|
||||
*/
|
||||
public class YunActivity extends Activity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "YunActivity";
|
||||
|
||||
public static final String DEBUG_HOST = "http://10.8.0.250:456";
|
||||
public static final String YUN_HOST = "https://yun.winboll.cc";
|
||||
|
||||
String mHost = "";
|
||||
RadioButton mrbYunHost;
|
||||
RadioButton mrbDebugHost;
|
||||
LogView mLogView;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_yun);
|
||||
mLogView = findViewById(R.id.logview);
|
||||
mLogView.start();
|
||||
|
||||
mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
|
||||
if (BuildConfig.DEBUG) {
|
||||
mrbYunHost = findViewById(R.id.rb_yunhost);
|
||||
mrbDebugHost = findViewById(R.id.rb_debughost);
|
||||
mrbYunHost.setChecked(!BuildConfig.DEBUG);
|
||||
mrbDebugHost.setChecked(BuildConfig.DEBUG);
|
||||
} else {
|
||||
findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSwitchHost(View view) {
|
||||
if (view.getId() == R.id.rb_yunhost) {
|
||||
mrbDebugHost.setChecked(false);
|
||||
mHost = YUN_HOST;
|
||||
} else if (view.getId() == R.id.rb_debughost) {
|
||||
mrbYunHost.setChecked(false);
|
||||
mHost = DEBUG_HOST;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mLogView.start();
|
||||
}
|
||||
|
||||
public void onTestYun(View view) {
|
||||
LogUtils.d(TAG, "onTestYun");
|
||||
(new Thread(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
testYun();
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
void testYun() {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request = new Request.Builder()
|
||||
.url(mHost + "/backups/")
|
||||
.build();
|
||||
|
||||
Response response = null;
|
||||
try {
|
||||
response = client.newCall(request).execute();
|
||||
if (response.isSuccessful()) {
|
||||
String responseBody = "";
|
||||
if (response.body() != null) {
|
||||
responseBody = response.body().string();
|
||||
}
|
||||
|
||||
// 正则匹配:任意主机名 -> Test OK(主机名部分匹配非空字符)
|
||||
boolean isMatch = responseBody.matches(".+? -> Test OK");
|
||||
|
||||
if (isMatch) {
|
||||
LogUtils.d(TAG, responseBody);
|
||||
} else {
|
||||
LogUtils.d(TAG, "响应内容不匹配,内容:" + responseBody);
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "请求失败,状态码:" + response.code());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "读取响应体失败:" + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "异常:" + e.getMessage());
|
||||
e.printStackTrace(); // Java 7 需显式打印堆栈
|
||||
} finally {
|
||||
// 手动关闭 Response(Java 7 不支持 try-with-resources)
|
||||
if (response != null && response.body() != null) {
|
||||
response.body().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,6 +17,8 @@ public class APPModel extends BaseBean {
|
||||
// 应用是否处于正在调试状态
|
||||
//
|
||||
boolean isDebuging = false;
|
||||
// 用本机 RSA 加密后保存的令牌
|
||||
String rsaToken = "";
|
||||
|
||||
public APPModel() {
|
||||
this.isDebuging = false;
|
||||
@@ -26,6 +28,14 @@ public class APPModel extends BaseBean {
|
||||
this.isDebuging = isDebuging;
|
||||
}
|
||||
|
||||
public void setRsaToken(String rsaToken) {
|
||||
this.rsaToken = rsaToken;
|
||||
}
|
||||
|
||||
public String getRsaToken() {
|
||||
return rsaToken;
|
||||
}
|
||||
|
||||
public void setIsDebuging(boolean isDebuging) {
|
||||
this.isDebuging = isDebuging;
|
||||
}
|
||||
@@ -43,6 +53,7 @@ public class APPModel extends BaseBean {
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isDebuging").value(isDebuging());
|
||||
jsonWriter.name("rsaToken").value(getRsaToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,6 +61,8 @@ public class APPModel extends BaseBean {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("isDebuging")) {
|
||||
setIsDebuging(jsonReader.nextBoolean());
|
||||
} else if (name.equals("rsaToken")) {
|
||||
setRsaToken(jsonReader.nextString());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@@ -0,0 +1,124 @@
|
||||
package cc.winboll.studio.libappbase.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@188.com>
|
||||
* @Date 2025/06/04 13:36
|
||||
* @Describe RSA加密工具
|
||||
*/
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class RSAUtils {
|
||||
public static final String TAG = "RSAUtils";
|
||||
private static final RSAUtils INSTANCE = new RSAUtils();
|
||||
private static final int KEY_SIZE = 2048;
|
||||
private static final String KEY_ALGORITHM = "RSA";
|
||||
private static final String PUBLIC_KEY_FILE = "public.key";
|
||||
private static final String PRIVATE_KEY_FILE = "private.key";
|
||||
|
||||
private RSAUtils() {}
|
||||
|
||||
public static RSAUtils getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查密钥文件是否存在
|
||||
*/
|
||||
public boolean keysExist(String path) {
|
||||
File publicKeyFile = new File(path + PUBLIC_KEY_FILE);
|
||||
File privateKeyFile = new File(path + PRIVATE_KEY_FILE);
|
||||
return publicKeyFile.exists() && privateKeyFile.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成密钥对并保存为字节文件
|
||||
*/
|
||||
public void generateAndSaveKeys(String path) throws Exception {
|
||||
LogUtils.d(TAG, "正在生成密钥对");
|
||||
// 生成密钥对
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
|
||||
generator.initialize(KEY_SIZE);
|
||||
KeyPair keyPair = generator.generateKeyPair();
|
||||
|
||||
// 保存公钥(X.509字节)
|
||||
saveKey(path, PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded());
|
||||
// 保存私钥(PKCS#8字节)
|
||||
saveKey(path, PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded());
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取密钥对(存在则读取,不存在则生成)
|
||||
*/
|
||||
public KeyPair getOrGenerateKeys(String path) throws Exception {
|
||||
if (keysExist(path)) {
|
||||
LogUtils.d(TAG, "密钥对已存在");
|
||||
return readKeysFromFile(path);
|
||||
} else {
|
||||
LogUtils.d(TAG, "未生成密钥对");
|
||||
generateAndSaveKeys(path);
|
||||
return readKeysFromFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件读取密钥对
|
||||
*/
|
||||
private KeyPair readKeysFromFile(String path) throws Exception {
|
||||
LogUtils.d(TAG, "正在读取密钥对");
|
||||
byte[] publicKeyBytes = Files.readAllBytes(Paths.get(path + PUBLIC_KEY_FILE));
|
||||
byte[] privateKeyBytes = Files.readAllBytes(Paths.get(path + PRIVATE_KEY_FILE));
|
||||
|
||||
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes);
|
||||
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||
|
||||
KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||
PublicKey publicKey = factory.generatePublic(publicSpec);
|
||||
PrivateKey privateKey = factory.generatePrivate(privateSpec);
|
||||
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用保存密钥字节方法
|
||||
*/
|
||||
private void saveKey(String path, String fileName, byte[] keyBytes) throws IOException {
|
||||
File dir = new File(path);
|
||||
if (!dir.exists()) dir.mkdirs(); // 自动创建目录
|
||||
try (FileOutputStream fos = new FileOutputStream(path + fileName)) {
|
||||
fos.write(keyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公钥加密
|
||||
*/
|
||||
public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM + "/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
return cipher.doFinal(plainText.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 私钥解密
|
||||
*/
|
||||
public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM + "/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
byte[] decryptedBytes = cipher.doFinal(encryptedData);
|
||||
return new String(decryptedBytes, "UTF-8");
|
||||
}
|
||||
}
|
||||
|
37
libappbase/src/main/res/layout/activity_logon.xml
Normal file
37
libappbase/src/main/res/layout/activity_logon.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test RSA"
|
||||
android:onClick="onTestRSA"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<cc.winboll.studio.libappbase.LogView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/logview"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
63
libappbase/src/main/res/layout/activity_yun.xml
Normal file
63
libappbase/src/main/res/layout/activity_yun.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:gravity="right"
|
||||
android:padding="10dp"
|
||||
android:id="@+id/ll_hostbar">
|
||||
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="10.8.0.250:456"
|
||||
android:id="@+id/rb_debughost"
|
||||
android:onClick="onSwitchHost"/>
|
||||
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="yun.winboll.cc"
|
||||
android:id="@+id/rb_yunhost"
|
||||
android:onClick="onSwitchHost"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:gravity="right">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestYun"
|
||||
android:onClick="onTestYun"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<cc.winboll.studio.libappbase.LogView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/logview"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@@ -21,7 +21,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
api 'cc.winboll.studio:libappbase:15.8.0'
|
||||
api 'cc.winboll.studio:libappbase:15.8.2'
|
||||
|
||||
// 二维码类库
|
||||
api 'com.google.zxing:core:3.4.1'
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue May 13 11:17:53 HKT 2025
|
||||
stageCount=2
|
||||
#Tue Jun 03 15:05:42 HKT 2025
|
||||
stageCount=5
|
||||
libraryProject=libapputils
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.1
|
||||
publishVersion=15.8.4
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.2
|
||||
baseBetaVersion=15.8.5
|
||||
|
1
ollama/.gitignore
vendored
Normal file
1
ollama/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
1
ollama/app_update_description.txt
Normal file
1
ollama/app_update_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
73
ollama/build.gradle
Normal file
73
ollama/build.gradle
Normal file
@@ -0,0 +1,73 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: '../.winboll/winboll_app_build.gradle'
|
||||
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||
|
||||
def genVersionName(def versionName){
|
||||
// 检查编译标志位配置
|
||||
assert (winbollBuildProps['stageCount'] != null)
|
||||
assert (winbollBuildProps['baseVersion'] != null)
|
||||
// 保存基础版本号
|
||||
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||
//保存编译标志配置
|
||||
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||
fos.close();
|
||||
|
||||
// 返回编译版本号
|
||||
return "${versionName}." + winbollBuildProps['stageCount']
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 32
|
||||
buildToolsVersion "32.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "cc.winboll.studio.ollama"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
// versionName 更新后需要手动设置
|
||||
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.0"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||
|
||||
// 吐司类库
|
||||
api 'com.github.getActivity:ToastUtils:10.5'
|
||||
|
||||
// Android 类库
|
||||
api 'com.android.support:appcompat-v7:28.0.0' // 包含 AppCompatActivity
|
||||
// https://mvnrepository.com/artifact/com.android.support/support-compat
|
||||
api 'com.android.support:support-compat:28.0.0' // 保留原有依赖(可选)
|
||||
// https://mvnrepository.com/artifact/com.android.support/support-v4
|
||||
api 'com.android.support:support-v4:28.0.0'
|
||||
// https://mvnrepository.com/artifact/com.android.support/support-media-compat
|
||||
api 'com.android.support:support-media-compat:28.0.0'
|
||||
// https://mvnrepository.com/artifact/com.android.support/support-core-utils
|
||||
api 'com.android.support:support-core-utils:28.0.0'
|
||||
// https://mvnrepository.com/artifact/com.android.support/support-core-ui
|
||||
api 'com.android.support:support-core-ui:28.0.0'
|
||||
// https://mvnrepository.com/artifact/com.android.support/support-fragment
|
||||
api 'com.android.support:support-fragment:28.0.0'
|
||||
// https://mvnrepository.com/artifact/com.android.support/recyclerview-v7
|
||||
api 'com.android.support:recyclerview-v7:28.0.0'
|
||||
|
||||
api 'cc.winboll.studio:libappbase:15.0.9'
|
||||
api 'cc.winboll.studio:libapputils:15.0.11'
|
||||
}
|
8
ollama/build.properties
Normal file
8
ollama/build.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Fri Mar 28 05:09:21 HKT 2025
|
||||
stageCount=1
|
||||
libraryProject=
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.0
|
||||
buildCount=0
|
||||
baseBetaVersion=15.0.1
|
21
ollama/proguard-rules.pro
vendored
Normal file
21
ollama/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
12
ollama/src/beta/AndroidManifest.xml
Normal file
12
ollama/src/beta/AndroidManifest.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<application>
|
||||
|
||||
<!-- Put flavor specific code here -->
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
7
ollama/src/beta/res/values/strings.xml
Normal file
7
ollama/src/beta/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
|
||||
<string name="app_name">Ollama +</string>
|
||||
|
||||
</resources>
|
41
ollama/src/main/AndroidManifest.xml
Normal file
41
ollama/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cc.winboll.studio.ollama">
|
||||
|
||||
<!-- 拥有完全的网络访问权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/MyAppTheme"
|
||||
android:resizeableActivity="true"
|
||||
android:name=".App"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="4.0"/>
|
||||
|
||||
<activity android:name=".GlobalApplication$CrashActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
334
ollama/src/main/java/cc/winboll/studio/ollama/App.java
Normal file
334
ollama/src/main/java/cc/winboll/studio/ollama/App.java
Normal file
@@ -0,0 +1,334 @@
|
||||
package cc.winboll.studio.ollama;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
|
||||
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
//CrashHandler.getInstance().registerGlobal(this);
|
||||
//CrashHandler.getInstance().registerPart(this);
|
||||
}
|
||||
|
||||
public static void write(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buf = new byte[1024 * 8];
|
||||
int len;
|
||||
while ((len = input.read(buf)) != -1) {
|
||||
output.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
public static void write(File file, byte[] data) throws IOException {
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null && !parent.exists()) parent.mkdirs();
|
||||
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(data);
|
||||
FileOutputStream output = new FileOutputStream(file);
|
||||
try {
|
||||
write(input, output);
|
||||
} finally {
|
||||
closeIO(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toString(InputStream input) throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
write(input, output);
|
||||
try {
|
||||
return output.toString("UTF-8");
|
||||
} finally {
|
||||
closeIO(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeIO(Closeable... closeables) {
|
||||
for (Closeable closeable : closeables) {
|
||||
try {
|
||||
if (closeable != null) closeable.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CrashHandler {
|
||||
|
||||
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||
|
||||
private static CrashHandler sInstance;
|
||||
|
||||
private PartCrashHandler mPartCrashHandler;
|
||||
|
||||
public static CrashHandler getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new CrashHandler();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void registerGlobal(Context context) {
|
||||
registerGlobal(context, null);
|
||||
}
|
||||
|
||||
public void registerGlobal(Context context, String crashDir) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
|
||||
}
|
||||
|
||||
public void registerPart(Context context) {
|
||||
unregisterPart(context);
|
||||
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
|
||||
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
|
||||
}
|
||||
|
||||
public void unregisterPart(Context context) {
|
||||
if (mPartCrashHandler != null) {
|
||||
mPartCrashHandler.isRunning.set(false);
|
||||
mPartCrashHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PartCrashHandler implements Runnable {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||
|
||||
public PartCrashHandler(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (isRunning.get()) {
|
||||
try {
|
||||
Looper.loop();
|
||||
} catch (final Throwable e) {
|
||||
e.printStackTrace();
|
||||
if (isRunning.get()) {
|
||||
MAIN_HANDLER.post(new Runnable(){
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException)e;
|
||||
} else {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
|
||||
|
||||
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final File mCrashDir;
|
||||
|
||||
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
|
||||
this.mContext = context;
|
||||
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
try {
|
||||
|
||||
String log = buildLog(throwable);
|
||||
writeLog(log);
|
||||
|
||||
try {
|
||||
Intent intent = new Intent(mContext, CrashActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, log);
|
||||
mContext.startActivity(intent);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
writeLog(e.toString());
|
||||
}
|
||||
|
||||
throwable.printStackTrace();
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
|
||||
} catch (Throwable e) {
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildLog(Throwable throwable) {
|
||||
String time = DATE_FORMAT.format(new Date());
|
||||
|
||||
String versionName = "unknown";
|
||||
long versionCode = 0;
|
||||
try {
|
||||
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
|
||||
versionName = packageInfo.versionName;
|
||||
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
|
||||
head.put("Time Of Crash", time);
|
||||
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
|
||||
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
|
||||
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
|
||||
head.put("Kernel", getKernel());
|
||||
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
|
||||
head.put("Fingerprint", Build.FINGERPRINT);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (String key : head.keySet()) {
|
||||
if (builder.length() != 0) builder.append("\n");
|
||||
builder.append(key);
|
||||
builder.append(" : ");
|
||||
builder.append(head.get(key));
|
||||
}
|
||||
|
||||
builder.append("\n\n");
|
||||
builder.append(Log.getStackTraceString(throwable));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void writeLog(String log) {
|
||||
String time = DATE_FORMAT.format(new Date());
|
||||
File file = new File(mCrashDir, "crash_" + time + ".txt");
|
||||
try {
|
||||
write(file, log.getBytes("UTF-8"));
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getKernel() {
|
||||
try {
|
||||
return App.toString(new FileInputStream("/proc/version")).trim();
|
||||
} catch (Throwable e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CrashActivity extends Activity {
|
||||
|
||||
private String mLog;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTheme(android.R.style.Theme_DeviceDefault);
|
||||
setTitle("App Crash");
|
||||
|
||||
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
|
||||
ScrollView contentView = new ScrollView(this);
|
||||
contentView.setFillViewport(true);
|
||||
|
||||
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
|
||||
|
||||
TextView textView = new TextView(this);
|
||||
int padding = dp2px(16);
|
||||
textView.setPadding(padding, padding, padding, padding);
|
||||
textView.setText(mLog);
|
||||
textView.setTextIsSelectable(true);
|
||||
textView.setTypeface(Typeface.DEFAULT);
|
||||
textView.setLinksClickable(true);
|
||||
|
||||
horizontalScrollView.addView(textView);
|
||||
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
|
||||
setContentView(contentView);
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
|
||||
if (intent != null) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
finish();
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static int dp2px(float dpValue) {
|
||||
final float scale = Resources.getSystem().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.copy:
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
package cc.winboll.studio.ollama;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
public final static int MSG_APPEND = 0;
|
||||
|
||||
private Handler _Handler = new Handler(Looper.getMainLooper());
|
||||
private TextView mtvMessage;
|
||||
private EditText metAsk;
|
||||
private Button mbtSend;
|
||||
private ScrollView msvMessage;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
mtvMessage = (TextView) findViewById(R.id.message_tv);
|
||||
metAsk = (EditText) findViewById(R.id.ask_et);
|
||||
mbtSend = (Button) findViewById(R.id.send_bt);
|
||||
msvMessage = findViewById(R.id.message_sv);
|
||||
|
||||
mbtSend.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
sendQuestion();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 设置输入框获得焦点的类
|
||||
//
|
||||
// static class MyHandler extends Handler {
|
||||
// WeakReference<MainActivity> mActivity;
|
||||
// MyHandler(MainActivity activity) {
|
||||
// mActivity = new WeakReference<MainActivity>(activity);
|
||||
// }
|
||||
// public void handleMessage(Message msg) {
|
||||
// MainActivity theActivity = mActivity.get();
|
||||
// switch (msg.what) {
|
||||
// case MSG_APPEND:
|
||||
// theActivity.mtvMessage.append((String)msg.obj);
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// super.handleMessage(msg);
|
||||
// }
|
||||
// }
|
||||
|
||||
private void sendQuestion() {
|
||||
final String question = metAsk.getText().toString().trim();
|
||||
if (!question.equals("")) {
|
||||
mtvMessage.append("\n\nI :" + metAsk.getText().toString() + "\nOllama : ");
|
||||
metAsk.setText("");
|
||||
new OllamaClient.SyncAskThread(question, new OllamaClient.OnAnswerCallback() {
|
||||
@Override
|
||||
public void onAnswer(final String answer) {
|
||||
_Handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mtvMessage.append(answer);
|
||||
msvMessage.post(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
msvMessage.fullScroll(View.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
223
ollama/src/main/java/cc/winboll/studio/ollama/OllamaClient.java
Normal file
223
ollama/src/main/java/cc/winboll/studio/ollama/OllamaClient.java
Normal file
@@ -0,0 +1,223 @@
|
||||
package cc.winboll.studio.ollama;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen@AliYun.Com
|
||||
* @Date 2025/03/27 19:55:28
|
||||
* @Describe 简单Http协议访问客户端
|
||||
*/
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.Buffer;
|
||||
import okio.Source;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class OllamaClient {
|
||||
|
||||
public static final String TAG = "OllamaClient";
|
||||
|
||||
private static final String API_BASE_URL = "https://ollama-api.winboll.cc";
|
||||
//private static final String API_BASE_URL = "http://10.8.0.10:11434";
|
||||
//private static final OkHttpClient client = new OkHttpClient();
|
||||
private static final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
// 1. 生成文本示例
|
||||
// static void generateText(String prompt, String model) {
|
||||
// String url = API_BASE_URL + "/api/generate";
|
||||
// try {
|
||||
// JSONObject payload = new JSONObject()
|
||||
// .put("model", model)
|
||||
// .put("prompt", prompt)
|
||||
// .put("temperature", 0.7)
|
||||
// .put("max_tokens", 200);
|
||||
//
|
||||
// Request request = new Request.Builder()
|
||||
// .url(url)
|
||||
// .post(RequestBody.create(payload.toString(), MediaType.get("application/json")))
|
||||
// .build();
|
||||
//
|
||||
// Response response = client.newCall(request).execute();
|
||||
// if (response.isSuccessful()) {
|
||||
// String result = response.body().string();
|
||||
// String formattedStream = OllamaResponseFormatter.formatStreamingResponse(result);
|
||||
//
|
||||
// // 输出示例:
|
||||
//// [2025-03-27T19:34:29.274955439Z] [llama3.1:8b] It looks like you might have miss
|
||||
//// [2025-03-27T19:34:30.482553089Z] [llama3.1:8b] pelled the word "Ollama" or perhaps said something that is not a standard word in the English language. However, I'm here to provide information and assistance on various topics, so please let me know what you meant by "Ollama." Was it related to a name, place, movie, game, or something else?
|
||||
//
|
||||
// LogUtils.d(TAG, formattedStream);
|
||||
//// JSONObject json = new JSONObject(result);
|
||||
//// LogUtils.d(TAG, "生成结果: " + json.getString("response"));
|
||||
// //System.out.println("生成结果: " + json.getString("response"));
|
||||
// } else {
|
||||
// LogUtils.d(TAG, "请求失败: " + response.code());
|
||||
// //System.out.println("请求失败: " + response.code() + " " + response.message());
|
||||
// }
|
||||
// } catch (JSONException|IOException e) {
|
||||
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
// }
|
||||
// }
|
||||
|
||||
// 实时输出流式响应的函数
|
||||
static void generateTextStream(String prompt, String model, final OnAnswerCallback callback) {
|
||||
String url = API_BASE_URL + "/api/generate";
|
||||
try {
|
||||
JSONObject payload = new JSONObject()
|
||||
.put("model", model)
|
||||
.put("prompt", prompt)
|
||||
.put("temperature", 0.7)
|
||||
.put("max_tokens", 200)
|
||||
.put("stream", true); // 启用流式响应
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(RequestBody.create(payload.toString(), MediaType.get("application/json")))
|
||||
.build();
|
||||
|
||||
LogUtils.d(TAG, "Request request");
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
LogUtils.d(TAG, "请求失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (!response.isSuccessful()) {
|
||||
LogUtils.d(TAG, "请求失败: " + response.code());
|
||||
return;
|
||||
}
|
||||
|
||||
try (ResponseBody body = response.body()) {
|
||||
if (body == null) return;
|
||||
|
||||
// 实时流式解析
|
||||
LogUtils.d(TAG, "实时流式解析");
|
||||
Source source = body.source();
|
||||
Buffer buffer = new Buffer();
|
||||
StringBuilder fullResponse = new StringBuilder();
|
||||
boolean isDone = false;
|
||||
|
||||
while (!isDone && source.read(buffer, 1024) != -1) {
|
||||
//LogUtils.d(TAG, "!isDone");
|
||||
String chunk = buffer.readUtf8();
|
||||
String[] lines = chunk.split("\n");
|
||||
|
||||
for (String line : lines) {
|
||||
LogUtils.d(TAG, line);
|
||||
if (line.trim().startsWith("{\"model\":")) {
|
||||
LogUtils.d(TAG, line);
|
||||
String jsonStr = line;
|
||||
if (jsonStr.equals("[DONE]")) {
|
||||
isDone = true;
|
||||
LogUtils.d(TAG, "流式生成完成");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
//LogUtils.d(TAG, jsonStr);
|
||||
JSONObject json = new JSONObject(jsonStr);
|
||||
//LogUtils.d(TAG, json.toString());
|
||||
String responseText = json.getString("response");
|
||||
//LogUtils.d(TAG, responseText);
|
||||
fullResponse.append(responseText);
|
||||
|
||||
// 实时输出
|
||||
callback.onAnswer(responseText);
|
||||
LogUtils.d(TAG, "实时响应: " + responseText);
|
||||
|
||||
// 处理完成状态
|
||||
if (json.getBoolean("done")) {
|
||||
isDone = true;
|
||||
String doneReason = json.optString("done_reason", "unknown");
|
||||
LogUtils.d(TAG, "生成完成 (原因: " + doneReason + ")");
|
||||
LogUtils.d(TAG, "完整回答: " + fullResponse.toString());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
LogUtils.d(TAG, "JSON解析错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
LogUtils.d(TAG, "JSON格式错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 2. 获取模型列表示例
|
||||
static void getModelList() {
|
||||
String url = API_BASE_URL + "/v1/models";
|
||||
LogUtils.d(TAG, "url : " + url);
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
if (response.isSuccessful()) {
|
||||
LogUtils.d(TAG, response.body().string());
|
||||
// JSONArray models = new JSONArray(response.body().string());
|
||||
// //System.out.println("可用模型列表:");
|
||||
// LogUtils.d(TAG, "可用模型列表:");
|
||||
// for (int i = 0; i < models.length(); i++) {
|
||||
// JSONObject model = models.getJSONObject(i);
|
||||
// LogUtils.d(TAG, "- " + model.getString("name") + " (" + model.getString("size") + ")");
|
||||
// //System.out.println("- " + model.getString("name") + " (" + model.getString("size") + ")");
|
||||
// }
|
||||
} else {
|
||||
LogUtils.d(TAG, "获取模型列表失败: " + response.code());
|
||||
//System.out.println("获取模型列表失败: " + response.code());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
static void unittest(String ask, OnAnswerCallback callback) {
|
||||
// 获取模型列表
|
||||
getModelList();
|
||||
|
||||
// 生成文本
|
||||
generateTextStream(ask, "llama3.1:8b", callback);
|
||||
}
|
||||
|
||||
public static class SyncAskThread extends Thread {
|
||||
private String ask;
|
||||
private OnAnswerCallback callback;
|
||||
|
||||
public SyncAskThread(String ask, OnAnswerCallback callback) {
|
||||
this.ask = ask;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
super.run();
|
||||
LogUtils.d(TAG, "run() start.");
|
||||
unittest(ask, callback);
|
||||
LogUtils.d(TAG, "run() end.");
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnAnswerCallback {
|
||||
void onAnswer(String answer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,72 @@
|
||||
package cc.winboll.studio.ollama;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen@AliYun.Com
|
||||
* @Date 2025/03/28 03:38:50
|
||||
*/
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class OllamaResponseFormatter {
|
||||
public static final String TAG = "OllamaResponseFormatter";
|
||||
|
||||
// 处理模型列表响应
|
||||
public static String formatModelList(String jsonResponse) {
|
||||
try {
|
||||
JSONObject json = new JSONObject(jsonResponse);
|
||||
JSONArray models = json.getJSONArray("data");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("可用模型列表:\n");
|
||||
|
||||
for (int i = 0; i < models.length(); i++) {
|
||||
JSONObject model = models.getJSONObject(i);
|
||||
String modelId = model.getString("id");
|
||||
sb.append(String.format("-%d. %s\n", i + 1, modelId));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
return "格式解析错误: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理流式生成文本响应
|
||||
public static String formatStreamingResponse(String jsonResponse) {
|
||||
try {
|
||||
JSONObject json = new JSONObject(jsonResponse);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String responseText = json.getString("response");
|
||||
boolean isDone = json.getBoolean("done");
|
||||
|
||||
// 添加时间戳和模型标识
|
||||
String timestamp = json.getString("created_at");
|
||||
String modelName = json.getString("model");
|
||||
sb.append(String.format("[%s] [%s] ", timestamp, modelName));
|
||||
|
||||
// 处理响应内容
|
||||
if (responseText.isEmpty() && isDone) {
|
||||
sb.append("生成完成\n");
|
||||
} else {
|
||||
sb.append(responseText);
|
||||
if (isDone) {
|
||||
String doneReason = json.optString("done_reason", "unknown");
|
||||
sb.append(String.format(" (完成原因: %s)\n", doneReason));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
return "格式解析错误: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
public static void main(String[] args) {
|
||||
// 模型列表测试
|
||||
String modelListJson = "{\"object\":\"list\",\"data\":[{...}]}";
|
||||
System.out.println(formatModelList(modelListJson));
|
||||
|
||||
// 流式响应测试
|
||||
String streamingJson = "{\"model\":\"llama3.1:8b\",\"created_at\":\"2025-03-27T19:34:29.274955439Z\",\"response\":\"It\",\"done\":false}";
|
||||
System.out.println(formatStreamingResponse(streamingJson));
|
||||
}
|
||||
}
|
||||
|
BIN
ollama/src/main/res/drawable/ic_launcher.png
Normal file
BIN
ollama/src/main/res/drawable/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
69
ollama/src/main/res/layout/activity_main.xml
Normal file
69
ollama/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center_vertical|center_horizontal"
|
||||
android:layout_weight="1.0"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/message_sv">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="Asking Ollama ..."
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:id="@+id/message_tv"/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<EditText
|
||||
android:layout_width="0dp"
|
||||
android:ems="10"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/ask_et"
|
||||
android:layout_weight="1.0"
|
||||
android:text="Hello, World!"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Send"
|
||||
android:id="@+id/send_bt"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<cc.winboll.studio.libappbase.LogView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Text"
|
||||
android:id="@+id/logview"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
9
ollama/src/main/res/values-v21/styles.xml
Normal file
9
ollama/src/main/res/values-v21/styles.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
|
||||
<item name="android:colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="android:colorAccent">@color/colorAccent</item>
|
||||
<item name="android:navigationBarColor">?android:colorPrimary</item>
|
||||
</style>
|
||||
</resources>
|
6
ollama/src/main/res/values/colors.xml
Normal file
6
ollama/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#009688</color>
|
||||
<color name="colorPrimaryDark">#00796B</color>
|
||||
<color name="colorAccent">#FF9800</color>
|
||||
</resources>
|
4
ollama/src/main/res/values/strings.xml
Normal file
4
ollama/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Ollama</string>
|
||||
</resources>
|
5
ollama/src/main/res/values/styles.xml
Normal file
5
ollama/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="MyAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
</style>
|
||||
</resources>
|
6
ollama/src/main/res/xml/network_security_config.xml
Normal file
6
ollama/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">10.8.0.10</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
12
ollama/src/stage/AndroidManifest.xml
Normal file
12
ollama/src/stage/AndroidManifest.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<application>
|
||||
|
||||
<!-- Put flavor specific code here -->
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
6
ollama/src/stage/res/values/strings.xml
Normal file
6
ollama/src/stage/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Put flavor specific strings here -->
|
||||
|
||||
</resources>
|
@@ -29,7 +29,7 @@ android {
|
||||
// versionName 更新后需要手动设置
|
||||
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.3"
|
||||
versionName "15.4"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sat May 03 06:21:11 GMT 2025
|
||||
stageCount=1
|
||||
#Thu May 29 09:43:37 HKT 2025
|
||||
stageCount=2
|
||||
libraryProject=
|
||||
baseVersion=15.3
|
||||
publishVersion=15.3.0
|
||||
buildCount=5
|
||||
baseBetaVersion=15.3.1
|
||||
baseVersion=15.4
|
||||
publishVersion=15.4.1
|
||||
buildCount=0
|
||||
baseBetaVersion=15.4.2
|
||||
|
@@ -48,3 +48,7 @@
|
||||
// AndroidXDemo 项目编译设置
|
||||
//include ':androidxdemo'
|
||||
//rootProject.name = "androidxdemo"
|
||||
|
||||
// Ollama 项目编译设置
|
||||
//include ':ollama'
|
||||
//rootProject.name = "ollama"
|
||||
|
Reference in New Issue
Block a user