├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── ic_launcher-playstore.png
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── ic_default_new.png
│ │ │ │ ├── bitmap_shadow_material.xml
│ │ │ │ ├── bitmap_shadow_harmony.xml
│ │ │ │ ├── ic_file.xml
│ │ │ │ ├── ic_file_upload.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── drag_mask.xml
│ │ │ │ ├── harmony_ic_public_file_filled.xml
│ │ │ │ └── harmony_ic_public_upload_filled.xml
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── bools.xml
│ │ │ │ ├── plurals.xml
│ │ │ │ ├── styles_harmony.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── info_strings.xml
│ │ │ │ ├── styles_material3.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── themes_harmony.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── styles_material.xml
│ │ │ │ ├── themes_material.xml
│ │ │ │ ├── themes_material3.xml
│ │ │ │ ├── themes_material2.xml
│ │ │ │ └── strings.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-v34
│ │ │ │ └── bools.xml
│ │ │ ├── values-zh-rCN
│ │ │ │ ├── plurals.xml
│ │ │ │ ├── info_strings.xml
│ │ │ │ └── strings.xml
│ │ │ ├── raw
│ │ │ │ └── about.html
│ │ │ ├── color
│ │ │ │ ├── system_window_scrim.xml
│ │ │ │ ├── secondary_emphasis_disabled_background.xml
│ │ │ │ ├── text_primary_selector.xml
│ │ │ │ └── text_secondary_selector.xml
│ │ │ ├── xml
│ │ │ │ ├── locales_config.xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ ├── menu
│ │ │ │ ├── menu_main_info.xml
│ │ │ │ ├── menu_legal.xml
│ │ │ │ ├── menu_main_info_basic.xml
│ │ │ │ └── menu_main.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values-v28
│ │ │ │ ├── themes_material.xml
│ │ │ │ ├── themes_material2.xml
│ │ │ │ └── themes_material3.xml
│ │ │ ├── values-v29
│ │ │ │ ├── themes_material.xml
│ │ │ │ ├── themes_material2.xml
│ │ │ │ └── themes_material3.xml
│ │ │ ├── values-v26
│ │ │ │ ├── themes_material.xml
│ │ │ │ ├── themes_material2.xml
│ │ │ │ └── themes_material3.xml
│ │ │ ├── values-night
│ │ │ │ └── colors.xml
│ │ │ └── layout
│ │ │ │ ├── drop_mask.xml
│ │ │ │ ├── item_list_harmony.xml
│ │ │ │ ├── item_list_material.xml
│ │ │ │ ├── activity_main_details.xml
│ │ │ │ ├── activity_main_permissions.xml
│ │ │ │ └── activity_main_basic.xml
│ │ ├── aidl
│ │ │ └── org
│ │ │ │ └── ohosdev
│ │ │ │ └── hapviewerandroid
│ │ │ │ ├── util
│ │ │ │ └── ExecuteResult.aidl
│ │ │ │ └── IUserService.aidl
│ │ ├── java
│ │ │ └── org
│ │ │ │ └── ohosdev
│ │ │ │ └── hapviewerandroid
│ │ │ │ ├── extensions
│ │ │ │ ├── RectExtensions.kt
│ │ │ │ ├── ReflectExtensions.kt
│ │ │ │ ├── FragmentExtensions.kt
│ │ │ │ ├── ClipDataExtensions.kt
│ │ │ │ ├── DragEventExtensions.kt
│ │ │ │ ├── ViewExtensions.kt
│ │ │ │ ├── ShizukuExtensions.kt
│ │ │ │ ├── RecyclerViewExtensions.kt
│ │ │ │ ├── ActivityExtensions.kt
│ │ │ │ ├── DialogExtensions.kt
│ │ │ │ ├── ContentResolverExtensions.kt
│ │ │ │ ├── UriExtensions.kt
│ │ │ │ ├── SnackBarExtensions.kt
│ │ │ │ ├── HapInfoExtensions.kt
│ │ │ │ ├── FileExtensions.kt
│ │ │ │ ├── ContextExtensions.kt
│ │ │ │ ├── BitmapExtensions.kt
│ │ │ │ └── DocumentFileExtensions.kt
│ │ │ │ ├── util
│ │ │ │ ├── ohos
│ │ │ │ │ └── Permission.kt
│ │ │ │ ├── event
│ │ │ │ │ ├── SnackBarEvent.kt
│ │ │ │ │ └── BaseEvent.kt
│ │ │ │ ├── ExecuteResult.kt
│ │ │ │ ├── SystemUtil.kt
│ │ │ │ ├── highlight
│ │ │ │ │ └── JSONHighlighter.kt
│ │ │ │ ├── helper
│ │ │ │ │ └── ShizukuServiceHelper.kt
│ │ │ │ └── ShizukuUtil.kt
│ │ │ │ ├── ui
│ │ │ │ ├── about
│ │ │ │ │ ├── AboutDialogFragment.kt
│ │ │ │ │ └── AboutDialogBuilder.kt
│ │ │ │ ├── common
│ │ │ │ │ ├── dialog
│ │ │ │ │ │ ├── AlertDialogBuilder.kt
│ │ │ │ │ │ ├── RequestPermissionDialogBuilder.kt
│ │ │ │ │ │ └── RequestPermissionDialogFragment.kt
│ │ │ │ │ └── BaseActivity.kt
│ │ │ │ └── main
│ │ │ │ │ ├── BasicInfoCard.kt
│ │ │ │ │ ├── PermissionsAdapter.kt
│ │ │ │ │ └── MoreInfoDialogFragment.kt
│ │ │ │ ├── app
│ │ │ │ ├── Constant.kt
│ │ │ │ ├── HapViewerApp.kt
│ │ │ │ └── AppPreference.kt
│ │ │ │ ├── view
│ │ │ │ ├── NestedScrollView.kt
│ │ │ │ ├── behavior
│ │ │ │ │ └── AppBarLayoutBehavior.kt
│ │ │ │ ├── AppBarLayout.kt
│ │ │ │ ├── AdvancedRecyclerView.kt
│ │ │ │ ├── list
│ │ │ │ │ ├── ListItemGroup.kt
│ │ │ │ │ └── ListItem.kt
│ │ │ │ └── drawable
│ │ │ │ │ └── ShadowBitmapDrawable.kt
│ │ │ │ ├── service
│ │ │ │ └── shizuku
│ │ │ │ │ └── UserService.kt
│ │ │ │ ├── manager
│ │ │ │ └── ThemeManager.kt
│ │ │ │ └── model
│ │ │ │ └── HapInfo.java
│ │ └── AndroidManifest.xml
│ ├── debug
│ │ └── res
│ │ │ ├── values-zh-rCN
│ │ │ └── strings.xml
│ │ │ └── values
│ │ │ └── strings.xml
│ ├── test
│ │ └── java
│ │ │ └── org
│ │ │ └── ohosdev
│ │ │ └── hapviewerandroid
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── org
│ │ └── ohosdev
│ │ └── hapviewerandroid
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── harmonystyle
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── bools.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── values-w600dp
│ │ │ │ └── bools.xml
│ │ │ ├── anim
│ │ │ │ ├── harmony_dialog_enter_interpolator.xml
│ │ │ │ ├── harmony_menu_enter.xml
│ │ │ │ ├── harmony_menu_exit.xml
│ │ │ │ ├── harmony_dialog_exit.xml
│ │ │ │ └── harmony_dialog_enter.xml
│ │ │ ├── values-night
│ │ │ │ ├── themes.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── colors.xml
│ │ │ ├── color
│ │ │ │ ├── harmony_on_background.xml
│ │ │ │ └── harmony_pressed.xml
│ │ │ ├── values-v28
│ │ │ │ └── themes.xml
│ │ │ ├── drawable
│ │ │ │ ├── harmony_divider_menu.xml
│ │ │ │ ├── harmony_divider_horizontal_buttons.xml
│ │ │ │ ├── harmony_divider.xml
│ │ │ │ ├── harmony_button_dialog.xml
│ │ │ │ ├── harmony_action_bar_item_background.xml
│ │ │ │ ├── harmony_menu_choice_background_indicator.xml
│ │ │ │ ├── harmony_list_choice_background_indicator.xml
│ │ │ │ ├── harmony_ic_public_more.xml
│ │ │ │ └── harmony_popupmenu_background.xml
│ │ │ ├── values-v29
│ │ │ │ └── themes.xml
│ │ │ ├── values-v26
│ │ │ │ └── themes.xml
│ │ │ └── layout
│ │ │ │ ├── harmony_alert_dialog_title.xml
│ │ │ │ ├── harmony_alert_dialog_button_bar.xml
│ │ │ │ └── harmony_alert_dialog.xml
│ │ └── java
│ │ │ └── org
│ │ │ └── ohosdev
│ │ │ └── hapviewerandroid
│ │ │ └── harmonystyle
│ │ │ ├── dialog
│ │ │ └── AlertDialogLayout.kt
│ │ │ └── drawable
│ │ │ └── ShadowDrawable.kt
│ ├── test
│ │ └── java
│ │ │ └── org
│ │ │ └── ohosdev
│ │ │ └── hapviewerandroid
│ │ │ └── harmonystyle
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── org
│ │ └── ohosdev
│ │ └── hapviewerandroid
│ │ └── harmonystyle
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── .idea
└── .gitignore
├── screenshot
└── all.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── gradle.properties
├── README.md
├── PrivacyPolicy.md
└── gradlew.bat
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/harmonystyle/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/harmonystyle/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/screenshot/all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/screenshot/all.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_default_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/drawable/ic_default_new.png
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 92dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-v34/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
--------------------------------------------------------------------------------
/app/src/debug/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | HAP查看器 Debug
4 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | HAP Viewer Debug
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/aidl/org/ohosdev/hapviewerandroid/util/ExecuteResult.aidl:
--------------------------------------------------------------------------------
1 | // ExecuteResult.aidl
2 | package org.ohosdev.hapviewerandroid.util;
3 |
4 | parcelable ExecuteResult;
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/westinyang/hap-viewer-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/harmonystyle/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values-w600dp/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/RectExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.graphics.Rect
4 |
5 | val Rect.ratio get() = width().toFloat() / height()
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/plurals.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 权限申请
5 |
6 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/anim/harmony_dialog_enter_interpolator.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{name}}
5 |
6 | {{description}}
7 |
8 | {{information}}
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/color/system_window_scrim.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/locales_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 13 01:11:23 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.4-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/secondary_emphasis_disabled_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/plurals.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Permission request
5 | - Permissions request
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/ohos/Permission.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util.ohos
2 |
3 | const val PREFIX_OHOS_PERMISSION = "ohos.permission."
4 | fun String.getOhosPermSortName() =
5 | if (this.startsWith(PREFIX_OHOS_PERMISSION)) this.substring(PREFIX_OHOS_PERMISSION.length) else null
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/color/harmony_on_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.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 |
17 | /.vscode
18 | /keystore
19 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/ReflectExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | inline fun T.setDeclaredField(name: String, value: Any?) {
4 | T::class.java.getDeclaredField(name).apply {
5 | isAccessible = true
6 | set(this@setDeclaredField, value)
7 | }
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/event/SnackBarEvent.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util.event
2 |
3 | import android.content.Context
4 | import androidx.annotation.StringRes
5 |
6 | class SnackBarEvent(val text: String) : BaseEvent() {
7 | constructor(context: Context, @StringRes resId: Int) : this(context.getString(resId))
8 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bitmap_shadow_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/text_primary_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/color/text_secondary_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values-v28/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/anim/harmony_menu_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/anim/harmony_menu_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/FragmentExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 |
6 | /**
7 | * 确保存在参数Bundle。如果不存在,则创建一个空Bundle。
8 | * */
9 | fun Fragment.ensureArguments(): Bundle {
10 | arguments?.let { return it }
11 | return Bundle().apply { arguments = this }
12 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_legal.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bitmap_shadow_harmony.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v28/themes_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_divider_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
8 |
9 |
10 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_divider_horizontal_buttons.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/aidl/org/ohosdev/hapviewerandroid/IUserService.aidl:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid;
2 |
3 | import org.ohosdev.hapviewerandroid.util.ExecuteResult;
4 |
5 | interface IUserService {
6 |
7 | void destroy() = 16777114; // Destroy method defined by Shizuku server
8 |
9 | void exit() = 1; // Exit method defined by user
10 |
11 | ExecuteResult execute(in List cmdarray, in List envp, String dir) = 2;
12 |
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-v28/themes_material2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v28/themes_material3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/color/harmony_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/ClipDataExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.content.ClipData
4 | import android.net.Uri
5 |
6 | /**
7 | * 获取 ClipData 内第一个 Uri
8 | * */
9 | fun ClipData.getFirstUri(): Uri? {
10 | for (index in 0 until itemCount) {
11 | val item = getItemAt(index)
12 | if (item.uri != null) {
13 | return item.uri
14 | }
15 | }
16 | return null
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/ExecuteResult.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | @Parcelize
7 | class ExecuteResult(val exitCode: Int, val error: String?, val output: String?) : Parcelable {
8 | val isSuccess get() = exitCode == 0
9 | override fun toString(): String {
10 | return "ExecuteResult(exitCode=$exitCode, error='$error', output='$output')"
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/event/BaseEvent.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util.event
2 |
3 | abstract class BaseEvent {
4 | private var consumed = false
5 |
6 | /**
7 | * 消费此事件。
8 | * @param onConsume 仅当第一次调用该方法时立即执行此回调
9 | * */
10 | fun consume(onConsume: (() -> Unit)? = null): Boolean {
11 | if (consumed) return false
12 | consumed = true
13 | onConsume?.invoke()
14 | return true
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_upload.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/org/ohosdev/hapviewerandroid/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values-v29/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/java/org/ohosdev/hapviewerandroid/harmonystyle/dialog/AlertDialogLayout.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.harmonystyle.dialog
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.util.AttributeSet
6 |
7 | @SuppressLint("RestrictedApi")
8 | class AlertDialogLayout : androidx.appcompat.widget.AlertDialogLayout {
9 | constructor(context: Context?) : super(context)
10 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
11 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles_harmony.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/about/AboutDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.about
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import androidx.fragment.app.DialogFragment
6 |
7 | class AboutDialogFragment : DialogFragment() {
8 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
9 | return AboutDialogBuilder(requireContext()).create()
10 | }
11 |
12 | companion object {
13 | const val TAG = "AboutDialogFragment"
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-v29/themes_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/harmonystyle/src/test/java/org/ohosdev/hapviewerandroid/harmonystyle/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.harmonystyle;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values-v26/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/DragEventExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.content.ClipDescription
4 | import android.view.DragEvent
5 |
6 | /**
7 | * 判断是否有非文字的 MIME,也就是非 [ClipDescription.MIMETYPE_TEXT_PLAIN] 的 MIME。
8 | * */
9 | fun DragEvent.hasFileMime(): Boolean {
10 | for (index in 0 until clipDescription.mimeTypeCount) {
11 | if (clipDescription.getMimeType(index) != ClipDescription.MIMETYPE_TEXT_PLAIN) {
12 | return true
13 | }
14 | }
15 | return false
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/ViewExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.view.ContextMenu
4 | import android.view.ContextMenu.ContextMenuInfo
5 | import android.view.View
6 |
7 | inline fun View.setOnCreateContextMenuListenerWithInfo(
8 | crossinline block: ContextMenu.(M) -> Unit
9 | ) {
10 | setOnCreateContextMenuListener { menu: ContextMenu, _: View, menuInfo: ContextMenuInfo? ->
11 | if (menuInfo is M) {
12 | menu.block(menuInfo)
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-v29/themes_material2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v29/themes_material3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/info_strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 应用图标
4 | 应用名称
5 | 应用包名
6 | 版本名称
7 | 版本号码
8 | 编译目标
9 | 技术探测
10 | 原生开发或未知开发技术
11 | 供应商
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v26/themes_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/app/Constant.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.app
2 |
3 | const val DIR_PATH_EXTERNAL_FILES = "external_files"
4 |
5 | const val URL_HOME_WESTINYANG = "https://kaihongpai.feishu.cn/wiki/CqWLwJRadibxztkrIWZcogWxnXd"
6 | const val URL_HOME_JESSE205 = "https://gitee.com/Jesse205"
7 |
8 | const val URL_REPOSITORY = "https://gitee.com/westinyang/hap-viewer-android"
9 | const val URL_OPEN_SOURCE_LICENSES = "${URL_REPOSITORY}#%E8%AE%B8%E5%8F%AF%E5%A3%B0%E6%98%8E"
10 | const val URL_PRIVACY_POLICY = "${URL_REPOSITORY}/blob/master/PrivacyPolicy.md"
11 |
12 | const val LICENSE_APP = "Apache 2.0"
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/anim/harmony_dialog_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v26/themes_material2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v26/themes_material3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/SystemUtil.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util
2 |
3 | import android.os.Build
4 |
5 | object SystemUtil {
6 | val isOhosSupported by lazy {
7 | runCatching { Class.forName("ohos.app.Application") }.isSuccess
8 | }
9 | val isEmui by lazy {
10 | runCatching { Class.forName("androidhwext.R") }.isSuccess
11 | }
12 | val isMagic by lazy {
13 | runCatching { Class.forName("androidhnext.R") }.isSuccess
14 | }
15 | val isLightNavigationBarSupported by lazy {
16 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.O || isEmui || isMagic
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main_info_basic.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/info_strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | App icon
4 | App name
5 | App bundle name
6 | Version name
7 | Version number
8 | Compile target
9 | Tech
10 | Native development or unknown development techniques
11 | Vendor
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/anim/harmony_dialog_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/ShizukuExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.app.Activity
4 | import android.content.pm.PackageManager
5 | import org.ohosdev.hapviewerandroid.util.ShizukuUtil
6 | import rikka.shizuku.Shizuku
7 |
8 | val ShizukuUtil.ShizukuStatus.isGranted get() = this == ShizukuUtil.ShizukuStatus.GRANTED
9 |
10 | fun Activity.requestShizukuPermission(requestCode: Int) {
11 | runCatching {
12 | Shizuku.requestPermission(requestCode)
13 | }.onFailure {
14 | it.printStackTrace()
15 | onRequestPermissionsResult(
16 | requestCode, arrayOf(ShizukuUtil.PERMISSION), intArrayOf(PackageManager.PERMISSION_DENIED)
17 | )
18 | }
19 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 8dp
5 | 16dp
6 | 12dp
7 | 12dp
8 | 12dp
9 |
10 |
11 |
12 | 4dp
13 | 50dp
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles_material3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/common/dialog/AlertDialogBuilder.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.common.dialog
2 |
3 | import android.content.Context
4 | import androidx.appcompat.app.AlertDialog
5 | import org.ohosdev.hapviewerandroid.extensions.fixDialogGravityIfNeeded
6 |
7 | /**
8 | * 整个应用使用的对话框,主要添加对话框重力修正。
9 | * */
10 | open class AlertDialogBuilder> : MaterialAlertDialogBuilderBridge {
11 | constructor(context: Context) : super(context)
12 | constructor(context: Context, overrideThemeResId: Int) : super(context, overrideThemeResId)
13 |
14 | override fun create(): AlertDialog {
15 | return super.create().apply {
16 | fixDialogGravityIfNeeded()
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/app/HapViewerApp.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.app
2 |
3 | import android.app.Application
4 | import cn.hutool.core.io.FileUtil
5 | import java.io.File
6 |
7 | class HapViewerApp : Application() {
8 | lateinit var appPreference: AppPreference
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 | instance = this
13 | appPreference = AppPreference(this)
14 | deleteExternalFilesCaches()
15 | }
16 |
17 | private fun deleteExternalFilesCaches() {
18 | File(cacheDir, DIR_PATH_EXTERNAL_FILES).deleteRecursively()
19 | externalCacheDirs.forEach { File(it, DIR_PATH_EXTERNAL_FILES).deleteRecursively() }
20 | }
21 |
22 | companion object {
23 | lateinit var instance: HapViewerApp
24 | }
25 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_button_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
-
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @color/harmony_color_primary_dark
5 | @color/harmony_color_surface_dark
6 | @color/harmony_color_background_dark
7 | @color/harmony_color_pressed_dark
8 | @color/harmony_color_divider_dark
9 | @color/harmony_color_hovered_dark
10 | @color/harmony_color_focused_dark
11 | @color/harmony_color_common_dark
12 |
13 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/harmonystyle/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/main/BasicInfoCard.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.main
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.ContextMenu
6 | import com.google.android.material.card.MaterialCardView
7 |
8 | class BasicInfoCard : MaterialCardView {
9 | private val contextMenuInfo: ContextMenuInfo? by lazy { ContextMenuInfo() }
10 |
11 | constructor(context: Context) : super(context)
12 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
13 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
14 |
15 | override fun getContextMenuInfo(): ContextMenu.ContextMenuInfo? {
16 | return contextMenuInfo
17 | }
18 |
19 | class ContextMenuInfo : ContextMenu.ContextMenuInfo
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/RecyclerViewExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import androidx.recyclerview.widget.DividerItemDecoration
4 | import androidx.recyclerview.widget.RecyclerView
5 | import com.google.android.material.divider.MaterialDividerItemDecoration
6 | import org.ohosdev.hapviewerandroid.R
7 |
8 | /**
9 | * 如果主题中已启用分割线,就应用到布局中。
10 | * */
11 | fun RecyclerView.applyDividerIfEnabled(orientation: Int = DividerItemDecoration.VERTICAL) {
12 | if (!context.resolveBoolean(R.attr.enableDivider, false)) {
13 | return
14 | }
15 | addItemDecoration(object : MaterialDividerItemDecoration(context, orientation) {
16 | override fun shouldDrawDivider(position: Int, adapter: RecyclerView.Adapter<*>?) =
17 | adapter?.run { position != itemCount - 1 } ?: false
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -dontwarn *
24 | -dontobfuscate
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drag_mask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
-
6 |
7 |
8 |
9 |
10 |
11 |
12 | -
13 |
14 |
-
15 |
16 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_action_bar_item_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
-
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #29B6F6
4 | #66BB6A
5 |
6 | @color/system_window_scrim
7 |
8 | #66BB6A
9 | #42A5F5
10 | #7E57C2
11 |
12 | #a1d39a
13 | #a3c9fe
14 | #d1bcfd
15 |
16 | #5BA854
17 | #4796C4
18 | #8C55C2
19 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_menu_choice_background_indicator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
6 |
7 |
8 |
-
9 |
10 |
11 |
12 |
13 |
14 | -
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/org/ohosdev/hapviewerandroid/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("org.ohosdev.hapviewerandroid", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_list_choice_background_indicator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
6 |
7 |
8 |
9 |
10 |
11 | -
14 |
15 |
-
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/ActivityExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.FragmentActivity
5 | import androidx.fragment.app.FragmentResultListener
6 | import org.ohosdev.hapviewerandroid.R
7 | import org.ohosdev.hapviewerandroid.ui.common.BaseActivity
8 |
9 | fun FragmentActivity.setFragmentResultListener(key: String, listener: (result: Bundle) -> Unit) {
10 | setFragmentResultListener(key) { _, result -> listener(result) }
11 | }
12 |
13 | fun FragmentActivity.setFragmentResultListener(key: String, listener: FragmentResultListener) {
14 | supportFragmentManager.setFragmentResultListener(key, this, listener)
15 | }
16 |
17 | fun BaseActivity.copyAndShowSnackBar(text: String?, name: String? = null): Boolean {
18 | if (text.isNullOrEmpty()) return false
19 | copyText(text)
20 | showSnackBar(getString(R.string.copied_withName, name ?: text))
21 | return true
22 | }
--------------------------------------------------------------------------------
/harmonystyle/src/androidTest/java/org/ohosdev/hapviewerandroid/harmonystyle/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.harmonystyle;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("org.ohosdev.hapviewerandroid.harmonystyle.test", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/view/NestedScrollView.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.view
2 |
3 | import android.content.Context
4 | import android.graphics.Rect
5 | import android.util.AttributeSet
6 | import androidx.core.widget.NestedScrollView as AndroiXNestedScrollView
7 |
8 | class NestedScrollView : AndroiXNestedScrollView {
9 | constructor(context: Context) : super(context)
10 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
11 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
12 |
13 | /**
14 | * 是否跟随焦点滚动
15 | * */
16 | var isScrollWithFocus: Boolean = true
17 |
18 | override fun computeScrollDeltaToGetChildRectOnScreen(rect: Rect?): Int {
19 | // 解决自动跟随焦点滚动问题
20 | // https://blog.csdn.net/ZYJWR/article/details/108386309
21 | return if (isScrollWithFocus) super.computeScrollDeltaToGetChildRectOnScreen(rect) else 0
22 | }
23 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_ic_public_more.xml:
--------------------------------------------------------------------------------
1 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/harmony_ic_public_file_filled.xml:
--------------------------------------------------------------------------------
1 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven { url 'https://maven.aliyun.com/repository/central' }
4 | maven { url 'https://maven.aliyun.com/repository/public' }
5 | maven { url 'https://maven.aliyun.com/repository/google' }
6 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
7 | maven { url 'https://developer.huawei.com/repo/' }
8 | google()
9 | mavenCentral()
10 | gradlePluginPortal()
11 | }
12 | }
13 | dependencyResolutionManagement {
14 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
15 | repositories {
16 | maven { url 'https://maven.aliyun.com/repository/central' }
17 | maven { url 'https://maven.aliyun.com/repository/public' }
18 | maven { url 'https://maven.aliyun.com/repository/google' }
19 | maven { url 'https://developer.huawei.com/repo/' }
20 | google()
21 | mavenCentral()
22 | }
23 | }
24 | rootProject.name = "HapViewerAndroid"
25 | include ':app'
26 | include ':harmonystyle'
27 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/drawable/harmony_popupmenu_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
12 |
13 | -
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drop_mask.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #039BE5
4 | #66BB6A
5 |
6 | #43A047
7 | #1E88E5
8 | #5E35B1
9 |
10 | #3b6939
11 | #39608f
12 | #66558e
13 |
14 | #64BB5C
15 | #46B1E3
16 | #AC49F5
17 |
18 | #44000000
19 | #B3000000
20 |
21 | @color/system_window_scrim_dark
22 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/DialogExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.app.Dialog
4 | import android.text.method.MovementMethod
5 | import android.view.Gravity
6 | import android.widget.TextView
7 | import org.ohosdev.hapviewerandroid.harmonystyle.R.attr.windowGravityBottom
8 |
9 | val Dialog.messageView: TextView get() = findViewById(android.R.id.message)
10 |
11 | var Dialog.contentSelectable
12 | get() = messageView.isTextSelectable
13 | set(value) {
14 | messageView.setTextIsSelectable(value)
15 | }
16 |
17 | var Dialog.contentMovementMethod: MovementMethod
18 | get() = messageView.movementMethod
19 | set(value) {
20 | messageView.movementMethod = value
21 | }
22 |
23 |
24 | fun Dialog.setContentAutoLinkMask(mask: Int) {
25 | messageView.apply {
26 | linksClickable = true
27 | autoLinkMask = mask
28 | }
29 | }
30 |
31 | /**
32 | * 鸿蒙风格将对话框的 Gravity 修正为底部
33 | * */
34 | fun Dialog.fixDialogGravityIfNeeded() {
35 | if (!context.resolveBoolean(windowGravityBottom, false)) return
36 | if (window == null) throw RuntimeException("Dialog window is null")
37 | window!!.setGravity(Gravity.BOTTOM)
38 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/harmony_ic_public_upload_filled.xml:
--------------------------------------------------------------------------------
1 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/harmonystyle/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'org.ohosdev.hapviewerandroid.harmonystyle'
8 | compileSdk 34
9 |
10 | defaultConfig {
11 | minSdk 24
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | kotlinOptions {
28 | jvmTarget = JavaVersion.VERSION_1_8
29 | }
30 | }
31 |
32 | dependencies {
33 |
34 | implementation 'androidx.appcompat:appcompat:1.6.1'
35 | implementation 'com.google.android.material:material:1.11.0'
36 | implementation 'com.huawei.ui.uikit:hwradiobutton:1.0.0.500'
37 | testImplementation 'junit:junit:4.13.2'
38 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/view/behavior/AppBarLayoutBehavior.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.view.behavior
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import androidx.coordinatorlayout.widget.CoordinatorLayout
7 | import com.google.android.material.appbar.AppBarLayout
8 |
9 |
10 | class AppBarLayoutBehavior : AppBarLayout.Behavior {
11 | constructor() : super()
12 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
13 |
14 | override fun onNestedScroll(
15 | coordinatorLayout: CoordinatorLayout,
16 | child: AppBarLayout,
17 | target: View,
18 | dxConsumed: Int,
19 | dyConsumed: Int,
20 | dxUnconsumed: Int,
21 | dyUnconsumed: Int,
22 | type: Int,
23 | consumed: IntArray
24 | ) {
25 | super.onNestedScroll(
26 | coordinatorLayout,
27 | child,
28 | target,
29 | dxConsumed,
30 | dyConsumed,
31 | dxUnconsumed,
32 | dyUnconsumed,
33 | type,
34 | consumed
35 | )
36 | if (child.isLiftOnScroll && dyUnconsumed < 0) {
37 | child.isLifted = false
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/highlight/JSONHighlighter.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util.highlight
2 |
3 | object JSONHighlighter {
4 | private const val COLOR_STRING = "#009688"
5 | private const val COLOR_KEYWORD = "#673AB7"
6 | private const val COLOR_NUMBER = "#2196F3"
7 |
8 | private val PATTERN_STRING = Regex("([^\\\\])(\"[^\"]*[^\\\\]\")")
9 | private val PATTERN_KEYWORD = Regex("\\b(true|false)\\b")
10 | private val PATTERN_NUMBER = Regex("([\\s{])(\\d+)([\\s},])")
11 | private val PATTERN_WRAP = Regex("\\n")
12 | private val PATTERN_SPACE = Regex(" ")
13 |
14 | fun highlight(
15 | jsonText: String,
16 | stringColor: String = COLOR_STRING,
17 | keywordColor: String = COLOR_KEYWORD,
18 | numberColor: String = COLOR_NUMBER
19 | ) = jsonText
20 | .replace(PATTERN_SPACE, " ")
21 | .replace(PATTERN_STRING) { "${it.groupValues[1]}${it.groupValues[2]}" }
22 | .replace(PATTERN_KEYWORD) { "${it.value}" }
23 | .replace(PATTERN_NUMBER) { "${it.groupValues[1]}${it.groupValues[2]}${it.groupValues[3]}" }
24 | .replace(PATTERN_WRAP, "
")
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/ContentResolverExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.content.ContentResolver
4 | import android.net.Uri
5 | import android.provider.DocumentsContract
6 | import java.io.IOException
7 |
8 | private const val TAG = "ContentResolverExtensions"
9 |
10 | /**
11 | * 获取 `_data` 列的值作为字符串并返回,一般用于获取文件路径。
12 | * */
13 | @Throws(IOException::class)
14 | fun ContentResolver.getDataColumn(uri: Uri, selection: String? = null, selectionArgs: Array? = null) =
15 | queryForString(uri, "_data", selection, selectionArgs)
16 |
17 | /**
18 | * 从 ContentResolver 中搜索 `uri`,将`column` 的值作为字符串并返回。
19 | * @see ContentResolver.query
20 | * */
21 | fun ContentResolver.queryForString(
22 | uri: Uri,
23 | column: String,
24 | selection: String? = null,
25 | selectionArgs: Array? = null
26 | ): String? = runCatching {
27 | return query(uri, arrayOf(column), selection, selectionArgs, null)?.use {
28 | if (it.moveToFirst() && !it.isNull(0)) {
29 | it.getString(0)
30 | } else {
31 | null
32 | }
33 | }
34 | }.onFailure { it.printStackTrace() }.getOrNull()
35 |
36 | fun ContentResolver.getDocumentName(uri: Uri) = queryForString(uri, DocumentsContract.Document.COLUMN_DISPLAY_NAME)
37 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/common/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.common
2 |
3 | import android.os.Bundle
4 | import androidx.annotation.StringRes
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.coordinatorlayout.widget.CoordinatorLayout
7 | import com.google.android.material.snackbar.Snackbar
8 | import org.ohosdev.hapviewerandroid.manager.ThemeManager
9 | import rikka.insets.WindowInsetsHelper
10 | import rikka.layoutinflater.view.LayoutInflaterFactory
11 |
12 | abstract class BaseActivity : AppCompatActivity() {
13 | protected val themeManager: ThemeManager = ThemeManager(this)
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | layoutInflater.factory2 = LayoutInflaterFactory(delegate)
16 | .addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
17 | super.onCreate(savedInstanceState)
18 | themeManager.applyTheme()
19 | }
20 |
21 | abstract val rootView: CoordinatorLayout
22 |
23 | /**
24 | * 在屏幕上显示一个 SnackBar
25 | * */
26 | fun showSnackBar(@StringRes textId: Int): Snackbar {
27 | return showSnackBar(getString(textId))
28 | }
29 |
30 | open fun showSnackBar(text: String): Snackbar {
31 | return Snackbar.make(rootView, text, Snackbar.LENGTH_SHORT).apply { show() }
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/app/AppPreference.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.app
2 |
3 | import android.content.Context
4 | import androidx.core.content.edit
5 | import androidx.preference.PreferenceManager
6 | import org.ohosdev.hapviewerandroid.app.AppPreference.ThemeType
7 |
8 | class AppPreference(private val context: Context) {
9 |
10 | companion object {
11 | const val KEY_THEME_TYPE = "pref_theme_type"
12 | }
13 |
14 | private val sharedPreference = PreferenceManager.getDefaultSharedPreferences(context)
15 |
16 | var themeType: ThemeType
17 | get() = sharedPreference.getString(KEY_THEME_TYPE, ThemeType.HARMONY.value)!!.toThemeType()
18 | set(value) {
19 | sharedPreference.edit { this.putString(KEY_THEME_TYPE, value.value) }
20 | }
21 |
22 | enum class ThemeType(val value: String) {
23 | MATERIAL1("material1"),
24 | MATERIAL2("material2"),
25 | MATERIAL3("material3"),
26 | HARMONY("harmony")
27 | }
28 | }
29 |
30 | fun String.toThemeType(): ThemeType {
31 | return when (this) {
32 | ThemeType.MATERIAL1.value -> ThemeType.MATERIAL1
33 | ThemeType.MATERIAL2.value -> ThemeType.MATERIAL2
34 | ThemeType.MATERIAL3.value -> ThemeType.MATERIAL3
35 | ThemeType.HARMONY.value -> ThemeType.HARMONY
36 | else -> ThemeType.HARMONY
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/themes_harmony.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | android.enableJetifier=true
19 | # Enables namespacing of each library's R class so that its R class includes only the
20 | # resources declared in the library itself and none from the library's dependencies,
21 | # thereby reducing the size of the R class for that library
22 | android.nonTransitiveRClass=true
23 | android.defaults.buildfeatures.buildconfig=true
24 | android.nonFinalResIds=false
25 | #android.enableR8.fullMode=true
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/service/shizuku/UserService.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.service.shizuku
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.annotation.Keep
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.runBlocking
8 | import org.ohosdev.hapviewerandroid.IUserService
9 | import org.ohosdev.hapviewerandroid.util.ExecuteResult
10 | import java.io.File
11 | import kotlin.system.exitProcess
12 |
13 | class UserService() : IUserService.Stub() {
14 | @Keep
15 | constructor(context: Context) : this() {
16 | Log.i(TAG, "constructor with Context: context=$context")
17 | }
18 |
19 | override fun destroy() {
20 | Log.i(TAG, "destroy")
21 | exitProcess(0)
22 | }
23 |
24 | override fun exit() {
25 | destroy()
26 | }
27 |
28 | override fun execute(cmdarray: MutableList, envp: MutableList?, dir: String?) =
29 | runBlocking(Dispatchers.IO) {
30 | val process = Runtime.getRuntime().exec(
31 | cmdarray.toTypedArray(), envp?.toTypedArray(), dir?.let { File(it) }
32 | )
33 | val exitCode = process.waitFor()
34 | val error = process.errorStream.readBytes().decodeToString()
35 | val output = process.inputStream.readBytes().decodeToString()
36 | return@runBlocking ExecuteResult(exitCode, error, output)
37 | }
38 |
39 |
40 | companion object {
41 | private const val TAG = "UserService"
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/UriExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.content.ContentResolver.SCHEME_CONTENT
4 | import android.content.ContentResolver.SCHEME_FILE
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.pm.PackageManager
8 | import android.net.Uri
9 | import androidx.documentfile.provider.DocumentFile
10 | import java.io.File
11 |
12 | private const val TAG = "UriExtensions"
13 |
14 | val Uri.isExternalStorageDocument get() = authority.equals("com.android.externalstorage.documents")
15 | val Uri.isDownloadsDocument get() = authority.equals("com.android.providers.downloads.documents")
16 | val Uri.isMediaDocument get() = authority.equals("com.android.providers.media.documents")
17 |
18 | /**
19 | * - 当 `scheme` 为 `file` 并且 [Uri.getPath] 不为 `null` 时,使用 `DocumentFile.fromFile` 获取;
20 | * - 当 `scheme` 为 `content` 时,使用 `DocumentFile.fromSingleUri` 获取;
21 | * - 否则返回 `null`。
22 | * */
23 | fun Uri.toDocumentFile(context: Context) = when (scheme) {
24 | SCHEME_FILE -> path?.let { DocumentFile.fromFile(File(it)) }
25 | SCHEME_CONTENT -> DocumentFile.fromSingleUri(context, this)!!
26 | else -> null
27 | }
28 |
29 | /**
30 | * 判断uri是否可以被读取
31 | *
32 | * 和 [DocumentFile.canRead] 不同的是,此方法不会检查 MimeType,因为部分应用不支持获取 MimeType。
33 | * */
34 | fun Uri.canRead(context: Context) = when (scheme) {
35 | SCHEME_FILE -> path?.let { File(it).canRead() } ?: false
36 | SCHEME_CONTENT -> (context.checkCallingOrSelfUriPermission(this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
37 | == PackageManager.PERMISSION_GRANTED)
38 |
39 | else -> false
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/SnackBarExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.annotation.SuppressLint
4 | import com.google.android.material.R
5 | import com.google.android.material.motion.MotionUtils.resolveThemeDuration
6 | import com.google.android.material.snackbar.BaseTransientBottomBar
7 |
8 | private const val DEFAULT_SLIDE_ANIMATION_DURATION = 250
9 | private const val DEFAULT_ANIMATION_FADE_IN_DURATION = 150
10 | private const val DEFAULT_ANIMATION_FADE_OUT_DURATION = 75
11 |
12 |
13 | /**
14 | * 谷歌更新 MD3 之后,修改了 SnackBar 的过渡时间,导致 MD2 的动画时间较长。
15 | *
16 | * 这里重写了动画时间,使得动画效果与先前一致。
17 | *
18 | * 注意:该方法不会因为修改失败而抛出异常。
19 | *
20 | * */
21 | @SuppressLint("PrivateResource")
22 | fun > T.overrideAnimationDurationIfNeeded() = apply {
23 | val isMaterial3Theme: Boolean
24 | context.theme.obtainStyledAttributes(intArrayOf(R.attr.isMaterial3Theme)).apply {
25 | isMaterial3Theme = getBoolean(0, false)
26 | }.recycle()
27 | if (isMaterial3Theme) return@apply
28 | runCatching, Unit> {
29 | setDeclaredField(
30 | "animationSlideDuration",
31 | resolveThemeDuration(context, R.attr.motionDurationMedium2, DEFAULT_SLIDE_ANIMATION_DURATION)
32 | )
33 | setDeclaredField(
34 | "animationFadeInDuration",
35 | resolveThemeDuration(context, R.attr.motionDurationShort2, DEFAULT_ANIMATION_FADE_IN_DURATION)
36 | )
37 | setDeclaredField(
38 | "animationFadeOutDuration",
39 | resolveThemeDuration(context, R.attr.motionDurationShort1, DEFAULT_ANIMATION_FADE_OUT_DURATION)
40 | )
41 | }.onFailure { it.printStackTrace() }
42 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_list_harmony.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
21 |
22 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/view/AppBarLayout.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.view
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.os.Build
6 | import android.util.AttributeSet
7 | import com.google.android.material.shape.MaterialShapeDrawable
8 | import com.google.android.material.shape.MaterialShapeUtils
9 | import org.ohosdev.hapviewerandroid.extensions.resolveBoolean
10 | import com.google.android.material.appbar.AppBarLayout as GoogleAppBarLayout
11 |
12 | /**
13 | * 修复 AndroidP 以下应用栏有阴影的问题
14 | * */
15 | class AppBarLayout : GoogleAppBarLayout {
16 |
17 | @delegate:SuppressLint("PrivateResource")
18 | val isMaterial3Theme by lazy {
19 | context.resolveBoolean(com.google.android.material.R.attr.isMaterial3Theme, false)
20 | }
21 | val materialBackground
22 | get() = background.run { if (this is MaterialShapeDrawable) this else null }
23 |
24 | constructor(context: Context) : super(context)
25 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
26 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
27 |
28 |
29 | @SuppressLint("RestrictedApi", "PrivateResource")
30 | override fun setElevation(elevation: Float) {
31 | // 阻止使用原始的方法调用阴影,因为MD3的应用栏不需要阴影
32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P || !isMaterial3Theme) {
33 | super.setElevation(elevation)
34 | } else {
35 | MaterialShapeUtils.setElevation(this, elevation)
36 | }
37 | }
38 |
39 | override fun getElevation() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P || !isMaterial3Theme) {
40 | super.getElevation()
41 | } else {
42 | materialBackground?.elevation ?: 0f
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_list_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
24 |
25 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
21 |
22 |
27 |
28 |
33 |
34 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/HapInfoExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.content.Context
4 | import android.os.RemoteException
5 | import android.util.Log
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.withContext
8 | import org.ohosdev.hapviewerandroid.model.HapInfo
9 | import org.ohosdev.hapviewerandroid.util.ExecuteResult
10 | import org.ohosdev.hapviewerandroid.util.helper.ShizukuServiceHelper
11 | import java.io.File
12 |
13 | private const val TAG = "HapInfoExtensions"
14 |
15 | /**
16 | * 销毁 HapInfo,**删除文件**,回收图片。
17 | *
18 | * 注:仅当文件路径为缓存路径时才会删除文件。
19 | * */
20 | fun HapInfo.destroy(context: Context) {
21 | icon?.also {
22 | if (!it.isRecycled){
23 | it.recycle()
24 | }
25 | }
26 | hapFilePath?.also {
27 | File(it).deleteIfCache(context)
28 | }
29 | }
30 |
31 | @Throws(RemoteException::class)
32 | suspend fun HapInfo.installToSelf(helper: ShizukuServiceHelper) = withContext(Dispatchers.Default) {
33 | if (!helper.isServiceBound) {
34 | throw RuntimeException("Shizuku not bound.")
35 | }
36 | isInstalling = true
37 | val result: ExecuteResult = runCatching {
38 | helper.service!!.execute(listOf("bm", "install", "-r", this@installToSelf.hapFilePath), null, null).also {
39 | Log.i(TAG, "installed hap: $it")
40 | }
41 | }.getOrElse { ExecuteResult(1, it.localizedMessage, null) }
42 | isInstalling = false
43 | result
44 | }
45 |
46 | fun HapInfo.getTechDesc(context: Context) = techList.let {
47 | // techList可能为空
48 | if (!it.isNullOrEmpty()) it.joinToString(context.localisedSeparator) else null
49 | }
50 |
51 | /**
52 | * 获取版本名和版本号,格式为 `版本名 (版本号)`。
53 | * */
54 | fun HapInfo.getVersionNameAndCode(unknownString: String) =
55 | if (versionCode != null || versionCode != null)
56 | "%s (%s)".format(versionName ?: unknownString, versionCode ?: unknownString)
57 | else null
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/view/AdvancedRecyclerView.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.ContextMenu
6 | import android.view.View
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | class AdvancedRecyclerView : RecyclerView {
10 | private var contextMenuInfo: ContextMenu.ContextMenuInfo? = null
11 |
12 | constructor(context: Context) : super(context)
13 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
14 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
15 | context,
16 | attrs,
17 | defStyleAttr
18 | )
19 |
20 | override fun getContextMenuInfo(): ContextMenu.ContextMenuInfo? = contextMenuInfo
21 |
22 | override fun showContextMenuForChild(originalView: View?): Boolean {
23 | val adapter = adapter
24 | if (originalView == null || adapter == null || adapter !is ContextMenuInfoProvider<*>) return false
25 |
26 | val position = getChildAdapterPosition(originalView)
27 | if (position >= 0) {
28 | contextMenuInfo = adapter.createContextMenuInfo(originalView, position)
29 | return super.showContextMenuForChild(originalView)
30 | }
31 | contextMenuInfo = null
32 | return false
33 | }
34 |
35 | override fun showContextMenu(): Boolean {
36 | contextMenuInfo = null
37 | return super.showContextMenu()
38 | }
39 |
40 | override fun showContextMenu(x: Float, y: Float): Boolean {
41 | contextMenuInfo = null
42 | return super.showContextMenu(x, y)
43 | }
44 |
45 | class AdapterContextMenuInfo(
46 | val key: String, val position: Int, val item: T
47 | ) : ContextMenu.ContextMenuInfo
48 |
49 | interface ContextMenuInfoProvider {
50 | fun createContextMenuInfo(view: View, position: Int): T?
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main_permissions.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
21 |
24 |
25 |
33 |
34 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/main/PermissionsAdapter.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.main
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.DiffUtil
7 | import androidx.recyclerview.widget.ListAdapter
8 | import androidx.recyclerview.widget.RecyclerView
9 | import org.ohosdev.hapviewerandroid.view.AdvancedRecyclerView
10 | import org.ohosdev.hapviewerandroid.view.AdvancedRecyclerView.AdapterContextMenuInfo
11 | import org.ohosdev.hapviewerandroid.view.list.ListItem
12 |
13 | class PermissionsAdapter(val context: Context) :
14 | ListAdapter(DIFF_CALLBACK),
15 | AdvancedRecyclerView.ContextMenuInfoProvider> {
16 |
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
18 | ViewHolder(ListItem(context))
19 |
20 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
21 | holder.bindTo(getItem(position))
22 | }
23 |
24 | class ViewHolder(private val listItem: ListItem) : RecyclerView.ViewHolder(listItem) {
25 | init {
26 | listItem.layoutParams = ViewGroup.LayoutParams(
27 | ViewGroup.LayoutParams.MATCH_PARENT,
28 | ViewGroup.LayoutParams.WRAP_CONTENT
29 | )
30 | listItem.isContextClickable = true
31 | }
32 |
33 | fun bindTo(name: String) {
34 | listItem.title = name
35 | }
36 | }
37 |
38 | override fun createContextMenuInfo(view: View, position: Int): AdapterContextMenuInfo =
39 | AdapterContextMenuInfo(KEY, position, getItem(position))
40 |
41 | companion object {
42 | const val KEY = "permissions"
43 | val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
44 | override fun areItemsTheSame(oldItem: String, newItem: String) = oldItem == newItem
45 |
46 | override fun areContentsTheSame(oldItem: String, newItem: String) = oldItem == newItem
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HapViewerAndroid
2 |
3 |
4 |
5 | ## 项目介绍
6 |
7 | 安卓版的hap查看器,支持解析 OpenHarmony(开源鸿蒙)、HarmonyOS(鸿蒙)、HarmonyOS NEXT(鸿蒙星河版) API9+(Stage模型)的应用安装包,支持在 Android 7+ 的安卓设备上运行
8 |
9 | 开源仓库
10 |
11 | - [Gitee](https://gitee.com/westinyang/hap-viewer-android)
12 | - [Github](https://github.com/westinyang/hap-viewer-android)
13 |
14 | ### 系列项目
15 |
16 | - 电脑版(跨平台):[westinyang/hap-viewer](https://gitee.com/westinyang/hap-viewer)
17 | - **手机版(Android)**:[westinyang/hap-viewer-android](https://gitee.com/westinyang/hap-viewer-android)
18 | - 网页版(响应式):[westinyang/hap-viewer-web](https://gitee.com/westinyang/hap-viewer-web)
19 |
20 | ## 下载安装
21 |
22 | - https://gitee.com/westinyang/hap-viewer-android/releases
23 |
24 | ## 开发环境
25 |
26 | - Android Studio Giraffe | 2022.3.1 Patch 4
27 |
28 | ## 截图预览
29 |
30 | 
31 |
32 | ## 视频演示
33 |
34 | - [“文化入侵”:安卓上的hap查看器 OpenHarmony开源鸿蒙应用解析](https://www.bilibili.com/video/BV1pX4y147V8)
35 | - [开源鸿蒙hap查看器,新增探测Flutter、Qt技术,安卓版新主题](https://www.bilibili.com/video/BV1cg4y197mc)
36 |
37 | ## 许可声明
38 |
39 | - 本项目是以Apache2.0许可开源,如需二开、衍生或商用请注明原作者(westinyang & Jesse205)和原仓库
40 | - 使用的开源软件
41 | - [Material Components for Android](https://github.com/material-components/material-components-android) `Apache-2.0 license`
42 | - [Android Jetpack](https://github.com/androidx/androidx) `Apache-2.0 license`
43 | - [AndroidFastScroll](https://github.com/zhanghai/AndroidFastScroll) `Apache-2.0 license`
44 | - [Pictogrammers Material Design Icons](https://pictogrammers.com/library/mdi/) `Apache-2.0 license`
45 | - [Android-RTEditor](https://github.com/1gravity/Android-RTEditor) `Apache-2.0 license`
46 | - 字体
47 | - 图标:[HarmonyOS Sans Fonts](https://developer.harmonyos.com/cn/design/resource)
48 |
49 | ## 开源贡献
50 |
51 | > HapViewerAndroid 项目由 westinyang 创建于 2023 年,并获得包括但不限于以下人士的贡献:
52 |
53 | - [westinyang](https://gitee.com/westinyang)
54 | - [Jesse205](https://gitee.com/Jesse205)
55 |
56 | ## 技术交流
57 |
58 | [🐧 加入OpenHarmony技术交流群](https://kaihongpai.feishu.cn/wiki/R93ywdop6iuryDkJ5ACc0L3ynEc)
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/view/list/ListItemGroup.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.view.list
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.ContextMenu
6 | import android.view.View
7 | import android.widget.LinearLayout
8 | import org.ohosdev.hapviewerandroid.R
9 |
10 | class ListItemGroup : LinearLayout {
11 | private var contextMenuInfo: ContextMenuInfo? = null
12 | var key: String = ""
13 |
14 | constructor(context: Context) : this(context, null)
15 | constructor(context: Context, attrs: AttributeSet?) : this(
16 | context, attrs, R.attr.listItemGroupStyle
17 | )
18 |
19 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(
20 | context, attrs, defStyleAttr, R.style.Widget_ListItemGroup_Material
21 | )
22 |
23 | constructor(
24 | context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int
25 | ) : super(context, attrs, defStyleAttr, defStyleRes) {
26 | context.obtainStyledAttributes(attrs, R.styleable.ListItemGroup, defStyleAttr, defStyleRes).also {
27 | key = it.getString(R.styleable.ListItemGroup_android_key) ?: ""
28 | }.recycle()
29 | }
30 |
31 |
32 | override fun getContextMenuInfo(): ContextMenu.ContextMenuInfo? = contextMenuInfo
33 |
34 | override fun showContextMenuForChild(originalView: View?): Boolean {
35 | if (originalView is ListItem) {
36 | contextMenuInfo = ContextMenuInfo(key, originalView.title, originalView.valueText)
37 | return super.showContextMenuForChild(originalView)
38 | }
39 | contextMenuInfo = null
40 | return false
41 | }
42 |
43 | override fun showContextMenu(): Boolean {
44 | contextMenuInfo = null
45 | return super.showContextMenu()
46 | }
47 |
48 | override fun showContextMenu(x: Float, y: Float): Boolean {
49 | contextMenuInfo = null
50 | return super.showContextMenu(x, y)
51 | }
52 |
53 | class ContextMenuInfo(val key: String, val title: String?, val valueText: String?) :
54 | ContextMenu.ContextMenuInfo
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/FileExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.os.FileUtils
6 | import androidx.annotation.ChecksSdkIntAtLeast
7 | import java.io.File
8 | import java.io.FileInputStream
9 | import java.io.FileOutputStream
10 | import java.io.InputStream
11 | import java.io.OutputStream
12 | import java.nio.channels.FileChannel
13 | import kotlin.io.copyTo as ktCopyTo
14 |
15 | @get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
16 | val isSystemFileUtilsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
17 |
18 | fun FileChannel.copyTo(out: FileChannel) {
19 | val size = this.size()
20 | var left = size
21 | while (left > 0) {
22 | left -= this.transferTo(size - left, left, out)
23 | }
24 | }
25 |
26 | /**
27 | * 复制文件。
28 | *
29 | * Android 10 以上使用系统自带的方法,速度会有所提升。
30 | * */
31 | fun FileInputStream.copyTo(out: FileOutputStream) {
32 | if (isSystemFileUtilsSupported) {
33 | FileUtils.copy(this.fd, out.fd)
34 | } else {
35 | channel.use { inputChannel ->
36 | out.channel.use { outputChannel ->
37 | inputChannel.copyTo(outputChannel)
38 | }
39 | }
40 | }
41 | }
42 |
43 | fun InputStream.copyTo(out: OutputStream) {
44 | if (isSystemFileUtilsSupported) {
45 | FileUtils.copy(this, out)
46 | } else if (this is FileInputStream && out is FileOutputStream) {
47 | copyTo(out)
48 | } else {
49 | ktCopyTo(out)
50 | }
51 | }
52 |
53 | fun File.copyTo(out: File) {
54 | FileInputStream(this).use { inputStream ->
55 | FileOutputStream(out).use { outputStream ->
56 | inputStream.copyTo(outputStream)
57 | }
58 | }
59 | }
60 |
61 | /**
62 | * 判断文件是否位于缓存路径 [Context.getCacheDir]、[Context.getExternalCacheDirs] 下。
63 | * */
64 | fun File.isInCache(context: Context) =
65 | startsWith(context.cacheDir) || context.externalCacheDirs.find { startsWith(it) } != null
66 |
67 |
68 | fun File.deleteIfCache(context: Context) {
69 | if (isInCache(context)) {
70 | if (!delete()) {
71 | deleteOnExit()
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/view/list/ListItem.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.view.list
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import android.widget.FrameLayout
7 | import android.widget.TextView
8 | import org.ohosdev.hapviewerandroid.R
9 |
10 | class ListItem : FrameLayout {
11 | var title: String? = null
12 | set(value) {
13 | field = value
14 | titleTextView?.apply {
15 | visibility = if (value == null) GONE else VISIBLE
16 | text = value
17 | }
18 | }
19 | var valueText: String? = null
20 | set(value) {
21 | field = value
22 | valueTextView?.apply {
23 | visibility = if (value == null) GONE else VISIBLE
24 | text = value
25 | }
26 | }
27 |
28 | private val titleTextView: TextView? by lazy { findViewById(R.id.titleText) }
29 | private val valueTextView: TextView? by lazy { findViewById(R.id.valueText) }
30 |
31 | constructor(context: Context) : this(context, null)
32 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.attr.listItemStyle)
33 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(
34 | context, attrs, defStyleAttr, R.style.Widget_ListItem_Material
35 | )
36 |
37 | constructor(
38 | context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int
39 | ) : super(context, attrs, defStyleAttr, defStyleRes) {
40 | context.obtainStyledAttributes(attrs, R.styleable.ListItem, defStyleAttr, defStyleRes).also {
41 | LayoutInflater.from(context).inflate(it.getResourceId(R.styleable.ListItem_android_layout, R.layout.item_list_material),this)
42 | title = it.getString(R.styleable.ListItem_android_title)
43 | valueText = it.getString(R.styleable.ListItem_android_value)
44 | isEnabled = it.getBoolean(R.styleable.ListItem_android_enabled, true)
45 | }.recycle()
46 | }
47 |
48 | override fun setEnabled(enabled: Boolean) {
49 | super.setEnabled(enabled)
50 | titleTextView?.isEnabled = enabled
51 | valueTextView?.isEnabled = enabled
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #0A59F7
5 | #317AF7
6 | #FFFFFF
7 | #2E3033
8 | #000000
9 | #FFFFFF
10 | #F1F3F5
11 | #000000
12 | #19000000
13 | #26FFFFFF
14 | #0C000000
15 | #0Cffffff
16 | @color/harmony_color_primary_light
17 | @color/harmony_color_primary_dark
18 | #33000000
19 | #33FFFFFF
20 | #19000000
21 | #19FFFFFF
22 | #00001E
23 |
24 |
25 | @color/harmony_color_primary_light
26 | @color/harmony_color_surface_light
27 | @color/harmony_color_background_light
28 | @color/harmony_color_pressed_light
29 | @color/harmony_color_hovered_light
30 | @color/harmony_color_focused_light
31 | @color/harmony_color_divider_light
32 | @color/harmony_color_common_light
33 |
34 |
35 | @color/harmony_color_primary
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/manager/ThemeManager.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.manager
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.util.Log
6 | import androidx.core.view.WindowCompat
7 | import org.ohosdev.hapviewerandroid.R
8 | import org.ohosdev.hapviewerandroid.app.AppPreference.ThemeType
9 | import org.ohosdev.hapviewerandroid.app.AppPreference.ThemeType.HARMONY
10 | import org.ohosdev.hapviewerandroid.app.AppPreference.ThemeType.MATERIAL1
11 | import org.ohosdev.hapviewerandroid.app.AppPreference.ThemeType.MATERIAL2
12 | import org.ohosdev.hapviewerandroid.app.AppPreference.ThemeType.MATERIAL3
13 | import org.ohosdev.hapviewerandroid.extensions.resolveBoolean
14 | import org.ohosdev.hapviewerandroid.extensions.thisApp
15 |
16 | /**
17 | * @author Jesse205
18 | * */
19 | class ThemeManager(val context: Context) {
20 |
21 | private var themeType: ThemeType? = null
22 | private val preferenceThemeType get() = context.thisApp.appPreference.themeType
23 |
24 | fun applyTheme() {
25 | applyTheme(preferenceThemeType)
26 | }
27 |
28 | private fun applyTheme(themeType: ThemeType) {
29 | this.themeType = themeType
30 | val themeId: Int = when (themeType) {
31 | MATERIAL1 -> R.style.Theme_HapViewerAndroid
32 | MATERIAL2 -> R.style.Theme_HapViewerAndroid_Material2
33 | MATERIAL3 -> R.style.Theme_HapViewerAndroid_Material3
34 | HARMONY -> R.style.Theme_HapViewerAndroid_Harmony
35 | }
36 | context.setTheme(themeId)
37 | if (context is Activity) {
38 | context.window.also {
39 | WindowCompat.getInsetsController(it, it.decorView).apply {
40 | isAppearanceLightNavigationBars = context.resolveBoolean(R.attr.windowLightNavigationBar, false)
41 | isAppearanceLightStatusBars = context.resolveBoolean(R.attr.windowLightStatusBar, false)
42 | }
43 | }
44 | }
45 | }
46 |
47 | fun isThemeChanged(): Boolean {
48 | return isThemeChanged(preferenceThemeType)
49 | }
50 |
51 | fun isThemeChanged(themeType: ThemeType): Boolean {
52 | return (themeType != this.themeType).also {
53 | Log.i(TAG, "isThemeChanged: $it")
54 | }
55 | }
56 |
57 | companion object {
58 | private const val TAG = "ThemeManager"
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/common/dialog/RequestPermissionDialogBuilder.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.common.dialog
2 |
3 | import android.content.Context
4 | import androidx.annotation.StringRes
5 | import androidx.appcompat.app.AlertDialog
6 | import org.ohosdev.hapviewerandroid.R
7 | import org.ohosdev.hapviewerandroid.extensions.getQuantityString
8 | import org.ohosdev.hapviewerandroid.extensions.localisedSeparator
9 |
10 | class RequestPermissionDialogBuilder>(context: Context) :
11 | AlertDialogBuilder(context) {
12 | private var permissionNames: Array = arrayOf()
13 | private var functionNames: Array = arrayOf()
14 | private var onAgree: (() -> Unit)? = null
15 | private var additional: String = ""
16 |
17 | init {
18 | setPositiveButton(android.R.string.ok) { _, _ ->
19 | onAgree?.invoke()
20 | }
21 | setNegativeButton(android.R.string.cancel, null)
22 | }
23 |
24 | fun setPermissionNames(names: Array) = apply {
25 | this.permissionNames = names
26 | }
27 |
28 | fun setPermissionNames(names: IntArray) = apply {
29 | this.permissionNames = Array(names.size) { context.getString(names[it]) }
30 | }
31 |
32 | fun setFunctionNames(names: Array) = apply {
33 | this.functionNames = names
34 | }
35 |
36 | fun setFunctionNames(names: IntArray) = apply {
37 | this.functionNames = Array(names.size) { context.getString(names[it]) }
38 | }
39 |
40 | fun setOnAgree(onAgree: () -> Unit) = apply {
41 | this.onAgree = onAgree
42 | }
43 |
44 |
45 | fun setAdditional(@StringRes additional: Int) = apply {
46 | this.additional = context.getString(additional)
47 | }
48 |
49 |
50 | override fun create(): AlertDialog {
51 | val separator = context.localisedSeparator
52 | val permissionNamesText = permissionNames.joinToString(separator = separator)
53 | val functionNamesText = functionNames.joinToString(separator = separator)
54 | setTitle(context.getQuantityString(R.plurals.permission_request, permissionNames.size))
55 | setMessage(
56 | context.getString(
57 | R.string.permission_request_message,
58 | permissionNamesText,
59 | functionNamesText,
60 | additional
61 | ).trim()
62 | )
63 | return super.create()
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/layout/harmony_alert_dialog_title.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
23 |
24 |
25 |
34 |
35 |
43 |
44 |
53 |
54 |
55 |
56 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/main/MoreInfoDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.main
2 |
3 | import android.app.Dialog
4 | import android.graphics.Color
5 | import android.os.Bundle
6 | import androidx.core.text.HtmlCompat
7 | import androidx.fragment.app.DialogFragment
8 | import org.ohosdev.hapviewerandroid.R
9 | import org.ohosdev.hapviewerandroid.extensions.contentSelectable
10 | import org.ohosdev.hapviewerandroid.extensions.ensureArguments
11 | import org.ohosdev.hapviewerandroid.ui.common.dialog.AlertDialogBuilder
12 | import org.ohosdev.hapviewerandroid.util.highlight.JSONHighlighter
13 |
14 | class MoreInfoDialogFragment : DialogFragment() {
15 | private lateinit var htmlSpanned: CharSequence
16 |
17 | @OptIn(ExperimentalStdlibApi::class)
18 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
19 | val stringColor: String
20 | val numberColor: String
21 | val keywordColor: String
22 | requireContext().obtainStyledAttributes(
23 | intArrayOf(
24 | R.attr.codeColorString,
25 | R.attr.codeColorNumber,
26 | R.attr.codeColorKeyword
27 | )
28 | ).also {
29 | val noAlphaBlack = 0xff000000.toInt()
30 | fun getCodeColor(index: Int) =
31 | it.getColor(index, Color.RED).run { "#${(this - noAlphaBlack).toHexString()}" }
32 | stringColor = getCodeColor(0)
33 | numberColor = getCodeColor(1)
34 | keywordColor = getCodeColor(2)
35 | }.recycle()
36 |
37 | htmlSpanned = HtmlCompat.fromHtml(
38 | JSONHighlighter.highlight(
39 | arguments?.getString("info") ?: "",
40 | stringColor = stringColor,
41 | numberColor = numberColor,
42 | keywordColor = keywordColor
43 | ),
44 | HtmlCompat.FROM_HTML_MODE_LEGACY
45 | )
46 | return AlertDialogBuilder(requireContext())
47 | .setTitle(R.string.more_info)
48 | .setMessage(htmlSpanned)
49 | .setPositiveButton(android.R.string.ok, null)
50 | .create()
51 | }
52 |
53 | override fun onStart() {
54 | super.onStart()
55 | dialog?.apply {
56 | contentSelectable = true
57 | }
58 | }
59 |
60 | fun setInfoJson(info: CharSequence) = apply {
61 | ensureArguments()
62 | arguments?.putCharSequence("info", info)
63 | }
64 |
65 | companion object {
66 | const val TAG = "MoreInfoDialogFragment"
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
13 |
14 |
15 |
18 |
19 |
24 |
25 |
26 |
30 |
31 |
36 |
37 |
38 |
41 |
42 |
50 |
51 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/layout/harmony_alert_dialog_button_bar.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
24 |
25 |
38 |
39 |
46 |
47 |
54 |
55 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/model/HapInfo.java:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.model;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.alibaba.fastjson.JSONArray;
6 |
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Set;
10 |
11 |
12 | /**
13 | * Hap安装包信息
14 | *
15 | * @author westinyang
16 | */
17 | public class HapInfo {
18 | public static final HapInfo INIT = new HapInfo(true);
19 | public static final int FLAG_FILE_STATE_INSTALLING = 1;
20 | public static final int FLAG_FILE_STATE_PREINSTALLATION = 1<<1;
21 | public static final int FLAG_FILE_STATE_INIT = 1 << 2;
22 |
23 | public int fileStateFlags = 0;
24 |
25 | /* app. */
26 | public Bitmap icon;
27 | public String iconPath;
28 | public byte[] iconBytes;
29 | public String labelName;
30 | public String appName;
31 | public String bundleName;
32 | public String versionName;
33 | public String versionCode;
34 | public String vendor;
35 | public String minAPIVersion;
36 | public String targetAPIVersion;
37 | public String apiReleaseType;
38 |
39 | /* module. */
40 | public String mainElement;
41 |
42 | public JSONArray requestPermissions;
43 | public List requestPermissionNames;
44 |
45 | /* more */
46 | public Map moreInfo;
47 |
48 | /* 额外 */
49 | public String hapFilePath;
50 | public Set techList;
51 |
52 | public HapInfo() {
53 | }
54 |
55 | private HapInfo(boolean init) {
56 | if (init) {
57 | fileStateFlags |= FLAG_FILE_STATE_INIT;
58 | }
59 | }
60 |
61 | public boolean isInit() {
62 | return (fileStateFlags & FLAG_FILE_STATE_INIT) != 0;
63 | }
64 |
65 | public boolean isInstalling() {
66 | return (fileStateFlags & FLAG_FILE_STATE_INSTALLING) != 0;
67 | }
68 |
69 | public boolean isPreinstallation() {
70 | return (fileStateFlags & FLAG_FILE_STATE_PREINSTALLATION) != 0;
71 | }
72 |
73 |
74 | public void setInstalling(boolean installing) {
75 | synchronized (this) {
76 | if (installing) {
77 | fileStateFlags |= FLAG_FILE_STATE_INSTALLING;
78 | } else {
79 | fileStateFlags &= ~FLAG_FILE_STATE_INSTALLING;
80 | }
81 | }
82 | }
83 |
84 | public void setPreinstallation(boolean preinstallation) {
85 | synchronized (this) {
86 | if (preinstallation) {
87 | fileStateFlags |= FLAG_FILE_STATE_PREINSTALLATION;
88 | } else {
89 | fileStateFlags &= ~FLAG_FILE_STATE_PREINSTALLATION;
90 | }
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
13 |
14 |
26 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/ContextExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.ClipData
5 | import android.content.ClipboardManager
6 | import android.content.Context
7 | import android.content.pm.PackageManager
8 | import android.content.res.Resources
9 | import android.graphics.Bitmap
10 | import android.graphics.BitmapFactory
11 | import android.net.Uri
12 | import androidx.annotation.AttrRes
13 | import androidx.annotation.DrawableRes
14 | import androidx.annotation.PluralsRes
15 | import androidx.browser.customtabs.CustomTabsIntent
16 | import androidx.core.content.ContextCompat
17 | import androidx.core.content.getSystemService
18 | import com.google.android.material.resources.MaterialAttributes
19 | import org.ohosdev.hapviewerandroid.R
20 | import org.ohosdev.hapviewerandroid.app.HapViewerApp
21 | import java.io.File
22 |
23 | val Context.thisApp get() = applicationContext as HapViewerApp
24 |
25 | /**
26 | * 自动获取缓存目录。如果外部存储可用,就使用外部存储缓存目录,否则获取内部缓存目录
27 | * @see Context.getExternalCacheDir
28 | * @see Context.getCacheDir
29 | * */
30 | val Context.autoCacheDir: File get() = externalCacheDir ?: cacheDir
31 |
32 | fun Context.isPermissionGranted(permission: String) =
33 | ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
34 |
35 | /**
36 | * 获取资源中的 `Bitmap`
37 | * @see BitmapFactory.decodeResource
38 | * */
39 | fun Context.getBitmap(@DrawableRes resId: Int): Bitmap? {
40 | return BitmapFactory.decodeResource(resources, resId)
41 | }
42 |
43 | /**
44 | * 使用 CustomTabs 打开网页链接
45 | * */
46 | fun Context.openUrl(url: String) {
47 | val tabsIntent = CustomTabsIntent.Builder()
48 | .build()
49 | tabsIntent.launchUrl(this, Uri.parse(url))
50 | }
51 |
52 |
53 | /**
54 | * 获取具有复数的字符串
55 | * @see Resources.getQuantityString
56 | * */
57 | fun Context.getQuantityString(@PluralsRes id: Int, quantity: Int, vararg formatArgs: Any) =
58 | resources.getQuantityString(id, quantity, *formatArgs)
59 |
60 | /**
61 | * 复制文字到剪贴板
62 | * */
63 | fun Context.copyText(text: CharSequence) {
64 | val manager = getSystemService()!!
65 | manager.setPrimaryClip(ClipData.newPlainText(null, text))
66 | }
67 |
68 | /**
69 | * 本地化的分隔符,中文为”,“,英文为“, ”
70 | * */
71 | val Context.localisedSeparator get() = getString(R.string.symbol_separator)
72 |
73 | /**
74 | * 本地化的冒号,中文为”:“,英文为“: ”
75 | * */
76 | val Context.localisedColon get() = getString(R.string.symbol_colon)
77 |
78 | /**
79 | * 返回所提供属性 `attributeResId` 的布尔值,如果属性不是布尔值或不存在于当前主题中,则返回 `defaultValue`。
80 | * */
81 | @SuppressLint("RestrictedApi")
82 | fun Context.resolveBoolean(@AttrRes attributeResId: Int, defaultValue: Boolean) =
83 | MaterialAttributes.resolveBoolean(this, attributeResId, defaultValue)
84 |
--------------------------------------------------------------------------------
/PrivacyPolicy.md:
--------------------------------------------------------------------------------
1 | # 隐私政策
2 |
3 | 更新日期:**2023/6/19**
4 |
5 | 生效日期:**2023/6/19**
6 |
7 | ## 导言
8 |
9 | _HAP查看器_ 是一款由 _westinyang_ (以下简称“我们”)提供的产品。 您在使用我们的服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明,在使用我们的服务时,我们如何收集、使用、储存和分享这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。 本《隐私政策》与您所使用的 _HAP查看器_ 服务息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。
10 |
11 | **您使用或继续使用我们的服务,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。**
12 |
13 | 如对本《隐私政策》或相关事宜有任何问题,请通过 **https://gitee.com/ohos-dev/hap-viewer-android** 与我们联系。
14 |
15 | ## 1\. 我们收集的信息
16 |
17 | 我们或我们的第三方合作伙伴提供服务时,可能会收集、储存和使用下列与您有关的信息。如果您不提供相关信息,可能无法注册成为我们的用户或无法享受我们提供的某些服务,或者无法达到相关服务拟达到的效果。
18 |
19 | ## 2\. 信息的存储
20 |
21 | **2.1 信息存储的方式和期限**
22 |
23 | * 我们会通过安全的方式存储您的信息,包括本地存储(例如利用APP进行数据缓存)、数据库和服务器日志。
24 | * 一般情况下,我们只会在为实现服务目的所必需的时间内或法律法规规定的条件下存储您的个人信息。
25 |
26 | **2.2 信息存储的地域**
27 |
28 | * 我们会按照法律法规规定,将境内收集的用户个人信息存储于中国境内。
29 | * 目前我们不会跨境传输或存储您的个人信息。将来如需跨境传输或存储的,我们会向您告知信息出境的目的、接收方、安全保证措施和安全风险,并征得您的同意。
30 |
31 | **2.3 产品或服务停止运营时的通知**
32 |
33 | * 当我们的产品或服务发生停止运营的情况时,我们将以推送通知、公告等形式通知您,并在合理期限内删除您的个人信息或进行匿名化处理,法律法规另有规定的除外。
34 |
35 | ## 3\. 信息安全
36 |
37 | 我们使用各种安全技术和程序,以防信息的丢失、不当使用、未经授权阅览或披露。例如,在某些服务中,我们将利用加密技术(例如SSL)来保护您提供的个人信息。但请您理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便竭尽所能加强安全措施,也不可能始终保证信息百分之百的安全。您需要了解,您接入我们的服务所用的系统和通讯网络,有可能因我们可控范围外的因素而出现问题。
38 |
39 | ## 4\. 我们如何使用信息
40 |
41 | 我们可能将在向您提供服务的过程之中所收集的信息用作下列用途:
42 |
43 | * 向您提供服务;
44 | * 在我们提供服务时,用于身份验证、客户服务、安全防范、诈骗监测、存档和备份用途,确保我们向您提供的产品和服务的安全性;
45 | * 帮助我们设计新服务,改善我们现有服务;
46 | * 使我们更加了解您如何接入和使用我们的服务,从而针对性地回应您的个性化需求,例如语言设定、位置设定、个性化的帮助服务和指示,或对您和其他用户作出其他方面的回应;
47 | * 向您提供与您更加相关的广告以替代普遍投放的广告;
48 | * 评估我们服务中的广告和其他促销及推广活动的效果,并加以改善;
49 | * 软件认证或管理软件升级;
50 | * 让您参与有关我们产品和服务的调查。
51 |
52 | ## 5\. 信息共享
53 |
54 | 目前,我们不会主动共享或转让您的个人信息至第三方,如存在其他共享或转让您的个人信息或您需要我们将您的个人信息共享或转让至第三方情形时,我们会直接或确认第三方征得您对上述行为的明示同意。
55 |
56 | 为了投放广告,评估、优化广告投放效果等目的,我们需要向广告主及其代理商等第三方合作伙伴共享您的部分数据,要求其严格遵守我们关于数据隐私保护的措施与要求,包括但不限于根据数据保护协议、承诺书及相关数据处理政策进行处理,避免识别出个人身份,保障隐私安全。
57 |
58 | 我们不会向合作伙伴分享可用于识别您个人身份的信息(例如您的姓名或电子邮件地址),除非您明确授权。
59 |
60 | 我们不会对外公开披露所收集的个人信息,如必须公开披露时,我们会向您告知此次公开披露的目的、披露信息的类型及可能涉及的敏感信息,并征得您的明示同意。
61 |
62 | 随着我们业务的持续发展,我们有可能进行合并、收购、资产转让等交易,我们将告知您相关情形,按照法律法规及不低于本《隐私政策》所要求的标准继续保护或要求新的控制者继续保护您的个人信息。
63 |
64 | 另外,根据相关法律法规及国家标准,以下情形中,我们可能会共享、转让、公开披露个人信息无需事先征得您的授权同意:
65 |
66 | * 与国家安全、国防安全直接相关的;
67 | * 与公共安全、公共卫生、重大公共利益直接相关的;
68 | * 犯罪侦查、起诉、审判和判决执行等直接相关的;
69 | * 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
70 | * 个人信息主体自行向社会公众公开个人信息的;
71 | * 从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。
72 |
73 | ## 6\. 您的权利
74 |
75 | 在您使用我们的服务期间,我们可能会视产品具体情况为您提供相应的操作设置,以便您可以查询、删除、更正或撤回您的相关个人信息,您可参考相应的具体指引进行操作。此外,我们还设置了投诉举报渠道,您的意见将会得到及时的处理。如果您无法通过上述途径和方式行使您的个人信息主体权利,您可以通过本《隐私政策》中提供的联系方式提出您的请求,我们会按照法律法规的规定予以反馈。
76 |
77 | ## 7\. 变更
78 |
79 | 我们可能适时修订本《隐私政策》的条款。当变更发生时,我们会在版本更新时向您提示新的《隐私政策》,并向您说明生效日期。请您仔细阅读变更后的《隐私政策》内容,**若您继续使用我们的服务,即表示您同意我们按照更新后的《隐私政策》处理您的个人信息。**
80 |
81 | ## 8\. 未成年人保护
82 |
83 | 我们鼓励父母或监护人指导未满十八岁的未成年人使用我们的服务。我们建议未成年人鼓励他们的父母或监护人阅读本《隐私政策》,并建议未成年人在提交的个人信息之前寻求父母或监护人的同意和指导。
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-parcelize'
5 | }
6 |
7 | android {
8 | namespace 'org.ohosdev.hapviewerandroid'
9 | compileSdk 34
10 |
11 | defaultConfig {
12 | applicationId "org.ohosdev.hapviewerandroid"
13 | minSdk 24
14 | targetSdk 33
15 | versionCode 7
16 | versionName "2.0.0"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 |
20 | // resConfigs "en", "zh-rCN"
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled true
26 | shrinkResources true
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | signingConfig signingConfigs.debug
29 | }
30 | debug {
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
33 | signingConfig signingConfigs.debug
34 | applicationIdSuffix '.debug'
35 | }
36 | }
37 |
38 | compileOptions {
39 | sourceCompatibility JavaVersion.VERSION_1_8
40 | targetCompatibility JavaVersion.VERSION_1_8
41 | }
42 |
43 | kotlinOptions {
44 | jvmTarget = JavaVersion.VERSION_1_8
45 | }
46 |
47 | buildFeatures {
48 | viewBinding true
49 | aidl true
50 | buildConfig true
51 | }
52 |
53 | packagingOptions {
54 | exclude "/META-INF/*.kotlin_module"
55 | exclude "/kotlin/**"
56 | }
57 | }
58 |
59 | dependencies {
60 |
61 | implementation 'androidx.appcompat:appcompat:1.6.1'
62 | implementation 'com.google.android.material:material:1.11.0'
63 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
64 | implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
65 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0"
66 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
67 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
68 | implementation "androidx.preference:preference:1.2.1"
69 | implementation "androidx.preference:preference-ktx:1.2.1"
70 | implementation 'androidx.browser:browser:1.7.0'
71 |
72 | implementation project(':harmonystyle')
73 |
74 | testImplementation 'junit:junit:4.13.2'
75 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
76 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
77 |
78 | implementation 'cn.hutool:hutool-all:5.8.16'
79 | implementation 'com.alibaba:fastjson:1.2.83'
80 | implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
81 | implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0'
82 | implementation "dev.rikka.shizuku:api:13.1.5"
83 | implementation "dev.rikka.shizuku:provider:13.1.5"
84 |
85 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/themes_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/BitmapExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.Canvas
6 | import android.graphics.Rect
7 | import android.renderscript.Allocation
8 | import android.renderscript.Element
9 | import android.renderscript.RenderScript
10 | import android.renderscript.ScriptIntrinsicBlur
11 | import android.util.Log
12 | import androidx.annotation.FloatRange
13 | import kotlin.math.min
14 | import kotlin.math.roundToInt
15 |
16 | private const val TAG = "BitmapExtensions"
17 |
18 | val Bitmap.ratio get() = width.toFloat() / height
19 | val Bitmap.widthF get() = width.toFloat()
20 | val Bitmap.heightF get() = height.toFloat()
21 |
22 | /**
23 | * 模糊Bitmap
24 | *
25 | * [https://www.jianshu.com/p/dc6120570cea](https://www.jianshu.com/p/dc6120570cea)
26 | *
27 | * (这个类虽然废弃了,但是也可以用)
28 | * @param context 上下文
29 | */
30 | @Suppress("DEPRECATION")
31 | fun Bitmap.blur(context: Context, @FloatRange(from = 0.0, to = 25.0) radius: Float = 8f) {
32 | val renderScript = RenderScript.create(context)
33 | val scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
34 | val input = Allocation.createFromBitmap(renderScript, this)
35 | val output = Allocation.createTyped(renderScript, input.type)
36 | scriptIntrinsicBlur.setRadius(radius)
37 | scriptIntrinsicBlur.setInput(input)
38 | scriptIntrinsicBlur.forEach(output)
39 | // 将数据填充到Allocation中
40 | output.copyTo(this)
41 | }
42 |
43 | /**
44 | * 创建已模糊的Bitmap,并且如果模糊半径大于25,则将缩小图片,并在大小上添加模糊半径
45 | *
46 | * */
47 | fun createBlurredBitmap(
48 | context: Context,
49 | bitmap: Bitmap,
50 | maxWidth: Int,
51 | maxHeight: Int,
52 | @FloatRange(from = 0.0) radius: Float
53 | ): Bitmap {
54 | // 不超过 25 的模糊半径
55 | val radiusScale = if (radius > 25f) 25f / radius else 1f
56 | // 不超过 [maxWidth, maxHeight] 的大小
57 | val scale =
58 | if (bitmap.ratio > maxWidth.toFloat() / maxHeight) maxWidth / bitmap.widthF else maxHeight / bitmap.heightF
59 |
60 | Log.d(TAG, "createBlurredBitmap: $scale")
61 | val scaledWidth = (radiusScale * scale * bitmap.width).roundToInt()
62 | val scaledHeight = (radiusScale * scale * bitmap.height).roundToInt()
63 | val scaledRadius = min(radiusScale * radius, 25f)
64 | val scaledRadiusInt = scaledRadius.roundToInt()
65 | val doubleScaledRadiusInt = (scaledRadius * 2).roundToInt()
66 | val scaledBitmap = Bitmap.createBitmap(
67 | scaledWidth + doubleScaledRadiusInt,
68 | scaledHeight + doubleScaledRadiusInt,
69 | Bitmap.Config.ARGB_8888
70 | ).apply {
71 | Canvas(this).run {
72 | val rect = Rect(0, 0, scaledWidth, scaledHeight).apply {
73 | offset(scaledRadiusInt, scaledRadiusInt)
74 | }
75 | drawBitmap(bitmap, null, rect, null)
76 | }
77 | if (scaledRadius > 0) {
78 | blur(context, scaledRadius)
79 | }
80 | }
81 | return scaledBitmap
82 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/themes_material3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/helper/ShizukuServiceHelper.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util.helper
2 |
3 | import android.content.ComponentName
4 | import android.content.ServiceConnection
5 | import android.os.IBinder
6 | import android.util.Log
7 | import org.ohosdev.hapviewerandroid.BuildConfig
8 | import org.ohosdev.hapviewerandroid.IUserService
9 | import org.ohosdev.hapviewerandroid.service.shizuku.UserService
10 | import rikka.shizuku.Shizuku
11 | import rikka.shizuku.Shizuku.UserServiceArgs
12 |
13 | /**
14 | * 用于简化 Shizuku 绑定、解绑的过程。
15 | * */
16 | class ShizukuServiceHelper {
17 | private var _service: IUserService? = null
18 | val service get() = _service
19 | val isServiceBound get() = _service != null
20 | private val onServiceConnectedListeners = mutableListOf<() -> Unit>()
21 |
22 | private val userServiceConnection: ServiceConnection = object : ServiceConnection {
23 | override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
24 | binder.pingBinder().let {
25 | _service = if (it) IUserService.Stub.asInterface(binder) else null
26 | if (!it) {
27 | Log.e(TAG, "onServiceConnected: invalid binder for $componentName received")
28 | }
29 | }
30 | onServiceConnectedListeners.forEach { it() }
31 | onServiceConnectedListeners.clear()
32 | }
33 |
34 | override fun onServiceDisconnected(componentName: ComponentName) {
35 | _service = null
36 | }
37 | }
38 |
39 | private val userServiceArgs = UserServiceArgs(
40 | ComponentName(BuildConfig.APPLICATION_ID, UserService::class.java.name)
41 | )
42 | .daemon(false)
43 | .processNameSuffix("service")
44 | .debuggable(BuildConfig.DEBUG)
45 | .version(BuildConfig.VERSION_CODE)
46 |
47 | /**
48 | * 绑定服务并执行`onBound`。如果先前已绑定,将直接执行`onBound`。
49 | * */
50 | fun bindUserService(onBound: (() -> Unit)?) {
51 | if (isServiceBound) {
52 | onBound?.invoke()
53 | return
54 | }
55 | if (!isSupported()) {
56 | throw RuntimeException("Current Shizuku version is not supported: ${Shizuku.getVersion()}")
57 | }
58 | if (onBound != null) {
59 | onServiceConnectedListeners.add(onBound)
60 | }
61 | runCatching {
62 | Shizuku.bindUserService(userServiceArgs, userServiceConnection)
63 | }.onFailure {
64 | if (onBound != null) {
65 | onServiceConnectedListeners.remove(onBound)
66 | }
67 | throw it
68 | }
69 | }
70 |
71 | /**
72 | * 解绑服务
73 | * */
74 | fun unbindUserService() {
75 | if (!isServiceBound) return
76 | if (!isSupported()) {
77 | throw RuntimeException("Current Shizuku version is not supported: ${Shizuku.getVersion()}")
78 | }
79 | Shizuku.unbindUserService(userServiceArgs, userServiceConnection, true)
80 | }
81 |
82 | companion object {
83 | private const val TAG = "ShizukuHelper"
84 |
85 | fun isSupported(): Boolean {
86 | return Shizuku.getVersion() >= 10
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | HAP查看器
4 | HAP查看器 for Android
5 | 支持解析 OpenHarmony(开源鸿蒙)、HarmonyOS(鸿蒙) API 9+(Stage模型) 的应用安装包,支持在 Android 7+ 的安卓设备上运行。
6 |
7 |
8 |
9 | 关于
10 | 应用版本
11 | 开源仓库
12 | 开源许可
13 | 开源贡献
14 |
15 |
16 | 复制
17 | 已复制
18 | 已复制 %s
19 |
20 |
21 | 、
22 | :
23 |
24 |
25 | 主题…
26 |
27 |
28 | 安装
29 | 安装完成
30 | 安装 HAP
31 | 安装 HAP 需要操作系统的支持,且必须拥有正确的签名,所以您从非官方应用市场中下载的软件包很可能无法安装。因未知原因,无法确定软件是否安装成功,因此本软件将始终显示“安装完成”。
32 |
33 |
34 | 权限
35 | 权限请求
36 | 权限申请失败
37 | HAP 查看器需要 %1$s 来确保可以 %2$s。%3$s
38 | Shizuku 权限
39 | 存储权限
40 | 如果您不授予该权限,HAP 查看器将先复制文件到私有空间后再读取。
41 |
42 |
43 | 复制包名
44 | 复制版本号
45 | 复制版本名
46 | 复制应用名
47 |
48 |
49 | 法律信息…
50 | 隐私政策
51 | 开源许可
52 |
53 |
54 | 应用名:%1$s,版本名:%2$s,版本号:%3$s,包名:%4$s。
55 |
56 |
57 | 文件不可读。
58 | 此 URI 不是文档。
59 | URI 不合法。
60 |
61 |
62 | 文件获取失败
63 | hap 文件解析失败,目前仅支持解析 API 9+ (Stage 模型) 的应用安装包
64 | 文件类型错误,请选择一个 hap 安装包
65 | 忽略错误,继续解析
66 |
67 |
68 | 未知
69 | 未知应用名
70 | 未知版本
71 | 未知包名
72 | 未知错误
73 |
74 |
75 | 拖到此处
76 | 再按一次返回键退出
77 | 指南
78 | 更多信息
79 | 直接读取文件
80 | 选择 HAP 文件
81 | 空空如也
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/common/dialog/RequestPermissionDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.common.dialog
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import androidx.annotation.StringRes
6 | import androidx.appcompat.app.AlertDialog
7 | import androidx.fragment.app.setFragmentResult
8 | import org.ohosdev.hapviewerandroid.R
9 | import org.ohosdev.hapviewerandroid.extensions.ensureArguments
10 | import org.ohosdev.hapviewerandroid.extensions.openUrl
11 |
12 | class RequestPermissionDialogFragment : AlertDialogFragment() {
13 |
14 | override fun onCreateAlertDialogBuilder(): RequestPermissionDialogBuilder<*> =
15 | RequestPermissionDialogBuilder(requireContext())
16 |
17 | override fun onAttachAlertDialogBuilder(builder: AlertDialogBuilder<*>) {
18 | super.onAttachAlertDialogBuilder(builder)
19 | builder as RequestPermissionDialogBuilder<*>
20 | arguments?.also { args ->
21 | args.getString(ARG_KEY_ON_AGREE)?.also {
22 | builder.setOnAgree {
23 | setFragmentResult(it, Bundle())
24 | }
25 | }
26 | args.getStringArray(ARG_KEY_PERMISSION_NAMES)?.also { builder.setPermissionNames(it) }
27 | args.getIntArray(ARG_KEY_PERMISSION_NAME_IDS)?.also { builder.setPermissionNames(it) }
28 | args.getStringArray(ARG_KEY_FUNCTION_NAMES)?.also { builder.setFunctionNames(it) }
29 | args.getIntArray(ARG_KEY_FUNCTION_NAME_IDS)?.also { builder.setFunctionNames(it) }
30 | args.getInt(ARG_KEY_ADDITIONAL_ID, 0).also { if (it != 0) builder.setAdditional(it) }
31 | args.getString(ARG_KEY_GUIDE_URL)
32 | ?.also { builder.setNeutralButton(R.string.guide, null) }
33 | }
34 | }
35 |
36 | override fun onStart() {
37 | super.onStart()
38 | dialog?.apply {
39 | this as AlertDialog
40 | arguments?.getString(ARG_KEY_GUIDE_URL)?.also { url ->
41 | getButton(Dialog.BUTTON_NEUTRAL).setOnClickListener {
42 | requireContext().openUrl(url)
43 | }
44 | }
45 | }
46 | }
47 |
48 | fun setPermissionNames(names: Array) = apply {
49 | ensureArguments().putStringArray(ARG_KEY_PERMISSION_NAMES, names)
50 | }
51 |
52 | fun setPermissionNames(names: IntArray) = apply {
53 | ensureArguments().putIntArray(ARG_KEY_PERMISSION_NAME_IDS, names)
54 | }
55 |
56 | fun setFunctionNames(names: Array) = apply {
57 | ensureArguments().putStringArray(ARG_KEY_FUNCTION_NAMES, names)
58 | }
59 |
60 | fun setFunctionNames(names: IntArray) = apply {
61 | ensureArguments().putIntArray(ARG_KEY_FUNCTION_NAME_IDS, names)
62 | }
63 |
64 | fun setOnAgreeKey(key: String) = apply {
65 | ensureArguments().putString(ARG_KEY_ON_AGREE, key)
66 | }
67 |
68 | fun setAdditional(@StringRes additional: Int) = apply {
69 | ensureArguments().putInt(ARG_KEY_ADDITIONAL_ID, additional)
70 | }
71 |
72 | fun setGuideUrl(url: String) = apply {
73 | ensureArguments().putString(ARG_KEY_GUIDE_URL, url)
74 | }
75 |
76 | companion object {
77 | const val ARG_KEY_PERMISSION_NAMES = "permissionNames"
78 | const val ARG_KEY_PERMISSION_NAME_IDS = "permissionNameIds"
79 | const val ARG_KEY_FUNCTION_NAMES = "functionNames"
80 | const val ARG_KEY_FUNCTION_NAME_IDS = "functionNameIds"
81 | const val ARG_KEY_ON_AGREE = "agreeKey"
82 | const val ARG_KEY_ADDITIONAL_ID = "additionalId"
83 | const val ARG_KEY_GUIDE_URL = "guideUrl"
84 | }
85 |
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/util/ShizukuUtil.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.util
2 |
3 | import android.content.pm.PackageManager
4 | import androidx.lifecycle.DefaultLifecycleObserver
5 | import androidx.lifecycle.LifecycleOwner
6 | import org.ohosdev.hapviewerandroid.util.ShizukuUtil.ShizukuStatus.ERROR
7 | import org.ohosdev.hapviewerandroid.util.ShizukuUtil.ShizukuStatus.GRANTED
8 | import org.ohosdev.hapviewerandroid.util.ShizukuUtil.ShizukuStatus.NOT_GRANTED
9 | import org.ohosdev.hapviewerandroid.util.ShizukuUtil.ShizukuStatus.NOT_SUPPORT
10 | import org.ohosdev.hapviewerandroid.util.ShizukuUtil.ShizukuStatus.SHOULD_SHOW_REQUEST_PERMISSION_RATIONALE
11 | import rikka.shizuku.Shizuku
12 |
13 |
14 | class ShizukuUtil {
15 | companion object {
16 | const val PERMISSION = "moe.shizuku.manager.permission.API_V23"
17 | const val URL_GUIDE = "https://shizuku.rikka.app/zh-hans/guide/setup/"
18 |
19 | fun checkPermission() = runCatching {
20 | return@runCatching if (Shizuku.isPreV11()) {
21 | NOT_SUPPORT
22 | } else if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
23 | GRANTED
24 | } else if (Shizuku.shouldShowRequestPermissionRationale()) {
25 | SHOULD_SHOW_REQUEST_PERMISSION_RATIONALE
26 | } else {
27 | NOT_GRANTED
28 | }
29 | }.getOrElse { ERROR }
30 | }
31 |
32 | enum class ShizukuStatus {
33 | NOT_SUPPORT,
34 | SHOULD_SHOW_REQUEST_PERMISSION_RATIONALE,
35 | GRANTED,
36 | NOT_GRANTED,
37 | ERROR
38 | }
39 |
40 | class ShizukuLifecycleObserver : DefaultLifecycleObserver {
41 | private val onRequestPermissionResultListener =
42 | Shizuku.OnRequestPermissionResultListener { requestCode, grantResult ->
43 | requestPermissionResultListener?.onRequestPermissionResult(requestCode, grantResult)
44 | }
45 | private val onBinderReceivedListener =
46 | Shizuku.OnBinderReceivedListener { binderReceivedListener?.onBinderReceived() }
47 | private val onBinderDeadListener = Shizuku.OnBinderDeadListener { binderDeadListener?.onBinderDead() }
48 |
49 | private var requestPermissionResultListener: Shizuku.OnRequestPermissionResultListener? = null
50 | private var binderReceivedListener: Shizuku.OnBinderReceivedListener? = null
51 | private var binderDeadListener: Shizuku.OnBinderDeadListener? = null
52 |
53 | override fun onCreate(owner: LifecycleOwner) {
54 | super.onCreate(owner)
55 | Shizuku.addRequestPermissionResultListener(onRequestPermissionResultListener)
56 | Shizuku.addBinderReceivedListener(onBinderReceivedListener)
57 | Shizuku.addBinderDeadListener(onBinderDeadListener)
58 | }
59 |
60 | override fun onDestroy(owner: LifecycleOwner) {
61 | super.onDestroy(owner)
62 | Shizuku.removeRequestPermissionResultListener(onRequestPermissionResultListener)
63 | Shizuku.removeBinderReceivedListener(onBinderReceivedListener)
64 | Shizuku.removeBinderDeadListener(onBinderDeadListener)
65 | }
66 |
67 | fun setRequestPermissionResultListener(listener: Shizuku.OnRequestPermissionResultListener) {
68 | requestPermissionResultListener = listener
69 | }
70 |
71 | fun setBinderReceivedListener(listener: Shizuku.OnBinderReceivedListener) {
72 | binderReceivedListener = listener
73 | }
74 |
75 | fun setBinderDeadListener(listener: Shizuku.OnBinderDeadListener) {
76 | binderDeadListener = listener
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/themes_material2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main_basic.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
25 |
26 |
42 |
43 |
58 |
59 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/harmonystyle/src/main/java/org/ohosdev/hapviewerandroid/harmonystyle/drawable/ShadowDrawable.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.harmonystyle.drawable
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.res.Resources
5 | import android.graphics.Bitmap
6 | import android.graphics.Canvas
7 | import android.graphics.Color
8 | import android.graphics.ColorFilter
9 | import android.graphics.Paint
10 | import android.graphics.PixelFormat
11 | import android.graphics.PorterDuff
12 | import android.graphics.Rect
13 | import android.graphics.RectF
14 | import android.graphics.drawable.Drawable
15 | import android.util.AttributeSet
16 | import androidx.annotation.Keep
17 | import androidx.core.content.res.TypedArrayUtils.obtainAttributes
18 | import org.ohosdev.hapviewerandroid.harmonystyle.R
19 | import org.xmlpull.v1.XmlPullParser
20 |
21 | @Suppress("unused")
22 | @Keep
23 | class ShadowDrawable : Drawable() {
24 |
25 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
26 | color = Color.TRANSPARENT
27 | style = Paint.Style.FILL
28 | }
29 |
30 | private var radius = 0f
31 | private var shadowRadius = 10f
32 | private val doubleShadowRadiusInt get() = (shadowRadius * 2).toInt()
33 | private var shadowColor = Color.BLACK
34 |
35 | /**
36 | * 阴影偏移量
37 | *
38 | * - 0:横向偏移量
39 | * - 1:纵向偏移量
40 | * */
41 | private var shadowOffset = arrayOf(0f, 0f)
42 |
43 | // 低版本安卓绘制阴影不能开启硬件加速
44 | private val noHardwareBitmap by lazy {
45 | Bitmap.createBitmap(
46 | bounds.width() + doubleShadowRadiusInt,
47 | bounds.height() + doubleShadowRadiusInt,
48 | Bitmap.Config.ARGB_8888
49 | ).also {
50 | noHardwareCanvas.setBitmap(it)
51 | }
52 | }
53 | private var noHardwareCanvas = Canvas()
54 |
55 | override fun draw(canvas: Canvas) {
56 | // 清空画布
57 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
58 | noHardwareCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
59 |
60 | // 设置阴影
61 | paint.setShadowLayer(shadowRadius, 0f, 0f, shadowColor)
62 | // 绘图并从没有硬件加速的`noHardwareCanvas`复制到`canvas`
63 | noHardwareCanvas.drawRoundRect(
64 | RectF(
65 | shadowRadius,
66 | shadowRadius,
67 | bounds.width() + shadowRadius,
68 | bounds.height() + shadowRadius
69 | ), radius, radius, paint
70 | )
71 | canvas.drawBitmap(
72 | noHardwareBitmap,
73 | -shadowRadius + shadowOffset[0],
74 | -shadowRadius + shadowOffset[1],
75 | null
76 | )
77 | }
78 |
79 | override fun onBoundsChange(bounds: Rect) {
80 | super.onBoundsChange(bounds)
81 | noHardwareBitmap.apply {
82 | width = bounds.width() + doubleShadowRadiusInt
83 | height = bounds.height() + doubleShadowRadiusInt
84 | }
85 | }
86 |
87 | override fun setAlpha(alpha: Int) {
88 | paint.alpha = alpha
89 | }
90 |
91 | override fun setColorFilter(colorFilter: ColorFilter?) {
92 | paint.setColorFilter(colorFilter)
93 | }
94 |
95 | @Suppress("OVERRIDE_DEPRECATION")
96 | override fun getOpacity() = PixelFormat.TRANSLUCENT
97 |
98 | fun setRadius(radius: Float) {
99 | this.radius = radius
100 | }
101 |
102 | fun setShadowColor(color: Int) {
103 | shadowColor = color
104 | }
105 |
106 | @SuppressLint("RestrictedApi")
107 | override fun inflate(
108 | r: Resources, parser: XmlPullParser, attrs: AttributeSet, theme: Resources.Theme?
109 | ) {
110 | super.inflate(r, parser, attrs, theme)
111 | obtainAttributes(r, theme, attrs, R.styleable.ShadowDrawable).also {
112 | radius = it.getDimension(R.styleable.ShadowDrawable_android_radius, radius)
113 | shadowRadius = it.getDimension(R.styleable.ShadowDrawable_shadowRadius, radius)
114 | shadowColor = it.getColor(R.styleable.ShadowDrawable_shadowColor, shadowColor)
115 | shadowOffset[0] = it.getDimension(R.styleable.ShadowDrawable_shadowX, 0f)
116 | shadowOffset[1] = it.getDimension(R.styleable.ShadowDrawable_shadowY, 0f)
117 | }.recycle()
118 | }
119 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/layout/harmony_alert_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
26 |
27 |
28 |
29 |
34 |
35 |
43 |
44 |
49 |
50 |
54 |
55 |
60 |
61 |
69 |
70 |
75 |
76 |
77 |
78 |
86 |
87 |
88 |
89 |
94 |
95 |
99 |
100 |
101 |
104 |
105 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/extensions/DocumentFileExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.extensions
2 |
3 | import android.Manifest
4 | import android.content.ContentResolver
5 | import android.content.ContentUris
6 | import android.content.Context
7 | import android.net.Uri
8 | import android.os.Environment
9 | import android.provider.DocumentsContract
10 | import android.provider.MediaStore
11 | import android.util.Log
12 | import androidx.core.text.isDigitsOnly
13 | import androidx.documentfile.provider.DocumentFile
14 | import cn.hutool.core.util.RandomUtil
15 | import org.ohosdev.hapviewerandroid.app.DIR_PATH_EXTERNAL_FILES
16 | import java.io.File
17 | import java.io.FileOutputStream
18 | import java.io.IOException
19 |
20 | private const val TAG = "DocumentFileExtensions"
21 |
22 | /**
23 | * 获取文件的真实路径,可能会获取失败。
24 | *
25 | * - 如果 `uri` 的 `scheme` 为 `content`,则使用 [getDataColumn] 获取文件路径。
26 | * - 如果 `uri` 的 `scheme` 为 `file`,则使用 [Uri.getPath] 获取文件路径。
27 | *
28 | * [Android使用系统文件管理器选择文件,并将Uri转换为File](https://blog.csdn.net/weixin_40255793/article/details/79496076)
29 | *
30 | * @author paulburke
31 | * */
32 | fun DocumentFile.getFilePath(context: Context): String? {
33 | val uri = uri
34 | val scheme = uri.scheme
35 | if (DocumentsContract.isDocumentUri(context, uri)) {
36 | val docId = DocumentsContract.getDocumentId(uri)
37 | if (uri.isExternalStorageDocument) {
38 | val split = docId.split(":")
39 | val (type, relativePath) = split
40 | if ("primary".equals(type, true))
41 | return "${Environment.getExternalStorageDirectory().absolutePath}/$relativePath"
42 | else {
43 | TODO("handle non-primary volumes")
44 | }
45 | } else if (uri.isDownloadsDocument) {
46 | if (docId.isEmpty() || !docId.isDigitsOnly()) return null
47 | val contentUri =
48 | ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), docId.toLong())
49 | return context.contentResolver.getDataColumn(contentUri)
50 | } else if (uri.isMediaDocument) {
51 | val (type, id) = docId.split(":")
52 | val contentUri: Uri = when (type) {
53 | "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
54 | "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
55 | "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
56 | else -> return null
57 | }
58 | return context.contentResolver.getDataColumn(contentUri, "_id=?", arrayOf(id))
59 | }
60 | } else if (ContentResolver.SCHEME_CONTENT == scheme) {
61 | return context.contentResolver.getDataColumn(uri)
62 | } else if (ContentResolver.SCHEME_FILE == scheme) {
63 | return uri.path
64 | }
65 | return null
66 | }
67 |
68 |
69 | /**
70 | * 仅当有存储权限,且可以获取文件路径时返回原文件,否则返回临时文件
71 | *
72 | * @param context 上下文
73 | * @return 获取到的文件路径,或者是复制到的新文件路径
74 | */
75 | fun DocumentFile.getOrCopyFile(context: Context, name: String): File? {
76 | if (context.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) {
77 | runCatching {
78 | val path = getFilePath(context) ?: return@runCatching
79 | val file = File(path)
80 | if (file.isFile && file.canRead()) {
81 | return file
82 | }
83 | Log.i(TAG, "getOrCopyFile: got path, but cannot read: $file")
84 | }.onFailure {
85 | Log.w(TAG, "getOrCopyFile: Failed to get file path: $this: ${it.localizedMessage}\n${it.stackTrace}")
86 | }
87 | }
88 | runCatching {
89 | return copyFileToPrivateDir(context, name)
90 | }.onFailure { it.printStackTrace() }
91 | return null
92 | }
93 |
94 |
95 | /**
96 | * 把文件复制到沙盒目录
97 | *
98 | * **注意**:仅支持 scheme 为 content 的 uri,否则将抛出异常
99 | *
100 | * [android10以上 uri转file uri转真实路径](https://blog.csdn.net/jingzz1/article/details/106188462)
101 | *
102 | * @throws IOException 当文件无法获取时抛出异常
103 | */
104 | @Throws(IOException::class)
105 | fun DocumentFile.copyFileToPrivateDir(
106 | context: Context,
107 | name: String = getName() ?: RandomUtil.randomString(10)
108 | ): File {
109 | val destFile = File(context.autoCacheDir, "$DIR_PATH_EXTERNAL_FILES/${name}")
110 | destFile.parentFile?.mkdirs()
111 | context.contentResolver.openInputStream(uri).use { inputStream ->
112 | if (inputStream == null) {
113 | throw IOException("Cannot open ${this@copyFileToPrivateDir} using contentResolver.openInputStream.")
114 | }
115 | FileOutputStream(destFile).use { outputStream ->
116 | inputStream.copyTo(outputStream)
117 | }
118 | }
119 | return destFile
120 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/view/drawable/ShadowBitmapDrawable.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.view.drawable
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.res.Resources
5 | import android.graphics.Bitmap
6 | import android.graphics.Canvas
7 | import android.graphics.Color
8 | import android.graphics.ColorFilter
9 | import android.graphics.ColorMatrix
10 | import android.graphics.ColorMatrixColorFilter
11 | import android.graphics.Paint
12 | import android.graphics.PixelFormat
13 | import android.graphics.Rect
14 | import android.graphics.drawable.Drawable
15 | import android.util.AttributeSet
16 | import androidx.core.content.res.TypedArrayUtils
17 | import org.ohosdev.hapviewerandroid.R
18 | import org.ohosdev.hapviewerandroid.app.HapViewerApp
19 | import org.ohosdev.hapviewerandroid.extensions.createBlurredBitmap
20 | import org.ohosdev.hapviewerandroid.extensions.ratio
21 | import org.xmlpull.v1.XmlPullParser
22 | import kotlin.math.roundToInt
23 |
24 | class ShadowBitmapDrawable : Drawable() {
25 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
26 | color = Color.BLACK
27 | style = Paint.Style.FILL
28 | colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {
29 | setScale(0f, 0f, 0f, 1f)
30 | })
31 | }
32 |
33 | var shadowRadius: Float = 0f
34 | set(value) {
35 | field = value
36 | refreshShadowBitmap()
37 | }
38 |
39 | var shadowColor
40 | get() = paint.color
41 | set(value) {
42 | paint.color = value
43 | }
44 |
45 | private val shadowRadiusI get() = shadowRadius.roundToInt()
46 |
47 | /**
48 | * 原始 Bitmap,用于在大小变化后重新生成`shadowBitmap`
49 | */
50 | var originBitmap: Bitmap? = null
51 | set(value) {
52 | field = value
53 | refreshShadowBitmap()
54 | }
55 |
56 | private var shadowBitmap: Bitmap? = null
57 |
58 | /**
59 | * 阴影偏移量
60 | *
61 | * - 0:横向偏移量
62 | * - 1:纵向偏移量
63 | * */
64 | private var shadowOffset = arrayOf(0, 0)
65 | private val rect = Rect(0, 0, 0, 0)
66 |
67 | override fun draw(canvas: Canvas) {
68 | shadowBitmap?.let {
69 | canvas.drawBitmap(it, null, rect, paint)
70 | }
71 | }
72 |
73 | override fun setAlpha(alpha: Int) {
74 | paint.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {
75 | setScale(0f, 0f, 0f, alpha / 255f)
76 | })
77 | }
78 |
79 | override fun setColorFilter(colorFilter: ColorFilter?) {
80 | paint.colorFilter = colorFilter
81 | }
82 |
83 | @Suppress("OVERRIDE_DEPRECATION")
84 | override fun getOpacity() = PixelFormat.TRANSLUCENT
85 |
86 | override fun onBoundsChange(bounds: Rect) {
87 | super.onBoundsChange(bounds)
88 | refreshShadowBitmap()
89 | }
90 |
91 | @SuppressLint("RestrictedApi")
92 | override fun inflate(r: Resources, parser: XmlPullParser, attrs: AttributeSet, theme: Resources.Theme?) {
93 | super.inflate(r, parser, attrs, theme)
94 | TypedArrayUtils.obtainAttributes(r, theme, attrs, R.styleable.ShadowBitmapDrawable).also {
95 | alpha = (it.getFloat(R.styleable.ShadowBitmapDrawable_android_alpha, 1f) * 255).toInt()
96 | shadowRadius = it.getDimension(R.styleable.ShadowBitmapDrawable_shadowRadius, 1f)
97 | shadowColor = it.getColor(R.styleable.ShadowBitmapDrawable_shadowColor, Color.BLACK)
98 | shadowOffset[0] = it.getDimensionPixelOffset(R.styleable.ShadowBitmapDrawable_shadowX, 0)
99 | shadowOffset[1] = it.getDimensionPixelOffset(R.styleable.ShadowBitmapDrawable_shadowY, 0)
100 | }.recycle()
101 | }
102 |
103 | private fun refreshShadowBitmap() = bounds.run {
104 | if (isEmpty) {
105 | return@run
106 | }
107 | originBitmap?.let {
108 | val scale = if (it.ratio > ratio) width().toFloat() / it.width else height().toFloat() / it.height
109 | val scaledWidth = (it.width * scale).roundToInt()
110 | val scaledHeight = (it.height * scale).roundToInt()
111 | shadowBitmap?.recycle()
112 | shadowBitmap = createBlurredBitmap(HapViewerApp.instance, it, width(), height(), shadowRadius)
113 | rect.set(
114 | /* left = */ bounds.centerX() - scaledWidth / 2 - shadowRadiusI,
115 | /* top = */ bounds.centerY() - scaledHeight / 2 - shadowRadiusI,
116 | /* right = */ bounds.centerX() + scaledWidth / 2 + shadowRadiusI,
117 | /* bottom = */ bounds.centerY() + scaledHeight / 2 + shadowRadiusI
118 | ).apply {
119 | offset(shadowOffset[0], shadowOffset[1])
120 | }
121 | }
122 | }
123 |
124 | companion object {
125 | private const val TAG = "ShadowBitmapDrawable"
126 | }
127 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | HAP Viewer
4 | HAP Viewer for Android
5 | Support for parsing OpenHarmony, HarmonyOS API 9+ (Stage model) application installation packages, support for running on Android devices with Android 7+.
6 |
7 |
8 |
9 | About
10 | Version
11 | Source
12 | License
13 | Contributors
14 |
15 |
16 | Copy
17 | Copied.
18 | Copied %s.
19 |
20 |
21 | ", "
22 | ": "
23 |
24 |
25 | Theme…
26 | Material 1
27 | Material 2
28 | Material 3
29 | HarmonyOS
30 |
31 |
32 | Install
33 | Installation finished
34 | Install HAP
35 | Installation of HAP requires the support of the operating system and must have the correct signature, so packages you download from unofficial application markets will most likely not install. For unknown reasons, it is not possible to determine whether the installation of the software was successful or not, so the software will always display \"Installation finished\".
36 |
37 |
38 | Permissions
39 | Permissions request
40 | HAP Viewer needs %1$s in order for %2$s to work correctly. %3$s
41 | Permission request failed.
42 | Shizuku permission
43 | Storage permission
44 | If you do not grant this permission, HAP Viewer will copy the file to private space before reading it.
45 |
46 |
47 | Copy bundle name
48 | Copy version code
49 | Copy version name
50 | Copy app name
51 |
52 |
53 | Legal info…
54 | Privacy policy
55 | Open source licenses
56 |
57 |
58 | App name: %1$s, version name: %2$s, version code: %3$s, bundle name: %4$s.
59 |
60 |
61 | The file is unreadable.
62 | This URI is not a document.
63 | The URI is not valid.
64 |
65 |
66 | File acquisition failure.
67 | Hap file parsing failed, currently only supports parsing API 9+ (Stage model) application installers.
68 | Wrong file type, please select a hap file.
69 | Ignore error, Continue parse
70 |
71 |
72 | Unknown
73 | Unknown app name
74 | Unknown version
75 | Unknown bundle name
76 | Unknown error
77 |
78 |
79 | Drop here
80 | Press the back button again to exit.
81 | Guide
82 | More information
83 | Reading files directly
84 | Select HAP file
85 | No data
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/java/org/ohosdev/hapviewerandroid/ui/about/AboutDialogBuilder.kt:
--------------------------------------------------------------------------------
1 | package org.ohosdev.hapviewerandroid.ui.about
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import androidx.appcompat.app.AlertDialog
6 | import androidx.appcompat.widget.PopupMenu
7 | import androidx.core.text.HtmlCompat
8 | import com.onegravity.rteditor.RTEditorMovementMethod
9 | import org.ohosdev.hapviewerandroid.BuildConfig
10 | import org.ohosdev.hapviewerandroid.R
11 | import org.ohosdev.hapviewerandroid.app.LICENSE_APP
12 | import org.ohosdev.hapviewerandroid.app.URL_HOME_JESSE205
13 | import org.ohosdev.hapviewerandroid.app.URL_HOME_WESTINYANG
14 | import org.ohosdev.hapviewerandroid.app.URL_OPEN_SOURCE_LICENSES
15 | import org.ohosdev.hapviewerandroid.app.URL_PRIVACY_POLICY
16 | import org.ohosdev.hapviewerandroid.app.URL_REPOSITORY
17 | import org.ohosdev.hapviewerandroid.extensions.contentMovementMethod
18 | import org.ohosdev.hapviewerandroid.extensions.contentSelectable
19 | import org.ohosdev.hapviewerandroid.extensions.localisedColon
20 | import org.ohosdev.hapviewerandroid.extensions.localisedSeparator
21 | import org.ohosdev.hapviewerandroid.extensions.openUrl
22 | import org.ohosdev.hapviewerandroid.ui.common.dialog.AlertDialogBuilder
23 |
24 | class AboutDialogBuilder(context: Context) : AlertDialogBuilder(context) {
25 | init {
26 | setTitle(R.string.about)
27 | val messageHtml = context.run {
28 | resources.openRawResource(R.raw.about).use { it.readBytes().decodeToString() }
29 | .replace(REGEX_KEY) {
30 | when (it.groupValues[1]) {
31 | KEY_NAME -> getString(R.string.app_name_full)
32 | KEY_DESC -> getString(R.string.app_description)
33 | KEY_INFO -> getInformationHtml()
34 | else -> ""
35 | }
36 | }
37 | }
38 | setMessage(HtmlCompat.fromHtml(messageHtml, HtmlCompat.FROM_HTML_MODE_LEGACY))
39 | setPositiveButton(android.R.string.ok, null)
40 | setNeutralButton(R.string.legal_submenu, null)
41 | }
42 |
43 | @SuppressLint("ClickableViewAccessibility")
44 | override fun create(): AlertDialog = super.create().apply {
45 | setOnShowListener {
46 | contentSelectable = true
47 | contentMovementMethod = RTEditorMovementMethod.getInstance()
48 | getButton(AlertDialog.BUTTON_NEUTRAL).let { button ->
49 | PopupMenu(context, button).apply {
50 | inflate(R.menu.menu_legal)
51 | setOnMenuItemClickListener {
52 | val link = when (it.itemId) {
53 | R.id.action_privacy_policy -> URL_PRIVACY_POLICY
54 | R.id.action_open_source_licenses -> URL_OPEN_SOURCE_LICENSES
55 | else -> throw NoSuchElementException("Unknown itemId: $it")
56 | }
57 | context.openUrl(link)
58 | true
59 | }
60 | button.setOnTouchListener(dragToOpenListener)
61 | button.setOnClickListener { show() }
62 | }
63 | }
64 | }
65 | }
66 |
67 | /**
68 | * 获取应用信息字符串
69 | *
70 | * ```txt
71 | * 开源许可:xxx
72 | * 应用版本:xxx
73 | * ...
74 | * ```
75 | * */
76 | private fun getInformationHtml() = context.run {
77 | val colon = localisedColon
78 | val builder = StringBuilder()
79 | // 版本号
80 | builder.append(getString(R.string.about_version))
81 | builder.append(colon)
82 | builder.append("${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
83 | builder.append(ELEMENT_BR)
84 | // 源代码
85 | builder.append(getString(R.string.about_source))
86 | builder.append(colon)
87 | builder.append(URL_REPOSITORY.let { "${it}" })
88 | builder.append(ELEMENT_BR)
89 | // 许可证
90 | builder.append(getString(R.string.about_license))
91 | builder.append(colon)
92 | builder.append(LICENSE_APP)
93 | builder.append(ELEMENT_BR)
94 | // 贡献者
95 | builder.append(getString(R.string.about_contributors))
96 | builder.append(colon)
97 | builder.append(getContributorsHtml())
98 | builder.toString()
99 | }
100 |
101 |
102 | private fun getContributorsHtml() =
103 | contributors.joinToString(context.localisedSeparator) { "${it.name}" }
104 |
105 | companion object {
106 | const val KEY_NAME = "name"
107 | const val KEY_DESC = "description"
108 | const val KEY_INFO = "information"
109 | const val ELEMENT_BR = "
"
110 |
111 | /**
112 | * 匹配{{xxx}},用于替换字符串
113 | * */
114 | val REGEX_KEY = Regex("\\{\\{\\s*(\\S*)\\s*\\}\\}")
115 | val contributors = arrayOf(
116 | Person("westinyang", URL_HOME_WESTINYANG),
117 | Person("Jesse205", URL_HOME_JESSE205),
118 | )
119 | }
120 |
121 | data class Person(val name: String, val url: String)
122 | }
--------------------------------------------------------------------------------
/harmonystyle/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------