├── .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 | 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 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AndroidLib 2 | ========== 3 | 4 | Common utils for Android apps development 5 | 6 | ![CI](https://github.com/yongce/AndroidLib/workflows/CI/badge.svg) 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 |