From af9ceae83f32ff3a2d3e971164eb7a5f505169e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Enrique=20Villar=20Misa?= Date: Thu, 20 Nov 2025 13:13:51 +0100 Subject: [PATCH 1/5] build: new version 1.3.2 fix: build errors with flutter 3.35.0 or superior --- .idea/caches/deviceStreaming.xml | 1029 +++++++++++++++++ .idea/deviceManager.xml | 13 + .idea/libraries/Dart_SDK.xml | 43 +- .idea/markdown.xml | 8 + .idea/misc.xml | 1 - .idea/workspace.xml | 79 +- CHANGELOG.md | 4 + android/build.gradle | 12 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/app/build.gradle | 19 +- .../android/app/src/main/AndroidManifest.xml | 2 +- example/android/build.gradle | 13 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/settings.gradle | 30 +- example/pubspec.lock | 76 +- example/pubspec.yaml | 1 + ios/usage_stats.podspec | 16 +- pubspec.yaml | 4 +- test/usage_stats_test.dart | 270 ++++- 19 files changed, 1504 insertions(+), 120 deletions(-) create mode 100644 .idea/caches/deviceStreaming.xml create mode 100644 .idea/deviceManager.xml create mode 100644 .idea/markdown.xml diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..ac9e2a4 --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,1029 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml index 4f3719c..9d5a4d4 100644 --- a/.idea/libraries/Dart_SDK.xml +++ b/.idea/libraries/Dart_SDK.xml @@ -1,26 +1,29 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..c61ea33 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index c333ca1..d002923 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d6a53cb..d383164 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,18 +5,33 @@ - + + + + - + + + + + - + + + + + + + + - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4796c75..33b0052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 1.3.1 +* Fix build errors with flutter 3.35.0 or superior + +## 1.3.1 + * Fix build errors with flutter 3.29.0 ## 1.3.0 diff --git a/android/build.gradle b/android/build.gradle index 2ea5797..1a83a9f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,13 +2,13 @@ group 'io.github.parassharmaa.usage_stats' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.9.0' + ext.kotlin_version = '2.2.0' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.2' + classpath 'com.android.tools.build:gradle:8.13.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -16,7 +16,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } @@ -25,7 +25,7 @@ apply plugin: 'kotlin-android' android { namespace "io.github.parassharmaa.usage_stats" - compileSdkVersion 30 + compileSdkVersion 36 compileOptions { sourceCompatibility = JavaVersion.VERSION_17 @@ -50,5 +50,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2" } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7666e22..02767eb 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 4f4137c..3fe10b8 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "org.jetbrains.kotlin.android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,10 +22,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { namespace "io.github.parassharmaa.example" compileSdkVersion flutter.compileSdkVersion @@ -48,7 +45,7 @@ android { applicationId "io.github.parassharmaa.example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion 22 + minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -68,5 +65,5 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.0" } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index bc68fb0..da23dd9 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" /> diff --git a/example/android/build.gradle b/example/android/build.gradle index 2d6d9ab..bc157bd 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.9.0' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.2.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 91e6113..83e933e 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..b340351 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.13.1" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false +} + +include ":app" diff --git a/example/pubspec.lock b/example/pubspec.lock index 2c2ee59..2efa250 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,26 +21,26 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,15 +53,23 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -71,34 +79,42 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "6.0.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -111,18 +127,18 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter @@ -140,18 +156,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -172,25 +188,25 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.6" usage_stats: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.2.1" + version: "1.3.2" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -200,5 +216,5 @@ packages: source: hosted version: "14.3.0" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.8.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 8d5bbd7..d4b23f5 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^6.0.0 flutter: uses-material-design: true diff --git a/ios/usage_stats.podspec b/ios/usage_stats.podspec index cacb315..b820efa 100644 --- a/ios/usage_stats.podspec +++ b/ios/usage_stats.podspec @@ -2,22 +2,22 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint usage_stats.podspec' to validate before publishing. # +# This plugin is Android-only and does not support iOS. Pod::Spec.new do |s| s.name = 'usage_stats' s.version = '0.0.1' - s.summary = 'A new Flutter plugin.' + s.summary = 'Android-only plugin for usage statistics.' s.description = <<-DESC -A new Flutter plugin. +This plugin is Android-only and does not support iOS. DESC - s.homepage = 'http://example.com' + s.homepage = 'https://github.com/Parassharmaa/usage_stats' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'Paras Sharma' => 'parassharmaa@gmail.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.platform = :ios, '8.0' + s.platform = :ios, '13.0' - # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } - s.swift_version = '5.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.9' end diff --git a/pubspec.yaml b/pubspec.yaml index 0cf34ea..4ace2fe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: usage_stats description: Query Android Usage Statistics (Configurations, Events, Usage) -version: 1.3.1 +version: 1.3.2 homepage: https://github.com/Parassharmaa/usage_stats environment: @@ -21,3 +21,5 @@ flutter: android: package: io.github.parassharmaa.usage_stats pluginClass: UsageStatsPlugin + ios: + pluginClass: none diff --git a/test/usage_stats_test.dart b/test/usage_stats_test.dart index 60e2cd6..f25969b 100644 --- a/test/usage_stats_test.dart +++ b/test/usage_stats_test.dart @@ -3,17 +3,277 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:usage_stats/usage_stats.dart'; void main() { - const MethodChannel channel = MethodChannel('usage_stats'); - TestWidgetsFlutterBinding.ensureInitialized(); + const MethodChannel channel = MethodChannel('usage_stats'); + final List log = []; + setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; + log.clear(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + log.add(methodCall); + + switch (methodCall.method) { + case 'isUsagePermission': + return true; + case 'grantUsagePermission': + return null; + case 'queryEvents': + return [ + { + 'eventType': '1', + 'timeStamp': '1234567890', + 'packageName': 'com.example.app', + 'className': 'MainActivity', + } + ]; + case 'queryConfiguration': + return [ + { + 'activationCount': '5', + 'totalTimeActive': '3600000', + 'configuration': 'default', + 'lastTimeActive': '1234567890', + 'firstTimeStamp': '1234567000', + 'lastTimeStamp': '1234567890', + } + ]; + case 'queryEventStats': + return [ + { + 'firstTimeStamp': '1234567000', + 'lastTimeStamp': '1234567890', + 'totalTime': '3600000', + 'lastEventTime': '1234567890', + 'eventType': '1', + 'count': '10', + } + ]; + case 'queryUsageStats': + return [ + { + 'firstTimeStamp': '1234567000', + 'lastTimeStamp': '1234567890', + 'lastTimeUsed': '1234567890', + 'totalTimeInForeground': '3600000', + 'packageName': 'com.example.app', + } + ]; + case 'queryAndAggregateUsageStats': + return { + 'com.example.app': { + 'firstTimeStamp': '1234567000', + 'lastTimeStamp': '1234567890', + 'lastTimeUsed': '1234567890', + 'totalTimeInForeground': '3600000', + 'packageName': 'com.example.app', + } + }; + case 'queryNetworkUsageStats': + return [ + { + 'packageName': 'com.example.app', + 'rxTotalBytes': '1024000', + 'txTotalBytes': '512000', + } + ]; + case 'queryNetworkUsageStatsByPackage': + return { + 'packageName': 'com.example.app', + 'rxTotalBytes': '1024000', + 'txTotalBytes': '512000', + }; + default: + return null; + } }); }); tearDown(() { - channel.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + group('UsageStats Permission Methods', () { + test('checkUsagePermission returns true', () async { + final result = await UsageStats.checkUsagePermission(); + + expect(result, true); + expect(log, [ + isMethodCall('isUsagePermission', arguments: null), + ]); + }); + + test('grantUsagePermission is called', () async { + await UsageStats.grantUsagePermission(); + + expect(log, [ + isMethodCall('grantUsagePermission', arguments: null), + ]); + }); + }); + + group('UsageStats Query Methods', () { + final startDate = DateTime(2024, 1, 1); + final endDate = DateTime(2024, 1, 31); + + test('queryEvents returns list of EventUsageInfo', () async { + final result = await UsageStats.queryEvents(startDate, endDate); + + expect(result, isA>()); + expect(result.length, 1); + expect(result[0].eventType, '1'); + expect(result[0].packageName, 'com.example.app'); + expect(result[0].className, 'MainActivity'); + + expect(log[0].method, 'queryEvents'); + expect(log[0].arguments['start'], startDate.millisecondsSinceEpoch); + expect(log[0].arguments['end'], endDate.millisecondsSinceEpoch); + }); + + test('queryConfiguration returns list of ConfigurationInfo', () async { + final result = await UsageStats.queryConfiguration(startDate, endDate); + + expect(result, isA>()); + expect(result.length, 1); + expect(result[0].activationCount, '5'); + expect(result[0].totalTimeActive, '3600000'); + expect(result[0].configuration, 'default'); + + expect(log[0].method, 'queryConfiguration'); + expect(log[0].arguments['start'], startDate.millisecondsSinceEpoch); + expect(log[0].arguments['end'], endDate.millisecondsSinceEpoch); + }); + + test('queryEventStats returns list of EventInfo', () async { + final result = await UsageStats.queryEventStats(startDate, endDate); + + expect(result, isA>()); + expect(result.length, 1); + expect(result[0].eventType, '1'); + expect(result[0].count, '10'); + expect(result[0].totalTime, '3600000'); + + expect(log[0].method, 'queryEventStats'); + expect(log[0].arguments['start'], startDate.millisecondsSinceEpoch); + expect(log[0].arguments['end'], endDate.millisecondsSinceEpoch); + }); + + test('queryUsageStats returns list of UsageInfo', () async { + final result = await UsageStats.queryUsageStats(startDate, endDate); + + expect(result, isA>()); + expect(result.length, 1); + expect(result[0].packageName, 'com.example.app'); + expect(result[0].totalTimeInForeground, '3600000'); + + expect(log[0].method, 'queryUsageStats'); + expect(log[0].arguments['start'], startDate.millisecondsSinceEpoch); + expect(log[0].arguments['end'], endDate.millisecondsSinceEpoch); + }); + + test('queryAndAggregateUsageStats returns map of UsageInfo', () async { + final result = await UsageStats.queryAndAggregateUsageStats(startDate, endDate); + + expect(result, isA>()); + expect(result.containsKey('com.example.app'), true); + expect(result['com.example.app']?.packageName, 'com.example.app'); + expect(result['com.example.app']?.totalTimeInForeground, '3600000'); + + expect(log[0].method, 'queryAndAggregateUsageStats'); + expect(log[0].arguments['start'], startDate.millisecondsSinceEpoch); + expect(log[0].arguments['end'], endDate.millisecondsSinceEpoch); + }); + }); + + group('Network Usage Stats Methods', () { + final startDate = DateTime(2024, 1, 1); + final endDate = DateTime(2024, 1, 31); + + test('queryNetworkUsageStats returns list of NetworkInfo', () async { + final result = await UsageStats.queryNetworkUsageStats( + startDate, + endDate, + ); + + expect(result, isA>()); + expect(result.length, 1); + expect(result[0].packageName, 'com.example.app'); + expect(result[0].rxTotalBytes, '1024000'); + expect(result[0].txTotalBytes, '512000'); + + expect(log[0].method, 'queryNetworkUsageStats'); + expect(log[0].arguments['start'], startDate.millisecondsSinceEpoch); + expect(log[0].arguments['end'], endDate.millisecondsSinceEpoch); + expect(log[0].arguments['type'], NetworkType.all.value); + }); + + test('queryNetworkUsageStats with WiFi networkType', () async { + final result = await UsageStats.queryNetworkUsageStats( + startDate, + endDate, + networkType: NetworkType.wifi, + ); + + expect(result, isA>()); + expect(log[0].arguments['type'], NetworkType.wifi.value); + }); + + test('queryNetworkUsageStats with Mobile networkType', () async { + final result = await UsageStats.queryNetworkUsageStats( + startDate, + endDate, + networkType: NetworkType.mobile, + ); + + expect(result, isA>()); + expect(log[0].arguments['type'], NetworkType.mobile.value); + }); + + test('queryNetworkUsageStatsByPackage returns NetworkInfo', () async { + final result = await UsageStats.queryNetworkUsageStatsByPackage( + startDate, + endDate, + packageName: 'com.example.app', + ); + + expect(result, isA()); + expect(result.packageName, 'com.example.app'); + expect(result.rxTotalBytes, '1024000'); + expect(result.txTotalBytes, '512000'); + + expect(log[0].method, 'queryNetworkUsageStatsByPackage'); + expect(log[0].arguments['start'], startDate.millisecondsSinceEpoch); + expect(log[0].arguments['end'], endDate.millisecondsSinceEpoch); + expect(log[0].arguments['type'], NetworkType.all.value); + expect(log[0].arguments['packageName'], 'com.example.app'); + }); + + test('queryNetworkUsageStatsByPackage with WiFi networkType', () async { + final result = await UsageStats.queryNetworkUsageStatsByPackage( + startDate, + endDate, + packageName: 'com.example.app', + networkType: NetworkType.wifi, + ); + + expect(result, isA()); + expect(log[0].arguments['type'], NetworkType.wifi.value); + }); + }); + + group('NetworkType Extension', () { + test('NetworkType.all has correct value', () { + expect(NetworkType.all.value, 1); + }); + + test('NetworkType.wifi has correct value', () { + expect(NetworkType.wifi.value, 2); + }); + + test('NetworkType.mobile has correct value', () { + expect(NetworkType.mobile.value, 3); + }); }); } From 5996be5e5506bb7c95086bdf29d467387714fac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Enrique=20Villar=20Misa?= Date: Thu, 20 Nov 2025 13:13:57 +0100 Subject: [PATCH 2/5] build: new version 1.3.2 fix: build errors with flutter 3.35.0 or superior --- .../Flutter/ephemeral/flutter_lldb_helper.py | 32 +++++++++++++++++++ .../ios/Flutter/ephemeral/flutter_lldbinit | 5 +++ 2 files changed, 37 insertions(+) create mode 100644 example/ios/Flutter/ephemeral/flutter_lldb_helper.py create mode 100644 example/ios/Flutter/ephemeral/flutter_lldbinit diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py From 77803d5c3ac7145823197b65e153699dfad129ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Enrique=20Villar=20Misa?= Date: Thu, 20 Nov 2025 14:06:15 +0100 Subject: [PATCH 3/5] chore: explicitly declare Android-only platform support --- .idea/workspace.xml | 104 +++++++++++++++++++++++--------------------- pubspec.yaml | 5 ++- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d383164..03dc730 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,25 +4,8 @@ - - - - - + - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 4ace2fe..fc65e6e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,6 +7,9 @@ environment: sdk: ">=3.0.0 <4.0.0" flutter: ">=3.0.0" +platforms: + android: # only Android + dependencies: flutter: sdk: flutter @@ -21,5 +24,3 @@ flutter: android: package: io.github.parassharmaa.usage_stats pluginClass: UsageStatsPlugin - ios: - pluginClass: none From 3b99f8a98723668012600010f24862f6eaaa9261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Enrique=20Villar=20Misa?= Date: Thu, 20 Nov 2025 14:17:26 +0100 Subject: [PATCH 4/5] chore: remove Flutter debug helper files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove ephemeral Flutter LLDB helper files that should not be tracked in version control. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Flutter/ephemeral/flutter_lldb_helper.py | 32 ------------------- .../ios/Flutter/ephemeral/flutter_lldbinit | 5 --- 2 files changed, 37 deletions(-) delete mode 100644 example/ios/Flutter/ephemeral/flutter_lldb_helper.py delete mode 100644 example/ios/Flutter/ephemeral/flutter_lldbinit diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py deleted file mode 100644 index a88caf9..0000000 --- a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Generated file, do not edit. -# - -import lldb - -def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): - """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" - base = frame.register["x0"].GetValueAsAddress() - page_len = frame.register["x1"].GetValueAsUnsigned() - - # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the - # first page to see if handled it correctly. This makes diagnosing - # misconfiguration (e.g. missing breakpoint) easier. - data = bytearray(page_len) - data[0:8] = b'IHELPED!' - - error = lldb.SBError() - frame.GetThread().GetProcess().WriteMemory(base, data, error) - if not error.Success(): - print(f'Failed to write into {base}[+{page_len}]', error) - return - -def __lldb_init_module(debugger: lldb.SBDebugger, _): - target = debugger.GetDummyTarget() - # Caveat: must use BreakpointCreateByRegEx here and not - # BreakpointCreateByName. For some reasons callback function does not - # get carried over from dummy target for the later. - bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") - bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) - bp.SetAutoContinue(True) - print("-- LLDB integration loaded --") diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit deleted file mode 100644 index e3ba6fb..0000000 --- a/example/ios/Flutter/ephemeral/flutter_lldbinit +++ /dev/null @@ -1,5 +0,0 @@ -# -# Generated file, do not edit. -# - -command script import --relative-to-command-file flutter_lldb_helper.py From 96b6d6d9d8a76780af954004104e2791cad87b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Enrique=20Villar=20Misa?= Date: Thu, 20 Nov 2025 14:19:55 +0100 Subject: [PATCH 5/5] build: git add .gitignore && git commit -m "chore: add Flutter ephemeral files to .gitignore fix: Prevent Flutter debug helper files from being tracked in version control. --- .gitignore | 5 ++++- .idea/workspace.xml | 17 +++++++++++++---- CHANGELOG.md | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index cf41c11..53455c9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ build/ -*.lock \ No newline at end of file +*.lock + +# Flutter ephemeral files +**/Flutter/ephemeral/ \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 03dc730..a3e36f9 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,8 +4,8 @@