├── .editorconfig
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .idea
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── jarRepositories.xml
├── kotlinc.xml
└── misc.xml
├── LICENSE
├── README.md
├── android_module_common.gradle
├── android_project_common.gradle
├── aosp.keystore
├── archLib
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── ycdev
│ └── android
│ └── arch
│ ├── ArchConstants.kt
│ ├── activity
│ ├── AppCompatBaseActivity.kt
│ └── BaseActivity.kt
│ └── wrapper
│ └── ToastHelper.kt
├── archLintRules
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── me
│ │ └── ycdev
│ │ └── android
│ │ └── arch
│ │ └── lint
│ │ ├── MyBaseActivityDetector.kt
│ │ ├── MyBroadcastHelperDetector.kt
│ │ ├── MyIntentHelperDetector.kt
│ │ ├── MyIssueRegistry.kt
│ │ ├── MyToastHelperDetector.kt
│ │ └── base
│ │ ├── InheritDetectorBase.kt
│ │ └── WrapperDetectorBase.kt
│ └── test
│ └── java
│ └── me
│ └── ycdev
│ └── android
│ └── arch
│ └── lint
│ ├── MyBaseActivityDetectorTest.kt
│ ├── MyBroadcastHelperDetectorTest.kt
│ ├── MyIntentHelperDetectorTest.kt
│ ├── MyToastHelperDetectorTest.kt
│ └── utils
│ └── TestFileStubs.kt
├── archLintRulesTestDemo
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── me
│ │ └── ycdev
│ │ └── android
│ │ └── arch
│ │ └── demo
│ │ ├── activity
│ │ ├── LintGood2Activity.kt
│ │ ├── LintGood3Activity.kt
│ │ ├── LintGoodActivity.kt
│ │ ├── LintViolation2Activity.kt
│ │ └── LintViolationActivity.kt
│ │ └── wrapper
│ │ ├── BroadcastHelperLintCase.kt
│ │ ├── IntentHelperLintCase.kt
│ │ └── ToastHelperLintCase.kt
│ └── res
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ └── values
│ ├── strings.xml
│ └── styles.xml
├── baseLib
├── build.gradle
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ ├── aidl
│ │ └── me
│ │ │ └── ycdev
│ │ │ └── android
│ │ │ └── lib
│ │ │ └── common
│ │ │ └── demo
│ │ │ └── service
│ │ │ └── IDemoService.aidl
│ ├── build.xml
│ ├── java
│ │ └── me
│ │ │ └── ycdev
│ │ │ └── android
│ │ │ └── lib
│ │ │ └── common
│ │ │ ├── AndroidLibTestApplication.kt
│ │ │ ├── async
│ │ │ ├── AsyncTaskQueueTest.kt
│ │ │ ├── HandlerTaskExecutorTest.kt
│ │ │ └── TaskSchedulerTest.kt
│ │ │ ├── demo
│ │ │ ├── provider
│ │ │ │ └── InfoProviderImpl.kt
│ │ │ └── service
│ │ │ │ ├── LocalService.kt
│ │ │ │ ├── LocalServiceClient.kt
│ │ │ │ ├── LocalServiceConnector.kt
│ │ │ │ ├── RemoteService.kt
│ │ │ │ ├── RemoteServiceClient.kt
│ │ │ │ ├── RemoteServiceConnector.kt
│ │ │ │ └── operation
│ │ │ │ ├── HelloOperation.kt
│ │ │ │ └── WhoOperation.kt
│ │ │ ├── internalapi
│ │ │ └── android
│ │ │ │ ├── app
│ │ │ │ └── ActivityManagerIATest.kt
│ │ │ │ └── os
│ │ │ │ ├── PowerManagerIATest.kt
│ │ │ │ ├── ProcessIATest.kt
│ │ │ │ ├── ServiceManagerIATest.kt
│ │ │ │ ├── SystemPropertiesIATest.kt
│ │ │ │ └── UserHandleIATest.kt
│ │ │ ├── ipc
│ │ │ ├── ServiceClientBaseTest.kt
│ │ │ └── ServiceConnectorTest.kt
│ │ │ ├── net
│ │ │ └── NetworkUtilsTest.kt
│ │ │ ├── provider
│ │ │ └── InfoProviderClientTest.kt
│ │ │ ├── suite
│ │ │ └── InternalApisTestSuite.kt
│ │ │ └── utils
│ │ │ ├── GcHelperTest2.kt
│ │ │ ├── SystemSwitchUtils.kt
│ │ │ └── ThreadManager.kt
│ ├── proguard-project.txt
│ └── project.properties
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── me
│ │ │ └── ycdev
│ │ │ └── android
│ │ │ └── lib
│ │ │ └── common
│ │ │ ├── activity
│ │ │ ├── ActivityMeta.kt
│ │ │ ├── ActivityRunningState.kt
│ │ │ ├── ActivityTask.kt
│ │ │ └── ActivityTaskTracker.kt
│ │ │ ├── androidx
│ │ │ └── app
│ │ │ │ └── JobScheduler.kt
│ │ │ ├── annotation
│ │ │ └── HandlerWork.kt
│ │ │ ├── apps
│ │ │ ├── AppInfo.kt
│ │ │ ├── AppsLoadConfig.kt
│ │ │ ├── AppsLoadFilter.kt
│ │ │ ├── AppsLoadListener.kt
│ │ │ └── AppsLoader.kt
│ │ │ ├── async
│ │ │ ├── AsyncTaskQueue.kt
│ │ │ ├── HandlerTaskExecutor.kt
│ │ │ ├── ITaskExecutor.kt
│ │ │ ├── TaskInfo.kt
│ │ │ └── TaskScheduler.kt
│ │ │ ├── base
│ │ │ └── ICallback.kt
│ │ │ ├── dbmgr
│ │ │ ├── SQLiteDbCreator.kt
│ │ │ └── SQLiteDbMgr.kt
│ │ │ ├── internalapi
│ │ │ └── android
│ │ │ │ ├── app
│ │ │ │ └── ActivityManagerIA.kt
│ │ │ │ └── os
│ │ │ │ ├── EnvironmentIA.kt
│ │ │ │ ├── PowerManagerIA.kt
│ │ │ │ ├── ProcessIA.kt
│ │ │ │ ├── ServiceManagerIA.kt
│ │ │ │ ├── SystemPropertiesIA.kt
│ │ │ │ └── UserHandleIA.kt
│ │ │ ├── ipc
│ │ │ ├── ConnectStateListener.kt
│ │ │ ├── IpcHandler.kt
│ │ │ ├── IpcOperation.kt
│ │ │ ├── ServiceClientBase.kt
│ │ │ └── ServiceConnector.kt
│ │ │ ├── kotlinx
│ │ │ └── IsNullOrEmpty.kt
│ │ │ ├── manager
│ │ │ ├── ListenerManager.kt
│ │ │ ├── NotifyAction.kt
│ │ │ └── ObjectManager.kt
│ │ │ ├── net
│ │ │ ├── HttpClient.kt
│ │ │ └── NetworkUtils.kt
│ │ │ ├── packets
│ │ │ ├── PacketsException.kt
│ │ │ ├── PacketsWorker.kt
│ │ │ ├── RawPacketsWorker.kt
│ │ │ └── TinyPacketsWorker.kt
│ │ │ ├── pattern
│ │ │ └── SingletonHolderP1.kt
│ │ │ ├── perms
│ │ │ ├── PermissionCallback.kt
│ │ │ ├── PermissionRequestParams.kt
│ │ │ └── PermissionUtils.kt
│ │ │ ├── provider
│ │ │ ├── InfoProvider.kt
│ │ │ └── InfoProviderClient.kt
│ │ │ ├── tracker
│ │ │ ├── BatteryInfoTracker.kt
│ │ │ ├── InteractiveStateTracker.kt
│ │ │ └── WeakTracker.kt
│ │ │ ├── type
│ │ │ ├── BooleanHolder.kt
│ │ │ ├── IntegerHolder.kt
│ │ │ └── LongHolder.kt
│ │ │ ├── utils
│ │ │ ├── ApplicationUtils.kt
│ │ │ ├── DateTimeUtils.kt
│ │ │ ├── DebugUtils.kt
│ │ │ ├── DigestUtils.kt
│ │ │ ├── EncodingUtils.kt
│ │ │ ├── FileLogger.kt
│ │ │ ├── GcHelper.kt
│ │ │ ├── GsonHelper.kt
│ │ │ ├── ImageUtils.kt
│ │ │ ├── IntentUtils.kt
│ │ │ ├── IoUtils.kt
│ │ │ ├── LibLogger.kt
│ │ │ ├── MainHandler.kt
│ │ │ ├── MiscUtils.kt
│ │ │ ├── PackageUtils.kt
│ │ │ ├── Preconditions.kt
│ │ │ ├── ReflectionUtils.kt
│ │ │ ├── StorageUtils.kt
│ │ │ ├── StringUtils.kt
│ │ │ ├── SystemServiceHelper.kt
│ │ │ ├── ThreadUtils.kt
│ │ │ ├── TypeUtils.kt
│ │ │ └── WeakHandler.kt
│ │ │ └── wrapper
│ │ │ ├── BroadcastHelper.kt
│ │ │ └── IntentHelper.kt
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── me
│ └── ycdev
│ └── android
│ └── lib
│ └── common
│ ├── activity
│ ├── ActivityRunningStateTest.kt
│ ├── ActivityTaskTest.kt
│ └── ActivityTaskTrackerTest.kt
│ ├── manager
│ ├── ListenerManagerTest.kt
│ └── ObjectManagerTest.kt
│ ├── net
│ └── NetworkUtilsTestBasic.kt
│ ├── packets
│ ├── PacketsWorkerTestBase.kt
│ ├── RawPacketsWorkerTest.kt
│ └── TinyPacketsWorkerTest.kt
│ ├── type
│ ├── BooleanHolderTest.kt
│ ├── IntegerHolderTest.kt
│ └── LongHolderTest.kt
│ └── utils
│ ├── EncodingUtilsTest.kt
│ ├── GcHelperTest.kt
│ ├── GsonHelperTest.kt
│ ├── MiscUtilsTest.kt
│ ├── ReflectionUtilsTest.kt
│ └── TypeUtilsTest.kt
├── build.gradle
├── build_common.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jniLib
├── CMakeLists.txt
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── me
│ │ └── ycdev
│ │ └── android
│ │ └── lib
│ │ └── commonjni
│ │ ├── FileStatusHelperTest.kt
│ │ └── SysResourceLimitHelperTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ ├── CommonJni.cpp
│ ├── CommonJni.h
│ ├── FileStatusHelper.cpp
│ └── SysResourceLimitHelper.cpp
│ └── java
│ └── me
│ └── ycdev
│ └── android
│ └── lib
│ └── commonjni
│ ├── CommonJniLoader.kt
│ ├── FileStatusHelper.kt
│ └── SysResourceLimitHelper.kt
├── jniLibDemo
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── me
│ │ └── ycdev
│ │ └── android
│ │ └── lib
│ │ └── commonjni
│ │ └── demo
│ │ ├── MainActivity.kt
│ │ └── ResourceLimitActivity.kt
│ └── res
│ ├── layout
│ ├── activity_main.xml
│ └── activity_resource_limit.xml
│ ├── menu
│ ├── menu_main.xml
│ └── menu_resource_limit.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── publish-module.gradle
├── publish-root.gradle
├── settings.gradle
├── testLib
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── me
│ └── ycdev
│ └── android
│ └── lib
│ └── test
│ ├── base
│ └── RobolectricBase.kt
│ ├── log
│ ├── AndroidLogHelper.kt
│ └── TimberJvmTree.kt
│ ├── rules
│ └── TimberJvmRule.kt
│ └── ui
│ └── ScrollViewsAction.kt
└── uiLib
├── build.gradle
└── src
└── main
├── AndroidManifest.xml
├── java
└── me
│ └── ycdev
│ └── android
│ └── lib
│ └── commonui
│ ├── activity
│ └── GridEntriesActivity.kt
│ └── recyclerview
│ └── MarginItemDecoration.kt
└── res
├── drawable
├── ycdev_grid_entries_item_bkg.xml
├── ycdev_scrollbar_thumb.xml
├── ycdev_scrollbar_thumb_normal.xml
├── ycdev_scrollbar_track.xml
└── ycdev_scrollbar_track_normal.xml
├── layout
├── ycdev_grid_entries.xml
└── ycdev_grid_entries_item.xml
├── values-w820dp
└── ycdev_dimens.xml
└── values
├── ycdev_attrs.xml
├── ycdev_colors.xml
├── ycdev_dimens.xml
├── ycdev_publics.xml
├── ycdev_strings.xml
├── ycdev_styles.xml
└── ycdev_themes.xml
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 |
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.{kt,kts}]
14 | ktlint_code_style = android
15 | ktlint_ignore_back_ticked_identifier = true
16 |
17 | ktlint_standard = enabled
18 | ktlint_standard_import-ordering = disabled
19 | ktlint_standard_argument-list-wrapping = disabled
20 | ktlint_standard_wrapping = disabled
21 |
22 | max_line_length = off
23 |
24 | [*.md]
25 | trim_trailing_whitespace = false
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - name: checkout
11 | uses: actions/checkout@v3
12 | - name: setup Java 17
13 | uses: actions/setup-java@v2
14 | with:
15 | distribution: 'zulu' # See 'Supported distributions' for available options
16 | java-version: '17'
17 | - name: Build the project
18 | run: ./gradlew build
19 |
20 | connectedCheck:
21 | runs-on: macos-latest
22 |
23 | strategy:
24 | fail-fast: false
25 | matrix:
26 | api-level: [24, 29, 31, 33]
27 | target: [default]
28 |
29 | steps:
30 | - name: checkout
31 | uses: actions/checkout@v3
32 | - name: setup Java 17
33 | uses: actions/setup-java@v2
34 | with:
35 | distribution: 'zulu' # See 'Supported distributions' for available options
36 | java-version: '17'
37 |
38 | - name: run connectedCheck
39 | uses: reactivecircus/android-emulator-runner@v2
40 | with:
41 | api-level: ${{ matrix.api-level }}
42 | ndk: 21.3.6528147
43 | cmake: 3.22.1
44 | target: ${{ matrix.target }}
45 | arch: x86_64
46 | profile: Nexus 6
47 | cores: 4
48 | emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
49 | disable-animations: true
50 | script: ./gradlew connectedCheck mergeAndroidReports --continue
51 |
52 | - name: Archive the test reports
53 | uses: actions/upload-artifact@v2
54 | if: failure()
55 | with:
56 | name: androidTest-results-${{ matrix.api-level }}-${{ matrix.target }}
57 | path: build/androidTest-results
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/assetWizardSettings.xml
45 | .idea/dictionaries
46 | .idea/libraries
47 | # Android Studio 3 in .gitignore file.
48 | .idea/caches
49 | .idea/modules.xml
50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
51 | .idea/navEditor.xml
52 |
53 | # Keystore files
54 | # Uncomment the following lines if you do not want to check your keystore files in.
55 | #*.jks
56 | #*.keystore
57 |
58 | # External native build folder generated in Android Studio 2.2 and later
59 | .externalNativeBuild
60 | .cxx/
61 |
62 | # Google Services (e.g. APIs or Firebase)
63 | # google-services.json
64 |
65 | # Freeline
66 | freeline.py
67 | freeline/
68 | freeline_project_description.json
69 |
70 | # fastlane
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 | fastlane/readme.md
76 |
77 | # Version control
78 | vcs.xml
79 |
80 | # lint
81 | lint/intermediates/
82 | lint/generated/
83 | lint/outputs/
84 | lint/tmp/
85 | # lint/reports/
86 |
87 | # Mac OS
88 | .DS_Store
89 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | AndroidLibProject
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.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 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AndroidLib
2 | ==========
3 |
4 | Common utils for Android apps development
5 |
6 | 
7 |
--------------------------------------------------------------------------------
/aosp.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/aosp.keystore
--------------------------------------------------------------------------------
/archLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply from: "${androidModuleCommon}"
4 | apply from: '../build_common.gradle'
5 |
6 | android {
7 | namespace 'me.ycdev.android.arch'
8 | defaultConfig {
9 | minSdkVersion versions.minSdk
10 | }
11 | }
12 |
13 | dependencies {
14 | lintChecks project(':archLintRules')
15 | lintPublish project(':archLintRules')
16 | api deps.ycdev.androidBase
17 |
18 | implementation deps.kotlin.stdlib
19 | implementation deps.androidx.appcompat
20 | }
21 |
--------------------------------------------------------------------------------
/archLib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/pub/tools/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/archLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/archLib/src/main/java/me/ycdev/android/arch/ArchConstants.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch
2 |
3 | import android.widget.Toast
4 | import androidx.annotation.IntDef
5 |
6 | object ArchConstants {
7 |
8 | /*
9 | * Durations for toast
10 | */
11 | @IntDef(Toast.LENGTH_SHORT, Toast.LENGTH_LONG)
12 | @Retention(AnnotationRetention.SOURCE)
13 | annotation class ToastDuration
14 | }
15 |
--------------------------------------------------------------------------------
/archLib/src/main/java/me/ycdev/android/arch/activity/AppCompatBaseActivity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.activity
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | /**
7 | * Base class for Activity which wants to inherit
8 | * [androidx.appcompat.app.AppCompatActivity].
9 | */
10 | abstract class AppCompatBaseActivity : AppCompatActivity() {
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | if (shouldSetDisplayHomeAsUpEnabled()) {
14 | val actionBar = supportActionBar
15 | actionBar?.setDisplayHomeAsUpEnabled(true)
16 | }
17 | }
18 |
19 | protected open fun shouldSetDisplayHomeAsUpEnabled(): Boolean {
20 | return true
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/archLib/src/main/java/me/ycdev/android/arch/activity/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.activity
2 |
3 | import android.app.Activity
4 |
5 | /**
6 | * Base class for Activity which wants to inherit [android.app.Activity].
7 | */
8 | abstract class BaseActivity : Activity() // nothing to do right now
9 |
--------------------------------------------------------------------------------
/archLib/src/main/java/me/ycdev/android/arch/wrapper/ToastHelper.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.wrapper
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.annotation.StringRes
6 | import me.ycdev.android.arch.ArchConstants.ToastDuration
7 |
8 | /**
9 | * A wrapper class for Toast so that we can customize and unify the UI in future.
10 | */
11 | object ToastHelper {
12 |
13 | fun show(cxt: Context, @StringRes msgResId: Int, @ToastDuration duration: Int) {
14 | Toast.makeText(cxt, msgResId, duration).show()
15 | }
16 |
17 | fun show(cxt: Context, msg: CharSequence, @ToastDuration duration: Int) {
18 | Toast.makeText(cxt, msg, duration).show()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/archLintRules/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | dependencies {
4 | compileOnly "com.android.tools.lint:lint-api:${versions.lintLib}"
5 | compileOnly "com.android.tools.lint:lint-checks:${versions.lintLib}"
6 | // Workaround to fix the issue:
7 | // "Found more than one jar in the 'lintPublish' configuration. Only one file is supported"
8 | compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}"
9 |
10 | testImplementation "junit:junit:${versions.junit}"
11 | testImplementation "com.android.tools.lint:lint:${versions.lintLib}"
12 | testImplementation "com.android.tools.lint:lint-tests:${versions.lintLib}"
13 | testImplementation "com.android.tools:testutils:${versions.lintLib}"
14 | }
15 |
16 | jar {
17 | manifest {
18 | attributes("Lint-Registry-v2": "me.ycdev.android.arch.lint.MyIssueRegistry")
19 | }
20 | }
21 |
22 | test {
23 | testLogging {
24 | events "passed", "skipped", "failed", "standardOut", "standardError"
25 | outputs.upToDateWhen { false }
26 | showStandardStreams = true
27 | }
28 | }
29 |
30 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
31 | kotlinOptions {
32 | jvmTarget = "17"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/archLintRules/src/main/java/me/ycdev/android/arch/lint/MyBaseActivityDetector.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.lint
2 |
3 | import com.android.tools.lint.detector.api.Category
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Issue
6 | import com.android.tools.lint.detector.api.JavaContext
7 | import com.android.tools.lint.detector.api.Scope
8 | import com.android.tools.lint.detector.api.Severity
9 | import me.ycdev.android.arch.lint.base.InheritDetectorBase
10 | import org.jetbrains.uast.UElement
11 | import java.util.HashSet
12 |
13 | class MyBaseActivityDetector : InheritDetectorBase() {
14 | override val applicableClasses: List = arrayListOf("android.app.Activity")
15 |
16 | override val wrapperClasses: HashSet = hashSetOf(
17 | "me.ycdev.android.arch.activity.BaseActivity",
18 | "me.ycdev.android.arch.activity.PreferenceBaseActivity",
19 | "me.ycdev.android.arch.activity.AppCompatBaseActivity"
20 | )
21 |
22 | override fun reportViolation(context: JavaContext, element: UElement) {
23 | context.report(
24 | ISSUE, element, context.getNameLocation(element),
25 | "Please use the base classes for Activity."
26 | )
27 | }
28 |
29 | companion object {
30 | internal val ISSUE = Issue.create(
31 | "MyBaseActivity",
32 | "Base classes for Activity should be used.",
33 | "Please use the base classes for Activity." + " So that we can do some unified behaviors.",
34 | Category.CORRECTNESS, 5, Severity.ERROR,
35 | Implementation(MyBaseActivityDetector::class.java, Scope.JAVA_FILE_SCOPE)
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/archLintRules/src/main/java/me/ycdev/android/arch/lint/MyBroadcastHelperDetector.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.lint
2 |
3 | import com.android.tools.lint.detector.api.Category
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Issue
6 | import com.android.tools.lint.detector.api.JavaContext
7 | import com.android.tools.lint.detector.api.Scope
8 | import com.android.tools.lint.detector.api.Severity
9 | import me.ycdev.android.arch.lint.base.WrapperDetectorBase
10 | import org.jetbrains.uast.UElement
11 |
12 | class MyBroadcastHelperDetector : WrapperDetectorBase() {
13 | override val applicableMethods = arrayListOf("registerReceiver", "sendBroadcast")
14 |
15 | override val wrapperClassName = "me.ycdev.android.lib.common.wrapper.BroadcastHelper"
16 |
17 | override val targetClassNames = arrayOf("android.content.Context")
18 |
19 | override fun reportViolation(context: JavaContext, element: UElement) {
20 | context.report(
21 | ISSUE, element, context.getLocation(element),
22 | "Please use the wrapper class 'BroadcastHelper'."
23 | )
24 | }
25 |
26 | companion object {
27 | internal val ISSUE = Issue.create(
28 | "MyBroadcastHelper",
29 | "BroadcastHelper should be used.",
30 | "Please use the wrapper class 'BroadcastHelper' to register broadcast receivers" +
31 | " and send broadcasts to avoid security issues.",
32 | Category.CORRECTNESS, 5, Severity.ERROR,
33 | Implementation(MyBroadcastHelperDetector::class.java, Scope.JAVA_FILE_SCOPE)
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/archLintRules/src/main/java/me/ycdev/android/arch/lint/MyIntentHelperDetector.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.lint
2 |
3 | import com.android.tools.lint.detector.api.Category
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Issue
6 | import com.android.tools.lint.detector.api.JavaContext
7 | import com.android.tools.lint.detector.api.Scope
8 | import com.android.tools.lint.detector.api.Severity
9 | import me.ycdev.android.arch.lint.base.WrapperDetectorBase
10 | import org.jetbrains.uast.UElement
11 |
12 | class MyIntentHelperDetector : WrapperDetectorBase() {
13 | override val applicableMethods = arrayListOf(
14 | "hasExtra",
15 | "getBooleanArrayExtra",
16 | "getBooleanExtra",
17 | "getBundleExtra",
18 | "getByteArrayExtra",
19 | "getByteExtra",
20 | "getCharArrayExtra",
21 | "getCharExtra",
22 | "getCharSequenceArrayExtra",
23 | "getCharSequenceArrayListExtra",
24 | "getCharSequenceExtra",
25 | "getDoubleArrayExtra",
26 | "getDoubleExtra",
27 | "getExtra",
28 | "getExtras",
29 | "getFloatArrayExtra",
30 | "getFloatExtra",
31 | "getIBinderExtra",
32 | "getIntArrayExtra",
33 | "getIBinderExtra",
34 | "getIntArrayExtra",
35 | "getIntegerArrayListExtra",
36 | "getIntExtra",
37 | "getLongArrayExtra",
38 | "getLongExtra",
39 | "getParcelableArrayExtra",
40 | "getParcelableArrayListExtra",
41 | "getParcelableExtra",
42 | "getSerializableExtra",
43 | "getShortArrayExtra",
44 | "getShortExtra",
45 | "getStringArrayExtra",
46 | "getStringArrayListExtra",
47 | "getStringExtra"
48 | )
49 |
50 | override val wrapperClassName = "me.ycdev.android.lib.common.wrapper.IntentHelper"
51 |
52 | override val targetClassNames = arrayOf("android.content.Intent")
53 |
54 | override fun reportViolation(context: JavaContext, element: UElement) {
55 | context.report(
56 | ISSUE, element, context.getLocation(element),
57 | "Please use the wrapper class 'IntentHelper'."
58 | )
59 | }
60 |
61 | companion object {
62 | internal val ISSUE = Issue.create(
63 | "MyIntentHelper",
64 | "IntentHelper should be used.",
65 | "Please use the wrapper class 'IntentHelper' to get Intent extras" + " to avoid security issues.",
66 | Category.CORRECTNESS, 5, Severity.ERROR,
67 | Implementation(MyIntentHelperDetector::class.java, Scope.JAVA_FILE_SCOPE)
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/archLintRules/src/main/java/me/ycdev/android/arch/lint/MyIssueRegistry.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.lint
2 |
3 | import com.android.tools.lint.client.api.IssueRegistry
4 | import com.android.tools.lint.client.api.Vendor
5 | import com.android.tools.lint.detector.api.Issue
6 |
7 | @Suppress("unused")
8 | class MyIssueRegistry : IssueRegistry() {
9 | override val issues: List
10 | get() {
11 | println("!!!!!!!!!!!!! ArchLib lint rules works")
12 | return listOf(
13 | MyToastHelperDetector.ISSUE,
14 | MyBroadcastHelperDetector.ISSUE,
15 | MyBaseActivityDetector.ISSUE,
16 | MyIntentHelperDetector.ISSUE
17 | )
18 | }
19 |
20 | override val vendor: Vendor = Vendor("ycdev", "android-lib", "https://github.com/yongce/AndroidLib")
21 | override val api: Int = com.android.tools.lint.detector.api.CURRENT_API
22 | }
23 |
--------------------------------------------------------------------------------
/archLintRules/src/main/java/me/ycdev/android/arch/lint/MyToastHelperDetector.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.lint
2 |
3 | import com.android.tools.lint.detector.api.Category
4 | import com.android.tools.lint.detector.api.Implementation
5 | import com.android.tools.lint.detector.api.Issue
6 | import com.android.tools.lint.detector.api.JavaContext
7 | import com.android.tools.lint.detector.api.Scope
8 | import com.android.tools.lint.detector.api.Severity
9 | import me.ycdev.android.arch.lint.base.WrapperDetectorBase
10 | import org.jetbrains.uast.UElement
11 |
12 | class MyToastHelperDetector : WrapperDetectorBase() {
13 | override val applicableMethods = arrayListOf("makeText")
14 |
15 | override val wrapperClassName = "me.ycdev.android.arch.wrapper.ToastHelper"
16 |
17 | override val targetClassNames = arrayOf("android.widget.Toast")
18 |
19 | override fun reportViolation(context: JavaContext, element: UElement) {
20 | context.report(
21 | ISSUE, element, context.getLocation(element),
22 | "Please use the wrapper class 'ToastHelper'."
23 | )
24 | }
25 |
26 | companion object {
27 | internal val ISSUE = Issue.create(
28 | "MyToastHelper",
29 | "ToastHelper should be used.",
30 | "Please use the wrapper class 'ToastHelper' to show toast." + " So that we can customize and unify the UI in future.",
31 | Category.CORRECTNESS, 5, Severity.ERROR,
32 | Implementation(MyToastHelperDetector::class.java, Scope.JAVA_FILE_SCOPE)
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/archLintRules/src/main/java/me/ycdev/android/arch/lint/base/InheritDetectorBase.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.lint.base
2 |
3 | import com.android.tools.lint.detector.api.Detector
4 | import com.android.tools.lint.detector.api.JavaContext
5 | import org.jetbrains.uast.UClass
6 | import org.jetbrains.uast.UElement
7 | import java.util.HashSet
8 |
9 | abstract class InheritDetectorBase : Detector(), Detector.UastScanner {
10 | protected abstract val applicableClasses: List
11 | protected abstract val wrapperClasses: HashSet
12 | protected abstract fun reportViolation(context: JavaContext, element: UElement)
13 |
14 | override fun applicableSuperClasses(): List? = applicableClasses
15 |
16 | override fun visitClass(context: JavaContext, declaration: UClass) {
17 | val wrappers = wrapperClasses
18 | val className = declaration.qualifiedName
19 | if (wrappers.contains(className)) {
20 | return // ignore the wrapper classes
21 | }
22 |
23 | val evaluator = context.evaluator
24 | var found = false
25 | for (wrapperClass in wrappers) {
26 | if (evaluator.inheritsFrom(declaration, wrapperClass, false)) {
27 | found = true
28 | break
29 | }
30 | }
31 | if (!found) {
32 | reportViolation(context, declaration)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/archLintRules/src/main/java/me/ycdev/android/arch/lint/base/WrapperDetectorBase.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.lint.base
2 |
3 | import com.android.tools.lint.detector.api.Detector
4 | import com.android.tools.lint.detector.api.JavaContext
5 | import com.intellij.psi.PsiMethod
6 | import org.jetbrains.uast.UCallExpression
7 | import org.jetbrains.uast.UElement
8 | import org.jetbrains.uast.getContainingUClass
9 |
10 | abstract class WrapperDetectorBase : Detector(), Detector.UastScanner {
11 | protected abstract val applicableMethods: List
12 | protected abstract val wrapperClassName: String
13 | protected abstract val targetClassNames: Array
14 | protected abstract fun reportViolation(context: JavaContext, element: UElement)
15 |
16 | override fun getApplicableMethodNames(): List = applicableMethods
17 |
18 | override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
19 | val evaluator = context.evaluator
20 | val surroundingClass = node.getContainingUClass()?.javaPsi
21 | if (surroundingClass == null) {
22 | println(
23 | "Fatal error in WrapperDetectorBase! Failed to get surrounding" +
24 | " class \'" + node.uastParent + "\'"
25 | )
26 | return
27 | }
28 |
29 | val containingClassName = surroundingClass.qualifiedName
30 | if (wrapperClassName == containingClassName) {
31 | return
32 | }
33 |
34 | for (targetClassName in targetClassNames) {
35 | if (evaluator.isMemberInSubClassOf(method, targetClassName, false)) {
36 | reportViolation(context, node)
37 | return
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/pub/tools/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
28 |
29 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/activity/LintGood2Activity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.demo.activity
2 |
3 | import me.ycdev.android.arch.activity.BaseActivity
4 |
5 | open class LintGood2Activity : BaseActivity() { // lint good
6 | }
7 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/activity/LintGood3Activity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.demo.activity
2 |
3 | class LintGood3Activity : LintGood2Activity() // lint good
4 | // nothing to do
5 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/activity/LintGoodActivity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.demo.activity
2 |
3 | import android.view.Menu
4 | import android.view.MenuItem
5 | import me.ycdev.android.arch.activity.AppCompatBaseActivity
6 |
7 | class LintGoodActivity : AppCompatBaseActivity() { // lint good
8 |
9 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
10 | // Inflate the menu; this adds items to the action bar if it is present.
11 | return true
12 | }
13 |
14 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
15 | // Handle action bar item clicks here. The action bar will
16 | // automatically handle clicks on the Home/Up button, so long
17 | // as you specify a parent activity in AndroidManifest.xml.
18 | item.itemId
19 |
20 | return super.onOptionsItemSelected(item)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/activity/LintViolation2Activity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.demo.activity
2 |
3 | import android.app.Activity
4 |
5 | // class comment for test
6 | class LintViolation2Activity : Activity() { // lint violation
7 | }
8 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/activity/LintViolationActivity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.demo.activity
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.IntentFilter
7 | import android.os.Bundle
8 | import android.view.MenuItem
9 | import androidx.appcompat.app.AppCompatActivity
10 |
11 | /**
12 | * Class doc for test
13 | */
14 | class LintViolationActivity : AppCompatActivity() { // lint violation
15 |
16 | private val receiver = object : BroadcastReceiver() {
17 | override fun onReceive(context: Context, intent: Intent) {
18 | // nothing to do
19 | }
20 | }
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 |
25 | val filter = IntentFilter()
26 | filter.addAction(TEST_ACTION)
27 | registerReceiver(receiver, filter) // lint violation
28 | }
29 |
30 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
31 | // Handle action bar item clicks here. The action bar will
32 | // automatically handle clicks on the Home/Up button, so long
33 | // as you specify a parent activity in AndroidManifest.xml.
34 | item.itemId
35 |
36 | sendBroadcast(Intent(TEST_ACTION)) // lint violation
37 |
38 | return super.onOptionsItemSelected(item)
39 | }
40 |
41 | override fun onDestroy() {
42 | super.onDestroy()
43 | unregisterReceiver(receiver)
44 | }
45 |
46 | companion object {
47 | private const val TEST_ACTION = "action.test"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/wrapper/BroadcastHelperLintCase.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package me.ycdev.android.arch.demo.wrapper
4 |
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.content.IntentFilter
9 | import me.ycdev.android.lib.common.wrapper.BroadcastHelper
10 |
11 | object BroadcastHelperLintCase {
12 | private class Foo {
13 | fun registerReceiver() { // lint good
14 | }
15 |
16 | fun sendBroadcast() { // lint good
17 | }
18 | }
19 |
20 | fun registerReceiver() { // lint good
21 | Foo().registerReceiver()
22 | }
23 |
24 | fun sendBroadcast() { // lint good
25 | Foo().sendBroadcast()
26 | }
27 |
28 | fun registerGood(cxt: Context, receiver: BroadcastReceiver, filter: IntentFilter): Intent? {
29 | return BroadcastHelper.registerForInternal(cxt, receiver, filter) // lint good
30 | }
31 |
32 | fun sendToInternalGood(cxt: Context, intent: Intent) {
33 | BroadcastHelper.sendToInternal(cxt, intent) // lint good
34 | }
35 |
36 | fun sendToExternalGood(cxt: Context, intent: Intent, perm: String) {
37 | BroadcastHelper.sendToExternal(cxt, intent, perm) // lint good
38 | }
39 |
40 | fun sendToExternal(cxt: Context, intent: Intent) {
41 | BroadcastHelper.sendToExternal(cxt, intent) // lint good
42 | }
43 |
44 | fun registerViolation(
45 | cxt: Context,
46 | receiver: BroadcastReceiver,
47 | filter: IntentFilter
48 | ): Intent? {
49 | return cxt.registerReceiver(receiver, filter) // lint violation
50 | }
51 |
52 | fun registerViolation2(
53 | cxt: Context,
54 | receiver: BroadcastReceiver,
55 | filter: IntentFilter
56 | ): Intent? {
57 | return cxt.registerReceiver(receiver, filter, null, null) // lint violation
58 | }
59 |
60 | fun sendViolation(cxt: Context, intent: Intent, perm: String) {
61 | cxt.sendBroadcast(intent, perm) // lint violation
62 | }
63 |
64 | fun sendViolation2(cxt: Context, intent: Intent) {
65 | cxt.sendBroadcast(intent) // lint violation
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/wrapper/IntentHelperLintCase.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.demo.wrapper
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import me.ycdev.android.lib.common.wrapper.IntentHelper
6 |
7 | object IntentHelperLintCase {
8 | private class Foo {
9 | fun hasExtra() { // lint good
10 | }
11 |
12 | fun getBundleExtra() { // lint good
13 | }
14 | }
15 |
16 | fun hasExtra() { // lint good
17 | Foo().hasExtra()
18 | }
19 |
20 | fun getBundleExtra() { // lint good
21 | Foo().getBundleExtra()
22 | }
23 |
24 | fun hasExtraGood(intent: Intent, key: String): Boolean {
25 | return IntentHelper.hasExtra(intent, key) // lint good
26 | }
27 |
28 | fun getBooleanExtraGood(intent: Intent, key: String, defValue: Boolean): Boolean {
29 | return IntentHelper.getBooleanExtra(intent, key, defValue) // lint good
30 | }
31 |
32 | fun getBundleExtraGood(intent: Intent, key: String): Bundle? {
33 | return IntentHelper.getBundleExtra(intent, key) // lint good
34 | }
35 |
36 | fun hasExtraBad(intent: Intent, key: String): Boolean {
37 | return intent.hasExtra(key) // lint violation
38 | }
39 |
40 | fun getBooleanExtraBad(intent: Intent, key: String, defValue: Boolean): Boolean {
41 | return intent.getBooleanExtra(key, defValue) // lint violation
42 | }
43 |
44 | fun getBundleExtraBad(intent: Intent, key: String): Bundle? {
45 | return intent.getBundleExtra(key) // lint violation
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/java/me/ycdev/android/arch/demo/wrapper/ToastHelperLintCase.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.arch.demo.wrapper
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import me.ycdev.android.arch.wrapper.ToastHelper
6 |
7 | object ToastHelperLintCase {
8 | private class Foo {
9 | fun show() { // lint good
10 | }
11 |
12 | fun makeText() { // lint good
13 | }
14 | }
15 |
16 | fun show() { // lint good
17 | Foo().show()
18 | }
19 |
20 | fun makeText() { // lint good
21 | Foo().makeText()
22 | }
23 |
24 | fun showGood(cxt: Context, msgResId: Int, duration: Int) {
25 | ToastHelper.show(cxt, msgResId, duration) // lint good
26 | }
27 |
28 | fun showGood(cxt: Context, msg: CharSequence, duration: Int) {
29 | ToastHelper.show(cxt, msg, duration) // lint good
30 | }
31 |
32 | fun showViolation(cxt: Context, msgResId: Int, duration: Int) {
33 | Toast.makeText(cxt, msgResId, duration).show() // lint violation
34 | }
35 |
36 | fun showViolation(cxt: Context, msg: CharSequence, duration: Int) {
37 | Toast.makeText(cxt, msg, duration).show() // lint violation
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/archLintRulesTestDemo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/archLintRulesTestDemo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/archLintRulesTestDemo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/archLintRulesTestDemo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | demo
3 |
4 | LintViolationActivity
5 | LintViolation2Activity
6 | LintGoodActivity
7 | LintGood2Activity
8 | LintGood2Activity
9 |
10 |
--------------------------------------------------------------------------------
/archLintRulesTestDemo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/baseLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply from: "${androidModuleCommon}"
4 | apply from: '../build_common.gradle'
5 |
6 | project.archivesBaseName = 'android-common-base'
7 |
8 | android {
9 | namespace 'me.ycdev.android.lib.common'
10 | defaultConfig {
11 | minSdkVersion versions.minSdk
12 | }
13 | buildFeatures {
14 | aidl true
15 | }
16 |
17 | lint {
18 | disable 'PrivateApi'
19 | }
20 | }
21 |
22 | dependencies {
23 | api deps.androidx.annotation
24 |
25 | implementation deps.kotlin.stdlib
26 | implementation deps.kotlin.coroutinesAndroid
27 | implementation deps.androidx.core
28 | implementation deps.androidx.fragment
29 | implementation deps.gson
30 | implementation deps.timber
31 |
32 | // Dependencies for local unit tests
33 | testImplementation deps.ycdev.androidTest
34 | testImplementation deps.test.junit
35 | testImplementation deps.test.runner
36 | testImplementation deps.test.rules
37 | testImplementation deps.test.truth
38 | testImplementation deps.test.mockk
39 | testImplementation deps.test.robolectric
40 |
41 | // Android Testing Support Library's runner and rules
42 | androidTestImplementation deps.test.core
43 | androidTestImplementation deps.test.junit
44 | androidTestImplementation deps.test.runner
45 | androidTestImplementation deps.test.rules
46 | androidTestImplementation deps.test.truth
47 | }
48 |
49 | project.ext {
50 | moduleName = 'me.ycdev.android.common-base'
51 | moduleDesc = 'Common basic module in AndroidLib project'
52 | }
53 |
54 | if (publishEnabled) {
55 | apply from: rootProject.file('publish-module.gradle')
56 | }
57 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
20 |
21 |
22 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/aidl/me/ycdev/android/lib/common/demo/service/IDemoService.aidl:
--------------------------------------------------------------------------------
1 | // IDemoService.aidl
2 | package me.ycdev.android.lib.common.demo.service;
3 |
4 | interface IDemoService {
5 | String who();
6 | void sayHello(String gift);
7 | }
8 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/AndroidLibTestApplication.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common
2 |
3 | import android.app.Application
4 | import me.ycdev.android.lib.common.utils.DebugUtils
5 | import me.ycdev.android.lib.common.utils.LibLogger
6 | import timber.log.Timber
7 |
8 | class AndroidLibTestApplication : Application() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 | LibLogger.d(TAG, "onCreate")
13 | DebugUtils.enableStrictMode()
14 | Timber.plant(Timber.DebugTree())
15 | }
16 |
17 | companion object {
18 | private const val TAG = "BaseLibTestApp"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/async/HandlerTaskExecutorTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.async
2 |
3 | import android.os.Looper
4 | import android.os.SystemClock
5 | import androidx.test.filters.LargeTest
6 | import com.google.common.truth.Truth.assertThat
7 | import org.junit.Test
8 | import java.util.ArrayList
9 | import java.util.concurrent.CountDownLatch
10 | import java.util.concurrent.TimeUnit
11 |
12 | @LargeTest
13 | class HandlerTaskExecutorTest {
14 | @Test
15 | @Throws(InterruptedException::class)
16 | fun checkLooper() {
17 | println("test thread id=" + Thread.currentThread().id)
18 | val executor = HandlerTaskExecutor.withMainLooper()
19 | val latch = CountDownLatch(4)
20 | createTasks(latch, 4, 150).forEach {
21 | executor.postTask(it)
22 | }
23 | latch.await(10, TimeUnit.SECONDS)
24 | assertThat(latch.count).isEqualTo(0L)
25 | }
26 |
27 | @Suppress("SameParameterValue")
28 | private fun createTasks(latch: CountDownLatch, count: Int, sleepMs: Long): List {
29 | val tasks = ArrayList(count)
30 | for (i in 0 until count) {
31 | tasks.add(Runnable {
32 | println("main thread id=" + Thread.currentThread().id)
33 | assertThat(Looper.myLooper()).isSameInstanceAs(Looper.getMainLooper())
34 | SystemClock.sleep(sleepMs)
35 | latch.countDown()
36 | })
37 | }
38 | return tasks
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/provider/InfoProviderImpl.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.provider
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.annotation.NonNull
6 | import me.ycdev.android.lib.common.provider.InfoProvider
7 |
8 | class InfoProviderImpl : InfoProvider() {
9 | override fun remove(@NonNull table: String, @NonNull name: String): Boolean {
10 | getStoragePrefs(table).edit().remove(name).apply()
11 | return true
12 | }
13 |
14 | override fun get(@NonNull table: String, @NonNull name: String): String? {
15 | return getStoragePrefs(table).getString(name, null)
16 | }
17 |
18 | override fun put(@NonNull table: String, @NonNull name: String, @NonNull value: String): Boolean {
19 | getStoragePrefs(table).edit().putString(name, value).apply()
20 | return true
21 | }
22 |
23 | private fun getStoragePrefs(table: String): SharedPreferences {
24 | return context!!.getSharedPreferences("info_provider_$table", Context.MODE_PRIVATE)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/LocalService.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.os.IBinder
6 | import android.os.RemoteException
7 | import androidx.annotation.Nullable
8 | import timber.log.Timber
9 |
10 | class LocalService : Service() {
11 |
12 | override fun onCreate() {
13 | super.onCreate()
14 | Timber.tag(TAG).d("onCreate")
15 | }
16 |
17 | override fun onDestroy() {
18 | super.onDestroy()
19 | Timber.tag(TAG).d("onDestroy")
20 | }
21 |
22 | @Nullable
23 | override fun onBind(intent: Intent): IBinder {
24 | return BinderServer()
25 | }
26 |
27 | private inner class BinderServer : IDemoService.Stub() {
28 | @Throws(RemoteException::class)
29 | override fun who(): String {
30 | return "LocalService"
31 | }
32 |
33 | @Throws(RemoteException::class)
34 | override fun sayHello(gift: String) {
35 | Timber.tag(TAG).d("Received gift from someone: %s", gift)
36 | }
37 | }
38 |
39 | companion object {
40 | private const val TAG = "LocalService"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/LocalServiceClient.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service
2 |
3 | import android.content.Context
4 | import androidx.annotation.NonNull
5 | import me.ycdev.android.lib.common.ipc.ServiceClientBase
6 | import me.ycdev.android.lib.common.utils.ThreadManager
7 |
8 | class LocalServiceClient(@NonNull context: Context) : ServiceClientBase(
9 | context,
10 | SERVICE_NAME,
11 | ThreadManager.instance.localServiceRequestIpcLooper(),
12 | LocalServiceConnector(context)
13 | ) {
14 | companion object {
15 | private const val SERVICE_NAME = "LocalService"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/LocalServiceConnector.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.IBinder
6 | import androidx.annotation.NonNull
7 | import me.ycdev.android.lib.common.ipc.ServiceConnector
8 |
9 | class LocalServiceConnector(cxt: Context) : ServiceConnector(cxt, SERVICE_NAME) {
10 |
11 | @NonNull
12 | override fun getServiceIntent(): Intent {
13 | return Intent(appContext, LocalService::class.java)
14 | }
15 |
16 | override fun asInterface(service: IBinder): IDemoService {
17 | return IDemoService.Stub.asInterface(service)
18 | }
19 |
20 | companion object {
21 | private const val SERVICE_NAME = "LocalService"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/RemoteService.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.os.IBinder
6 | import android.os.RemoteException
7 | import androidx.annotation.Nullable
8 | import timber.log.Timber
9 |
10 | class RemoteService : Service() {
11 |
12 | override fun onCreate() {
13 | super.onCreate()
14 | Timber.tag(TAG).d("onCreate")
15 | }
16 |
17 | override fun onDestroy() {
18 | super.onDestroy()
19 | Timber.tag(TAG).d("onDestroy")
20 | }
21 |
22 | @Nullable
23 | override fun onBind(intent: Intent): IBinder {
24 | return BinderServer()
25 | }
26 |
27 | private inner class BinderServer : IDemoService.Stub() {
28 | @Throws(RemoteException::class)
29 | override fun who(): String {
30 | return "RemoteService"
31 | }
32 |
33 | @Throws(RemoteException::class)
34 | override fun sayHello(gift: String) {
35 | Timber.tag(TAG).d("Received gift from someone: %s", gift)
36 | }
37 | }
38 |
39 | companion object {
40 | private const val TAG = "RemoteService"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/RemoteServiceClient.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service
2 |
3 | import android.content.Context
4 | import androidx.annotation.NonNull
5 | import me.ycdev.android.lib.common.ipc.ServiceClientBase
6 | import me.ycdev.android.lib.common.utils.ThreadManager
7 |
8 | class RemoteServiceClient(@NonNull context: Context) : ServiceClientBase(
9 | context,
10 | SERVICE_NAME,
11 | ThreadManager.instance.remoteServiceRequestIpcLooper(),
12 | RemoteServiceConnector(context)
13 | ) {
14 | companion object {
15 | private const val SERVICE_NAME = "RemoteService"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/RemoteServiceConnector.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.IBinder
6 | import androidx.annotation.NonNull
7 | import me.ycdev.android.lib.common.ipc.ServiceConnector
8 |
9 | open class RemoteServiceConnector(cxt: Context) :
10 | ServiceConnector(cxt, SERVICE_NAME) {
11 |
12 | @NonNull
13 | public override fun getServiceIntent(): Intent {
14 | return Intent(appContext, RemoteService::class.java)
15 | }
16 |
17 | override fun asInterface(service: IBinder): IDemoService {
18 | return IDemoService.Stub.asInterface(service)
19 | }
20 |
21 | companion object {
22 | private const val SERVICE_NAME = "RemoteService"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/operation/HelloOperation.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service.operation
2 |
3 | import android.os.RemoteException
4 | import androidx.annotation.NonNull
5 | import me.ycdev.android.lib.common.demo.service.IDemoService
6 | import me.ycdev.android.lib.common.ipc.IpcOperation
7 | import java.util.concurrent.CountDownLatch
8 |
9 | class HelloOperation(private val mGift: String) : IpcOperation {
10 | private var mLatch: CountDownLatch? = null
11 |
12 | fun setNotifier(latch: CountDownLatch): HelloOperation {
13 | mLatch = latch
14 | return this
15 | }
16 |
17 | @Throws(RemoteException::class)
18 | override fun execute(@NonNull service: IDemoService) {
19 | service.sayHello(mGift)
20 | if (mLatch != null) {
21 | mLatch!!.countDown()
22 | }
23 | }
24 |
25 | override fun toString(): String {
26 | return "Operation[Hello]"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/demo/service/operation/WhoOperation.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.demo.service.operation
2 |
3 | import android.os.RemoteException
4 | import androidx.annotation.NonNull
5 | import me.ycdev.android.lib.common.demo.service.IDemoService
6 | import me.ycdev.android.lib.common.ipc.IpcOperation
7 | import java.util.concurrent.CountDownLatch
8 |
9 | class WhoOperation : IpcOperation {
10 | private var mLatch: CountDownLatch? = null
11 |
12 | fun setNotifier(latch: CountDownLatch): WhoOperation {
13 | mLatch = latch
14 | return this
15 | }
16 |
17 | @Throws(RemoteException::class)
18 | override fun execute(@NonNull service: IDemoService) {
19 | service.who()
20 | if (mLatch != null) {
21 | mLatch!!.countDown()
22 | }
23 | }
24 |
25 | override fun toString(): String {
26 | return "Operation[who]"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/internalapi/android/app/ActivityManagerIATest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.internalapi.android.app
2 |
3 | import android.content.Context
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import me.ycdev.android.lib.common.internalapi.android.os.ServiceManagerIA
6 | import org.junit.Assert.assertNotNull
7 | import org.junit.Assert.assertTrue
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 |
11 | @RunWith(AndroidJUnit4::class)
12 | class ActivityManagerIATest {
13 | @Test
14 | fun test_asInterface() {
15 | val binder = ServiceManagerIA.getService(Context.ACTIVITY_SERVICE)
16 | assertNotNull(binder)
17 |
18 | val service = ActivityManagerIA.asInterface(binder!!)
19 | assertNotNull(service)
20 | }
21 |
22 | @Test
23 | fun test_getIActivityManager() {
24 | assertNotNull(ActivityManagerIA.getIActivityManager())
25 | }
26 |
27 | @Test
28 | fun test_forceStopPackage() {
29 | assertTrue(ActivityManagerIA.checkReflectForceStopPackage())
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/internalapi/android/os/PowerManagerIATest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.internalapi.android.os
2 |
3 | import android.content.Context
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import org.junit.Assert.assertNotNull
6 | import org.junit.Assert.assertTrue
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 |
10 | @RunWith(AndroidJUnit4::class)
11 | class PowerManagerIATest {
12 | @Test
13 | fun test_asInterface() {
14 | val binder = ServiceManagerIA.getService(Context.POWER_SERVICE)
15 | assertNotNull(binder)
16 |
17 | val service = PowerManagerIA.asInterface(binder!!)
18 | assertNotNull(service)
19 | }
20 |
21 | @Test
22 | fun test_getIPowerManager() {
23 | assertNotNull(PowerManagerIA.iPowerManager)
24 | }
25 |
26 | @Test
27 | fun test_reboot() {
28 | assertTrue(PowerManagerIA.checkReflectReboot())
29 | }
30 |
31 | @Test
32 | fun test_shutdown() {
33 | assertTrue(PowerManagerIA.checkReflectShutdown())
34 | }
35 |
36 | @Test
37 | fun test_crash() {
38 | assertTrue(PowerManagerIA.checkReflectCrash())
39 | }
40 |
41 | @Test
42 | fun test_goToSleep() {
43 | assertTrue(PowerManagerIA.checkReflectGoToSleep())
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/internalapi/android/os/ProcessIATest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.internalapi.android.os
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.filters.RequiresDevice
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Assert.assertTrue
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 |
10 | @RunWith(AndroidJUnit4::class)
11 | @RequiresDevice
12 | class ProcessIATest {
13 | @Test
14 | fun test_setArgV0() {
15 | assertTrue("failed to reflect #setArgV0", ProcessIA.checkReflectSetArgV0())
16 | }
17 |
18 | @Test
19 | fun test_readProcLines() {
20 | assertTrue("failed to reflect #readProcLines", ProcessIA.checkReflectReadProcLines())
21 | }
22 |
23 | @Test
24 | fun test_getParentPid() {
25 | assertTrue("failed to reflect #getParentPid", ProcessIA.checkReflectGetParentPid())
26 | // app process --> zygote
27 | val pid = android.os.Process.myPid()
28 | val zygotePid = ProcessIA.getParentPid(pid)
29 | assertTrue("failed to get pid of zygote", zygotePid != pid && zygotePid > 0)
30 | }
31 |
32 | @Test
33 | fun test_myPpid() {
34 | assertTrue("failed to reflect #myPpid", ProcessIA.checkReflectMyPpid())
35 | // app process --> zygote
36 | val pid = android.os.Process.myPid()
37 | val zygotePid = ProcessIA.myPpid()
38 | assertTrue("failed to get pid of zygote", zygotePid != pid && zygotePid > 0)
39 | }
40 |
41 | @Test
42 | fun test_getProcessName() {
43 | // this test app's process name is the package name
44 | val myProcName = ProcessIA.getProcessName(android.os.Process.myPid())
45 | assertEquals(
46 | "failed to validate the test app",
47 | "me.ycdev.android.lib.common.test",
48 | myProcName
49 | )
50 | }
51 |
52 | @Test
53 | fun test_getProcessPid() {
54 | // this test app's process name is the package name
55 | val myPid = ProcessIA.getProcessPid("me.ycdev.android.lib.common.test")
56 | assertEquals(
57 | "failed to validate the test app",
58 | android.os.Process.myPid().toLong(),
59 | myPid.toLong()
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/internalapi/android/os/ServiceManagerIATest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.internalapi.android.os
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import org.junit.Assert.assertTrue
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 |
8 | @RunWith(AndroidJUnit4::class)
9 | class ServiceManagerIATest {
10 | @Test
11 | fun test_getService() {
12 | assertTrue(ServiceManagerIA.checkReflectGetService())
13 | }
14 |
15 | @Test
16 | fun test_checkService() {
17 | assertTrue(ServiceManagerIA.checkReflectCheckService())
18 | }
19 |
20 | @Test
21 | fun test_addService() {
22 | assertTrue(ServiceManagerIA.checkReflectAddService())
23 | }
24 |
25 | @Test
26 | fun test_listServices() {
27 | assertTrue(ServiceManagerIA.checkReflectListServices())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/internalapi/android/os/SystemPropertiesIATest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.internalapi.android.os
2 |
3 | import android.os.Build
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | @RunWith(AndroidJUnit4::class)
10 | class SystemPropertiesIATest {
11 |
12 | @Test
13 | fun test_get() {
14 | val defValue = "test.defValue"
15 | var actual = SystemPropertiesIA.get(TEST_KEY_NONE, defValue)
16 | assertEquals(defValue, actual)
17 |
18 | actual = SystemPropertiesIA.get("ro.product.model", defValue)
19 | assertEquals(Build.MODEL, actual)
20 |
21 | actual = SystemPropertiesIA.get("ro.build.fingerprint", defValue)
22 | assertEquals(Build.FINGERPRINT, actual)
23 | }
24 |
25 | @Test
26 | fun test_getInt() {
27 | val defValue = 123
28 | var actual = SystemPropertiesIA.getInt(TEST_KEY_NONE, defValue)
29 | assertEquals(defValue.toLong(), actual.toLong())
30 |
31 | actual = SystemPropertiesIA.getInt("ro.build.version.sdk", defValue)
32 | assertEquals(Build.VERSION.SDK_INT.toLong(), actual.toLong())
33 | }
34 |
35 | @Test
36 | fun test_getLong() {
37 | val defValue: Long = 123
38 | var actual = SystemPropertiesIA.getLong(TEST_KEY_NONE, defValue)
39 | assertEquals(defValue, actual)
40 |
41 | // Build.getLong("ro.build.date.utc") * 1000
42 | actual = SystemPropertiesIA.getLong("ro.build.date.utc", defValue) * 1000
43 | assertEquals(Build.TIME, actual)
44 | }
45 |
46 | @Test
47 | fun test_getBoolean() {
48 | val defValue = true
49 | var actual = SystemPropertiesIA.getBoolean(TEST_KEY_NONE, defValue)
50 | assertEquals(defValue, actual)
51 |
52 | actual = SystemPropertiesIA.getBoolean("ro.debuggable", true)
53 | val actual2 = SystemPropertiesIA.getBoolean("ro.debuggable", false)
54 | assertEquals(actual, actual2)
55 | }
56 |
57 | companion object {
58 | private const val TEST_KEY_NONE = "test.internalapis.none"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/internalapi/android/os/UserHandleIATest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.internalapi.android.os
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import org.junit.Assert.assertTrue
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 |
8 | @RunWith(AndroidJUnit4::class)
9 | class UserHandleIATest {
10 | @Test
11 | fun test_myUserId() {
12 | assertTrue(UserHandleIA.checkReflectMyUserId())
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/net/NetworkUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.net
2 |
3 | import android.content.Context
4 | import androidx.test.core.app.ApplicationProvider
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import androidx.test.filters.LargeTest
7 | import com.google.common.truth.Truth.assertWithMessage
8 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_2G
9 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_3G
10 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_4G
11 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_COMPANION_PROXY
12 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_MOBILE
13 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_NONE
14 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_WIFI
15 | import me.ycdev.android.lib.common.net.NetworkUtils.NetworkType
16 | import org.junit.Rule
17 | import org.junit.Test
18 | import org.junit.rules.Timeout
19 | import org.junit.runner.RunWith
20 |
21 | @RunWith(AndroidJUnit4::class)
22 | @LargeTest
23 | class NetworkUtilsTest {
24 | @Rule @JvmField
25 | var timeout: Timeout = Timeout.seconds(60)
26 |
27 | @Test
28 | fun test_getNetworkType() {
29 | // for any network
30 | val context = ApplicationProvider.getApplicationContext()
31 |
32 | @NetworkType val networkType = NetworkUtils.getNetworkType(context)
33 | assertWithMessage("check all return values")
34 | .that(networkType)
35 | .isAnyOf(
36 | NETWORK_TYPE_MOBILE,
37 | NETWORK_TYPE_WIFI,
38 | NETWORK_TYPE_COMPANION_PROXY,
39 | NETWORK_TYPE_NONE
40 | )
41 | }
42 |
43 | @Test
44 | fun test_getMobileNetworkType() {
45 | // for any network
46 | val context = ApplicationProvider.getApplicationContext()
47 |
48 | @NetworkType val networkType = NetworkUtils.getMobileNetworkType(context)
49 | assertWithMessage("check all return values")
50 | .that(networkType)
51 | .isAnyOf(NETWORK_TYPE_2G, NETWORK_TYPE_3G, NETWORK_TYPE_4G, NETWORK_TYPE_NONE)
52 | }
53 |
54 | @Test
55 | fun test_getMixedNetworkType() {
56 | // for any network
57 | val context = ApplicationProvider.getApplicationContext()
58 |
59 | @NetworkType val networkType = NetworkUtils.getMixedNetworkType(context)
60 | assertWithMessage("check all return values").that(networkType)
61 | .isAnyOf(
62 | NETWORK_TYPE_WIFI,
63 | NETWORK_TYPE_2G,
64 | NETWORK_TYPE_3G,
65 | NETWORK_TYPE_4G,
66 | NETWORK_TYPE_COMPANION_PROXY,
67 | NETWORK_TYPE_NONE
68 | )
69 | }
70 |
71 | @Test
72 | fun test_isActiveNetworkMetered() {
73 | // TODO
74 | }
75 |
76 | @Test
77 | fun test_openHttpURLConnection() {
78 | // TODO
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/suite/InternalApisTestSuite.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.suite
2 |
3 | import me.ycdev.android.lib.common.internalapi.android.app.ActivityManagerIATest
4 | import me.ycdev.android.lib.common.internalapi.android.os.PowerManagerIATest
5 | import me.ycdev.android.lib.common.internalapi.android.os.ProcessIATest
6 | import me.ycdev.android.lib.common.internalapi.android.os.ServiceManagerIATest
7 | import me.ycdev.android.lib.common.internalapi.android.os.SystemPropertiesIATest
8 | import me.ycdev.android.lib.common.internalapi.android.os.UserHandleIATest
9 | import org.junit.runner.RunWith
10 | import org.junit.runners.Suite
11 |
12 | @RunWith(Suite::class)
13 | @Suite.SuiteClasses(
14 | ActivityManagerIATest::class,
15 | PowerManagerIATest::class,
16 | ProcessIATest::class,
17 | ServiceManagerIATest::class,
18 | SystemPropertiesIATest::class,
19 | UserHandleIATest::class
20 | )
21 | class InternalApisTestSuite
22 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/utils/GcHelperTest2.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import me.ycdev.android.lib.common.type.BooleanHolder
5 | import org.junit.Test
6 | import timber.log.Timber
7 | import java.lang.ref.ReferenceQueue
8 | import java.lang.ref.WeakReference
9 |
10 | class GcHelperTest2 {
11 |
12 | @Test
13 | fun forceGc_default() {
14 | GcHelper.forceGc()
15 | // GC happened
16 | }
17 |
18 | @Test
19 | fun forceGc_holder() {
20 | val gcState = BooleanHolder(false)
21 | createGcWatcherObject(gcState)
22 | GcHelper.forceGc(gcState)
23 | }
24 |
25 | private fun createGcWatcherObject(gcState: BooleanHolder) {
26 | object : Any() {
27 | @Throws(Throwable::class)
28 | protected fun finalize() {
29 | Timber.tag(TAG).d("forceGc_holder, GC Partner object was collected")
30 | gcState.value = true
31 | }
32 | }
33 | }
34 |
35 | @Test
36 | fun checkWeakReference_demo1() {
37 | val objHolder = createWeakReferenceObject()
38 | GcHelper.forceGc()
39 | assertThat(objHolder.get()).isNull()
40 | }
41 |
42 | private fun createWeakReferenceObject(): WeakReference {
43 | val obj = Dummy()
44 | return WeakReference(obj)
45 | }
46 |
47 | @Test
48 | fun checkWeakReference_demo2() {
49 | val refQueue = ReferenceQueue()
50 | val objHolder = createWeakReferenceObject(refQueue)
51 | GcHelper.forceGc()
52 | assertThat(objHolder.get()).isNull()
53 | assertThat(refQueue.poll()).isSameInstanceAs(objHolder)
54 | }
55 |
56 | private fun createWeakReferenceObject(refQueue: ReferenceQueue): WeakReference {
57 | val obj = Dummy()
58 | return WeakReference(obj, refQueue)
59 | }
60 |
61 | private class Dummy
62 |
63 | companion object {
64 | private const val TAG = "GcHelperTest2"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/utils/SystemSwitchUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.net.wifi.WifiManager
6 |
7 | @SuppressLint("MissingPermission")
8 | object SystemSwitchUtils {
9 | fun isWifiEnabled(cxt: Context): Boolean {
10 | val wifiMgr = cxt.applicationContext.getSystemService(
11 | Context.WIFI_SERVICE
12 | ) as WifiManager
13 | val wifiState = wifiMgr.wifiState
14 | return wifiState == WifiManager.WIFI_STATE_ENABLED || wifiState == WifiManager.WIFI_STATE_ENABLING
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/java/me/ycdev/android/lib/common/utils/ThreadManager.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.os.HandlerThread
4 | import android.os.Looper
5 |
6 | class ThreadManager {
7 |
8 | private var mLocalServiceRequestIpcThread: HandlerThread? = null
9 | private var mRemoteServiceRequestIpcThread: HandlerThread? = null
10 |
11 | @Synchronized
12 | fun localServiceRequestIpcLooper(): Looper {
13 | if (mLocalServiceRequestIpcThread == null) {
14 | mLocalServiceRequestIpcThread = HandlerThread("LocalService.client")
15 | mLocalServiceRequestIpcThread!!.start()
16 | }
17 | return mLocalServiceRequestIpcThread!!.looper
18 | }
19 |
20 | @Synchronized
21 | fun remoteServiceRequestIpcLooper(): Looper {
22 | if (mRemoteServiceRequestIpcThread == null) {
23 | mRemoteServiceRequestIpcThread = HandlerThread("RemoteService.client")
24 | mRemoteServiceRequestIpcThread!!.start()
25 | }
26 | return mRemoteServiceRequestIpcThread!!.looper
27 | }
28 |
29 | companion object {
30 | val instance = ThreadManager()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/baseLib/src/androidTest/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 | android.library.reference.1=../main
16 |
--------------------------------------------------------------------------------
/baseLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/activity/ActivityMeta.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.activity
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.pm.ActivityInfo
6 | import android.content.pm.PackageManager
7 | import androidx.annotation.VisibleForTesting
8 | import java.util.concurrent.ConcurrentHashMap
9 |
10 | data class ActivityMeta(
11 | val componentName: ComponentName,
12 | val taskAffinity: String,
13 | val launchMode: Int,
14 | val allowTaskReparenting: Boolean
15 | ) {
16 | companion object {
17 | private val cache = ConcurrentHashMap()
18 |
19 | /**
20 | * @throws PackageManager.NameNotFoundException if component not found in the system
21 | */
22 | fun get(context: Context, activity: ComponentName): ActivityMeta {
23 | val key = activity.flattenToShortString()
24 | var meta = cache[key]
25 | if (meta != null) {
26 | return meta
27 | }
28 |
29 | @Suppress("DEPRECATION")
30 | val info = context.packageManager.getActivityInfo(activity, 0)
31 | val taskAffinity = info.taskAffinity ?: context.applicationInfo.taskAffinity
32 | val allowTaskReparenting = (info.flags and ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) > 0
33 | meta = ActivityMeta(activity, taskAffinity, info.launchMode, allowTaskReparenting)
34 | cache[key] = meta
35 | return meta
36 | }
37 |
38 | @VisibleForTesting
39 | internal fun initCache(vararg metas: ActivityMeta) {
40 | cache.clear()
41 | metas.forEach {
42 | val key = it.componentName.flattenToShortString()
43 | cache[key] = it
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/activity/ActivityRunningState.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.activity
2 |
3 | import android.content.ComponentName
4 |
5 | data class ActivityRunningState(
6 | val componentName: ComponentName,
7 | val hashCode: Int,
8 | var taskId: Int,
9 | var state: State = State.None
10 | ) {
11 | fun makeCopy(): ActivityRunningState {
12 | val cloned = ActivityRunningState(componentName, hashCode, taskId)
13 | cloned.state = state
14 | return cloned
15 | }
16 |
17 | enum class State {
18 | None,
19 | Created,
20 | Started,
21 | Resumed,
22 | Paused,
23 | Stopped,
24 | Destroyed
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/activity/ActivityTask.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.activity
2 |
3 | import android.content.ComponentName
4 | import java.util.Stack
5 |
6 | class ActivityTask(val taskId: Int, val taskAffinity: String) {
7 | private val activities = arrayListOf()
8 |
9 | internal fun addActivity(activity: ActivityRunningState) {
10 | if (activity.taskId != taskId) {
11 | throw RuntimeException("Activity taskId[${activity.taskId}] != AppTask[$taskId]")
12 | }
13 | activities.add(activity)
14 | }
15 |
16 | internal fun popActivity(componentName: ComponentName, hashCode: Int): ActivityRunningState {
17 | val it = activities.asReversed().iterator()
18 | while (it.hasNext()) {
19 | val activity = it.next()
20 | if (activity.componentName == componentName && activity.hashCode == hashCode) {
21 | it.remove()
22 | return activity
23 | }
24 | }
25 | val hashHex = Integer.toHexString(hashCode)
26 | throw RuntimeException("Cannot find $componentName@$hashHex")
27 | }
28 |
29 | fun lastActivity(componentName: ComponentName, hashCode: Int): ActivityRunningState {
30 | activities.asReversed().forEach {
31 | if (it.componentName == componentName && it.hashCode == hashCode) {
32 | return it
33 | }
34 | }
35 | val hashHex = Integer.toHexString(hashCode)
36 | throw RuntimeException("Cannot find $componentName@$hashHex")
37 | }
38 |
39 | fun topActivity(): ActivityRunningState {
40 | if (activities.isEmpty()) {
41 | throw RuntimeException("The task is empty. Cannot get the top Activity.")
42 | }
43 | return activities[activities.lastIndex]
44 | }
45 |
46 | /**
47 | * @return The last Activity in returned list is the top Activity
48 | */
49 | fun getActivityStack(): Stack {
50 | val stack = Stack()
51 | activities.forEach {
52 | stack.push(it)
53 | }
54 | return stack
55 | }
56 |
57 | fun isEmpty() = activities.isEmpty()
58 |
59 | fun makeCopy(): ActivityTask {
60 | val task = ActivityTask(taskId, taskAffinity)
61 | activities.forEach {
62 | task.activities.add(it.makeCopy())
63 | }
64 | return task
65 | }
66 |
67 | override fun toString(): String {
68 | return "AppTask[taskId=$taskId, taskAffinity=$taskAffinity, activities=$activities]"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/androidx/app/JobScheduler.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package me.ycdev.android.lib.common.androidx.app
4 |
5 | import android.app.job.JobScheduler
6 |
7 | fun JobScheduler.isJobScheduled(jobId: Int): Boolean {
8 | return getPendingJob(jobId) != null
9 | }
10 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/annotation/HandlerWork.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.annotation
2 |
3 | /**
4 | * Denotes that the annotated method can only be executed in the specified handler.
5 | */
6 | @Target(
7 | AnnotationTarget.FUNCTION,
8 | AnnotationTarget.PROPERTY_GETTER,
9 | AnnotationTarget.PROPERTY_SETTER
10 | )
11 | @Retention(AnnotationRetention.SOURCE)
12 | annotation class HandlerWork(val value: String)
13 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/apps/AppsLoadConfig.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.apps
2 |
3 | data class AppsLoadConfig(
4 | /**
5 | * Load the app name (true by default).
6 | */
7 | var loadLabel: Boolean = true,
8 | /**
9 | * Load the app icon (true by default).
10 | */
11 | var loadIcon: Boolean = true
12 | )
13 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/apps/AppsLoadFilter.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.apps
2 |
3 | class AppsLoadFilter {
4 | /**
5 | * Get mounted apps only (true by default).
6 | */
7 | var onlyMounted = true
8 |
9 | /**
10 | * Get enabled apps only (true by default).
11 | */
12 | var onlyEnabled = true
13 |
14 | /**
15 | * Include all system apps (true by default).
16 | * Note: if this config is true, [.includeUpdatedSysApp] will be ignored;
17 | * otherwise, [.includeUpdatedSysApp] will be checked.
18 | */
19 | var includeSysApp = true
20 |
21 | /**
22 | * Include updated system apps (true by default).
23 | * Note: this config will be ignored if [.includeSysApp] is true.
24 | */
25 | var includeUpdatedSysApp = true
26 |
27 | /**
28 | * Include myself (true by default).
29 | */
30 | var includeMyself = true
31 | }
32 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/apps/AppsLoadListener.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.apps
2 |
3 | interface AppsLoadListener {
4 | /**
5 | * This method can be used to cancel the apps loading.
6 | * @return false will be returned by default.
7 | */
8 | fun isCancelled(): Boolean = false
9 |
10 | /**
11 | * You can override this method to listen the loading progress and loaded app info.
12 | * Nothing to do in the default implementation.
13 | * @param percent Value range [1, 2, ..., 100]
14 | * @param appInfo May be null
15 | */
16 | fun onProgressUpdated(percent: Int, appInfo: AppInfo)
17 | }
18 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/async/HandlerTaskExecutor.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.async
2 |
3 | import android.os.Handler
4 | import android.os.HandlerThread
5 | import android.os.Looper
6 |
7 | open class HandlerTaskExecutor(val taskHandler: Handler) : ITaskExecutor {
8 |
9 | override fun postTask(task: Runnable) {
10 | taskHandler.post(task)
11 | }
12 |
13 | companion object {
14 | fun withMainLooper(): HandlerTaskExecutor {
15 | return HandlerTaskExecutor(Handler(Looper.getMainLooper()))
16 | }
17 |
18 | fun withHandlerThread(name: String): HandlerTaskExecutor {
19 | val thread = HandlerThread(name)
20 | thread.start()
21 | return HandlerTaskExecutor(Handler(thread.looper))
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/async/ITaskExecutor.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.async
2 |
3 | interface ITaskExecutor {
4 | /**
5 | * Post a task to execute.
6 | *
7 | * This method should return immediately and the task should be executed asynchronously.
8 | */
9 | fun postTask(task: Runnable)
10 | }
11 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/async/TaskInfo.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.async
2 |
3 | import android.os.SystemClock
4 | import me.ycdev.android.lib.common.utils.DateTimeUtils
5 | import java.lang.StringBuilder
6 | import java.util.concurrent.atomic.AtomicInteger
7 |
8 | internal class TaskInfo(val executor: ITaskExecutor, val task: Runnable, val delay: Long, val period: Long = -1) {
9 | private val taskId: Int = taskIdGenerator.incrementAndGet()
10 | var triggerAt: Long = SystemClock.elapsedRealtime() + delay
11 |
12 | override fun toString(): String {
13 | val timestamp = System.currentTimeMillis() - (SystemClock.elapsedRealtime() - triggerAt)
14 | return StringBuilder().append("TaskInfo[id=").append(taskId)
15 | .append(", delay=").append(delay)
16 | .append(", triggerAt=").append(DateTimeUtils.getReadableTimeStamp(timestamp))
17 | .append(", period=").append(period)
18 | .append(']')
19 | .toString()
20 | }
21 |
22 | companion object {
23 | private val taskIdGenerator = AtomicInteger(0)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/base/ICallback.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.base
2 |
3 | @FunctionalInterface
4 | interface ICallback {
5 | fun callback(vararg params: Any)
6 | }
7 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/dbmgr/SQLiteDbCreator.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.dbmgr
2 |
3 | import android.content.Context
4 | import android.database.sqlite.SQLiteDatabase
5 |
6 | interface SQLiteDbCreator {
7 | fun createDb(cxt: Context): SQLiteDatabase
8 | }
9 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/dbmgr/SQLiteDbMgr.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.dbmgr
2 |
3 | import android.content.Context
4 | import android.database.sqlite.SQLiteDatabase
5 | import me.ycdev.android.lib.common.pattern.SingletonHolderP1
6 | import timber.log.Timber
7 | import java.util.HashMap
8 |
9 | @Suppress("unused")
10 | class SQLiteDbMgr private constructor(cxt: Context) {
11 |
12 | private val mAppContext: Context = cxt.applicationContext
13 | private val mOpenHelpers = HashMap, DbInfo>()
14 |
15 | private class DbInfo {
16 | var db: SQLiteDatabase? = null
17 | var referenceCount: Int = 0
18 | }
19 |
20 | private fun acquireDatabase(dbInfoClass: Class): SQLiteDatabase? {
21 | Timber.tag(TAG).d("acquire DB: %s", dbInfoClass.name)
22 | val db: SQLiteDatabase?
23 | synchronized(SQLiteDbMgr::class.java) {
24 | var info = mOpenHelpers[dbInfoClass]
25 | if (info == null) {
26 | try {
27 | Timber.tag(TAG).d("create DB: %s", dbInfoClass.name)
28 | val helper = dbInfoClass.newInstance()
29 | info = DbInfo()
30 | info.db = helper.createDb(mAppContext)
31 | info.referenceCount = 0
32 | mOpenHelpers[dbInfoClass] = info
33 | } catch (e: Exception) {
34 | throw RuntimeException("failed to create SQLiteOpenHelper instance", e)
35 | }
36 | }
37 | info.referenceCount++
38 | db = info.db
39 | }
40 | return db
41 | }
42 |
43 | private fun releaseDatabase(dbInfoClass: Class) {
44 | Timber.tag(TAG).d("release DB: %s", dbInfoClass.name)
45 | synchronized(SQLiteDbMgr::class.java) {
46 | val info = mOpenHelpers[dbInfoClass]
47 | if (info != null) {
48 | info.referenceCount--
49 | if (info.referenceCount == 0) {
50 | Timber.tag(TAG).d("close DB: %s", dbInfoClass.name)
51 | info.db!!.close()
52 | info.db = null
53 | mOpenHelpers.remove(dbInfoClass)
54 | }
55 | }
56 | }
57 | }
58 |
59 | companion object : SingletonHolderP1(::SQLiteDbMgr) {
60 | private const val TAG = "SQLiteDbMgr"
61 |
62 | fun acquireDatabase(
63 | cxt: Context,
64 | dbInfoClass: Class
65 | ): SQLiteDatabase? {
66 | return getInstance(cxt).acquireDatabase(dbInfoClass)
67 | }
68 |
69 | fun releaseDatabase(
70 | cxt: Context,
71 | dbInfoClass: Class
72 | ) {
73 | getInstance(cxt).releaseDatabase(dbInfoClass)
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/internalapi/android/os/UserHandleIA.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.internalapi.android.os
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.UserHandle
5 | import androidx.annotation.RestrictTo
6 | import timber.log.Timber
7 | import java.lang.reflect.InvocationTargetException
8 | import java.lang.reflect.Method
9 |
10 | @SuppressLint("PrivateApi")
11 | object UserHandleIA {
12 | private const val TAG = "UserHandleIA"
13 |
14 | private var sMtd_myUserId: Method? = null
15 |
16 | init {
17 | try {
18 | sMtd_myUserId = UserHandle::class.java.getMethod("myUserId")
19 | } catch (e: NoSuchMethodException) {
20 | Timber.tag(TAG).w(e, "method not found")
21 | }
22 | }
23 |
24 | fun myUserId(): Int {
25 | if (sMtd_myUserId != null) {
26 | try {
27 | return sMtd_myUserId!!.invoke(null) as Int
28 | } catch (e: IllegalAccessException) {
29 | Timber.tag(TAG).w(e, "Failed to invoke #myUserId()")
30 | } catch (e: InvocationTargetException) {
31 | Timber.tag(TAG).w(e, "Failed to invoke #myUserId() ag")
32 | }
33 | }
34 | return 0
35 | }
36 |
37 | /**
38 | * Just for unit test.
39 | */
40 | @RestrictTo(RestrictTo.Scope.TESTS)
41 | internal fun checkReflectMyUserId(): Boolean {
42 | return sMtd_myUserId != null
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/ipc/ConnectStateListener.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.ipc
2 |
3 | interface ConnectStateListener {
4 | fun onStateChanged(@ServiceConnector.ConnectState newState: Int)
5 | }
6 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/ipc/IpcHandler.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.ipc
2 |
3 | import android.os.Handler
4 | import android.os.HandlerThread
5 | import android.os.Looper
6 |
7 | private fun createLooper(): Looper {
8 | val thread = HandlerThread("IpcHandler")
9 | thread.start()
10 | return thread.looper
11 | }
12 |
13 | @Suppress("unused")
14 | object IpcHandler : Handler(createLooper())
15 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/ipc/IpcOperation.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.ipc
2 |
3 | import android.os.RemoteException
4 |
5 | interface IpcOperation {
6 | @Throws(RemoteException::class)
7 | fun execute(service: IService)
8 | }
9 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/kotlinx/IsNullOrEmpty.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package me.ycdev.android.lib.common.kotlinx
4 |
5 | fun BooleanArray?.isNullOrEmpty(): Boolean {
6 | return this == null || this.isEmpty()
7 | }
8 |
9 | fun CharArray?.isNullOrEmpty(): Boolean {
10 | return this == null || this.isEmpty()
11 | }
12 |
13 | fun ByteArray?.isNullOrEmpty(): Boolean {
14 | return this == null || this.isEmpty()
15 | }
16 |
17 | fun ShortArray?.isNullOrEmpty(): Boolean {
18 | return this == null || this.isEmpty()
19 | }
20 |
21 | fun IntArray?.isNullOrEmpty(): Boolean {
22 | return this == null || this.isEmpty()
23 | }
24 |
25 | fun LongArray?.isNullOrEmpty(): Boolean {
26 | return this == null || this.isEmpty()
27 | }
28 |
29 | fun FloatArray?.isNullOrEmpty(): Boolean {
30 | return this == null || this.isEmpty()
31 | }
32 |
33 | fun DoubleArray?.isNullOrEmpty(): Boolean {
34 | return this == null || this.isEmpty()
35 | }
36 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/manager/ListenerManager.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.manager
2 |
3 | @Suppress("unused")
4 | open class ListenerManager(override val weakReference: Boolean) :
5 | ObjectManager(weakReference) {
6 |
7 | /**
8 | * Only invoked when invoke [addListener]
9 | */
10 | protected open fun onFirstListenerAdd() {
11 | // nothing to do
12 | }
13 |
14 | /**
15 | * Only invoked when invoke [removeListener]
16 | */
17 | protected open fun onLastListenerRemoved() {
18 | // nothing to do
19 | }
20 |
21 | /**
22 | * Override this method to notify the listener when registered.
23 | */
24 | protected open fun onListenerAdded(listener: IListener) {
25 | // nothing to do
26 | }
27 |
28 | final override fun onFirstObjectAdd() {
29 | onFirstListenerAdd()
30 | }
31 |
32 | final override fun onLastObjectRemoved() {
33 | onLastListenerRemoved()
34 | }
35 |
36 | final override fun onObjectAdded(obj: IListener) {
37 | onListenerAdded(obj)
38 | }
39 |
40 | /**
41 | * Get the listeners count right now.
42 | * But the returned value may be NOT accurate if [weakReference] is true.
43 | * Some of the listeners may be already collected by GC.
44 | */
45 | val listenersCount: Int by ::objectsCount
46 |
47 | fun addListener(listener: IListener) = super.addObject(listener)
48 |
49 | fun addListener(listener: IListener, tag: String) = super.addObject(listener, tag)
50 |
51 | fun removeListener(listener: IListener) = super.removeObject(listener)
52 |
53 | fun notifyListeners(action: NotifyAction) = super.notifyObjects(action)
54 |
55 | fun notifyListeners(action: (IListener) -> Unit) = super.notifyObjects(action)
56 |
57 | companion object {
58 | private const val TAG = "ListenerManager"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/manager/NotifyAction.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.manager
2 |
3 | interface NotifyAction {
4 | fun notify(listener: IListener)
5 | }
6 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/packets/PacketsException.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.packets
2 |
3 | class PacketsException(message: String) : Exception(message)
4 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/packets/PacketsWorker.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.packets
2 |
3 | import androidx.annotation.RestrictTo
4 | import androidx.annotation.VisibleForTesting
5 | import timber.log.Timber
6 | import java.nio.ByteBuffer
7 | import java.nio.ByteOrder
8 |
9 | abstract class PacketsWorker(
10 | private val ownerTag: String,
11 | protected val callback: ParserCallback
12 | ) {
13 | var maxPacketSize: Int = 0
14 | set(value) {
15 | if (value < MAX_PACKET_SIZE_MIN) {
16 | throw PacketsException("The value ($value) for maxPacketSize is too small.")
17 | }
18 | if (debugLog) {
19 | Timber.tag(ownerTag).d("setMaxPacketSize: %d", value)
20 | }
21 | field = value
22 | }
23 | var debugLog = false
24 |
25 | @RestrictTo(RestrictTo.Scope.LIBRARY)
26 | internal var parserState = ParserState.HEADER_MAGIC
27 | protected var readBuffer: ByteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE).order(ByteOrder.LITTLE_ENDIAN)
28 |
29 | fun reset() {
30 | parserState = ParserState.HEADER_MAGIC
31 | readBuffer.clear()
32 | }
33 |
34 | abstract fun packetData(data: ByteArray): List
35 |
36 | abstract fun parsePackets(data: ByteArray)
37 |
38 | interface ParserCallback {
39 | fun onDataParsed(data: ByteArray)
40 | }
41 |
42 | @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
43 | internal enum class ParserState {
44 | HEADER_MAGIC,
45 | VERSION,
46 | NUMBER,
47 | DATA_CRC,
48 | DATA_SIZE,
49 | DATA
50 | }
51 |
52 | object Version {
53 | const val UNKNOWN: Byte = 0
54 | const val V1: Byte = 1
55 | const val V2: Byte = 2
56 | const val V3: Byte = 3
57 | }
58 |
59 | companion object {
60 | const val MAX_PACKET_SIZE_MIN = 20
61 | private const val DEFAULT_BUFFER_SIZE = 1024
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/packets/RawPacketsWorker.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.packets
2 |
3 | class RawPacketsWorker(callback: ParserCallback) : PacketsWorker(TAG, callback) {
4 | override fun packetData(data: ByteArray): List {
5 | return arrayListOf(data)
6 | }
7 |
8 | override fun parsePackets(data: ByteArray) {
9 | // support empty data & ignore it
10 | if (data.isNotEmpty()) {
11 | callback.onDataParsed(data)
12 | }
13 | }
14 |
15 | companion object {
16 | const val TAG = "RawPacketsWorker"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/pattern/SingletonHolderP1.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.pattern
2 |
3 | open class SingletonHolderP1(private val creator: (P) -> T) {
4 | @Volatile
5 | private var instance: T? = null
6 |
7 | fun getInstance(param: P): T =
8 | instance ?: synchronized(this) {
9 | instance ?: creator(param).also { instance = it }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/perms/PermissionCallback.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.perms
2 |
3 | import androidx.core.app.ActivityCompat
4 |
5 | interface PermissionCallback : ActivityCompat.OnRequestPermissionsResultCallback {
6 | fun onRationaleDenied(requestCode: Int)
7 | }
8 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/perms/PermissionRequestParams.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.perms
2 |
3 | import androidx.annotation.IntDef
4 | import androidx.annotation.StringRes
5 |
6 | class PermissionRequestParams {
7 |
8 | var requestCode: Int = 0
9 | var permissions: Array? = null
10 |
11 | @RationalePolicy
12 | var rationalePolicy = RATIONALE_POLICY_ON_DEMAND
13 | var rationaleTitle: String? = null
14 | var rationaleContent: String? = null
15 |
16 | @StringRes
17 | var positiveBtnResId = android.R.string.ok
18 |
19 | @StringRes
20 | var negativeBtnResId = android.R.string.cancel
21 | var callback: PermissionCallback? = null
22 |
23 | @Retention(AnnotationRetention.SOURCE)
24 | @IntDef(RATIONALE_POLICY_ON_DEMAND, RATIONALE_POLICY_NEVER, RATIONALE_POLICY_ALWAYS)
25 | annotation class RationalePolicy
26 |
27 | companion object {
28 | const val RATIONALE_POLICY_ON_DEMAND = 1
29 | const val RATIONALE_POLICY_NEVER = 2
30 | const val RATIONALE_POLICY_ALWAYS = 3
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/tracker/InteractiveStateTracker.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.tracker
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.BroadcastReceiver
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.IntentFilter
8 | import android.os.PowerManager
9 | import me.ycdev.android.lib.common.utils.LibLogger
10 | import me.ycdev.android.lib.common.wrapper.BroadcastHelper
11 |
12 | /**
13 | * A tracker to track the interactive state of the device.
14 | */
15 | @Suppress("unused")
16 | class InteractiveStateTracker private constructor(cxt: Context) :
17 | WeakTracker() {
18 |
19 | private val appContext: Context = cxt.applicationContext
20 | private var interactive: Boolean = false
21 | private var needRefreshInteractiveState: Boolean = false
22 |
23 | private val receiver = object : BroadcastReceiver() {
24 | override fun onReceive(context: Context, intent: Intent) {
25 | val action = intent.action
26 | LibLogger.i(TAG, "Received: $action")
27 | if (Intent.ACTION_USER_PRESENT == action) {
28 | notifyUserPresent()
29 | } else {
30 | interactive = Intent.ACTION_SCREEN_ON == action
31 | notifyInteractiveChanged(interactive)
32 | }
33 | }
34 | }
35 |
36 | val isInteractive: Boolean
37 | get() {
38 | if (needRefreshInteractiveState) {
39 | refreshInteractiveState()
40 | }
41 | return interactive
42 | }
43 |
44 | interface InteractiveStateListener {
45 | /**
46 | * Will be invoked when Intent.ACTION_SCREEN_ON or Intent.ACTION_SCREEN_OFF received.
47 | */
48 | fun onInteractiveChanged(interactive: Boolean)
49 |
50 | /**
51 | * Will be invoked when Intent.ACTION_USER_PRESENT received.
52 | */
53 | fun onUserPresent()
54 | }
55 |
56 | private fun refreshInteractiveState() {
57 | val pm = appContext.getSystemService(Context.POWER_SERVICE) as PowerManager
58 | interactive = pm.isInteractive
59 | }
60 |
61 | override fun startTracker() {
62 | LibLogger.i(TAG, "Screen on/off tracker is running")
63 | val filter = IntentFilter()
64 | filter.addAction(Intent.ACTION_SCREEN_ON)
65 | filter.addAction(Intent.ACTION_SCREEN_OFF)
66 | filter.addAction(Intent.ACTION_USER_PRESENT)
67 | BroadcastHelper.registerForExternal(appContext, receiver, filter)
68 |
69 | refreshInteractiveState()
70 | needRefreshInteractiveState = false
71 | }
72 |
73 | override fun stopTracker() {
74 | LibLogger.i(TAG, "Screen on/off tracker is stopped")
75 | appContext.unregisterReceiver(receiver)
76 | needRefreshInteractiveState = true
77 | }
78 |
79 | override fun onListenerAdded(listener: InteractiveStateListener) {
80 | listener.onInteractiveChanged(interactive)
81 | }
82 |
83 | private fun notifyInteractiveChanged(interactive: Boolean) {
84 | notifyListeners { it.onInteractiveChanged(interactive) }
85 | }
86 |
87 | private fun notifyUserPresent() {
88 | notifyListeners { it.onUserPresent() }
89 | }
90 |
91 | companion object {
92 | private const val TAG = "InteractiveStateTracker"
93 |
94 | @SuppressLint("StaticFieldLeak")
95 | @Volatile
96 | private var instance: InteractiveStateTracker? = null
97 |
98 | fun getInstance(cxt: Context): InteractiveStateTracker {
99 | if (instance == null) {
100 | synchronized(InteractiveStateTracker::class.java) {
101 | if (instance == null) {
102 | instance = InteractiveStateTracker(cxt)
103 | }
104 | }
105 | }
106 | return instance!!
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/tracker/WeakTracker.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.tracker
2 |
3 | import me.ycdev.android.lib.common.manager.ListenerManager
4 |
5 | abstract class WeakTracker : ListenerManager(true) {
6 | protected abstract fun startTracker()
7 | protected abstract fun stopTracker()
8 |
9 | override fun onFirstListenerAdd() {
10 | startTracker()
11 | }
12 |
13 | override fun onLastListenerRemoved() {
14 | stopTracker()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/type/BooleanHolder.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.type
2 |
3 | class BooleanHolder(var value: Boolean)
4 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/type/IntegerHolder.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.type
2 |
3 | class IntegerHolder(var value: Int)
4 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/type/LongHolder.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.type
2 |
3 | class LongHolder(var value: Long)
4 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/ApplicationUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Application
5 | import android.content.Context
6 | import android.os.Process
7 | import android.text.TextUtils
8 | import java.io.IOException
9 |
10 | @Suppress("unused")
11 | object ApplicationUtils {
12 | private const val TAG = "ApplicationUtils"
13 |
14 | @SuppressLint("StaticFieldLeak")
15 | private lateinit var app: Application
16 | private var processName: String? = null
17 |
18 | val application: Application
19 | get() {
20 | Preconditions.checkNotNull(app)
21 | return app
22 | }
23 |
24 | // try AMS first
25 | // try "/proc"
26 | val currentProcessName: String?
27 | get() {
28 | Preconditions.checkNotNull(app)
29 |
30 | if (!TextUtils.isEmpty(processName)) {
31 | return processName
32 | }
33 | val pid = Process.myPid()
34 | processName = getProcessNameFromAMS(app, pid)
35 | if (!TextUtils.isEmpty(processName)) {
36 | return processName
37 | }
38 | processName = getProcessNameFromProc(pid)
39 | return processName
40 | }
41 |
42 | /**
43 | * Must be called in Application#onCreate() ASAP.
44 | */
45 | fun initApplication(app: Application) {
46 | this.app = app
47 | }
48 |
49 | private fun getProcessNameFromAMS(cxt: Context, pid: Int): String? {
50 | val am = SystemServiceHelper.getActivityManager(cxt) ?: return null
51 | val runningApps = SystemServiceHelper.getRunningAppProcesses(am)
52 | for (procInfo in runningApps) {
53 | if (procInfo.pid == pid) {
54 | return procInfo.processName
55 | }
56 | }
57 | return null
58 | }
59 |
60 | private fun getProcessNameFromProc(pid: Int): String? {
61 | var processName: String? = null
62 | try {
63 | val cmdlineFile = "/proc/$pid/cmdline"
64 | processName = IoUtils.readAllLines(cmdlineFile)
65 | } catch (e: IOException) {
66 | LibLogger.w(TAG, "failed to read process name from /proc for pid [%d]", pid)
67 | }
68 |
69 | if (processName != null) {
70 | processName = processName.trim { it <= ' ' }
71 | }
72 | return processName
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/DateTimeUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import androidx.annotation.VisibleForTesting
4 | import java.text.ParseException
5 | import java.text.SimpleDateFormat
6 | import java.util.Date
7 | import java.util.Locale
8 | import java.util.TimeZone
9 |
10 | @Suppress("unused")
11 | object DateTimeUtils {
12 | private val timeFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS", Locale.US)
13 | private var timeZone: TimeZone? = null
14 |
15 | private fun updateTimeZoneIfNeeded() {
16 | if (timeZone == null && TimeZone.getDefault() != timeFormatter.timeZone) {
17 | // system timezone changed!
18 | timeFormatter.timeZone = TimeZone.getDefault()
19 | }
20 | }
21 |
22 | @VisibleForTesting
23 | fun setTimeZoneForTestCases(zone: TimeZone) {
24 | timeZone = zone
25 | timeFormatter.timeZone = zone
26 | }
27 |
28 | /**
29 | * @param timeStr Time string in the format "yyyy-MM-dd HH:mm:ss:SSS"
30 | */
31 | @Throws(ParseException::class)
32 | fun parseTimestamp(timeStr: String): Long {
33 | updateTimeZoneIfNeeded()
34 | return timeFormatter.parse(timeStr)?.time ?: 0
35 | }
36 |
37 | /**
38 | * Generate file name from system time in the format "yyyyMMdd-HHmmss-SSS",
39 | * @param sysTime System time in milliseconds
40 | */
41 | fun generateFileName(sysTime: Long): String {
42 | return SimpleDateFormat("yyyyMMdd-HHmmss-SSS", Locale.US).format(Date(sysTime))
43 | }
44 |
45 | /**
46 | * Parse system time from string in the format "yyyyMMdd-HHmmss-SSS",
47 | * @param timeStr Time string in the format "yyyyMMdd-HHmmss-SSS"
48 | */
49 | @Throws(ParseException::class)
50 | fun parseFileName(timeStr: String): Long {
51 | return SimpleDateFormat("yyyyMMdd-HHmmss-SSS", Locale.US).parse(timeStr)?.time
52 | ?: throw ParseException("Cannot parse '$timeStr'", 0)
53 | }
54 |
55 | /**
56 | * Generate file name from system time in the format "yyyy-MM-dd HH:mm:ss:SSS",
57 | * @param timeStamp System time in milliseconds
58 | */
59 | fun getReadableTimeStamp(timeStamp: Long): String {
60 | updateTimeZoneIfNeeded()
61 | return timeFormatter.format(Date(timeStamp))
62 | }
63 |
64 | /**
65 | * Format the time usage to string like "1d17h37m3s728ms"
66 | */
67 | fun getReadableTimeUsage(timeUsageMs: Long): String {
68 | val millisecondsLeft = timeUsageMs % 1000
69 | if (timeUsageMs == millisecondsLeft) {
70 | return millisecondsLeft.toString() + "ms"
71 | }
72 |
73 | val seconds = timeUsageMs / 1000
74 | val secondsLeft = seconds % 60
75 | if (secondsLeft == seconds) {
76 | return secondsLeft.toString() + "s" + millisecondsLeft + "ms"
77 | }
78 |
79 | val minutes = seconds / 60
80 | val minutesLeft = minutes % 60
81 | if (minutesLeft == minutes) {
82 | return minutesLeft.toString() + "m" + secondsLeft + "s" + millisecondsLeft + "ms"
83 | }
84 |
85 | val hours = minutes / 60
86 | val hoursLeft = hours % 24
87 | if (hoursLeft == hours) {
88 | return hoursLeft.toString() + "h" + minutesLeft + "m" + secondsLeft + "s" + millisecondsLeft + "ms"
89 | }
90 |
91 | val days = hours / 24
92 | return days.toString() + "d" + hoursLeft + "h" + minutesLeft + "m" + secondsLeft + "s" + millisecondsLeft + "ms"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/DebugUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.os.StrictMode
4 |
5 | object DebugUtils {
6 | /**
7 | * Should only be invoked in debug version. Never invoke this method in release version!
8 | */
9 | fun enableStrictMode() {
10 | // thread policy
11 | val threadPolicyBuilder = StrictMode.ThreadPolicy.Builder()
12 | .detectAll()
13 | .penaltyLog()
14 | threadPolicyBuilder.penaltyFlashScreen()
15 | threadPolicyBuilder.penaltyDeathOnNetwork()
16 | StrictMode.setThreadPolicy(threadPolicyBuilder.build())
17 |
18 | // VM policy
19 | val vmPolicyBuilder = StrictMode.VmPolicy.Builder()
20 | .detectAll()
21 | .penaltyLog()
22 | .penaltyDeath()
23 | StrictMode.setVmPolicy(vmPolicyBuilder.build())
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/DigestUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import me.ycdev.android.lib.common.utils.EncodingUtils.encodeWithHex
4 | import java.io.File
5 | import java.io.FileInputStream
6 | import java.io.IOException
7 | import java.io.InputStream
8 | import java.io.UnsupportedEncodingException
9 | import java.security.MessageDigest
10 | import java.security.NoSuchAlgorithmException
11 |
12 | @Suppress("unused")
13 | object DigestUtils {
14 | @Throws(NoSuchAlgorithmException::class, UnsupportedEncodingException::class)
15 | fun md5(text: String): String {
16 | return hash(text, "MD5")
17 | }
18 |
19 | @Throws(NoSuchAlgorithmException::class)
20 | fun md5(data: ByteArray): String {
21 | return hash(data, "MD5")
22 | }
23 |
24 | @Throws(NoSuchAlgorithmException::class, UnsupportedEncodingException::class)
25 | fun sha1(text: String): String {
26 | return hash(text, "SHA-1")
27 | }
28 |
29 | @Throws(NoSuchAlgorithmException::class)
30 | fun sha1(data: ByteArray): String {
31 | return hash(data, "SHA-1")
32 | }
33 |
34 | @Throws(NoSuchAlgorithmException::class, UnsupportedEncodingException::class)
35 | fun hash(text: String, algorithm: String): String {
36 | return hash(text.toByteArray(charset("UTF-8")), algorithm)
37 | }
38 |
39 | @Throws(NoSuchAlgorithmException::class)
40 | fun hash(data: ByteArray, algorithm: String): String {
41 | val digest = MessageDigest.getInstance(algorithm)
42 | digest.update(data)
43 | val messageDigest = digest.digest()
44 | return encodeWithHex(messageDigest, false)
45 | }
46 |
47 | /**
48 | * The caller should close the stream.
49 | */
50 | @Throws(NoSuchAlgorithmException::class, IOException::class)
51 | fun md5(stream: InputStream?): String {
52 | if (stream == null) {
53 | throw IllegalArgumentException("Invalid input stream!")
54 | }
55 | val buffer = ByteArray(1024)
56 | val complete = MessageDigest.getInstance("MD5")
57 | var numRead: Int
58 | do {
59 | numRead = stream.read(buffer)
60 | if (numRead > 0) {
61 | complete.update(buffer, 0, numRead)
62 | }
63 | } while (numRead != -1)
64 | val digest = complete.digest()
65 | return encodeWithHex(digest, false)
66 | }
67 |
68 | @Throws(NoSuchAlgorithmException::class, IOException::class)
69 | fun md5(file: File): String {
70 | var stream: FileInputStream? = null
71 | try {
72 | stream = FileInputStream(file)
73 | return md5(stream)
74 | } finally {
75 | IoUtils.closeQuietly(stream)
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/EncodingUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | object EncodingUtils {
4 | private val HEX_ARRAY_UPPERCASE =
5 | charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
6 | private val HEX_ARRAY_LOWERCASE =
7 | charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
8 |
9 | /**
10 | * Encode the data with HEX (Base16) encoding
11 | */
12 | fun encodeWithHex(bytes: ByteArray?, uppercase: Boolean = true): String {
13 | return if (bytes == null) {
14 | "null"
15 | } else {
16 | encodeWithHex(bytes, 0, bytes.size, uppercase)
17 | }
18 | }
19 |
20 | /**
21 | * Encode the data with HEX (Base16) encoding
22 | */
23 | fun encodeWithHex(
24 | bytes: ByteArray,
25 | startPos: Int,
26 | endPos: Int,
27 | uppercase: Boolean = true
28 | ): String {
29 | var endPosTmp = endPos
30 | if (endPosTmp > bytes.size) {
31 | endPosTmp = bytes.size
32 | }
33 | val size = endPosTmp - startPos
34 | val charsArray = if (uppercase) HEX_ARRAY_UPPERCASE else HEX_ARRAY_LOWERCASE
35 | val hexChars = CharArray(size * 2)
36 | var i = startPos
37 | var j = 0
38 | while (i < endPosTmp) {
39 | val v = bytes[i].toInt() and 0xFF
40 | hexChars[j] = charsArray[v.ushr(4)]
41 | hexChars[j + 1] = charsArray[v and 0x0F]
42 | i++
43 | j += 2
44 | }
45 | return String(hexChars)
46 | }
47 |
48 | fun fromHexString(hexStr: String): ByteArray {
49 | val hexStrTmp = hexStr.replace(" ", "") // support spaces
50 | if (hexStrTmp.length % 2 != 0) {
51 | throw IllegalArgumentException("Bad length: $hexStrTmp")
52 | }
53 |
54 | val result = ByteArray(hexStrTmp.length / 2)
55 | for (i in result.indices) {
56 | val high = fromHexChar(hexStrTmp, i * 2) shl 4
57 | val low = fromHexChar(hexStrTmp, i * 2 + 1)
58 | result[i] = (high or low and 0xFF).toByte()
59 | }
60 | return result
61 | }
62 |
63 | private fun fromHexChar(hexStr: String, index: Int): Int {
64 | return when (val ch = hexStr[index]) {
65 | in '0'..'9' -> ch - '0'
66 | in 'a'..'f' -> 10 + (ch - 'a')
67 | in 'A'..'F' -> 10 + (ch - 'A')
68 | else -> throw IllegalArgumentException("Not hex string: $hexStr")
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/GcHelper.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import me.ycdev.android.lib.common.type.BooleanHolder
4 | import timber.log.Timber
5 |
6 | @Suppress("unused")
7 | object GcHelper {
8 | private const val TAG = "GcHelper"
9 |
10 | fun forceGc(gcState: BooleanHolder) {
11 | // Now, 'objPartner' can be collected by GC!
12 | val timeStart = System.currentTimeMillis()
13 |
14 | // create a lot of objects to force GC
15 | val memAllocSize = 1024 * 1024 // 1MB
16 | var memAllocCount: Long = 0
17 | while (true) {
18 | Runtime.getRuntime().gc()
19 | ThreadUtils.sleep(100) // wait for GC
20 | if (gcState.value) {
21 | break // GC happened
22 | }
23 | Timber.tag(TAG).d("Allocating mem...")
24 | ByteArray(memAllocSize)
25 | memAllocCount++
26 | }
27 |
28 | val timeUsed = System.currentTimeMillis() - timeStart
29 | Timber.tag(TAG).d("Force GC, time used: %d, memAlloc: %dMB", timeUsed, memAllocCount)
30 | }
31 |
32 | fun forceGc() {
33 | val gcState = BooleanHolder(false)
34 | createGcWatcherObject(gcState)
35 | forceGc(gcState)
36 | }
37 |
38 | private fun createGcWatcherObject(gcState: BooleanHolder) {
39 | object : Any() {
40 | @Throws(Throwable::class)
41 | protected fun finalize() {
42 | Timber.tag(TAG).d("GC Partner object was collected")
43 | gcState.value = true
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/GsonHelper.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import com.google.gson.JsonObject
4 |
5 | object GsonHelper {
6 | fun optString(json: JsonObject, key: String, defValue: String?): String? {
7 | return if (json.has(key)) {
8 | json.get(key).asString
9 | } else {
10 | defValue
11 | }
12 | }
13 |
14 | fun optBoolean(json: JsonObject, key: String, defValue: Boolean): Boolean {
15 | return if (json.has(key)) {
16 | json.get(key).asBoolean
17 | } else {
18 | defValue
19 | }
20 | }
21 |
22 | fun optInt(json: JsonObject, key: String, defValue: Int): Int {
23 | return if (json.has(key)) {
24 | json.get(key).asInt
25 | } else {
26 | defValue
27 | }
28 | }
29 |
30 | fun optLong(json: JsonObject, key: String, defValue: Long): Long {
31 | return if (json.has(key)) {
32 | json.get(key).asLong
33 | } else {
34 | defValue
35 | }
36 | }
37 |
38 | fun optFloat(json: JsonObject, key: String, defValue: Float): Float {
39 | return if (json.has(key)) {
40 | json.get(key).asFloat
41 | } else {
42 | defValue
43 | }
44 | }
45 |
46 | fun optDouble(json: JsonObject, key: String, defValue: Double): Double {
47 | return if (json.has(key)) {
48 | json.get(key).asDouble
49 | } else {
50 | defValue
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/MainHandler.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 |
6 | object MainHandler : Handler(Looper.getMainLooper())
7 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/MiscUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | object MiscUtils {
4 | fun calcProgressPercent(percentStart: Int, percentEnd: Int, i: Int, n: Int): Int {
5 | return percentStart + i * (percentEnd - percentStart) / n
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/Preconditions.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | object Preconditions {
4 | fun checkMainThread() {
5 | if (!ThreadUtils.isMainThread) {
6 | throw RuntimeException("Not in main thread")
7 | }
8 | }
9 |
10 | fun checkNonMainThread() {
11 | if (ThreadUtils.isMainThread) {
12 | throw RuntimeException("In main thread")
13 | }
14 | }
15 |
16 | fun checkArgument(expression: Boolean) {
17 | if (!expression) {
18 | throw IllegalArgumentException()
19 | }
20 | }
21 |
22 | fun checkNotNull(obj: T?): T {
23 | if (obj == null) {
24 | throw NullPointerException()
25 | }
26 | return obj
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/ReflectionUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import java.lang.reflect.Field
4 | import java.lang.reflect.Method
5 |
6 | object ReflectionUtils {
7 | @Throws(NoSuchMethodException::class)
8 | fun findMethod(
9 | classObj: Class<*>,
10 | methodName: String,
11 | vararg parameterTypes: Class<*>
12 | ): Method {
13 | // first, search public methods
14 | try {
15 | return classObj.getMethod(methodName, *parameterTypes)
16 | } catch (e: NoSuchMethodException) {
17 | // ignore
18 | }
19 |
20 | // next, search the non-public methods
21 | var c: Class<*>? = classObj
22 | while (c != null) {
23 | try {
24 | val method = c.getDeclaredMethod(methodName, *parameterTypes)
25 | method.isAccessible = true
26 | return method
27 | } catch (e: NoSuchMethodException) {
28 | // ignore
29 | }
30 |
31 | c = c.superclass
32 | }
33 |
34 | throw NoSuchMethodException("$methodName not found")
35 | }
36 |
37 | @Throws(NoSuchFieldException::class)
38 | fun findField(classObj: Class<*>, fieldName: String): Field {
39 | // first, search public fields
40 | try {
41 | return classObj.getField(fieldName)
42 | } catch (e: NoSuchFieldException) {
43 | // ignore
44 | }
45 |
46 | // next, search non-public fields
47 | var c: Class<*>? = classObj
48 | while (c != null) {
49 | try {
50 | val field = c.getDeclaredField(fieldName)
51 | field.isAccessible = true
52 | return field
53 | } catch (e: NoSuchFieldException) {
54 | // ignore
55 | }
56 |
57 | c = c.superclass
58 | }
59 |
60 | throw NoSuchFieldException("$fieldName not found")
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/StorageUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.os.Environment
6 | import android.os.storage.StorageManager
7 | import androidx.annotation.WorkerThread
8 | import java.io.File
9 |
10 | @Suppress("unused")
11 | object StorageUtils {
12 |
13 | /**
14 | * Check if the external storage is built-in or removable.
15 | * @return true if the external storage is removable (like an SD card), false
16 | * otherwise.
17 | * @see Environment.isExternalStorageRemovable
18 | */
19 | fun isExternalStorageRemovable(): Boolean = Environment.isExternalStorageRemovable()
20 |
21 | /**
22 | * Check if the external storage is emulated by a portion of the internal storage.
23 | * @return true if the external storage is emulated, false otherwise.
24 | * @see Environment.isExternalStorageEmulated
25 | */
26 | fun isExternalStorageEmulated(): Boolean = Environment.isExternalStorageEmulated()
27 |
28 | fun isExternalStorageAvailable(): Boolean =
29 | Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
30 |
31 | fun getExternalStoragePath(): String = Environment.getExternalStorageDirectory().absolutePath
32 |
33 | /**
34 | * Returns the number of usable free bytes on the partition containing this path.
35 | * Returns 0 if this path does not exist.
36 | * @see File.getUsableSpace
37 | */
38 | @WorkerThread
39 | fun getUsableSpace(path: File, context: Context): Long {
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
41 | val storageMgr = context.getSystemService(StorageManager::class.java) ?: return 0
42 | val uuid = storageMgr.getUuidForPath(path)
43 | return storageMgr.getAllocatableBytes(uuid)
44 | } else {
45 | return path.usableSpace
46 | }
47 | }
48 |
49 | /**
50 | * Returns the number of free bytes on the partition containing this path.
51 | * Returns 0 if this path does not exist.
52 | * @see File.getFreeSpace
53 | */
54 | fun getFreeSpace(path: File): Long {
55 | return path.freeSpace
56 | }
57 |
58 | /**
59 | * Returns the total size in bytes of the partition containing this path.
60 | * Returns 0 if this path does not exist.
61 | * @see File.getTotalSpace
62 | */
63 | fun getTotalSpace(path: File): Long {
64 | return path.totalSpace
65 | }
66 |
67 | /**
68 | * Get the external app cache directory.
69 | * @param context The context to use
70 | * @return The external cache dir
71 | * @see Context.getExternalCacheDir
72 | */
73 | fun getExternalCacheDir(context: Context): File? {
74 | return context.externalCacheDir
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/StringUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | @Suppress("unused")
4 | object StringUtils {
5 | fun trimPrefixSpaces(str: String): String {
6 | val size = str.length
7 | var index = 0
8 | while (index < size && (str[index] <= '\u0020' || str[index] == '\u00a0')) {
9 | index++
10 | }
11 | return if (index > 0) {
12 | str.substring(index)
13 | } else {
14 | str
15 | }
16 | }
17 |
18 | fun parseInt(value: String, defValue: Int): Int {
19 | return try {
20 | value.toInt()
21 | } catch (e: Exception) {
22 | defValue
23 | }
24 | }
25 |
26 | fun parseLong(value: String, defValue: Long): Long {
27 | return try {
28 | value.toLong()
29 | } catch (e: Exception) {
30 | defValue
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/SystemServiceHelper.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("DEPRECATION")
2 |
3 | package me.ycdev.android.lib.common.utils
4 |
5 | import android.app.ActivityManager
6 | import android.app.ActivityManager.RunningAppProcessInfo
7 | import android.app.ActivityManager.RunningServiceInfo
8 | import android.content.Context
9 | import android.content.pm.PackageInfo
10 | import android.content.pm.PackageManager
11 |
12 | @Suppress("unused")
13 | object SystemServiceHelper {
14 | private const val TAG = "SystemServiceHelper"
15 |
16 | fun getActivityManager(context: Context): ActivityManager? {
17 | var am: ActivityManager? = null
18 | try {
19 | am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
20 | } catch (e: Throwable) {
21 | // Exception may be thrown on some devices
22 | LibLogger.w(TAG, "unexpected when get AM", e)
23 | }
24 |
25 | return am
26 | }
27 |
28 | fun getPackageManager(context: Context): PackageManager? {
29 | var pm: PackageManager? = null
30 | try {
31 | pm = context.packageManager
32 | } catch (e: Throwable) {
33 | // Exception may be thrown on some devices
34 | LibLogger.w(TAG, "unexpected when get PM", e)
35 | }
36 |
37 | return pm
38 | }
39 |
40 | fun getRunningServices(am: ActivityManager, maxNum: Int): List {
41 | var runServiceList: List? = null
42 | try {
43 | @Suppress("DEPRECATION")
44 | runServiceList = am.getRunningServices(maxNum)
45 | } catch (e: Exception) {
46 | // Exception may be thrown on some devices
47 | LibLogger.w(TAG, "unexpected when get running services", e)
48 | }
49 |
50 | if (runServiceList == null) {
51 | runServiceList = emptyList()
52 | }
53 | return runServiceList
54 | }
55 |
56 | fun getRunningAppProcesses(am: ActivityManager): List {
57 | var runProcessList: List? = null
58 | try {
59 | runProcessList = am.runningAppProcesses
60 | } catch (e: Exception) {
61 | // Exception may be thrown on some devices
62 | LibLogger.w(TAG, "unexpected when get running processes", e)
63 | }
64 |
65 | if (runProcessList == null) {
66 | runProcessList = emptyList()
67 | }
68 | return runProcessList
69 | }
70 |
71 | fun getInstalledPackages(pm: PackageManager, flags: Int): List {
72 | var installedPackages: List? = null
73 | try {
74 | installedPackages = pm.getInstalledPackages(flags)
75 | } catch (e: Exception) {
76 | // Exception may be thrown on some devices
77 | LibLogger.w(TAG, "unexpected when get installed packages", e)
78 | }
79 |
80 | if (installedPackages == null) {
81 | installedPackages = emptyList()
82 | }
83 | return installedPackages
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/ThreadUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.os.Looper
4 |
5 | object ThreadUtils {
6 | val isMainThread: Boolean
7 | get() = Looper.myLooper() == Looper.getMainLooper()
8 |
9 | fun isThreadRunning(tid: Long): Boolean {
10 | val threadSet = Thread.getAllStackTraces().keys
11 | for (t in threadSet) {
12 | if (t.id == tid) {
13 | return true
14 | }
15 | }
16 | return false
17 | }
18 |
19 | fun sleep(millis: Long) {
20 | try {
21 | Thread.sleep(millis)
22 | } catch (e: InterruptedException) {
23 | e.printStackTrace()
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/TypeUtils.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import java.lang.reflect.GenericArrayType
4 | import java.lang.reflect.ParameterizedType
5 | import java.lang.reflect.Type
6 | import java.lang.reflect.TypeVariable
7 | import java.lang.reflect.WildcardType
8 |
9 | object TypeUtils {
10 | fun getRawType(type: Type): Class<*> {
11 | if (type is Class<*>) {
12 | // Type is a normal class.
13 | return type
14 | }
15 | if (type is ParameterizedType) {
16 | val parameterizedType: ParameterizedType = type
17 |
18 | // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but
19 | // suspects some pathological case related to nested classes exists.
20 | val rawType: Type = parameterizedType.rawType
21 | require(rawType is Class<*>)
22 | return rawType
23 | }
24 | if (type is GenericArrayType) {
25 | val componentType: Type = type.genericComponentType
26 | return java.lang.reflect.Array.newInstance(getRawType(componentType), 0).javaClass
27 | }
28 | if (type is TypeVariable<*>) {
29 | // We could use the variable's bounds, but that won't work if there are multiple. Having a raw
30 | // type that's more general than necessary is okay.
31 | return Any::class.java
32 | }
33 | if (type is WildcardType) {
34 | return getRawType(type.upperBounds[0])
35 | }
36 | throw IllegalArgumentException(
37 | "Expected a Class, ParameterizedType, or " +
38 | "GenericArrayType, but <" +
39 | type +
40 | "> is of type " +
41 | type.javaClass.name
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/utils/WeakHandler.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import android.os.Message
6 | import java.lang.ref.WeakReference
7 |
8 | @Suppress("unused")
9 | class WeakHandler(looper: Looper, msgHandler: Callback) : Handler(looper) {
10 | private val targetHandler: WeakReference = WeakReference(msgHandler)
11 |
12 | constructor(msgHandler: Callback) : this(Looper.myLooper()!!, msgHandler)
13 |
14 | override fun handleMessage(msg: Message) {
15 | val realHandler = targetHandler.get()
16 | realHandler?.handleMessage(msg)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/baseLib/src/main/java/me/ycdev/android/lib/common/wrapper/BroadcastHelper.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.wrapper
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.IntentFilter
7 | import androidx.core.content.ContextCompat
8 |
9 | /**
10 | * A wrapper class to avoid security issues when sending/receiving broadcast.
11 | */
12 | @Suppress("unused")
13 | object BroadcastHelper {
14 | private const val PERM_INTERNAL_BROADCAST_SUFFIX = ".permission.INTERNAL"
15 |
16 | fun getInternalBroadcastPerm(cxt: Context): String {
17 | return cxt.packageName + PERM_INTERNAL_BROADCAST_SUFFIX
18 | }
19 |
20 | /**
21 | * Register a receiver for internal broadcast.
22 | */
23 | fun registerForInternal(
24 | cxt: Context,
25 | receiver: BroadcastReceiver,
26 | filter: IntentFilter
27 | ): Intent? {
28 | val perm = cxt.packageName + PERM_INTERNAL_BROADCAST_SUFFIX
29 | return ContextCompat.registerReceiver(cxt, receiver, filter, perm, null, ContextCompat.RECEIVER_NOT_EXPORTED)
30 | }
31 |
32 | /**
33 | * Register a receiver for external broadcast (includes system broadcast).
34 | */
35 | fun registerForExternal(
36 | cxt: Context,
37 | receiver: BroadcastReceiver,
38 | filter: IntentFilter
39 | ): Intent? {
40 | return ContextCompat.registerReceiver(cxt, receiver, filter, ContextCompat.RECEIVER_EXPORTED)
41 | }
42 |
43 | /**
44 | * Send a broadcast to internal receivers.
45 | */
46 | fun sendToInternal(cxt: Context, intent: Intent) {
47 | val perm = cxt.packageName + PERM_INTERNAL_BROADCAST_SUFFIX
48 | intent.setPackage(cxt.packageName) // only works on Android 4.0 and higher versions
49 | cxt.sendBroadcast(intent, perm)
50 | }
51 |
52 | /**
53 | * Send a broadcast to external receivers.
54 | */
55 | fun sendToExternal(
56 | cxt: Context,
57 | intent: Intent,
58 | perm: String?
59 | ) {
60 | cxt.sendBroadcast(intent, perm)
61 | }
62 |
63 | /**
64 | * Send a broadcast to external receivers.
65 | */
66 | fun sendToExternal(cxt: Context, intent: Intent) {
67 | cxt.sendBroadcast(intent)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/baseLib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Guard permission
4 | To guard myself to prevent attacks
5 |
6 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/activity/ActivityRunningStateTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.activity
2 |
3 | import android.content.ComponentName
4 | import com.google.common.truth.Truth.assertThat
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 | import org.robolectric.RobolectricTestRunner
8 |
9 | @RunWith(RobolectricTestRunner::class)
10 | class ActivityRunningStateTest {
11 | private val testComponent = ComponentName("me.ycdev.test.pkg", "me.ycdev.test.clazz")
12 |
13 | @Test
14 | fun makeCopy() {
15 | val origin = ActivityRunningState(testComponent, 0xa0001, 10, ActivityRunningState.State.Started)
16 | val copied = origin.makeCopy()
17 | assertThat(copied.componentName).isEqualTo(testComponent)
18 | assertThat(copied.hashCode).isEqualTo(0xa0001)
19 | assertThat(copied.taskId).isEqualTo(10)
20 | assertThat(copied.state).isEqualTo(ActivityRunningState.State.Started)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/manager/ObjectManagerTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.manager
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import me.ycdev.android.lib.common.utils.GcHelper
5 | import me.ycdev.android.lib.test.rules.TimberJvmRule
6 | import org.junit.Rule
7 | import org.junit.Test
8 |
9 | class ObjectManagerTest {
10 | @get:Rule
11 | val timberRule = TimberJvmRule()
12 |
13 | @Test
14 | fun basic() {
15 | val managersList = arrayListOf>(
16 | ObjectManager(true),
17 | ObjectManager(false)
18 | )
19 | for (manager in managersList) {
20 | val obj1 = DemoObject(manager)
21 | val obj2 = DemoObject(manager)
22 |
23 | manager.addObject(obj1)
24 | manager.addObject(obj2)
25 |
26 | assertThat(manager.objectsCount).isEqualTo(2)
27 |
28 | manager.notifyObjects { l -> l.call(1) }
29 | assertThat(obj1.value).isEqualTo(1)
30 | assertThat(obj2.value).isEqualTo(1)
31 |
32 | manager.notifyObjects { l -> l.call(2) }
33 | assertThat(obj1.value).isEqualTo(2)
34 | assertThat(obj2.value).isEqualTo(2)
35 |
36 | assertThat(manager.objectsCount).isEqualTo(2)
37 | }
38 | }
39 |
40 | @Test
41 | fun objectLeak() {
42 | val managersList = arrayListOf>(
43 | ObjectManager(true),
44 | ObjectManager(false)
45 | )
46 | for (manager in managersList) {
47 | val obj1 = DemoObject(manager)
48 |
49 | manager.addObject(obj1)
50 | addLeakedObject(manager)
51 |
52 | // force GC
53 | GcHelper.forceGc()
54 |
55 | // before notify
56 | assertThat(manager.objectsCount).isEqualTo(2)
57 |
58 | manager.notifyObjects { l -> l.call(1) }
59 | assertThat(obj1.value).isEqualTo(1)
60 |
61 | // after notify
62 | if (manager.weakReference) {
63 | // the object collected by GC was also removed by ObjectManager!
64 | assertThat(manager.objectsCount).isEqualTo(1)
65 | } else {
66 | assertThat(manager.objectsCount).isEqualTo(2)
67 | }
68 |
69 | manager.notifyObjects { l -> l.call(2) }
70 | assertThat(obj1.value).isEqualTo(2)
71 | }
72 | }
73 |
74 | @Test
75 | fun objectRemovedWhenNotify() {
76 | val managersList = arrayListOf>(
77 | ObjectManager(true),
78 | ObjectManager(false)
79 | )
80 | for (manager in managersList) {
81 | val obj1 = DemoObject(manager, true)
82 | val obj2 = DemoObject(manager)
83 |
84 | manager.addObject(obj1)
85 | manager.addObject(obj2)
86 |
87 | assertThat(manager.objectsCount).isEqualTo(2)
88 |
89 | manager.notifyObjects { l -> l.call(1) }
90 | assertThat(obj1.value).isEqualTo(1)
91 | assertThat(obj2.value).isEqualTo(1)
92 |
93 | assertThat(manager.objectsCount).isEqualTo(1)
94 |
95 | manager.notifyObjects { l -> l.call(2) }
96 | assertThat(obj1.value).isEqualTo(1)
97 | assertThat(obj2.value).isEqualTo(2)
98 |
99 | assertThat(manager.objectsCount).isEqualTo(1)
100 | }
101 | }
102 |
103 | private fun addLeakedObject(manager: ObjectManager) {
104 | manager.addObject(DemoObject(manager))
105 | }
106 |
107 | class DemoObject(
108 | private val manager: ObjectManager,
109 | private val notifyOnce: Boolean = false
110 | ) {
111 | var value: Int = 0
112 |
113 | fun call(value: Int) {
114 | this.value = value
115 | if (notifyOnce) {
116 | manager.removeObject(this)
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/net/NetworkUtilsTestBasic.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.net
2 |
3 | import android.net.NetworkCapabilities
4 | import com.google.common.truth.Truth.assertThat
5 | import io.mockk.every
6 | import io.mockk.mockk
7 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_COMPANION_PROXY
8 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_MOBILE
9 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_NONE
10 | import me.ycdev.android.lib.common.net.NetworkUtils.NETWORK_TYPE_WIFI
11 | import org.junit.Test
12 |
13 | class NetworkUtilsTestBasic {
14 | @Test
15 | fun getNetworkType_common() {
16 | val capabilities = mockk()
17 |
18 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) } returns false
19 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) } returns false
20 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) } returns false
21 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) } returns false
22 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_LOWPAN) } returns false
23 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) } returns false
24 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE) } returns false
25 |
26 | // no network
27 | assertThat(NetworkUtils.getNetworkType(capabilities)).isEqualTo(NETWORK_TYPE_NONE)
28 |
29 | // Wi-Fi
30 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) } returns true
31 | assertThat(NetworkUtils.getNetworkType(capabilities)).isEqualTo(NETWORK_TYPE_WIFI)
32 | // reset
33 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) } returns false
34 | assertThat(NetworkUtils.getNetworkType(capabilities)).isEqualTo(NETWORK_TYPE_NONE)
35 |
36 | // mobile
37 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) } returns true
38 | assertThat(NetworkUtils.getNetworkType(capabilities)).isEqualTo(NETWORK_TYPE_MOBILE)
39 | // reset
40 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) } returns false
41 | assertThat(NetworkUtils.getNetworkType(capabilities)).isEqualTo(NETWORK_TYPE_NONE)
42 |
43 | // bluetooth proxy (Wear OS)
44 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) } returns true
45 | assertThat(NetworkUtils.getNetworkType(capabilities)).isEqualTo(NETWORK_TYPE_COMPANION_PROXY)
46 | // reset
47 | every { capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) } returns false
48 | assertThat(NetworkUtils.getNetworkType(capabilities)).isEqualTo(NETWORK_TYPE_NONE)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/packets/PacketsWorkerTestBase.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.packets
2 |
3 | import me.ycdev.android.lib.common.packets.PacketsWorker.ParserCallback
4 | import me.ycdev.android.lib.test.log.TimberJvmTree
5 | import org.junit.Before
6 | import timber.log.Timber
7 | import java.util.Random
8 | import java.util.concurrent.ArrayBlockingQueue
9 |
10 | open class PacketsWorkerTestBase {
11 | protected val parserCallback = CallbackImpl()
12 | private val random = Random(System.currentTimeMillis())
13 |
14 | @Before
15 | fun setup() {
16 | Timber.plant(TimberJvmTree())
17 | }
18 |
19 | fun generateData(length: Int): ByteArray {
20 | val data = ByteArray(length)
21 | random.nextBytes(data)
22 | return data
23 | }
24 |
25 | inner class CallbackImpl : ParserCallback {
26 | private val dataQueue = ArrayBlockingQueue(5)
27 |
28 | internal fun getData(): ByteArray? {
29 | return dataQueue.poll()
30 | }
31 |
32 | override fun onDataParsed(data: ByteArray) {
33 | dataQueue.add(data)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/packets/RawPacketsWorkerTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.packets
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 |
6 | class RawPacketsWorkerTest : PacketsWorkerTestBase() {
7 | @Test
8 | fun packetAndParse() {
9 | val packetsWorker = RawPacketsWorker(parserCallback)
10 | for (length in 1..1024) {
11 | val data = generateData(length)
12 | val packets = packetsWorker.packetData(data)
13 | assertThat(packets.size).isEqualTo(1)
14 |
15 | packetsWorker.parsePackets(packets[0])
16 | assertThat(parserCallback.getData()).isEqualTo(data)
17 | }
18 | }
19 |
20 | @Test
21 | fun parseEmptyData() {
22 | val packetsWorker = RawPacketsWorker(parserCallback)
23 | val packets = packetsWorker.packetData(byteArrayOf())
24 | assertThat(packets.size).isEqualTo(1)
25 |
26 | packetsWorker.parsePackets(packets[0])
27 | assertThat(parserCallback.getData()).isNull()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/type/BooleanHolderTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.type
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 |
6 | class BooleanHolderTest {
7 | @Test
8 | fun basic() {
9 | run {
10 | val holder = BooleanHolder(true)
11 | assertThat(holder.value).isTrue()
12 | }
13 |
14 | run {
15 | val holder = BooleanHolder(false)
16 | assertThat(holder.value).isFalse()
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/type/IntegerHolderTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.type
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 |
6 | class IntegerHolderTest {
7 | @Test
8 | fun basic() {
9 | run {
10 | val holder = IntegerHolder(0)
11 | assertThat(holder.value).isEqualTo(0)
12 | }
13 |
14 | run {
15 | val holder = IntegerHolder(-10)
16 | assertThat(holder.value).isEqualTo(-10)
17 | }
18 |
19 | run {
20 | val holder = IntegerHolder(100)
21 | assertThat(holder.value).isEqualTo(100)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/type/LongHolderTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.type
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 |
6 | class LongHolderTest {
7 | @Test
8 | fun basic() {
9 | run {
10 | val holder = LongHolder(0L)
11 | assertThat(holder.value).isEqualTo(0)
12 | }
13 |
14 | run {
15 | val holder = LongHolder(-10L)
16 | assertThat(holder.value).isEqualTo(-10L)
17 | }
18 |
19 | run {
20 | val holder = LongHolder(100L)
21 | assertThat(holder.value).isEqualTo(100L)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/utils/EncodingUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Assert
5 | import org.junit.Test
6 |
7 | class EncodingUtilsTest {
8 | @Test
9 | fun encodeWithHex() {
10 | val data = byteArrayOf(0x1a, 0x2b.toByte(), 0x3c, 0x4d, 0x5c.toByte(), 0x6d, 0x7e.toByte())
11 | var result = EncodingUtils.encodeWithHex(data, 0, data.size)
12 | assertThat(result).isEqualTo("1A2B3C4D5C6D7E")
13 | result = EncodingUtils.encodeWithHex(data, 1, 4, true)
14 | assertThat(result).isEqualTo("2B3C4D")
15 | result = EncodingUtils.encodeWithHex(data, 3, 20)
16 | assertThat(result).isEqualTo("4D5C6D7E")
17 |
18 | // lowercase
19 | result = EncodingUtils.encodeWithHex(data, 0, data.size, false)
20 | assertThat(result).isEqualTo("1a2b3c4d5c6d7e")
21 | result = EncodingUtils.encodeWithHex(data, 1, 4, false)
22 | assertThat(result).isEqualTo("2b3c4d")
23 | result = EncodingUtils.encodeWithHex(data, 3, 20, false)
24 | assertThat(result).isEqualTo("4d5c6d7e")
25 | }
26 |
27 | @Test
28 | fun test_fromHexString() {
29 | val hexStr = "01020304050607"
30 | val hexStr2 = " 010 20 30 405 060 7 "
31 | val hexStr3 = "010 203 040 506 07"
32 | val data = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)
33 | assertThat(EncodingUtils.fromHexString(hexStr)).isEqualTo(data)
34 | assertThat(EncodingUtils.fromHexString(hexStr2)).isEqualTo(data)
35 | assertThat(EncodingUtils.fromHexString(hexStr3)).isEqualTo(data)
36 | }
37 |
38 | @Test
39 | fun test_illegalLength() {
40 | val e = Assert.assertThrows(IllegalArgumentException::class.java) {
41 | EncodingUtils.fromHexString("10101")
42 | }
43 | assertThat(e).hasMessageThat().startsWith("Bad length: 10101")
44 | }
45 |
46 | @Test
47 | fun test_illegalCharacter() {
48 | val e = Assert.assertThrows(IllegalArgumentException::class.java) {
49 | EncodingUtils.fromHexString("10101X")
50 | }
51 | assertThat(e).hasMessageThat().startsWith("Not hex string: 10101X")
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/utils/GcHelperTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import com.google.common.truth.Truth
4 | import me.ycdev.android.lib.common.type.BooleanHolder
5 | import me.ycdev.android.lib.test.rules.TimberJvmRule
6 | import org.junit.ClassRule
7 | import org.junit.Test
8 | import timber.log.Timber
9 | import java.lang.ref.ReferenceQueue
10 | import java.lang.ref.SoftReference
11 | import java.lang.ref.WeakReference
12 |
13 | class GcHelperTest {
14 |
15 | @Test
16 | fun forceGc_default() {
17 | GcHelper.forceGc()
18 | // GC happened
19 | }
20 |
21 | @Test
22 | fun forceGc_holder() {
23 | val gcState = BooleanHolder(false)
24 | createGcWatcherObject(gcState)
25 | GcHelper.forceGc(gcState)
26 | }
27 |
28 | private fun createGcWatcherObject(gcState: BooleanHolder) {
29 | object : Any() {
30 | @Throws(Throwable::class)
31 | protected fun finalize() {
32 | Timber.tag(TAG).d("forceGc_holder, GC Partner object was collected")
33 | gcState.value = true
34 | }
35 | }
36 | }
37 |
38 | @Test
39 | fun checkWeakReference_demo1() {
40 | val objHolder = createWeakReferenceObject()
41 | GcHelper.forceGc()
42 | Truth.assertThat(objHolder.get()).isNull()
43 | }
44 |
45 | private fun createWeakReferenceObject(): WeakReference {
46 | val obj = Dummy()
47 | return WeakReference(obj)
48 | }
49 |
50 | @Test
51 | fun checkWeakReference_demo2() {
52 | val refQueue = ReferenceQueue()
53 | val objHolder = createWeakReferenceObject(refQueue)
54 | GcHelper.forceGc()
55 | Truth.assertThat(objHolder.get()).isNull()
56 | Truth.assertThat(refQueue.poll()).isSameInstanceAs(objHolder)
57 | }
58 |
59 | private fun createWeakReferenceObject(refQueue: ReferenceQueue): WeakReference {
60 | val obj = Dummy()
61 | return WeakReference(obj, refQueue)
62 | }
63 |
64 | @Test
65 | fun checkSoftReference() {
66 | val objHolder = createSoftReferenceObject()
67 | GcHelper.forceGc()
68 | Truth.assertThat(objHolder.get()).isNotNull()
69 | }
70 |
71 | private fun createSoftReferenceObject(): SoftReference {
72 | val obj = Dummy()
73 | return SoftReference(obj)
74 | }
75 |
76 | private class Dummy
77 |
78 | companion object {
79 | private const val TAG = "GcHelperTest"
80 |
81 | @ClassRule
82 | @JvmField
83 | val timberJvmRule = TimberJvmRule()
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/utils/MiscUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import androidx.test.filters.SmallTest
4 | import org.junit.After
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Before
7 | import org.junit.Test
8 |
9 | @SmallTest
10 | class MiscUtilsTest {
11 |
12 | @Before
13 | @Throws(Exception::class)
14 | fun setUp() {
15 | LibLogger.enableJvmLogger()
16 | LibLogger.i(TAG, "setup")
17 | }
18 |
19 | @After
20 | @Throws(Exception::class)
21 | fun tearDown() {
22 | LibLogger.i(TAG, "tearDown")
23 | }
24 |
25 | @Test
26 | fun test_calcProgressPercent() {
27 | for (i in 1..100) {
28 | assertEquals(i.toLong(), MiscUtils.calcProgressPercent(1, 100, i, 100).toLong())
29 | }
30 |
31 | for (i in 1..50) {
32 | assertEquals((i * 2).toLong(), MiscUtils.calcProgressPercent(1, 100, i, 50).toLong())
33 | }
34 |
35 | for (i in 1..100) {
36 | assertEquals(
37 | ((i + 1) / 2).toLong(),
38 | MiscUtils.calcProgressPercent(1, 100, i, 200).toLong()
39 | )
40 | }
41 |
42 | // special cases
43 | assertEquals(2, MiscUtils.calcProgressPercent(1, 100, 1, 57).toLong())
44 | assertEquals(4, MiscUtils.calcProgressPercent(1, 100, 2, 57).toLong())
45 | assertEquals(6, MiscUtils.calcProgressPercent(1, 100, 3, 57).toLong())
46 | assertEquals(7, MiscUtils.calcProgressPercent(1, 100, 4, 57).toLong())
47 | assertEquals(9, MiscUtils.calcProgressPercent(1, 100, 5, 57).toLong())
48 | // ...
49 | assertEquals(93, MiscUtils.calcProgressPercent(1, 100, 53, 57).toLong())
50 | assertEquals(94, MiscUtils.calcProgressPercent(1, 100, 54, 57).toLong())
51 | assertEquals(96, MiscUtils.calcProgressPercent(1, 100, 55, 57).toLong())
52 | assertEquals(98, MiscUtils.calcProgressPercent(1, 100, 56, 57).toLong())
53 | assertEquals(100, MiscUtils.calcProgressPercent(1, 100, 57, 57).toLong())
54 |
55 | // special cases
56 | assertEquals(1, MiscUtils.calcProgressPercent(1, 100, 1, 157).toLong())
57 | assertEquals(2, MiscUtils.calcProgressPercent(1, 100, 2, 157).toLong())
58 | assertEquals(2, MiscUtils.calcProgressPercent(1, 100, 3, 157).toLong())
59 | assertEquals(3, MiscUtils.calcProgressPercent(1, 100, 4, 157).toLong())
60 | assertEquals(4, MiscUtils.calcProgressPercent(1, 100, 5, 157).toLong())
61 | assertEquals(4, MiscUtils.calcProgressPercent(1, 100, 6, 157).toLong())
62 | // ...
63 | assertEquals(97, MiscUtils.calcProgressPercent(1, 100, 153, 157).toLong())
64 | assertEquals(98, MiscUtils.calcProgressPercent(1, 100, 154, 157).toLong())
65 | assertEquals(98, MiscUtils.calcProgressPercent(1, 100, 155, 157).toLong())
66 | assertEquals(99, MiscUtils.calcProgressPercent(1, 100, 156, 157).toLong())
67 | assertEquals(100, MiscUtils.calcProgressPercent(1, 100, 157, 157).toLong())
68 | }
69 |
70 | companion object {
71 | private const val TAG = "MiscUtilsTest"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/baseLib/src/test/java/me/ycdev/android/lib/common/utils/TypeUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.common.utils
2 |
3 | import com.google.common.truth.Truth.assertThat
4 | import org.junit.Test
5 |
6 | class TypeUtilsTest {
7 | @Test
8 | fun getRawType() {
9 | assertThat(TypeUtils.getRawType(TypeUtils::class.java)).isEqualTo(TypeUtils::class.java)
10 | assertThat(TypeUtils.getRawType(dummyArrayList().javaClass)).isEqualTo(ArrayList::class.java)
11 | assertThat(TypeUtils.getRawType(Array::class.java)).isEqualTo(Array::class.java)
12 | }
13 |
14 | private fun dummyArrayList(): ArrayList = arrayListOf()
15 |
16 | private fun dummyArray(): Array = arrayOf()
17 | }
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | androidProjectCommon = "${rootDir}/android_project_common.gradle"
4 | androidModuleCommon = "${rootDir}/android_module_common.gradle"
5 | }
6 | apply from: "${androidProjectCommon}"
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:8.1.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
11 | }
12 | }
13 |
14 | plugins {
15 | id("com.diffplug.spotless") version "6.18.0"
16 | id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
17 | }
18 |
19 | ext {
20 | versions.ndkVersion = "21.3.6528147"
21 |
22 | publishEnabled = true
23 | mavenMeta = [
24 | 'projectUrl': 'https://github.com/yongce/AndroidLib',
25 | 'projectScmConnection': 'https://github.com/yongce/AndroidLib.git',
26 | 'projectScmDevConnection': 'ssh://git@github.com/yongce/AndroidLib.git',
27 | 'projectInceptionYear': '2013',
28 | 'groupId': 'io.github.yongce',
29 | 'version': '2.0.1',
30 | 'developerId': 'yongce',
31 | 'developerName': 'Yongce Tu',
32 | 'developerEmail': 'yongce.tu@gmail.com',
33 | ]
34 |
35 | // Trick: other projects can redefine the mapping to include the modules directly
36 | deps.ycdev = [
37 | 'androidBase': project(':baseLib'),
38 | 'androidUi' : project(':uiLib'),
39 | 'androidJni' : project(':jniLib'),
40 | 'androidTest': project(':testLib'),
41 | ]
42 | }
43 |
44 | spotless {
45 | kotlin {
46 | target "**/*.kt"
47 | ktlint(versions.ktlint)
48 | }
49 | }
50 |
51 | apply from: "${rootDir}/publish-root.gradle"
52 | apply plugin: 'android-reporting'
53 |
--------------------------------------------------------------------------------
/build_common.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | versions.minSdk = 24
3 | }
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.useAndroidX=true
2 | android.enableJetifier=true
3 |
4 | org.gradle.jvmargs=-Xmx4096M
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Feb 05 15:43:13 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/jniLib/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | project("jniLib")
2 | cmake_minimum_required(VERSION 3.4.1)
3 |
4 | file(GLOB inc "src/main/cpp/*.h")
5 | file(GLOB src "src/main/cpp/*.cpp")
6 |
7 | add_library(ycdev-commonjni
8 | SHARED
9 | ${src}
10 | )
11 |
12 | include_directories(${inc})
13 |
14 | target_link_libraries(ycdev-commonjni
15 | log
16 | android)
17 |
--------------------------------------------------------------------------------
/jniLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply from: "${androidModuleCommon}"
4 | apply from: '../build_common.gradle'
5 |
6 | project.archivesBaseName = 'android-common-jni'
7 |
8 | android {
9 | namespace 'me.ycdev.android.lib.commonjni'
10 | defaultConfig {
11 | minSdkVersion versions.minSdk
12 |
13 | ndk {
14 | abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
15 | }
16 | }
17 |
18 | ndkVersion versions.ndkVersion
19 | externalNativeBuild {
20 | cmake {
21 | version "3.22.1"
22 | path file('CMakeLists.txt')
23 | }
24 | }
25 |
26 | buildTypes {
27 | release {
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
30 | }
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation deps.kotlin.stdlib
36 | implementation deps.timber
37 |
38 | // Android Testing Support Library's runner and rules
39 | androidTestImplementation deps.test.core
40 | androidTestImplementation deps.test.junit
41 | androidTestImplementation deps.test.runner
42 | androidTestImplementation deps.test.rules
43 | }
44 |
45 | project.ext {
46 | moduleName = 'me.ycdev.android.common-jni'
47 | moduleDesc = 'Common jni module in AndroidLib project'
48 | }
49 |
50 | if (publishEnabled) {
51 | apply from: rootProject.file('publish-module.gradle')
52 | }
53 |
--------------------------------------------------------------------------------
/jniLib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/pub/tools/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/jniLib/src/androidTest/java/me/ycdev/android/lib/commonjni/FileStatusHelperTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonjni
2 |
3 | import android.content.Context
4 | import androidx.test.core.app.ApplicationProvider
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import org.junit.Assert.assertEquals
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 | import timber.log.Timber
10 |
11 | @RunWith(AndroidJUnit4::class)
12 | class FileStatusHelperTest {
13 |
14 | @Test
15 | fun test_getFileStatus() {
16 | val targetContext = ApplicationProvider.getApplicationContext()
17 | val targetUid = targetContext.applicationInfo.uid
18 | val testFile = targetContext.filesDir
19 | val fileStatus = FileStatusHelper.getFileStatus(
20 | testFile.absolutePath
21 | )
22 | Timber.tag(TAG).i(
23 | "uid: " + fileStatus.uid + ", gid: " + fileStatus.gid +
24 | ", mode: " + Integer.toOctalString(fileStatus.mode)
25 | )
26 | assertEquals("check uid", targetUid.toLong(), fileStatus.uid.toLong())
27 | assertEquals("check gid", targetUid.toLong(), fileStatus.gid.toLong())
28 | }
29 |
30 | companion object {
31 | private const val TAG = "FileStatusHelperTest"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/jniLib/src/androidTest/java/me/ycdev/android/lib/commonjni/SysResourceLimitHelperTest.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonjni
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import org.junit.Assert.assertEquals
5 | import org.junit.Assert.assertNotNull
6 | import org.junit.Assert.assertTrue
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 | import timber.log.Timber
10 |
11 | @RunWith(AndroidJUnit4::class)
12 | class SysResourceLimitHelperTest {
13 |
14 | @Test
15 | fun test_getOpenFilesNumberLimit() {
16 | val ofLimit = SysResourceLimitHelper.getOpenFilesLimit()
17 | assertNotNull("failed to get open files limit", ofLimit)
18 | Timber.tag(TAG).d("cur limit: " + ofLimit.curLimit + ", max limit: " + ofLimit.maxLimit)
19 | }
20 |
21 | @Test
22 | fun test_setOpenFilesNumberLimit() {
23 | var ofLimit = SysResourceLimitHelper.getOpenFilesLimit()
24 | assertNotNull("failed to get open files limit", ofLimit)
25 | val oldOfLimit = ofLimit.curLimit
26 | var newOfLimit = oldOfLimit * 2
27 | if (newOfLimit > ofLimit.maxLimit) {
28 | newOfLimit = ofLimit.maxLimit
29 | }
30 | var result = SysResourceLimitHelper.setOpenFilesLimit(newOfLimit)
31 | assertTrue("failed to set open files limit", result)
32 | ofLimit = SysResourceLimitHelper.getOpenFilesLimit()
33 | assertNotNull("failed to get open files limit", ofLimit)
34 | assertEquals(
35 | "failed to set open files limit, double check",
36 | ofLimit.curLimit.toLong(),
37 | newOfLimit.toLong()
38 | )
39 |
40 | result = SysResourceLimitHelper.setOpenFilesLimit(oldOfLimit)
41 | assertTrue("failed to restore open files limit", result)
42 | ofLimit = SysResourceLimitHelper.getOpenFilesLimit()
43 | assertNotNull("failed to get open files limit", ofLimit)
44 | assertEquals(
45 | "failed to restore open files limit, double check",
46 | ofLimit.curLimit.toLong(),
47 | oldOfLimit.toLong()
48 | )
49 | }
50 |
51 | companion object {
52 | private const val TAG = "SRLimitHelperTest"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/jniLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/jniLib/src/main/cpp/CommonJni.cpp:
--------------------------------------------------------------------------------
1 | #define LOG_TAG "CommonJni"
2 | #include "CommonJni.h"
3 |
4 | #pragma clang diagnostic push
5 | #pragma ide diagnostic ignored "EmptyDeclOrStmt"
6 | /*******************************************************************************
7 | **
8 | ** Function: JNI_OnLoad
9 | **
10 | ** Description: Register all JNI functions with Java Virtual Machine.
11 | ** jvm: Java Virtual Machine.
12 | ** reserved: Not used.
13 | **
14 | ** Returns: JNI version.
15 | **
16 | *******************************************************************************/
17 | jint JNI_OnLoad(JavaVM* jvm, __attribute__((unused)) void* reserved)
18 | {
19 | LOGD("JNI_OnLoad...");
20 | JNIEnv *env = nullptr;
21 |
22 | // Check JNI version
23 | if (jvm->GetEnv ((void **) &env, JNI_VERSION_1_6))
24 | {
25 | LOGE("failed to get JVM environment");
26 | return JNI_ERR;
27 | }
28 |
29 | if (ycdev_commonjni::register_SysResourceLimitHelper(env) == -1)
30 | {
31 | LOGE("failed to register SysResourceLimitHelper");
32 | return JNI_ERR;
33 | }
34 |
35 | if (ycdev_commonjni::register_FileStatusHelper(env) == -1)
36 | {
37 | LOGE("failed to register FileStatusHelper");
38 | return JNI_ERR;
39 | }
40 |
41 | LOGD("JNI_OnLoad done");
42 | return JNI_VERSION_1_6;
43 | }
44 | #pragma clang diagnostic pop
45 |
--------------------------------------------------------------------------------
/jniLib/src/main/cpp/CommonJni.h:
--------------------------------------------------------------------------------
1 | #pragma clang diagnostic push
2 | #pragma ide diagnostic ignored "OCUnusedMacroInspection"
3 | #ifndef _YCDEV_COMMON_JNI_H_
4 | #define _YCDEV_COMMON_JNI_H_
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | // log helpers
13 | #ifndef COMMON_JNI_DEBUG
14 | #define LOGV(...)
15 | #define LOGD(...)
16 | #else
17 | #define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
18 | #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
19 | #endif
20 | #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
21 | #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
22 | #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
23 |
24 | // array size
25 | #ifndef NELEM
26 | # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
27 | #endif
28 |
29 | extern "C"
30 | {
31 | jint JNI_OnLoad(JavaVM* jvm, __attribute__((unused)) void* reserved);
32 | }
33 |
34 | namespace ycdev_commonjni {
35 | // register functions
36 | int register_SysResourceLimitHelper(JNIEnv* env);
37 | int register_FileStatusHelper (JNIEnv* env);
38 |
39 | } // namespace ycdev_commonjni
40 |
41 | #endif // _YCDEV_COMMON_JNI_H_
42 |
43 | #pragma clang diagnostic pop
44 |
--------------------------------------------------------------------------------
/jniLib/src/main/java/me/ycdev/android/lib/commonjni/CommonJniLoader.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonjni
2 |
3 | internal object CommonJniLoader {
4 | init {
5 | System.loadLibrary("ycdev-commonjni")
6 | }
7 |
8 | fun load() {
9 | // nothing to do
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/jniLib/src/main/java/me/ycdev/android/lib/commonjni/FileStatusHelper.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonjni
2 |
3 | object FileStatusHelper {
4 | init {
5 | CommonJniLoader.load()
6 | }
7 |
8 | data class FileStatus(var uid: Int = 0, var gid: Int = 0, var mode: Int = 0)
9 |
10 | external fun getFileStatus(filePath: String): FileStatus
11 | }
12 |
--------------------------------------------------------------------------------
/jniLib/src/main/java/me/ycdev/android/lib/commonjni/SysResourceLimitHelper.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonjni
2 |
3 | object SysResourceLimitHelper {
4 |
5 | init {
6 | CommonJniLoader.load()
7 | }
8 |
9 | data class LimitInfo(var curLimit: Int = 0, var maxLimit: Int = 0)
10 |
11 | /**
12 | * Get the maximum number of open files for this process.
13 | * @return null if failed
14 | */
15 | external fun getOpenFilesLimit(): LimitInfo
16 |
17 | /**
18 | * Set the maximum number of open files for this process.
19 | * @param newLimit The new limit to set. Can NOT greater than the max limit.
20 | * @return true if successful
21 | */
22 | external fun setOpenFilesLimit(newLimit: Int): Boolean
23 | }
24 |
--------------------------------------------------------------------------------
/jniLibDemo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply from: "${androidModuleCommon}"
4 | apply from: '../build_common.gradle'
5 |
6 | android {
7 | namespace 'me.ycdev.android.lib.commonjni.demo'
8 | defaultConfig {
9 | minSdkVersion versions.minSdk
10 | targetSdkVersion 34
11 |
12 | applicationId "me.ycdev.android.lib.commonjni.demo"
13 | versionCode 1
14 | versionName "1.0"
15 | }
16 |
17 | ndkVersion versions.ndkVersion
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | lint {
27 | disable 'GoogleAppIndexingWarning'
28 | disable 'MyBaseActivity','MyToastHelper'
29 | }
30 | }
31 |
32 | dependencies {
33 | implementation deps.ycdev.androidJni
34 | implementation project(':archLib')
35 |
36 | implementation deps.kotlin.stdlib
37 | implementation deps.androidx.appcompat
38 | implementation deps.timber
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/jniLibDemo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/pub/tools/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/java/me/ycdev/android/lib/commonjni/demo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonjni.demo
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.Menu
6 | import android.view.MenuItem
7 | import android.view.View
8 | import android.widget.Button
9 | import androidx.appcompat.app.AppCompatActivity
10 | import timber.log.Timber
11 |
12 | class MainActivity : AppCompatActivity(), View.OnClickListener {
13 |
14 | private lateinit var resourceLimitBtn: Button
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | setContentView(R.layout.activity_main)
19 | Timber.tag(TAG).d("onCreate")
20 |
21 | resourceLimitBtn = findViewById(R.id.resource_limit) as Button
22 | resourceLimitBtn.setOnClickListener(this)
23 | }
24 |
25 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
26 | // Inflate the menu; this adds items to the action bar if it is present.
27 | menuInflater.inflate(R.menu.menu_main, menu)
28 | return true
29 | }
30 |
31 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
32 | // Handle action bar item clicks here. The action bar will
33 | // automatically handle clicks on the Home/Up button, so long
34 | // as you specify a parent activity in AndroidManifest.xml.
35 | val id = item.itemId
36 |
37 | return if (id == R.id.action_settings) {
38 | true
39 | } else {
40 | super.onOptionsItemSelected(item)
41 | }
42 | }
43 |
44 | override fun onClick(v: View) {
45 | if (v === resourceLimitBtn) {
46 | val intent = Intent(this, ResourceLimitActivity::class.java)
47 | startActivity(intent)
48 | }
49 | }
50 |
51 | companion object {
52 | private const val TAG = "MainActivity"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/java/me/ycdev/android/lib/commonjni/demo/ResourceLimitActivity.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonjni.demo
2 |
3 | import android.os.Bundle
4 | import android.view.Menu
5 | import android.view.MenuItem
6 | import android.view.View
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.appcompat.app.AppCompatActivity
11 | import me.ycdev.android.lib.commonjni.SysResourceLimitHelper
12 |
13 | class ResourceLimitActivity : AppCompatActivity(), View.OnClickListener {
14 | private lateinit var ofLimitStatusView: TextView
15 | private lateinit var increaseOflimitBtn: Button
16 | private lateinit var decreaseOflimitBtn: Button
17 |
18 | private lateinit var curOflimit: SysResourceLimitHelper.LimitInfo
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | setContentView(R.layout.activity_resource_limit)
23 |
24 | ofLimitStatusView = findViewById(R.id.oflimit_status) as TextView
25 | increaseOflimitBtn = findViewById(R.id.oflimit_increase) as Button
26 | increaseOflimitBtn.setOnClickListener(this)
27 | decreaseOflimitBtn = findViewById(R.id.oflimit_decrease) as Button
28 | decreaseOflimitBtn.setOnClickListener(this)
29 |
30 | refreshOflimitStatus()
31 | }
32 |
33 | private fun refreshOflimitStatus() {
34 | curOflimit = SysResourceLimitHelper.getOpenFilesLimit()
35 | val status =
36 | getString(R.string.oflimit_status, curOflimit.curLimit, curOflimit.maxLimit)
37 | ofLimitStatusView.text = status
38 | }
39 |
40 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
41 | // Inflate the menu; this adds items to the action bar if it is present.
42 | menuInflater.inflate(R.menu.menu_resource_limit, menu)
43 | return true
44 | }
45 |
46 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
47 | // Handle action bar item clicks here. The action bar will
48 | // automatically handle clicks on the Home/Up button, so long
49 | // as you specify a parent activity in AndroidManifest.xml.
50 | val id = item.itemId
51 |
52 | return if (id == R.id.action_settings) {
53 | true
54 | } else {
55 | super.onOptionsItemSelected(item)
56 | }
57 | }
58 |
59 | override fun onClick(v: View) {
60 | if (v === increaseOflimitBtn) {
61 | if (curOflimit.curLimit == 0) {
62 | curOflimit.curLimit = 1
63 | }
64 | if (!SysResourceLimitHelper.setOpenFilesLimit(curOflimit.curLimit * 2)) {
65 | Toast.makeText(this, "failed to set limit", Toast.LENGTH_SHORT).show()
66 | }
67 | refreshOflimitStatus()
68 | } else if (v === decreaseOflimitBtn) {
69 | if (!SysResourceLimitHelper.setOpenFilesLimit(curOflimit.curLimit / 2)) {
70 | Toast.makeText(this, "failed to set limit", Toast.LENGTH_SHORT).show()
71 | }
72 | refreshOflimitStatus()
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/layout/activity_resource_limit.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
17 |
22 |
27 |
28 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/menu/menu_resource_limit.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/jniLibDemo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/jniLibDemo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/jniLibDemo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yongce/AndroidLib/d8ca24f7ef215e93191779e34b4e0740808ec232/jniLibDemo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidJniLibDemo
3 |
4 | Settings
5 |
6 | Resource limit
7 |
8 | Current open files limit: %1$d, max limit: %2$d
9 | Increase open files limit
10 | Decrease open files limit
11 | ResourceLimitActivity
12 |
13 |
14 |
--------------------------------------------------------------------------------
/jniLibDemo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/publish-module.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | group = mavenMeta.groupId
5 | version = mavenMeta.version
6 |
7 | if (project.plugins.findPlugin("com.android.library")) {
8 | android {
9 | publishing {
10 | singleVariant("release") {
11 | withSourcesJar()
12 | withJavadocJar()
13 | }
14 | }
15 | }
16 | }
17 |
18 | afterEvaluate {
19 | publishing {
20 | publications {
21 | release(MavenPublication) {
22 | if (project.plugins.findPlugin("com.android.library")) {
23 | from components.release
24 | } else {
25 | from components.java
26 | }
27 |
28 | groupId = mavenMeta.groupId
29 | artifactId = project.archivesBaseName
30 | version = mavenMeta.version
31 |
32 | pom {
33 | name = project.moduleName
34 | description = project.moduleDesc
35 | url = mavenMeta.projectUrl
36 |
37 | scm {
38 | url = mavenMeta.projectUrl
39 | connection = mavenMeta.projectScmConnection
40 | developerConnection = mavenMeta.projectScmDevConnection
41 | }
42 |
43 | licenses {
44 | license {
45 | name = 'The Apache Software License, Version 2.0'
46 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
47 | }
48 | }
49 |
50 | developers {
51 | developer {
52 | id = mavenMeta.developerId
53 | name = mavenMeta.developerName
54 | email = mavenMeta.developerEmail
55 | }
56 | }
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
63 | signing {
64 | useInMemoryPgpKeys(
65 | rootProject.ext["signing.keyId"],
66 | rootProject.ext["signing.key"],
67 | rootProject.ext["signing.password"],
68 | )
69 | sign publishing.publications
70 | }
--------------------------------------------------------------------------------
/publish-root.gradle:
--------------------------------------------------------------------------------
1 | // Create variables with empty default values
2 | ext["ossrhUsername"] = ''
3 | ext["ossrhPassword"] = ''
4 | ext["sonatypeStagingProfileId"] = ''
5 | ext["signing.keyId"] = ''
6 | ext["signing.password"] = ''
7 | ext["signing.key"] = ''
8 | ext["snapshot"] = ''
9 |
10 | File secretPropsFile = rootProject.file('local.properties')
11 | if (secretPropsFile.exists()) {
12 | Properties p = new Properties()
13 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
14 | ext["ossrhUsername"] = p.getProperty("ossrhUsername")
15 | ext["ossrhPassword"] = p.getProperty("ossrhPassword")
16 | ext["sonatypeStagingProfileId"] = p.getProperty("sonatypeStagingProfileId")
17 | ext["signing.keyId"] = p.getProperty("signing.keyId")
18 | ext["signing.password"] = p.getProperty("signing.password")
19 | ext["signing.key"] = p.getProperty("signing.key")
20 | ext["snapshot"] = p.getProperty("snapshot")
21 | } else {
22 | println("no local.properties")
23 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
24 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
25 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
26 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
27 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
28 | ext["signing.key"] = System.getenv('SIGNING_KEY')
29 | ext["snapshot"] = System.getenv('SNAPSHOT')
30 | }
31 |
32 | // Set up Sonatype repository
33 | nexusPublishing {
34 | repositories {
35 | sonatype {
36 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
37 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
38 |
39 | stagingProfileId = sonatypeStagingProfileId
40 | username = ossrhUsername
41 | password = ossrhPassword
42 | version = rootProject.ext.mavenMeta.version
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 |
9 | dependencyResolutionManagement {
10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 | }
16 |
17 | rootProject.name = 'AndroidLibProject'
18 |
19 | include ':baseLib'
20 |
21 | include ':jniLib'
22 | include ':jniLibDemo'
23 |
24 | include ':uiLib'
25 |
26 | include ':archLib'
27 | include ':archLintRules'
28 | include ':archLintRulesTestDemo'
29 |
30 | include ':testLib'
31 |
--------------------------------------------------------------------------------
/testLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply from: "${androidModuleCommon}"
4 | apply from: '../build_common.gradle'
5 |
6 | project.archivesBaseName = 'android-common-test'
7 |
8 | android {
9 | namespace 'me.ycdev.android.lib.test'
10 | defaultConfig {
11 | minSdkVersion versions.minSdk
12 | }
13 |
14 | lint {
15 | disable 'InvalidPackage'
16 | }
17 | }
18 |
19 | dependencies {
20 | compileOnly deps.test.junit
21 | compileOnly deps.test.robolectric
22 | compileOnly deps.test.espressoCore
23 | compileOnly deps.androidx.core
24 |
25 | implementation deps.kotlin.stdlib
26 | implementation deps.androidx.annotation
27 | implementation deps.timber
28 |
29 | // Dependencies for local unit tests
30 | testImplementation deps.test.junit
31 | testImplementation deps.test.truth
32 |
33 | // Android Testing Support Library's runner and rules
34 | androidTestImplementation deps.test.runner
35 | androidTestImplementation deps.test.rules
36 | androidTestImplementation deps.test.truth
37 | }
38 |
39 | project.ext {
40 | moduleName = 'me.ycdev.android.common-test'
41 | moduleDesc = 'Common test module in AndroidLib project'
42 | }
43 |
44 | if (publishEnabled) {
45 | apply from: rootProject.file('publish-module.gradle')
46 | }
47 |
--------------------------------------------------------------------------------
/testLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/testLib/src/main/java/me/ycdev/android/lib/test/base/RobolectricBase.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.test.base
2 |
3 | import org.junit.BeforeClass
4 | import org.robolectric.shadows.ShadowLog
5 |
6 | @Suppress("unused")
7 | open class RobolectricBase {
8 | companion object {
9 | @BeforeClass @JvmStatic
10 | fun setupClass() {
11 | ShadowLog.stream = System.out
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/testLib/src/main/java/me/ycdev/android/lib/test/log/AndroidLogHelper.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.test.log
2 |
3 | @Suppress("MemberVisibilityCanBePrivate")
4 | object AndroidLogHelper {
5 | // Copy the priority constants from android.util.Log
6 | const val VERBOSE = 2
7 | const val DEBUG = 3
8 | const val INFO = 4
9 | const val WARN = 5
10 | const val ERROR = 6
11 | const val ASSERT = 7
12 |
13 | fun getPriorityName(priority: Int): String {
14 | return when (priority) {
15 | VERBOSE -> "V"
16 | DEBUG -> "D"
17 | INFO -> "I"
18 | WARN -> "W"
19 | ERROR -> "E"
20 | ASSERT -> "A"
21 | else -> "U"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/testLib/src/main/java/me/ycdev/android/lib/test/log/TimberJvmTree.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.test.log
2 |
3 | import timber.log.Timber
4 | import java.util.ArrayList
5 |
6 | @Suppress("unused")
7 | class TimberJvmTree : Timber.Tree() {
8 | private var logs: ArrayList? = null
9 |
10 | fun clear() {
11 | logs?.clear()
12 | }
13 |
14 | fun hasLogs(): Boolean {
15 | return logs?.isNotEmpty() ?: false
16 | }
17 |
18 | fun keepLogs() {
19 | logs = ArrayList()
20 | }
21 |
22 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
23 | val log = AndroidLogHelper.getPriorityName(priority) + "/" + tag + ": " + message
24 | logs?.add(log)
25 | println(log)
26 | t?.printStackTrace(System.out)
27 | }
28 |
29 | companion object {
30 | fun plantIfNeeded() {
31 | // only plant TimberJvmTree once
32 | Timber.forest().forEach {
33 | if (it is TimberJvmTree) {
34 | return
35 | }
36 | }
37 | Timber.plant(TimberJvmTree())
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/testLib/src/main/java/me/ycdev/android/lib/test/rules/TimberJvmRule.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.test.rules
2 |
3 | import me.ycdev.android.lib.test.log.TimberJvmTree
4 | import org.junit.rules.ExternalResource
5 |
6 | class TimberJvmRule : ExternalResource() {
7 | override fun before() {
8 | TimberJvmTree.plantIfNeeded()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/testLib/src/main/java/me/ycdev/android/lib/test/ui/ScrollViewsAction.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.test.ui
2 |
3 | import android.view.View
4 | import android.widget.HorizontalScrollView
5 | import android.widget.ListView
6 | import android.widget.ScrollView
7 | import androidx.core.widget.NestedScrollView
8 | import androidx.test.espresso.ViewAction
9 | import androidx.test.espresso.action.ViewActions
10 | import androidx.test.espresso.matcher.ViewMatchers
11 | import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA
12 | import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
13 | import org.hamcrest.CoreMatchers.allOf
14 | import org.hamcrest.CoreMatchers.anyOf
15 | import org.hamcrest.Matcher
16 |
17 | class ScrollViewsAction(scrollTo: ViewAction = ViewActions.scrollTo()) : ViewAction by scrollTo {
18 | override fun getConstraints(): Matcher {
19 | return allOf(
20 | withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
21 | isDescendantOfA(
22 | anyOf(
23 | ViewMatchers.isAssignableFrom(NestedScrollView::class.java),
24 | ViewMatchers.isAssignableFrom(ScrollView::class.java),
25 | ViewMatchers.isAssignableFrom(HorizontalScrollView::class.java),
26 | ViewMatchers.isAssignableFrom(ListView::class.java)
27 | )
28 | )
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/uiLib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply from: "${androidModuleCommon}"
4 | apply from: '../build_common.gradle'
5 |
6 | project.archivesBaseName = 'android-common-ui'
7 |
8 | android {
9 | namespace 'me.ycdev.android.lib.commonui'
10 | defaultConfig {
11 | minSdkVersion versions.minSdk
12 | }
13 |
14 | resourcePrefix 'ycdev'
15 | buildFeatures {
16 | viewBinding = true
17 | }
18 |
19 | lint {
20 | disable 'UnusedResources'
21 | }
22 | }
23 |
24 | dependencies {
25 | api deps.ycdev.androidBase
26 |
27 | implementation deps.kotlin.stdlib
28 | implementation deps.androidx.appcompat
29 | implementation deps.androidx.material
30 | implementation deps.androidx.recyclerview
31 | implementation deps.lifecycle.runtimeKtx
32 | }
33 |
34 | project.ext {
35 | moduleName = 'me.ycdev.android.common-ui'
36 | moduleDesc = 'Common UI module in AndroidLib project'
37 | }
38 |
39 | if (publishEnabled) {
40 | apply from: rootProject.file('publish-module.gradle')
41 | }
42 |
--------------------------------------------------------------------------------
/uiLib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/uiLib/src/main/java/me/ycdev/android/lib/commonui/recyclerview/MarginItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package me.ycdev.android.lib.commonui.recyclerview
2 |
3 | import android.graphics.Rect
4 | import android.view.View
5 | import androidx.recyclerview.widget.RecyclerView
6 |
7 | class MarginItemDecoration(
8 | private val marginLeft: Int,
9 | private val marginTop: Int,
10 | private val marginRight: Int,
11 | private val marginBottom: Int
12 | ) : RecyclerView.ItemDecoration() {
13 | override fun getItemOffsets(
14 | outRect: Rect,
15 | view: View,
16 | parent: RecyclerView,
17 | state: RecyclerView.State
18 | ) {
19 | outRect.apply {
20 | left = marginLeft
21 | top = marginTop
22 | right = marginRight
23 | bottom = marginBottom
24 | }
25 | }
26 |
27 | companion object {
28 | fun create(margin: Int): MarginItemDecoration {
29 | return MarginItemDecoration(margin, margin, margin, margin)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/drawable/ycdev_grid_entries_item_bkg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/drawable/ycdev_scrollbar_thumb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/drawable/ycdev_scrollbar_thumb_normal.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/drawable/ycdev_scrollbar_track.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/drawable/ycdev_scrollbar_track_normal.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/layout/ycdev_grid_entries.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
29 |
30 |
35 |
36 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/layout/ycdev_grid_entries_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values-w820dp/ycdev_dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | 64dp
7 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values/ycdev_attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values/ycdev_colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #00aaaa
4 | #007777
5 | #008888
6 | #00aaaa
7 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values/ycdev_dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10dp
4 | 10dp
5 |
6 | 80dp
7 |
8 | 6dp
9 |
10 | 50dp
11 | 3dp
12 |
13 | 6dp
14 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values/ycdev_publics.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values/ycdev_strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Loading…
4 | Loading…%1$d%%
5 |
6 |
7 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values/ycdev_styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
20 |
21 |
25 |
26 |
32 |
33 |
39 |
40 |
46 |
47 |
54 |
--------------------------------------------------------------------------------
/uiLib/src/main/res/values/ycdev_themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------