├── .github ├── taamarinbot.py └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release.keystore └── src │ ├── androidTest │ └── java │ │ └── xyz │ │ └── chz │ │ └── bfm │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── xyz │ │ │ └── chz │ │ │ └── bfm │ │ │ ├── BFRApp.kt │ │ │ ├── adapter │ │ │ └── AppListAdapter.kt │ │ │ ├── data │ │ │ ├── AppInfo.kt │ │ │ └── AppManager.kt │ │ │ ├── dialog │ │ │ ├── IMakeDialog.kt │ │ │ ├── ISettingDialog.kt │ │ │ ├── MakeDialog.kt │ │ │ ├── MaterialDialogFragment.kt │ │ │ └── SettingDialog.kt │ │ │ ├── enm │ │ │ └── StatusConnection.kt │ │ │ ├── service │ │ │ └── TileBox.kt │ │ │ ├── ui │ │ │ ├── MainActivity.kt │ │ │ ├── base │ │ │ │ └── BaseActivity.kt │ │ │ ├── converter │ │ │ │ ├── ConfigManager.kt │ │ │ │ ├── ConverterActivity.kt │ │ │ │ └── config │ │ │ │ │ ├── ClashData.kt │ │ │ │ │ ├── ConfigType.kt │ │ │ │ │ ├── ConfigUtil.kt │ │ │ │ │ └── SingBoxData.kt │ │ │ ├── core │ │ │ │ ├── CoreActivity.kt │ │ │ │ └── util │ │ │ │ │ ├── CoreUtil.kt │ │ │ │ │ ├── DownloaderCore.kt │ │ │ │ │ └── IDownloadCore.kt │ │ │ ├── fragment │ │ │ │ ├── AppListFragment.kt │ │ │ │ ├── ConfigHelperFragment.kt │ │ │ │ ├── DashboardFragment.kt │ │ │ │ └── MainFragment.kt │ │ │ └── model │ │ │ │ └── MainViewModel.kt │ │ │ └── util │ │ │ ├── Constant.kt │ │ │ ├── OkHttpHelper.kt │ │ │ ├── Util.kt │ │ │ ├── ViewUtils.kt │ │ │ ├── command │ │ │ ├── CoreCmd.kt │ │ │ ├── SettingCmd.kt │ │ │ └── TermCmd.kt │ │ │ ├── modul │ │ │ └── ModuleManager.kt │ │ │ └── terminal │ │ │ └── TerminalHelper.kt │ └── res │ │ ├── drawable │ │ ├── ic_app.xml │ │ ├── ic_commit.xml │ │ ├── ic_config.xml │ │ ├── ic_converter.xml │ │ ├── ic_dashboard.xml │ │ ├── ic_disabled.xml │ │ ├── ic_done.xml │ │ ├── ic_download.xml │ │ ├── ic_enabled.xml │ │ ├── ic_error.xml │ │ ├── ic_home.xml │ │ ├── ic_info.xml │ │ ├── ic_launcher.png │ │ ├── ic_loading.xml │ │ ├── ic_log.xml │ │ ├── ic_modul.xml │ │ ├── ic_save.xml │ │ ├── ic_search.xml │ │ ├── ic_select_all.xml │ │ └── ic_setting.xml │ │ ├── layout │ │ ├── activity_converter.xml │ │ ├── activity_core.xml │ │ ├── activity_main.xml │ │ ├── custom_dialog.xml │ │ ├── fragment_app_list.xml │ │ ├── fragment_config_helper.xml │ │ ├── fragment_dashboard.xml │ │ ├── fragment_main.xml │ │ ├── item_applist.xml │ │ └── setting_dialog.xml │ │ ├── menu │ │ └── bottom_nav_menu.xml │ │ ├── navigation │ │ └── nav_main.xml │ │ ├── raw │ │ ├── clashtemplate │ │ └── singboxtemplate │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── xyz │ └── chz │ └── bfm │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ke ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── de │ │ └── markusressel │ │ └── kodeeditor │ │ └── library │ │ ├── extensions │ │ └── Extensions.kt │ │ └── view │ │ ├── CodeEditText.kt │ │ ├── CodeEditorLayout.kt │ │ ├── CodeEditorView.kt │ │ ├── CodeTextView.kt │ │ └── SelectionChangedListener.kt │ └── res │ ├── drawable │ ├── ic_launcher_background.xml │ └── ic_launcher_foreground.xml │ ├── layout │ ├── layout_code_editor__main_layout.xml │ ├── view_code_editor__divider.xml │ ├── view_code_editor__inner_layout.xml │ ├── view_code_editor__linenumbers.xml │ └── view_code_editor__minimap.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-night │ └── themes.xml │ └── values │ ├── attributes.xml │ ├── colors.xml │ ├── dimens.xml │ ├── library_kodeeditor_strings.xml │ ├── strings.xml │ └── themes.xml └── settings.gradle /.github/taamarinbot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import sys 4 | from telethon import TelegramClient 5 | from telethon.tl.functions.messages import GetMessagesRequest 6 | 7 | API_ID = os.environ.get("API_ID") 8 | API_HASH = os.environ.get("API_HASH") 9 | 10 | CHAT_ID = int(os.environ.get("CHAT_ID")) 11 | MESSAGE_THREAD_ID = int(os.environ.get("MESSAGE_THREAD_ID")) 12 | BOT_TOKEN = os.environ.get("BOT_TOKEN") 13 | VERSION = os.environ.get("VERSION") 14 | COMMIT = os.environ.get("COMMIT") 15 | # TAGS = os.environ.get("TAGS") 16 | MSG_TEMPLATE = """ 17 | {version} 18 | 19 | Commit: 20 | {commit} 21 | 22 | [source](https://github.com/taamarin/box.manager) 23 | [module](https://github.com/taamarin/box_for_magisk) 24 | 25 | #apk #manager #BFR #root 26 | """.strip() 27 | 28 | def get_caption(): 29 | msg = MSG_TEMPLATE.format( 30 | version=VERSION, 31 | # tags=TAGS, 32 | commit=COMMIT 33 | ) 34 | if len(msg) > 1024: 35 | return COMMIT 36 | return msg 37 | 38 | def check_environ(): 39 | if BOT_TOKEN is None: 40 | print("[-] Invalid BOT_TOKEN") 41 | exit(1) 42 | if CHAT_ID is None: 43 | print("[-] Invalid CHAT_ID") 44 | exit(1) 45 | if MESSAGE_THREAD_ID is None: 46 | print("[-] Invalid MESSAGE_THREAD_ID") 47 | exit(1) 48 | if VERSION is None: 49 | print("[-] Invalid VERSION") 50 | exit(1) 51 | if COMMIT is None: 52 | print("[-] Invalid COMMIT") 53 | exit(1) 54 | # if TAGS is None: 55 | # print("[-] Invalid TAGS") 56 | # exit(1) 57 | 58 | async def main(): 59 | print("[+] Uploading to telegram") 60 | check_environ() 61 | files = sys.argv[1:] 62 | print("[+] Files:", files) 63 | if len(files) <= 0: 64 | print("[-] No files to upload") 65 | exit(1) 66 | print("[+] Logging in Telegram with bot") 67 | script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) 68 | session_dir = os.path.join(script_dir, "bot.session") 69 | async with await TelegramClient(session=session_dir, api_id=API_ID, api_hash=API_HASH).start(bot_token=BOT_TOKEN) as bot: 70 | caption = [""] * len(files) 71 | caption[-1] = get_caption() 72 | print("[+] Caption: ") 73 | print("---") 74 | print(caption) 75 | print("---") 76 | print("[+] Sending") 77 | sent_messages = await bot.send_file(entity=CHAT_ID, file=files, caption=caption, reply_to=MESSAGE_THREAD_ID, parse_mode="markdown") 78 | 79 | # Pin the last sent message 80 | if sent_messages: 81 | last_message_id = sent_messages[-1].id 82 | await bot.pin_message(entity=CHAT_ID, message=last_message_id) 83 | 84 | print("[+] Done!") 85 | 86 | if __name__ == "__main__": 87 | try: 88 | asyncio.run(main()) 89 | except Exception as e: 90 | print(f"[-] An error occurred: {e}") 91 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build android apps 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths-ignore: 6 | - "docs/**" 7 | - "README.md" 8 | - ".github/ISSUE_TEMPLATE/**" 9 | branches: 10 | - main 11 | jobs: 12 | build: 13 | permissions: write-all 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: '16.x' 23 | 24 | - uses: actions/setup-java@v3 25 | with: 26 | java-version: '17' 27 | distribution: 'zulu' 28 | 29 | - name: update version 30 | run: | 31 | sed -i "s|versionCode .*|versionCode $(date +%Y%m%d)|g" app/build.gradle 32 | sed -i "s|versionName .*|versionName \"1.14-$(git rev-parse --short HEAD)\"|g" app/build.gradle 33 | 34 | - name: Build with Gradle 35 | run: | 36 | chmod +x gradlew 37 | ./gradlew --no-daemon --no-configuration-cache -q app:assembleRelease 38 | 39 | - name: checking release version 40 | id: version 41 | run: | 42 | echo ::set-output name=release_version::$(cat app/build.gradle | grep -o "versionName \"[0-9.]*-[a-z,0-9]*\"" | grep -o "[0-9.]*-[a-z,0-9]*") 43 | 44 | - name: rename apks 45 | run: | 46 | APK="Box4Root-Manager_v${{ steps.version.outputs.release_version }}.apk" 47 | mv -f ./app/build/outputs/apk/release/*.apk ./app/build/outputs/apk/release/$APK 48 | 49 | - name: upload artifact 50 | uses: actions/upload-artifact@v3 51 | if: ${{ success() }} 52 | with: 53 | name: b4r_manager 54 | path: ./app/build/outputs/apk/release/*.apk 55 | 56 | - uses: andreaswilli/delete-release-assets-action@v2.0.0 57 | with: 58 | github_token: ${{ secrets.GITHUB_TOKEN }} 59 | tag: Prerelease 60 | deleteOnlyFromDrafts: false 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | 64 | - uses: richardsimko/update-tag@v1.0.7 65 | with: 66 | tag_name: Prerelease 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | 70 | - uses: softprops/action-gh-release@v1 71 | with: 72 | tag_name: Prerelease 73 | files: ${{ github.workspace }}/app/build/outputs/apk/release/*.apk 74 | draft: false 75 | prerelease: true 76 | generate_release_notes: true 77 | 78 | - name: upload to telegram 79 | if: ${{ success() }} 80 | env: 81 | CHAT_ID: "-1001597117128" 82 | MESSAGE_THREAD_ID: "282263" 83 | API_ID: ${{ secrets.API_ID }} 84 | API_HASH: ${{ secrets.API_HASH }} 85 | BOT_TOKEN: ${{ secrets.BOT_TOKEN }} 86 | run: | 87 | if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then 88 | export VERSION="v${{ steps.version.outputs.release_version }}" 89 | export COMMIT=$(git log --oneline -n 10 --no-decorate | sed 's/^[0-9a-f]* //' | sed 's/^/— /') 90 | FILE=$(find app/build/outputs/apk/release -name "*.apk") 91 | pip3 install telethon==1.31.1 92 | python3 $GITHUB_WORKSPACE/.github/taamarinbot.py $FILE 93 | fi 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | .idea 17 | .gitmessage -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BFR Manager 2 |

3 | BOX 4 |
BOX
5 |

6 |

BFR Manager

7 | 8 |
9 | 10 | [![ANDROID](https://img.shields.io/badge/Android-3DDC84?logo=android&logoColor=white)]() 11 | [![BUILD APK](https://github.com/taamarin/box.manager/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/taamarin/box/actions/workflows/debug.yml) 12 | [![TELEGRAM CHANNEL](https://img.shields.io/badge/Telegram-2CA5E0?logo=telegram&logoColor=white)](https://t.me/nothing_taamarin) 13 | [![TELEGRAM](https://img.shields.io/badge/Telegram%20-Grups%20-blue?)](https://t.me/taamarin) 14 | 15 | 16 | 17 | 18 |
19 | 20 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-parcelize' 5 | id 'kotlin-kapt' 6 | id 'dagger.hilt.android.plugin' 7 | id 'androidx.navigation.safeargs' 8 | } 9 | 10 | android { 11 | namespace 'xyz.chz.bfm' 12 | compileSdk 33 13 | 14 | defaultConfig { 15 | applicationId "xyz.chz.bfm" 16 | minSdk 24 17 | targetSdk 33 18 | versionCode 20241205 19 | versionName "1.14" 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | vectorDrawables.useSupportLibrary = true 22 | } 23 | 24 | signingConfigs { 25 | release { 26 | storeFile file('release.keystore') 27 | storePassword 'qwerty' 28 | keyAlias 'key.alias' 29 | keyPassword 'qwerty' 30 | } 31 | } 32 | 33 | buildTypes { 34 | release { 35 | signingConfig signingConfigs.release 36 | minifyEnabled true 37 | shrinkResources true 38 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 39 | } 40 | } 41 | 42 | compileOptions { 43 | sourceCompatibility JavaVersion.VERSION_1_8 44 | targetCompatibility JavaVersion.VERSION_1_8 45 | } 46 | kotlinOptions { 47 | jvmTarget = '1.8' 48 | } 49 | 50 | buildFeatures { 51 | viewBinding true 52 | } 53 | } 54 | 55 | dependencies { 56 | implementation project(':ke') 57 | implementation 'androidx.core:core-ktx:1.9.0' 58 | implementation 'androidx.appcompat:appcompat:1.6.1' 59 | implementation 'com.google.android.material:material:1.9.0' 60 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 61 | testImplementation 'junit:junit:4.13.2' 62 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 63 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 64 | implementation 'com.google.android.material:material:1.9.0' 65 | implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' 66 | implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' 67 | implementation 'com.google.dagger:hilt-android:2.43.2' 68 | kapt 'com.google.dagger:hilt-compiler:2.43.2' 69 | implementation 'io.reactivex:rxjava:1.3.8' 70 | implementation 'io.reactivex:rxandroid:1.2.1' 71 | implementation 'androidx.preference:preference-ktx:1.2.1' 72 | implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.1' 73 | implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.1' 74 | } 75 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class xyz.chz.bfm.data.** { *; } 2 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { 3 | long producerIndex; 4 | long consumerIndex; 5 | } -------------------------------------------------------------------------------- /app/release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taamarin/box.manager/09f5683086e73d66beaa6725dcaf64b19d088ed5/app/release.keystore -------------------------------------------------------------------------------- /app/src/androidTest/java/xyz/chz/bfm/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("xyz.chz.bfm", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 23 | 26 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/BFRApp.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm 2 | 3 | import android.app.Application 4 | import android.os.StrictMode 5 | import dagger.hilt.android.HiltAndroidApp 6 | 7 | @HiltAndroidApp 8 | class BFRApp : Application() { 9 | override fun onCreate() { 10 | super.onCreate() 11 | val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() 12 | StrictMode.setThreadPolicy(policy) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/adapter/AppListAdapter.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.adapter 2 | 3 | import android.app.Activity 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.RecyclerView 8 | import xyz.chz.bfm.R 9 | import xyz.chz.bfm.data.AppInfo 10 | import xyz.chz.bfm.databinding.ItemApplistBinding 11 | 12 | class AppListAdapter( 13 | val activity: Activity, 14 | val apps: List, 15 | blacklist: MutableSet? 16 | ) : 17 | RecyclerView.Adapter() { 18 | 19 | companion object { 20 | private const val VIEW_TYPE_HEADER = 0 21 | private const val VIEW_TYPE_ITEM = 1 22 | } 23 | 24 | val blacklist = if (blacklist == null) HashSet() else HashSet(blacklist) 25 | 26 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { 27 | if (holder is AppViewHolder) { 28 | val appInfo = apps[position - 1] 29 | holder.bind(appInfo) 30 | } 31 | } 32 | 33 | override fun getItemCount() = apps.size + 1 34 | 35 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { 36 | val ctx = parent.context 37 | return when (viewType) { 38 | VIEW_TYPE_HEADER -> { 39 | val view = View(ctx) 40 | view.layoutParams = ViewGroup.LayoutParams( 41 | ViewGroup.LayoutParams.MATCH_PARENT, 42 | ctx.resources.getDimensionPixelSize(R.dimen.list_header_height) * 0 43 | ) 44 | BaseViewHolder(view) 45 | } 46 | 47 | else -> AppViewHolder( 48 | ItemApplistBinding.inflate( 49 | LayoutInflater.from(ctx), 50 | parent, 51 | false 52 | ) 53 | ) 54 | } 55 | } 56 | 57 | override fun getItemViewType(position: Int) = 58 | if (position == 0) VIEW_TYPE_HEADER else VIEW_TYPE_ITEM 59 | 60 | open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 61 | 62 | inner class AppViewHolder(private val item: ItemApplistBinding) : 63 | BaseViewHolder(item.root), 64 | View.OnClickListener { 65 | private lateinit var appInfo: AppInfo 66 | private val inBlacklist: Boolean get() = blacklist.contains(appInfo.packageName) 67 | 68 | fun bind(appInfo: AppInfo) { 69 | this.appInfo = appInfo 70 | with(item) { 71 | icon.setImageDrawable(appInfo.appIcon) 72 | checkBox.isChecked = inBlacklist 73 | item.packageName.text = appInfo.packageName 74 | if (appInfo.isSystemApp) { 75 | item.name.text = String.format("** %1s", appInfo.appName) 76 | } else { 77 | item.name.text = appInfo.appName 78 | } 79 | } 80 | itemView.setOnClickListener(this) 81 | } 82 | 83 | override fun onClick(v: View?) { 84 | if (inBlacklist) { 85 | blacklist.remove(appInfo.packageName) 86 | item.checkBox.isChecked = false 87 | } else { 88 | blacklist.add(appInfo.packageName) 89 | item.checkBox.isChecked = true 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/data/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.data 2 | 3 | import android.graphics.drawable.Drawable 4 | 5 | data class AppInfo( 6 | val appName: String, 7 | val packageName: String, 8 | val appIcon: Drawable, 9 | val isSystemApp: Boolean, 10 | var isSelected: Int 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/data/AppManager.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.data 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.pm.ApplicationInfo 6 | import android.content.pm.PackageInfo 7 | import android.content.pm.PackageManager 8 | import rx.Observable 9 | 10 | object AppManager { 11 | fun loadNetworkAppList(ctx: Context): ArrayList { 12 | val packageManager = ctx.packageManager 13 | val packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS) 14 | val apps = ArrayList() 15 | 16 | for (pkg in packages) { 17 | if (!pkg.hasInternetPermission && pkg.packageName != "android") continue 18 | 19 | val applicationInfo = pkg.applicationInfo 20 | 21 | val appName = applicationInfo.loadLabel(packageManager).toString() 22 | val appIcon = applicationInfo.loadIcon(packageManager) 23 | val isSystemApp = (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) > 0 24 | 25 | val appInfo = AppInfo(appName, pkg.packageName, appIcon, isSystemApp, 0) 26 | apps.add(appInfo) 27 | } 28 | return apps 29 | } 30 | 31 | fun rxLoadNetworkAppList(ctx: Context): Observable> = 32 | Observable.unsafeCreate { 33 | it.onNext(loadNetworkAppList(ctx)) 34 | } 35 | 36 | 37 | val PackageInfo.hasInternetPermission: Boolean 38 | get() { 39 | val permissions = requestedPermissions 40 | return permissions?.any { it == Manifest.permission.INTERNET } ?: false 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/dialog/IMakeDialog.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.dialog 2 | 3 | import androidx.fragment.app.DialogFragment 4 | 5 | interface IMakeDialog { 6 | fun onDialogPositiveButton(dialog: DialogFragment) 7 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/dialog/ISettingDialog.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.dialog 2 | 3 | import androidx.fragment.app.DialogFragment 4 | 5 | interface ISettingDialog { 6 | fun onLoading(dialog: DialogFragment) 7 | fun onAbout(dialog: DialogFragment) 8 | fun onUpdate(dialog: DialogFragment) 9 | fun onCheckIP(dialog: DialogFragment) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/dialog/MakeDialog.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.dialog 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import xyz.chz.bfm.databinding.CustomDialogBinding 10 | 11 | class MakeDialog( 12 | private val strTitle: String? = "", 13 | private val strMsg: String? = "", 14 | private val isClick: Boolean? = false, 15 | private val canCancel: Boolean? = true 16 | ) : MaterialDialogFragment() { 17 | private lateinit var binding: CustomDialogBinding 18 | 19 | lateinit var listener: IMakeDialog 20 | 21 | override fun onCreateView( 22 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? 23 | ): View { 24 | binding = CustomDialogBinding.inflate(layoutInflater, container, false) 25 | return binding.root 26 | } 27 | 28 | @Deprecated("Deprecated in Java") 29 | override fun onAttachFragment(childFragment: Fragment) { 30 | super.onAttachFragment(childFragment) 31 | try { 32 | listener = context as IMakeDialog 33 | } catch (e: ClassCastException) { 34 | throw ClassCastException("not cast") 35 | } 36 | } 37 | 38 | override fun onAttach(context: Context) { 39 | super.onAttach(context) 40 | try { 41 | listener = context as IMakeDialog 42 | } catch (e: ClassCastException) { 43 | throw ClassCastException("not cast") 44 | } 45 | } 46 | 47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 48 | super.onViewCreated(view, savedInstanceState) 49 | binding.apply { 50 | isCancelable = canCancel!! 51 | tvTitle.text = strTitle 52 | tvMessage.text = strMsg 53 | btnOk.setOnClickListener { 54 | if (isClick!!) listener.onDialogPositiveButton(this@MakeDialog) 55 | this@MakeDialog.dismiss() 56 | } 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/dialog/MaterialDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.dialog 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Dialog 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import androidx.fragment.app.DialogFragment 9 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 10 | import xyz.chz.bfm.R 11 | 12 | open class MaterialDialogFragment : DialogFragment() { 13 | 14 | private var dialogView: View? = null 15 | 16 | @SuppressLint("UseGetLayoutInflater") 17 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 18 | return MaterialAlertDialogBuilder( 19 | requireContext(), 20 | R.style.ThemeOverlay_MaterialComponents_MaterialAlertDialog_background 21 | ).apply { 22 | dialogView = 23 | onCreateView(LayoutInflater.from(requireContext()), null, savedInstanceState) 24 | 25 | dialogView?.let { onViewCreated(it, savedInstanceState) } 26 | setView(dialogView) 27 | }.create() 28 | } 29 | 30 | override fun getView(): View? { 31 | return dialogView 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/enm/StatusConnection.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.enm 2 | 3 | enum class StatusConnection(val str: String) { 4 | Enabled("Enabled"), 5 | Error("Error"), 6 | Disabled("Disabled"), 7 | Loading("Loading"), 8 | Unknown("Unknown") 9 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/service/TileBox.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.service 2 | 3 | import android.service.quicksettings.Tile 4 | import android.service.quicksettings.TileService 5 | import xyz.chz.bfm.util.Util 6 | import xyz.chz.bfm.util.command.TermCmd 7 | 8 | class TileBox : TileService() { 9 | override fun onClick() { 10 | super.onClick() 11 | when (qsTile.state) { 12 | 13 | Tile.STATE_INACTIVE -> { 14 | qsTile.state = Tile.STATE_ACTIVE 15 | qsTile.updateTile() 16 | TermCmd.start { 17 | Util.isProxyed = if (it) { 18 | qsTile.state = Tile.STATE_ACTIVE 19 | qsTile.updateTile() 20 | true 21 | } else { 22 | qsTile.state = Tile.STATE_INACTIVE 23 | qsTile.updateTile() 24 | false 25 | } 26 | } 27 | } 28 | 29 | else -> { 30 | qsTile.state = Tile.STATE_INACTIVE 31 | qsTile.updateTile() 32 | TermCmd.stop { 33 | Util.isProxyed = if (it) { 34 | qsTile.state = Tile.STATE_INACTIVE 35 | qsTile.updateTile() 36 | false 37 | } else { 38 | qsTile.state = Tile.STATE_ACTIVE 39 | qsTile.updateTile() 40 | true 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | override fun onStartListening() { 48 | super.onStartListening() 49 | if (Util.isProxyed) qsTile.state = Tile.STATE_ACTIVE else qsTile.state = Tile.STATE_INACTIVE 50 | qsTile.updateTile() 51 | } 52 | 53 | override fun onStopListening() { 54 | super.onStopListening() 55 | qsTile.updateTile() 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.view.WindowManager 6 | import androidx.navigation.findNavController 7 | import androidx.navigation.ui.setupWithNavController 8 | import com.google.android.material.bottomnavigation.BottomNavigationView 9 | import dagger.hilt.android.AndroidEntryPoint 10 | import xyz.chz.bfm.R 11 | import xyz.chz.bfm.databinding.ActivityMainBinding 12 | import xyz.chz.bfm.ui.base.BaseActivity 13 | 14 | @AndroidEntryPoint 15 | class MainActivity : BaseActivity() { 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | val binding = ActivityMainBinding.inflate(layoutInflater) 20 | setContentView(binding.root) 21 | 22 | val navView: BottomNavigationView = binding.navBottom 23 | val navController = findNavController(R.id.nav_host_fragment_container) 24 | navView.setupWithNavController(navController) 25 | 26 | navController.addOnDestinationChangedListener { _, dest, _ -> 27 | if (dest.id == R.id.nav_dashboard) this.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) 28 | if (dest.id == R.id.configHelperFragment) navView.visibility = 29 | View.GONE else navView.visibility = View.VISIBLE 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.base 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.fragment.app.DialogFragment 8 | import dagger.hilt.android.AndroidEntryPoint 9 | import xyz.chz.bfm.R 10 | import xyz.chz.bfm.dialog.IMakeDialog 11 | import xyz.chz.bfm.dialog.MakeDialog 12 | import xyz.chz.bfm.enm.StatusConnection 13 | import xyz.chz.bfm.util.modul.ModuleManager 14 | 15 | @AndroidEntryPoint 16 | open class BaseActivity : AppCompatActivity(), IMakeDialog { 17 | private var state: Int? = 0 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | if (ModuleManager.moduleVersionCode == "") { 22 | val df = MakeDialog( 23 | StatusConnection.Error.str, 24 | getString(R.string.module_not_found), 25 | true, 26 | false 27 | ) 28 | df.listener = this 29 | df.show(supportFragmentManager, "") 30 | } else { 31 | if (ModuleManager.moduleVersionCode.toInt() < getString(R.string.min_module_vrsion).toInt()) { 32 | val df = MakeDialog( 33 | StatusConnection.Loading.str, 34 | String.format( 35 | getString(R.string.update_module), 36 | ModuleManager.moduleVersionCode.toInt() 37 | ), 38 | true, 39 | false 40 | ) 41 | df.listener = this 42 | df.show(supportFragmentManager, "") 43 | state = 1 44 | } 45 | } 46 | } 47 | 48 | override fun onDialogPositiveButton(dialog: DialogFragment) { 49 | when (state) { 50 | 1 -> { 51 | val intent = 52 | Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.release_repo_url))) 53 | startActivity(intent) 54 | finish() 55 | } 56 | 57 | else -> { 58 | val intent = 59 | Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.module_repo_url))) 60 | startActivity(intent) 61 | finish() 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/converter/ConfigManager.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.converter 2 | 3 | import org.json.JSONObject 4 | import xyz.chz.bfm.ui.converter.config.ClashData 5 | import xyz.chz.bfm.ui.converter.config.ConfigType 6 | import xyz.chz.bfm.ui.converter.config.ConfigUtil 7 | import xyz.chz.bfm.ui.converter.config.SingBoxData 8 | import java.util.regex.Pattern 9 | 10 | object ConfigManager { 11 | 12 | fun importConfig(config: String, isClash: Boolean, useIndent: Boolean = false): String { 13 | try { 14 | if (config.startsWith(ConfigType.VMESS.scheme)) { 15 | var result = config.replace(ConfigType.VMESS.scheme, "") 16 | result = ConfigUtil.decode(result) 17 | if (result.isEmpty()) { 18 | return "failed decode vms" 19 | } 20 | return if (isClash) ClashData(result, useIndent).newVmessConfig() else SingBoxData( 21 | result 22 | ).vmessSing() 23 | } else if (config.startsWith(ConfigType.VLESS.scheme)) { 24 | return if (isClash) ClashData(config, useIndent).newVlessConfig() else SingBoxData( 25 | config 26 | ).vlessSing() 27 | } else if (config.startsWith(ConfigType.TROJAN.scheme) || config.startsWith(ConfigType.TROJANGO.scheme)) { 28 | return if (isClash) ClashData(config, useIndent).newTrojanConfig() else SingBoxData( 29 | config 30 | ).trojanSing() 31 | } else { 32 | return "" 33 | } 34 | } catch (_: Exception) { 35 | } 36 | return "" 37 | } 38 | 39 | fun fullClashSimple(config: String, strRaw: String): String { 40 | val name = "match" 41 | val p = Pattern.compile("- name:(.*)") 42 | val m = p.matcher(config) 43 | val sb = StringBuilder() 44 | while (m.find()) { 45 | sb.appendLine(" - ${m.group(1)?.trim()}") 46 | } 47 | return String.format( 48 | strRaw, 49 | config, 50 | ClashData().proxyGroupBuilder(name, sb.toString()), 51 | name 52 | ) 53 | } 54 | 55 | fun fullSingSimple(config: String, strRaw: String): String { 56 | val s = ArrayList() 57 | val p = Pattern.compile("\"tag\":(.*)") 58 | val m = p.matcher(config) 59 | val sb = StringBuilder() 60 | while (m.find()) { 61 | s.add(m.group(1)?.trim()!!.replace("\"|,", "")) 62 | } 63 | sb.appendLine(SingBoxData().buildHeaderSlector(s)) 64 | sb.appendLine(SingBoxData().buildHeaderBestUrl(s)) 65 | sb.append(config) 66 | sb.appendLine(SingBoxData().buildFooter()) 67 | val strResult = String.format(strRaw, sb.toString()) 68 | return JSONObject(strResult).toString(2).replace("\\", "").replace("null,", "") 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/converter/ConverterActivity.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.converter 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.RawRes 5 | import androidx.appcompat.app.AppCompatActivity 6 | import dagger.hilt.android.AndroidEntryPoint 7 | import xyz.chz.bfm.R 8 | import xyz.chz.bfm.databinding.ActivityConverterBinding 9 | import xyz.chz.bfm.util.copyToClipboard 10 | import xyz.chz.bfm.util.isValidCheck 11 | import xyz.chz.bfm.util.removeEmptyLines 12 | import xyz.chz.bfm.util.toast 13 | 14 | @AndroidEntryPoint 15 | class ConverterActivity : AppCompatActivity() { 16 | private lateinit var binding: ActivityConverterBinding 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | binding = ActivityConverterBinding.inflate(layoutInflater) 21 | setContentView(binding.root) 22 | setupConfig() 23 | } 24 | 25 | private fun setupConfig() = with(binding) { 26 | var isFull = false 27 | var isSing = false 28 | fullClashConfig.setOnCheckedChangeListener { _, b -> 29 | isFull = b 30 | isSing = false 31 | fullSingConfig.isChecked = false 32 | } 33 | fullSingConfig.setOnCheckedChangeListener { _, b -> 34 | if (b) { 35 | isSing = true 36 | isFull = false 37 | fullClashConfig.isChecked = false 38 | } 39 | } 40 | btnConvert.setOnClickListener { 41 | if (textInput.isValidCheck()) { 42 | val str = textInput.removeEmptyLines() 43 | val spltStr = str.split("\n") 44 | val result = StringBuilder() 45 | for (x in spltStr) { 46 | result.appendLine( 47 | if (isSing) ConfigManager.importConfig( 48 | x, 49 | false 50 | ) + "," else ConfigManager.importConfig(x, true) 51 | ) 52 | } 53 | if (isFull) 54 | tvResult.text = 55 | ConfigManager.fullClashSimple( 56 | result.toString(), 57 | readRawFile(R.raw.clashtemplate) 58 | ) 59 | else 60 | tvResult.text = if (isSing) ConfigManager.fullSingSimple( 61 | result.toString(), 62 | readRawFile(R.raw.singboxtemplate) 63 | ) else result 64 | tvResult.apply { 65 | setOnClickListener { 66 | toast("Copied to Clipboard!", this@ConverterActivity) 67 | copyToClipboard(this@ConverterActivity) 68 | } 69 | } 70 | toast("Click result for copy config", this@ConverterActivity) 71 | } else toast("???????????", this@ConverterActivity) 72 | } 73 | } 74 | 75 | 76 | private fun readRawFile(@RawRes id: Int): String { 77 | return resources.openRawResource(id).bufferedReader().readText() 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/converter/config/ConfigType.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.converter.config 2 | 3 | enum class ConfigType(val scheme: String) { 4 | VMESS("vmess://"), 5 | VLESS("vless://"), 6 | TROJAN("trojan://"), 7 | TROJANGO("trojan-go://") 8 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/converter/config/ConfigUtil.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.converter.config 2 | 3 | import android.net.Uri 4 | import android.util.Base64 5 | 6 | object ConfigUtil { 7 | 8 | fun safeDecodeURLB64(url: String): String { 9 | val split = url.split("://").toTypedArray() 10 | val schema = split[0] 11 | val result = split[1].substring(5) 12 | .replace(' ', '-') 13 | .replace('/', '_') 14 | .replace('+', '-') 15 | return "$schema://$result" 16 | } 17 | 18 | fun getQueryParams(uri: Uri, param: String): String? { 19 | for (p in uri.queryParameterNames) { 20 | if (param.equals(p, ignoreCase = true)) 21 | return uri.getQueryParameter(p) 22 | } 23 | return null 24 | } 25 | 26 | fun decode(text: String): String { 27 | tryDecodeBase64(text)?.let { return it } 28 | if (text.endsWith('=')) { 29 | // try again for some loosely formatted base64 30 | tryDecodeBase64(text.trimEnd('='))?.let { return it } 31 | } 32 | return "" 33 | } 34 | 35 | private fun tryDecodeBase64(text: String): String? { 36 | try { 37 | return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8")) 38 | } catch (e: Exception) { 39 | e.printStackTrace() 40 | } 41 | try { 42 | return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)) 43 | .toString(charset("UTF-8")) 44 | } catch (e: Exception) { 45 | e.printStackTrace() 46 | } 47 | return null 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/core/CoreActivity.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.core 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.core.view.isVisible 7 | import dagger.hilt.android.AndroidEntryPoint 8 | import okhttp3.Call 9 | import okhttp3.Callback 10 | import okhttp3.Response 11 | import org.json.JSONArray 12 | import org.json.JSONException 13 | import org.json.JSONObject 14 | import xyz.chz.bfm.databinding.ActivityCoreBinding 15 | import xyz.chz.bfm.ui.core.util.CoreUtil 16 | import xyz.chz.bfm.ui.core.util.DownloaderCore 17 | import xyz.chz.bfm.ui.core.util.IDownloadCore 18 | import xyz.chz.bfm.util.META_DOWNLOAD 19 | import xyz.chz.bfm.util.META_REPO 20 | import xyz.chz.bfm.util.OkHttpHelper 21 | import xyz.chz.bfm.util.SING_DOWNLOAD 22 | import xyz.chz.bfm.util.SING_REPO 23 | import xyz.chz.bfm.util.command.CoreCmd 24 | import xyz.chz.bfm.util.toast 25 | import xyz.chz.bfm.util.urlText 26 | import java.io.IOException 27 | 28 | @AndroidEntryPoint 29 | @SuppressLint("SetTextI18n") 30 | class CoreActivity : AppCompatActivity() { 31 | private lateinit var binding: ActivityCoreBinding 32 | private lateinit var dCore: DownloaderCore 33 | 34 | private var urlClash = "" 35 | private var urlSing = "" 36 | private var tagNameClash = "" 37 | private var tagNameSing = "" 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | binding = ActivityCoreBinding.inflate(layoutInflater) 42 | setContentView(binding.root) 43 | dCore = DownloaderCore(this) 44 | checkClash() 45 | buttonUp() 46 | } 47 | 48 | private fun checkClash() = with(binding) { 49 | tvClash.text = "Checking for mihomo" 50 | prgClash.isVisible = true 51 | OkHttpHelper().reqGithub(META_REPO, object : Callback { 52 | override fun onFailure(call: Call, e: IOException) { 53 | runOnUiThread { 54 | tvClash.text = "Failed to get repo clash, check ur internet connection" 55 | prgClash.isVisible = false 56 | } 57 | } 58 | 59 | override fun onResponse(call: Call, response: Response) { 60 | val respom = response.body!!.string() 61 | runOnUiThread { 62 | urlClash = parseClash(respom) 63 | if (parseVersionClashMeta() != CoreCmd.checkVerClashMeta) { 64 | tvClash.text = "Update available mihomo" 65 | btnClash.isVisible = true 66 | } else { 67 | tvClash.text = "mihomo has Latest Version" 68 | imgDoneClash.isVisible = true 69 | btnClash.isVisible = false 70 | } 71 | prgClash.isVisible = false 72 | } 73 | } 74 | }) 75 | } 76 | 77 | private fun parseClash(str: String): String { 78 | return try { 79 | val jo = JSONObject(str) 80 | tagNameClash = jo.getString("tag_name") 81 | if (CoreUtil.getAbis().contains("arm64")) { 82 | "$META_DOWNLOAD/$tagNameClash/mihomo-android-arm64-v8-$tagNameClash.gz" 83 | } else { 84 | "$META_DOWNLOAD/$tagNameClash/mihomo-android-armv7-$tagNameClash.gz" 85 | } 86 | } catch (e: JSONException) { 87 | e.message!!.toString() 88 | } 89 | } 90 | 91 | private fun parseVersionClashMeta(): String { 92 | return try { 93 | "$META_DOWNLOAD/$tagNameClash/version.txt".urlText() 94 | } catch (e: Exception) { 95 | e.message.toString() 96 | } 97 | } 98 | 99 | private fun buttonUp() = with(binding) { 100 | btnClash.apply { 101 | setOnClickListener { 102 | dCore.downloadFile( 103 | urlClash, 104 | "clash_meta.gz", 105 | "/data/adb/box/bin/xclash/mihomo", 106 | object : IDownloadCore { 107 | override fun onDownloadingStart() { 108 | prgHrzClash.isVisible = true 109 | btnClash.isVisible = false 110 | } 111 | 112 | override fun onDownloadingProgress(progress: Int) { 113 | prgHrzClash.progress = progress 114 | } 115 | 116 | override fun onDownloadingComplete() { 117 | btnClash.isVisible = false 118 | prgHrzClash.isVisible = false 119 | imgDoneClash.isVisible = true 120 | tvClash.text = "mihomo has Latest Version" 121 | } 122 | 123 | override fun onDownloadingFailed(e: Exception?) { 124 | toast("failed downloading clash", this@CoreActivity) 125 | btnClash.isVisible = true 126 | prgHrzClash.progress = 0 127 | prgHrzClash.isVisible = false 128 | } 129 | }) 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/core/util/CoreUtil.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.core.util 2 | 3 | import xyz.chz.bfm.util.terminal.TerminalHelper 4 | import java.io.FileInputStream 5 | import java.io.FileOutputStream 6 | import java.io.IOException 7 | import java.util.zip.GZIPInputStream 8 | 9 | object CoreUtil { 10 | 11 | fun gZipExtractor(gzipIn: String, gzipOut: String) { 12 | try { 13 | val fis = FileInputStream(gzipIn) 14 | val gis = GZIPInputStream(fis) 15 | val fos = FileOutputStream(gzipOut) 16 | val buff = ByteArray(1024) 17 | var len: Int 18 | while (gis.read(buff).also { len = it } != -1) { 19 | fos.write(buff, 0, len) 20 | } 21 | fos.close() 22 | gis.close() 23 | } catch (e: IOException) { 24 | e.printStackTrace() 25 | } 26 | } 27 | 28 | fun tarExtractUsingRoot(tarIn: String, tarOut: String) { 29 | TerminalHelper.execRootCmdSilent("tar xfz $tarIn -C $tarOut && mv -f $tarOut/sing-box-v* $tarOut/sing-box && chmod 755 $tarOut/sing-box && chown root:net_admin $tarOut/sing-box") 30 | } 31 | 32 | fun getAbis(): String { 33 | for (abis in android.os.Build.SUPPORTED_ABIS) { 34 | when (abis) { 35 | "arm64-v8a" -> return "arm64" 36 | "armeabi-v7a" -> return "armv7" 37 | } 38 | } 39 | return throw RuntimeException("unable get abis") 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/core/util/DownloaderCore.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.core.util 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import android.os.Looper 6 | import xyz.chz.bfm.util.command.CoreCmd 7 | import java.io.BufferedInputStream 8 | import java.io.File 9 | import java.io.FileOutputStream 10 | import java.io.InputStream 11 | import java.net.URL 12 | import java.util.concurrent.ExecutorService 13 | import java.util.concurrent.Executors 14 | 15 | open class DownloaderCore(private val ctx: Context) { 16 | private var exFile: File? = null 17 | 18 | fun downloadFile( 19 | stringUrl: String, 20 | nameFile: String, 21 | path: String, 22 | callback: IDownloadCore 23 | ) { 24 | val executor: ExecutorService = Executors.newSingleThreadExecutor() 25 | val handler = Handler(Looper.getMainLooper()) 26 | executor.execute { 27 | handler.post { callback.onDownloadingStart() } 28 | try { 29 | val url = URL(stringUrl) 30 | val conection = url.openConnection() 31 | conection.connect() 32 | val lenghtOfFile = conection.contentLength 33 | val input: InputStream = BufferedInputStream(url.openStream(), 8192) 34 | exFile = File(ctx.getExternalFilesDir(null), nameFile) 35 | if (exFile!!.exists()) exFile!!.delete() 36 | val output = FileOutputStream(exFile) 37 | val data = ByteArray(1024) 38 | var total: Long = 0 39 | while (true) { 40 | val read = input.read(data) 41 | if (read == -1) { 42 | break 43 | } 44 | output.write(data, 0, read) 45 | val j2 = total + read.toLong() 46 | if (lenghtOfFile > 0) { 47 | val progress = ((100 * j2 / lenghtOfFile.toLong()).toInt()) 48 | callback.onDownloadingProgress(progress) 49 | } 50 | total = j2 51 | } 52 | output.flush() 53 | output.close() 54 | input.close() 55 | handler.post { callback.onDownloadingComplete() } 56 | if (nameFile.contains("clash")) { 57 | val x = exFile.toString().replace(".gz", "") 58 | CoreUtil.gZipExtractor( 59 | exFile.toString(), 60 | x 61 | ) 62 | CoreCmd.moveResult(x, path) 63 | } else { 64 | CoreUtil.tarExtractUsingRoot(exFile.toString(), path) 65 | } 66 | exFile!!.delete() 67 | } catch (e: Exception) { 68 | handler.post { 69 | exFile!!.delete() 70 | callback.onDownloadingFailed(e) 71 | } 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/core/util/IDownloadCore.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.core.util 2 | 3 | interface IDownloadCore { 4 | fun onDownloadingStart() 5 | fun onDownloadingProgress(progress: Int) 6 | fun onDownloadingComplete() 7 | fun onDownloadingFailed(e: Exception?) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/fragment/AppListFragment.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.fragment 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.core.widget.doOnTextChanged 9 | import androidx.fragment.app.Fragment 10 | import androidx.preference.PreferenceManager 11 | import androidx.recyclerview.widget.DividerItemDecoration 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import dagger.hilt.android.AndroidEntryPoint 14 | import rx.android.schedulers.AndroidSchedulers 15 | import rx.schedulers.Schedulers 16 | import xyz.chz.bfm.R 17 | import xyz.chz.bfm.adapter.AppListAdapter 18 | import xyz.chz.bfm.data.AppInfo 19 | import xyz.chz.bfm.data.AppManager 20 | import xyz.chz.bfm.databinding.FragmentAppListBinding 21 | import xyz.chz.bfm.util.Util 22 | import xyz.chz.bfm.util.command.TermCmd 23 | import xyz.chz.bfm.util.setMyFab 24 | import xyz.chz.bfm.util.showKeyboard 25 | import java.text.Collator 26 | 27 | @AndroidEntryPoint 28 | class AppListFragment : Fragment() { 29 | 30 | private lateinit var binding: FragmentAppListBinding 31 | 32 | private var adapter: AppListAdapter? = null 33 | private var appsAll: List? = null 34 | private val defaultsSharedPreferences by lazy { 35 | PreferenceManager.getDefaultSharedPreferences( 36 | requireActivity() 37 | ) 38 | } 39 | 40 | override fun onCreateView( 41 | inflater: LayoutInflater, container: ViewGroup?, 42 | savedInstanceState: Bundle? 43 | ): View { 44 | binding = FragmentAppListBinding.inflate(layoutInflater, container, false) 45 | return binding.root 46 | } 47 | 48 | 49 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 50 | super.onViewCreated(view, savedInstanceState) 51 | binding.apply { 52 | val dividerItemDecoration = 53 | DividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL) 54 | rvApps.addItemDecoration(dividerItemDecoration) 55 | 56 | val applist = TermCmd.appidList 57 | 58 | AppManager.rxLoadNetworkAppList(requireActivity()) 59 | .subscribeOn(Schedulers.io()) 60 | .map { 61 | if (applist != null) { 62 | it.forEach { one -> 63 | if ((applist.contains(one.packageName))) { 64 | one.isSelected = 1 65 | } else { 66 | one.isSelected = 0 67 | } 68 | } 69 | val comparator = Comparator { p1, p2 -> 70 | when { 71 | p1.isSelected > p2.isSelected -> -1 72 | p1.isSelected == p2.isSelected -> 0 73 | else -> 1 74 | } 75 | } 76 | it.sortedWith(comparator) 77 | } else { 78 | val comparator = object : Comparator { 79 | val collator = Collator.getInstance() 80 | override fun compare(o1: AppInfo, o2: AppInfo) = 81 | collator.compare(o1.appName, o2.appName) 82 | } 83 | it.sortedWith(comparator) 84 | } 85 | } 86 | .observeOn(AndroidSchedulers.mainThread()) 87 | .subscribe { 88 | appsAll = it 89 | adapter = AppListAdapter(requireActivity(), it, applist) 90 | rvApps.adapter = adapter 91 | prgWaiting.visibility = View.GONE 92 | } 93 | 94 | with(fbSave) { 95 | setOnClickListener { 96 | setMyFab("#888F96", R.drawable.ic_commit) 97 | if (Util.isProxyed) { 98 | TermCmd.renewBox { 99 | Util.runOnUiThread { 100 | if (it) { 101 | setMyFab("#6fa251", R.drawable.ic_done) 102 | } else { 103 | setMyFab("#EC7474", R.drawable.ic_error) 104 | } 105 | } 106 | } 107 | } 108 | adapter?.let { 109 | TermCmd.setAppidList(it.blacklist) 110 | } 111 | } 112 | } 113 | } 114 | setupSearchApp() 115 | setupSelect() 116 | } 117 | 118 | @SuppressLint("NotifyDataSetChanged") 119 | private fun setupSelect() = with(binding) { 120 | selectAll.setOnClickListener { 121 | adapter?.let { 122 | if (it.blacklist.containsAll(it.apps.map { it.packageName })) { 123 | it.apps.forEach { 124 | adapter?.blacklist!!.remove(it.packageName) 125 | } 126 | } else { 127 | it.apps.forEach { 128 | adapter?.blacklist!!.add(it.packageName) 129 | 130 | } 131 | } 132 | it.notifyDataSetChanged() 133 | } 134 | } 135 | } 136 | 137 | private fun setupSearchApp() = with(binding) { 138 | searchBtn.setOnClickListener { 139 | tvInfoApps.visibility = View.GONE 140 | edSearch.visibility = View.VISIBLE 141 | edSearch.doOnTextChanged { text, _, _, _ -> 142 | search(text.toString()) 143 | } 144 | edSearch.showKeyboard() 145 | } 146 | } 147 | 148 | @SuppressLint("NotifyDataSetChanged") 149 | private fun search(str: String) { 150 | val apps = ArrayList() 151 | 152 | val key = str.uppercase() 153 | if (key.isNotEmpty()) { 154 | appsAll?.forEach { 155 | if (it.appName.uppercase().indexOf(key) >= 0 156 | || it.packageName.uppercase().indexOf(key) >= 0 157 | ) { 158 | apps.add(it) 159 | } 160 | } 161 | } else { 162 | appsAll?.forEach { 163 | apps.add(it) 164 | } 165 | } 166 | 167 | adapter = AppListAdapter(requireActivity(), apps, adapter?.blacklist) 168 | binding.rvApps.adapter = adapter 169 | adapter?.notifyDataSetChanged() 170 | } 171 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/fragment/ConfigHelperFragment.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.fragment 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.appcompat.app.AlertDialog 9 | import androidx.fragment.app.Fragment 10 | import xyz.chz.bfm.R 11 | import xyz.chz.bfm.databinding.FragmentConfigHelperBinding 12 | import xyz.chz.bfm.ui.converter.ConverterActivity 13 | import xyz.chz.bfm.util.Util 14 | import xyz.chz.bfm.util.command.TermCmd 15 | import xyz.chz.bfm.util.terminal.TerminalHelper.execRootCmd 16 | import xyz.chz.bfm.util.toast 17 | 18 | 19 | class ConfigHelperFragment : Fragment() { 20 | 21 | private lateinit var binding: FragmentConfigHelperBinding 22 | private var statePath: String? = null 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View? { 28 | binding = FragmentConfigHelperBinding.inflate(layoutInflater, container, false) 29 | return binding.root 30 | } 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | setupEditor() 35 | initEditor() 36 | editorApply() 37 | } 38 | 39 | private fun setupEditor() = with(binding.myEditor) { 40 | languageRuleBook = null 41 | lineNumberGenerator = { l -> 42 | (1..l).map { " $it " } 43 | } 44 | editable = true 45 | showDivider = false 46 | showMinimap = false 47 | } 48 | 49 | private fun initEditor() = with(binding) { 50 | dialogConfig() 51 | } 52 | 53 | private fun editorApply() = with(binding) { 54 | fbSave.apply { 55 | setOnClickListener { 56 | dialogSave() 57 | } 58 | } 59 | 60 | fbConverter.apply { 61 | setOnClickListener { 62 | val intent = Intent(context, ConverterActivity::class.java) 63 | startActivity(intent) 64 | } 65 | } 66 | } 67 | 68 | private fun dialogConfig() { 69 | val builder = AlertDialog.Builder( 70 | requireActivity(), 71 | R.style.ThemeOverlay_MaterialComponents_MaterialAlertDialog_background 72 | ) 73 | builder.setCancelable(false) 74 | val items = TermCmd.proxyProviderPath.toTypedArray() 75 | builder.setItems(items) { _, w -> 76 | val configSlected = execRootCmd("cat ${items[w]}") 77 | if (configSlected.isNotEmpty()) { 78 | binding.myEditor.text = configSlected 79 | } else { 80 | toast("Empty file", requireActivity()) 81 | } 82 | statePath = items[w] 83 | 84 | } 85 | val dialog = builder.create() 86 | dialog.show() 87 | } 88 | 89 | private fun dialogSave() { 90 | val builder = AlertDialog.Builder( 91 | requireActivity(), 92 | R.style.ThemeOverlay_MaterialComponents_MaterialAlertDialog_background 93 | ) 94 | builder.setTitle("Save") 95 | builder.setMessage("Do you want save config now ?") 96 | builder.setPositiveButton("yes") { _, _ -> 97 | val input = binding.myEditor.text 98 | TermCmd.saveConfig(requireActivity(), input, statePath!!) { 99 | Util.runOnUiThread { 100 | if (it) 101 | toast(requireActivity().getString(R.string.success), requireActivity()) 102 | else 103 | toast(requireActivity().getString(R.string.failed), requireActivity()) 104 | } 105 | } 106 | } 107 | builder.setNegativeButton("cancel") { d, _ -> 108 | d.dismiss() 109 | } 110 | val dialog = builder.create() 111 | dialog.show() 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/fragment/DashboardFragment.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.fragment 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.webkit.WebSettings 9 | import android.webkit.WebViewClient 10 | import androidx.fragment.app.DialogFragment 11 | import androidx.fragment.app.Fragment 12 | import dagger.hilt.android.AndroidEntryPoint 13 | import xyz.chz.bfm.databinding.FragmentDashboardBinding 14 | import xyz.chz.bfm.dialog.IMakeDialog 15 | import xyz.chz.bfm.dialog.MakeDialog 16 | import xyz.chz.bfm.util.Util 17 | import xyz.chz.bfm.util.command.SettingCmd 18 | import xyz.chz.bfm.util.command.TermCmd 19 | 20 | @AndroidEntryPoint 21 | class DashboardFragment : Fragment(), IMakeDialog { 22 | 23 | private lateinit var binding: FragmentDashboardBinding 24 | override fun onCreateView( 25 | inflater: LayoutInflater, container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View { 28 | binding = FragmentDashboardBinding.inflate(layoutInflater, container, false) 29 | return binding.root 30 | } 31 | 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | if (Util.isClashOrSing) { 36 | showRes() 37 | } else { 38 | MakeDialog( 39 | "Wowo :( ", 40 | "Your core is not clash or sing-box so dashboard not available", 41 | false, 42 | false 43 | ).show(requireActivity().supportFragmentManager, "") 44 | } 45 | } 46 | 47 | @SuppressLint("SetJavaScriptEnabled") 48 | private fun showRes() = with(binding) { 49 | val linkDB: String = if (SettingCmd.core == "clash") { 50 | TermCmd.linkDBClash 51 | } else { 52 | TermCmd.linkDBSing 53 | } 54 | dbWebview.loadUrl("${linkDB}/ui/#/proxies") 55 | 56 | with(dbWebview.settings) { 57 | domStorageEnabled = true 58 | databaseEnabled = true 59 | allowContentAccess = true 60 | javaScriptEnabled = true 61 | cacheMode = WebSettings.LOAD_NO_CACHE 62 | dbWebview.webViewClient = WebViewClient() 63 | } 64 | } 65 | 66 | override fun onDialogPositiveButton(dialog: DialogFragment) { 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/ui/model/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.ui.model 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.Job 10 | import kotlinx.coroutines.delay 11 | import kotlinx.coroutines.isActive 12 | import kotlinx.coroutines.launch 13 | import okhttp3.Call 14 | import okhttp3.Callback 15 | import okhttp3.Response 16 | import xyz.chz.bfm.util.OkHttpHelper 17 | import xyz.chz.bfm.util.command.TermCmd 18 | import xyz.chz.bfm.util.myReplacer 19 | import javax.inject.Inject 20 | 21 | @HiltViewModel 22 | class MainViewModel @Inject constructor() : ViewModel() { 23 | 24 | private val _log = MutableLiveData() 25 | val log: LiveData get() = _log 26 | 27 | private val _theresult = MutableLiveData() 28 | val theresult: LiveData = _theresult 29 | 30 | fun dataLog(): Job { 31 | return CoroutineScope(Dispatchers.IO).launch { 32 | while (isActive) { 33 | _log.postValue( 34 | TermCmd.readLog().myReplacer( 35 | "\\[Info\\]".toRegex() to "[Info] ", 36 | "\\[Debug\\]".toRegex() to "[Debug] ", 37 | "\\[Error\\]".toRegex() to "[Error] ", 38 | "\\[Warning\\]".toRegex() to "[Warning] ", 39 | "\n".toRegex() to "
" 40 | ) 41 | ) 42 | delay(1500) 43 | } 44 | } 45 | } 46 | 47 | fun getTextFromUrl(url: String) { 48 | OkHttpHelper().getRawTextFromURL(url, object : Callback { 49 | override fun onFailure(call: Call, e: java.io.IOException) { 50 | _theresult.value = e.message!! 51 | } 52 | 53 | override fun onResponse(call: Call, response: Response) { 54 | _theresult.postValue(response.body!!.string()) 55 | } 56 | }) 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/Constant.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util 2 | 3 | // bcs yq yaml cant get emojis 4 | // dont make this to regex 5 | const val EXTRACTOR = 6 | "sed 's/\\[//g' | sed 's/\\]//g' | sed 's/\"//g' | sed 's/,//g' | sed 's/ //g' | awk 'NF'" 7 | const val QUOTES = "sed 's/\"//g'" 8 | const val META_REPO = "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" 9 | const val META_DOWNLOAD = "https://github.com/MetaCubeX/mihomo/releases/download" 10 | const val SING_REPO = "https://api.github.com/repos/shioeri/sing-box/releases" 11 | const val SING_DOWNLOAD = "https://github.com/shioeri/sing-box/releases/download" -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/OkHttpHelper.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util 2 | 3 | import okhttp3.Call 4 | import okhttp3.Callback 5 | import okhttp3.OkHttpClient 6 | import okhttp3.Request 7 | 8 | class OkHttpHelper { 9 | fun getRawTextFromURL(url: String, callback: Callback): Call { 10 | val client = OkHttpClient() 11 | val request = Request.Builder() 12 | .url(url) 13 | .build() 14 | 15 | val call = client.newCall(request) 16 | call.enqueue(callback) 17 | return call 18 | } 19 | 20 | fun reqGithub(url: String, callback: Callback): Call { 21 | val client = OkHttpClient() 22 | val request = Request.Builder() 23 | .url(url) 24 | .addHeader("Accept", "application/vnd.github+json") 25 | .addHeader("X-GitHub-Api-Version", "2022-11-28") 26 | .build() 27 | 28 | val call = client.newCall(request) 29 | call.enqueue(callback) 30 | return call 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/Util.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import org.json.JSONArray 6 | import org.json.JSONObject 7 | import xyz.chz.bfm.util.command.SettingCmd 8 | import xyz.chz.bfm.util.command.TermCmd 9 | import java.util.ArrayDeque 10 | import java.util.Deque 11 | 12 | object Util { 13 | var isProxyed = TermCmd.isProxying() 14 | private val handler = Handler(Looper.getMainLooper()) 15 | 16 | fun runOnUiThread(action: () -> Unit) { 17 | if (Looper.myLooper() != Looper.getMainLooper()) { 18 | handler.post(action) 19 | } else { 20 | action.invoke() 21 | } 22 | } 23 | 24 | val isClashOrSing: Boolean 25 | get() { 26 | if (SettingCmd.core.contains("clash") or SettingCmd.core.contains("sing-box")) 27 | return true 28 | return false 29 | } 30 | 31 | fun json(build: JsonObjectBuilder.() -> Unit): JSONObject { 32 | return JsonObjectBuilder().json(build) 33 | } 34 | 35 | class JsonObjectBuilder { 36 | private val deque: Deque = ArrayDeque() 37 | 38 | fun json(build: JsonObjectBuilder.() -> Unit): JSONObject { 39 | deque.push(JSONObject()) 40 | this.build() 41 | return deque.pop() 42 | } 43 | 44 | infix fun String.to(value: T) { 45 | val wrapped = when (value) { 46 | is Function0<*> -> json { value.invoke() } 47 | is Array<*> -> JSONArray().apply { value.forEach { put(it) } } 48 | else -> value 49 | } 50 | 51 | deque.peek().put(this, wrapped) 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/ViewUtils.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.content.ClipData 6 | import android.content.ClipboardManager 7 | import android.content.Context 8 | import android.content.res.ColorStateList 9 | import android.graphics.Color 10 | import android.os.Build 11 | import android.text.Html 12 | import android.text.Spanned 13 | import android.util.TypedValue 14 | import android.view.inputmethod.InputMethodManager 15 | import android.widget.EditText 16 | import android.widget.ImageView 17 | import android.widget.TextView 18 | import android.widget.Toast 19 | import com.google.android.material.card.MaterialCardView 20 | import com.google.android.material.floatingactionbutton.FloatingActionButton 21 | import xyz.chz.bfm.ui.converter.config.ConfigType 22 | import xyz.chz.bfm.util.modul.ModuleManager 23 | import java.net.HttpURLConnection 24 | import java.net.URL 25 | 26 | 27 | fun MaterialCardView.setColorBackground(str: String) { 28 | this.setCardBackgroundColor(Color.parseColor(str)) 29 | this.radius = 30 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, context.resources.displayMetrics) 31 | } 32 | 33 | fun ImageView.setImage(res: Int) { 34 | this.setImageResource(res) 35 | } 36 | 37 | fun TextView.moduleVer() { 38 | this.text = ModuleManager.moduleVersion 39 | } 40 | 41 | fun FloatingActionButton.setMyFab(color: String, res: Int) { 42 | this.backgroundTintList = ColorStateList.valueOf(Color.parseColor(color)) 43 | this.setImageResource(res) 44 | } 45 | 46 | fun toast(str: String, ctx: Context) { 47 | Toast.makeText(ctx, str, Toast.LENGTH_SHORT).show() 48 | } 49 | 50 | fun String.myReplacer(vararg replacements: Pair): String = 51 | replacements.fold(this) { acc, (old, new) -> acc.replace(old, new) } 52 | 53 | @SuppressLint("ObsoleteSdkInt") 54 | fun setTextHtml(text: String): Spanned { 55 | val res: Spanned = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 56 | Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY) 57 | } else { 58 | Html.fromHtml(text) 59 | } 60 | return res 61 | } 62 | 63 | fun EditText.hideKeyboard(): Boolean { 64 | return (context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager) 65 | .hideSoftInputFromWindow(windowToken, 0) 66 | } 67 | 68 | fun EditText.showKeyboard(): Boolean { 69 | return (context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager) 70 | .showSoftInput(this, 0) 71 | } 72 | 73 | fun EditText.removeEmptyLines(): String { 74 | //val regex = Regex() 75 | return this.text.replace("(?m)^[ \t]*\r?\n".toRegex(), "") 76 | } 77 | 78 | fun TextView.copyToClipboard(context: Context) { 79 | (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) 80 | .setPrimaryClip(ClipData.newPlainText("copied", this.text.toString())) 81 | } 82 | 83 | fun EditText.isValidCheck(): Boolean { 84 | if (this.text.toString().isNotEmpty() || this.text.toString() 85 | .startsWith(ConfigType.VMESS.scheme) || this.text.toString().startsWith( 86 | ConfigType.VLESS.scheme 87 | ) || this.text.toString().startsWith(ConfigType.TROJAN.scheme) || this.text.toString() 88 | .startsWith(ConfigType.TROJANGO.scheme) 89 | ) { 90 | return true 91 | } 92 | return false 93 | } 94 | 95 | fun String.urlText(): String { 96 | return URL(this).run { 97 | openConnection().run { 98 | this as HttpURLConnection 99 | inputStream.bufferedReader().readText().trim() 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/command/CoreCmd.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util.command 2 | 3 | import xyz.chz.bfm.util.terminal.TerminalHelper.execRootCmd 4 | import xyz.chz.bfm.util.terminal.TerminalHelper.execRootCmdSilent 5 | 6 | object CoreCmd { 7 | private const val path = "/data/adb/box" 8 | 9 | val checkVerSing: String 10 | get() { 11 | val cmd = "$path/bin/sing-box version | awk '{print $3}' | awk '{print $1; exit}'" 12 | return execRootCmd(cmd) 13 | } 14 | 15 | val checkVerClashMeta: String 16 | get() { 17 | val cmd = "$path/bin/xclash/mihomo -v | awk '{print $3}' | awk '{print $1; exit}'" 18 | return execRootCmd(cmd) 19 | } 20 | 21 | fun moveResult(pathOut: String, pathDir: String) { 22 | val cmd = "mv -f $pathOut $pathDir && chmod 755 $pathDir && chown root:net_admin $pathDir" 23 | execRootCmdSilent(cmd) 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/command/SettingCmd.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util.command 2 | 3 | import xyz.chz.bfm.util.terminal.TerminalHelper.execRootCmd 4 | 5 | object SettingCmd { 6 | 7 | val networkMode: String 8 | get() = execRootCmd("grep 'network_mode=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 9 | 10 | fun setNetworkMode(mode: String): String { 11 | return execRootCmd("sed -i 's/network_mode=.*/network_mode=\"$mode\"/;' /data/adb/box/settings.ini") 12 | } 13 | 14 | val proxyMode: String 15 | get() = execRootCmd("sed -n 's/^mode:\\([^ ]*\\).*/\\1/p' /data/adb/box/package.list.cfg") 16 | 17 | fun setProxyMode(mode: String): String { 18 | return execRootCmd("sed -i 's/^mode:[^ ]*/mode:$mode/' /data/adb/box/package.list.cfg") 19 | } 20 | 21 | val cron: String 22 | get() = execRootCmd("grep 'run_crontab=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 23 | 24 | fun setCron(mode: String): String { 25 | return execRootCmd("sed -i 's/run_crontab=.*/run_crontab=\"$mode\"/;' /data/adb/box/settings.ini") 26 | } 27 | 28 | val geo: String 29 | get() = execRootCmd("grep 'update_geo=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 30 | 31 | fun setGeo(mode: String): String { 32 | return execRootCmd("sed -i 's/update_geo=.*/update_geo=\"$mode\"/;' /data/adb/box/settings.ini") 33 | } 34 | 35 | val memcg: String 36 | get() = execRootCmd("grep 'cgroup_memcg=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 37 | 38 | fun setMemcg(mode: String): String { 39 | return execRootCmd("sed -i 's/cgroup_memcg=.*/cgroup_memcg=\"$mode\"/;' /data/adb/box/settings.ini") 40 | } 41 | 42 | val blkio: String 43 | get() = execRootCmd("grep 'cgroup_blkio=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 44 | 45 | fun setBlkio(mode: String): String { 46 | return execRootCmd("sed -i 's/cgroup_blkio=.*/cgroup_blkio=\"$mode\"/;' /data/adb/box/settings.ini") 47 | } 48 | 49 | val cpuset: String 50 | get() = execRootCmd("grep 'cgroup_cpuset=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 51 | 52 | fun setCpuset(mode: String): String { 53 | return execRootCmd("sed -i 's/cgroup_cpuset=.*/cgroup_cpuset=\"$mode\"/;' /data/adb/box/settings.ini") 54 | } 55 | 56 | val subs: String 57 | get() = execRootCmd("grep 'update_subscription=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 58 | 59 | fun setSubs(mode: String): String { 60 | return execRootCmd("sed -i 's/update_subscription=.*/update_subscription=\"$mode\"/;' /data/adb/box/settings.ini") 61 | } 62 | 63 | val redirHost: Boolean 64 | get() = "redir-host" == execRootCmd("grep 'enhanced-mode:' /data/adb/box/clash/config.yaml | awk '{print $2}'") 65 | 66 | fun setRedirHost(mode: String): String { 67 | return execRootCmd("sed -i 's/enhanced-mode:.*/enhanced-mode: $mode/;' /data/adb/box/clash/config.yaml") 68 | } 69 | 70 | val quic: Boolean 71 | get() = "enable" == execRootCmd("grep 'quic=' /data/adb/box/scripts/box.iptables | sed 's/^.*=//' | sed 's/\"//g'") 72 | 73 | fun setQuic(mode: String): String { 74 | return execRootCmd("sed -i 's/quic=.*/quic=\"$mode\"/;' /data/adb/box/scripts/box.iptables") 75 | } 76 | 77 | val unified: Boolean 78 | get() = "true" == execRootCmd("grep 'unified-delay:' /data/adb/box/clash/config.yaml | awk '{print $2}'") 79 | 80 | fun setUnified(mode: String): String { 81 | return execRootCmd("sed -i 's/unified-delay:.*/unified-delay: $mode/;' /data/adb/box/clash/config.yaml") 82 | } 83 | 84 | val geodata: Boolean 85 | get() = "true" == execRootCmd("grep 'geodata-mode:' /data/adb/box/clash/config.yaml | awk '{print $2}'") 86 | 87 | fun setGeodata(mode: String): String { 88 | return execRootCmd("sed -i 's/geodata-mode:.*/geodata-mode: $mode/;' /data/adb/box/clash/config.yaml") 89 | } 90 | 91 | val tcpCon: Boolean 92 | get() = "true" == execRootCmd("grep 'tcp-concurrent:' /data/adb/box/clash/config.yaml | awk '{print $2}'") 93 | 94 | fun setTcpCon(mode: String): String { 95 | return execRootCmd("sed -i 's/tcp-concurrent:.*/tcp-concurrent: $mode/;' /data/adb/box/clash/config.yaml") 96 | } 97 | 98 | val sniffer: Boolean 99 | get() = "true" == execRootCmd("grep -C 1 'sniffer:' /data/adb/box/clash/config.yaml | grep 'enable:' | awk '{print $2}'") 100 | 101 | fun setSniffer(mode: String): String { 102 | return execRootCmd("sed -i '/^sniffer:/{n;s/enable:.*/enable: $mode/;}' /data/adb/box/clash/config.yaml") 103 | } 104 | 105 | val ipv6: Boolean 106 | get() = "true" == execRootCmd("grep 'ipv6=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 107 | 108 | fun setIpv6(mode: String): String { 109 | return execRootCmd("sed -i 's/ipv6=.*/ipv6=\"$mode\"/;' /data/adb/box/settings.ini") 110 | } 111 | 112 | val findProc: String 113 | get() = execRootCmd("grep 'find-process-mode:' /data/adb/box/clash/config.yaml | awk '{print $2}'") 114 | 115 | fun setFindProc(mode: String): String { 116 | return execRootCmd("sed -i 's/find-process-mode:.*/find-process-mode: $mode/;' /data/adb/box/clash/config.yaml") 117 | } 118 | 119 | val findConf: String 120 | get() = execRootCmd("grep 'name_clash_config=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 121 | 122 | fun setFindConf(mode: String): String { 123 | return execRootCmd("sed -i 's/name_clash_config=.*/name_clash_config=\"$mode\"/;' /data/adb/box/settings.ini") 124 | } 125 | 126 | val clashType: String 127 | get() = execRootCmd("grep 'xclash_option=' /data/adb/box/settings.ini | sed 's/^.*=//' | sed 's/\"//g'") 128 | 129 | fun setClashType(mode: String): String { 130 | return execRootCmd("sed -i 's/xclash_option=.*/xclash_option=\"$mode\"/;' /data/adb/box/settings.ini") 131 | } 132 | 133 | val core: String 134 | get() = execRootCmd("grep 'bin_name=' /data/adb/box/settings.ini | sed 's/^.*=//g' | sed 's/\"//g'") 135 | 136 | // fun setCore(x: String): Boolean { 137 | // return execRootCmdSilent("sed -i 's/bin_name=.*/bin_name=$x/;' /data/adb/box/settings.ini") != -1 138 | // } 139 | 140 | var setCore: String = "" 141 | set(value) { 142 | field = value 143 | execRootCmd("sed -i 's/bin_name=.*/bin_name=$field/;' /data/adb/box/settings.ini") 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/command/TermCmd.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util.command 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import xyz.chz.bfm.util.EXTRACTOR 6 | import xyz.chz.bfm.util.QUOTES 7 | import xyz.chz.bfm.util.terminal.TerminalHelper.execRootCmd 8 | import xyz.chz.bfm.util.terminal.TerminalHelper.execRootCmdSilent 9 | import xyz.chz.bfm.util.terminal.TerminalHelper.execRootCmdVoid 10 | import java.io.File 11 | import java.io.FileOutputStream 12 | import kotlin.concurrent.thread 13 | 14 | object TermCmd { 15 | private const val path = "/data/adb/box" 16 | private const val yq = "${path}/bin/yq" 17 | 18 | fun isProxying(): Boolean { 19 | return execRootCmdSilent("if [ -f ${path}/run/box.pid ] ; then exit 1;fi") == 1 20 | } 21 | 22 | fun renewBox(callback: (Boolean) -> Unit) { 23 | thread { 24 | val cmd = "${path}/scripts/box.iptables renew" 25 | execRootCmdVoid(cmd, callback) 26 | } 27 | } 28 | 29 | fun start(callback: (Boolean) -> Unit) { 30 | thread { 31 | val cmd = "${path}/scripts/box.service start && ${path}/scripts/box.iptables enable" 32 | execRootCmdVoid(cmd, callback) 33 | } 34 | } 35 | 36 | fun stop(callback: (Boolean) -> Unit) { 37 | thread { 38 | val cmd = "${path}/scripts/box.iptables disable && ${path}/scripts/box.service stop" 39 | execRootCmdVoid(cmd, callback) 40 | } 41 | } 42 | 43 | fun readLog(): String { 44 | return execRootCmd("cat ${path}/run/runs.log") 45 | } 46 | 47 | val linkDBClash: String 48 | get() { 49 | return execRootCmd("grep 'external-controller:' ${path}/clash/config.yaml | awk '{print $2}'") 50 | } 51 | 52 | 53 | val linkDBSing: String 54 | get() { 55 | val cmd = 56 | "grep -w 'external_controller' ${path}/sing-box/config.json | awk '{print $2}' | sed 's/\"//g' | sed 's/,//g'" 57 | return execRootCmd(cmd) 58 | } 59 | 60 | private val proxyProviderJsonKey: HashSet 61 | get() { 62 | val s = HashSet() 63 | val cmd = "$yq -oj '.proxy-providers | keys' $path/clash/config.yaml | $EXTRACTOR" 64 | val result = execRootCmd(cmd) 65 | if (result.isEmpty()) 66 | return s 67 | val list = result.split("\n") 68 | for (x in list) { 69 | s.add(x) 70 | } 71 | return s 72 | } 73 | 74 | val proxyProviderPath: HashSet 75 | @SuppressLint("SuspiciousIndentation") 76 | get() { 77 | val s = HashSet() 78 | val ppKey = proxyProviderJsonKey 79 | for (xs in ppKey) { 80 | if (SettingCmd.core == "clash") { 81 | val pth = 82 | execRootCmd("exp=$xs $yq -oj '.proxy-providers.[env(exp)].path' $getPathOnly | $QUOTES") 83 | if (pth.contains("./")) { 84 | s.add(pth.replace("./", "$path/clash/")) 85 | } else { 86 | s.add(pth) 87 | } 88 | } 89 | } 90 | s.add(getPathOnly) 91 | return s 92 | } 93 | 94 | val appidList: HashSet get() { 95 | val s = HashSet() 96 | val modeCommand = "sed -n 's/^\\(mode:[^ ]*\\).*/\\1/p' ${path}/package.list.cfg" 97 | val packageCommand = "sed -n '/^[^#]/s/^\\([^ ]*\\.[^ ]*\\).*/\\1/p' ${path}/package.list.cfg" 98 | val gidCommand = "sed -n '/^[^#]/s/^\\([0-9]\\{1,8\\}\\).*/\\1/p' ${path}/package.list.cfg" 99 | 100 | val modeResult = execRootCmd(modeCommand) 101 | val packageResult = execRootCmd(packageCommand) 102 | val gidResult = execRootCmd(gidCommand) 103 | 104 | val result = """ 105 | $modeResult 106 | $packageResult 107 | $gidResult 108 | """.trimIndent() 109 | 110 | if (result.isEmpty()) { 111 | return s 112 | } 113 | 114 | val appIds = result.split("\n") 115 | for (i in appIds) { 116 | if (i.isNotEmpty() && !i.startsWith("alook")) { 117 | s.add(i.trim()) 118 | } 119 | } 120 | return s 121 | } 122 | 123 | fun setAppidList(s: HashSet): Boolean { 124 | val content = s.filterNotNull() 125 | .filter { it.isNotEmpty() } 126 | .joinToString(" ") 127 | 128 | val command = """ 129 | echo "$content" > "$path/package.list.cfg" && 130 | sed -i '/^#/!s/ /\'$'\n/g' "${path}/package.list.cfg" 131 | sed -i '/alook\\|999_alook/s/^/#/' "${path}/package.list.cfg" 132 | """.trimIndent() 133 | 134 | return execRootCmdSilent(command) != -1 135 | 136 | } 137 | 138 | private fun getNameConfig(what: String, isClash: Boolean): String { 139 | val m = if (isClash) "yaml" else "json" 140 | return execRootCmd("find ${path}/$what/ -maxdepth 1 -name 'config.$m' -type f -exec basename {} \\;") 141 | } 142 | 143 | val getPathOnly: String 144 | get() { 145 | val what = SettingCmd.core 146 | val isClash = what == "clash" 147 | val name = getNameConfig(what, isClash) 148 | return "$path/$what/$name" 149 | } 150 | 151 | fun saveConfig(ctx: Context, str: String, pth: String, callback: (Boolean) -> Unit) { 152 | thread { 153 | val exFile = File(ctx.getExternalFilesDir(null), "out.txt") 154 | val fos = FileOutputStream(exFile) 155 | fos.write(str.toByteArray()) 156 | val cmd = "mv -f $exFile $pth" 157 | execRootCmdVoid(cmd, callback) 158 | } 159 | } 160 | 161 | private fun yqParser(dir: String, config: String, isClash: Boolean): String { 162 | return if (isClash) { 163 | val yamlToJson = "$yq -oj ${path}/${dir}/${config} > ${path}/${dir}/xtemp.json" 164 | execRootCmd("$yamlToJson && $yq -oy ${path}/${dir}/xtemp.json > ${path}/${dir}/${config} && rm -f ${path}/${dir}/xtemp.json && cat ${path}/${dir}/${config}") 165 | } else { 166 | execRootCmd("$yq -oj ${path}/${dir}/${config} > ${path}/${dir}/xtemp.json && mv -f ${path}/${dir}/xtemp.json ${path}/${dir}/${config} && cat ${path}/${dir}/${config}") 167 | } 168 | } 169 | 170 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/modul/ModuleManager.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util.modul 2 | 3 | import xyz.chz.bfm.util.terminal.TerminalHelper 4 | 5 | object ModuleManager { 6 | val moduleVersionCode: String 7 | get() { 8 | val cmd = "cat /data/adb/modules/box_for_root/module.prop | grep '^versionCode='" 9 | val result = TerminalHelper.execRootCmd(cmd) 10 | return if (result.isEmpty()) "" else result.split("=")[1] 11 | } 12 | 13 | val moduleVersion: String 14 | get() { 15 | val cmd = "cat /data/adb/modules/box_for_root/module.prop | grep '^version='" 16 | val result = TerminalHelper.execRootCmd(cmd) 17 | return if (result.isEmpty()) "" else result.split("=")[1] 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/chz/bfm/util/terminal/TerminalHelper.kt: -------------------------------------------------------------------------------- 1 | package xyz.chz.bfm.util.terminal 2 | 3 | import android.util.Log 4 | import xyz.chz.bfm.BuildConfig 5 | 6 | object TerminalHelper { 7 | private const val TAG = "BoxForRoot.Terminal" 8 | 9 | fun execRootCmd(cmd: String): String { 10 | return try { 11 | val process: Process = Runtime.getRuntime().exec("su -c $cmd") 12 | process.waitFor() 13 | val output = process.inputStream.bufferedReader().lineSequence().joinToString("\n") 14 | if (BuildConfig.DEBUG) Log.d(TAG, output) 15 | output 16 | } catch (e: Exception) { 17 | "" 18 | } 19 | } 20 | 21 | fun execRootCmdSilent(cmd: String): Int { 22 | return try { 23 | val process: Process = Runtime.getRuntime().exec("su -c $cmd") 24 | process.waitFor() 25 | process.exitValue() 26 | } catch (e: Exception) { 27 | -1 28 | } 29 | } 30 | 31 | fun execRootCmdVoid(cmd: String, callback: (Boolean) -> Unit) { 32 | try { 33 | val process = Runtime.getRuntime().exec("su -c $cmd") 34 | process.waitFor() 35 | callback(process.exitValue() == 0) 36 | } catch (e: Exception) { 37 | e.printStackTrace() 38 | callback(false) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_app.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_commit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_config.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_converter.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_disabled.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 15 | 18 | 22 | 25 | 28 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_download.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_enabled.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 14 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 15 | 18 | 22 | 25 | 28 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taamarin/box.manager/09f5683086e73d66beaa6725dcaf64b19d088ed5/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_loading.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_log.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_modul.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_all.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_converter.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 21 | 22 | 26 | 27 | 32 | 33 | 38 | 39 | 40 |