Skip to content

Commit 8c357fa

Browse files
authored
Merge pull request #5 from okmsbun/develop
Add getRequestedPermissions method to FlutterDeviceAppsAndroid for pe…
2 parents 3fd4832 + cef325b commit 8c357fa

11 files changed

Lines changed: 665 additions & 36 deletions

File tree

.github/workflows/android-test.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Android Tests
2+
3+
on:
4+
pull_request:
5+
branches: ["develop"]
6+
7+
jobs:
8+
android-test:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Setup Java
15+
uses: actions/setup-java@v4
16+
with:
17+
distribution: "temurin"
18+
java-version: "17"
19+
20+
- name: Setup Flutter
21+
uses: subosito/flutter-action@v2
22+
with:
23+
channel: stable
24+
cache: true
25+
26+
- name: Create temporary Flutter project
27+
run: |
28+
flutter create --template=app /tmp/test_app
29+
cd /tmp/test_app
30+
31+
# Add the plugin with path dependency
32+
flutter pub add flutter_device_apps_android --path $GITHUB_WORKSPACE
33+
34+
# Build to generate Gradle files
35+
flutter build apk --debug
36+
37+
- name: Run Android unit tests
38+
run: |
39+
cd /tmp/test_app/android
40+
./gradlew :flutter_device_apps_android:testDebugUnitTest
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: only-develop-to-main
2+
3+
on:
4+
pull_request:
5+
branches: ["main"]
6+
7+
jobs:
8+
only-develop-to-main:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Allow only develop -> main PRs
12+
run: |
13+
echo "head_ref: ${{ github.head_ref }}"
14+
echo "base_ref: ${{ github.base_ref }}"
15+
if [ "${{ github.base_ref }}" = "main" ] && [ "${{ github.head_ref }}" != "develop" ]; then
16+
echo "Only PRs from 'develop' to 'main' are allowed."
17+
exit 1
18+
fi

.github/workflows/quality.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Quality
2+
3+
on:
4+
pull_request:
5+
branches: ["develop"]
6+
7+
jobs:
8+
quality:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Setup Flutter
15+
uses: subosito/flutter-action@v2
16+
with:
17+
channel: stable
18+
cache: true
19+
20+
- name: Install dependencies
21+
run: flutter pub get
22+
23+
- name: Check formatting
24+
run: dart format --set-exit-if-changed .
25+
26+
- name: Static analysis
27+
run: flutter analyze
28+
29+
- name: pub.dev dry-run
30+
run: flutter pub publish --dry-run

.github/workflows/test.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Test
2+
3+
on:
4+
pull_request:
5+
branches: ["develop"]
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Setup Flutter
15+
uses: subosito/flutter-action@v2
16+
with:
17+
channel: stable
18+
cache: true
19+
20+
- name: Install dependencies
21+
run: flutter pub get
22+
23+
- name: Run tests
24+
run: flutter test

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 0.6.0
2+
- **BREAKING**: Updated to match platform interface 0.6.0 - `requestedPermissions` removed from `AppInfo`
3+
- Added `getRequestedPermissions(String packageName)` method implementation for on-demand permission retrieval
4+
- Added GitHub Actions workflows for Android unit tests, quality checks, and PR enforcement
5+
- Added comprehensive Dart unit tests (27 tests) for method channel mocking
6+
- Added Kotlin unit tests (11 tests) with Robolectric for Android plugin
7+
- Made `FlutterDeviceAppsAndroidPlugin` class `open` with `protected` fields for testability
8+
19
## 0.5.1
210
- Added support for additional `AppInfo` fields from the Android package manager: `category`, `targetSdkVersion`, `minSdkVersion`, `enabled`, `processName`, `installLocation`, `requestedPermissions`.
311
- Populates `requestedPermissions` via `PackageManager.GET_PERMISSIONS`.

android/build.gradle

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,21 @@ android {
4848
}
4949

5050
dependencies {
51-
testImplementation("org.jetbrains.kotlin:kotlin-test")
52-
testImplementation("org.mockito:mockito-core:5.0.0")
51+
testImplementation("junit:junit:4.13.2")
52+
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
53+
testImplementation("org.mockito:mockito-inline:5.2.0")
54+
testImplementation("org.robolectric:robolectric:4.11.1")
5355
}
5456

5557
testOptions {
56-
unitTests.all {
57-
useJUnitPlatform()
58-
59-
testLogging {
60-
events "passed", "skipped", "failed", "standardOut", "standardError"
61-
outputs.upToDateWhen {false}
62-
showStandardStreams = true
58+
unitTests {
59+
includeAndroidResources = true
60+
all {
61+
testLogging {
62+
events "passed", "skipped", "failed", "standardOut", "standardError"
63+
outputs.upToDateWhen {false}
64+
showStandardStreams = true
65+
}
6366
}
6467
}
6568
}

android/src/main/kotlin/com/okmsbun/flutter_device_apps_android/FlutterDeviceAppsAndroidPlugin.kt

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import io.flutter.plugin.common.MethodChannel
1919
import kotlinx.coroutines.*
2020
import java.io.ByteArrayOutputStream
2121

22-
class FlutterDeviceAppsAndroidPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
22+
open class FlutterDeviceAppsAndroidPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
2323

2424
private lateinit var methodChannel: MethodChannel
2525
private lateinit var eventChannel: EventChannel
26-
private lateinit var appContext: Context
27-
private lateinit var pm: PackageManager
26+
protected lateinit var appContext: Context
27+
protected lateinit var pm: PackageManager
2828

2929
private val mainHandler = Handler(Looper.getMainLooper())
3030
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
@@ -99,6 +99,35 @@ class FlutterDeviceAppsAndroidPlugin : FlutterPlugin, MethodChannel.MethodCallHa
9999
}
100100
}
101101
}
102+
"getRequestedPermissions" -> {
103+
val pkg = call.argument<String>("packageName")
104+
if (pkg == null) {
105+
result.error("ARG", "packageName required", null)
106+
return
107+
}
108+
scope.launch {
109+
try {
110+
val pInfo: PackageInfo = try {
111+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
112+
pm.getPackageInfo(
113+
pkg,
114+
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong())
115+
)
116+
} else {
117+
@Suppress("DEPRECATION")
118+
pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS)
119+
}
120+
} catch (_: Exception) {
121+
mainHandler.post { result.success(null) }
122+
return@launch
123+
}
124+
val requested = pInfo.requestedPermissions?.toList()
125+
mainHandler.post { result.success(requested) }
126+
} catch (e: Exception) {
127+
mainHandler.post { result.error("ERR_PERMS", e.message, null) }
128+
}
129+
}
130+
}
102131
"openApp" -> {
103132
val pkg = call.argument<String>("packageName")
104133
if (pkg == null) return result.error("ARG", "packageName required", null)
@@ -248,10 +277,13 @@ class FlutterDeviceAppsAndroidPlugin : FlutterPlugin, MethodChannel.MethodCallHa
248277
private fun getAppMap(packageName: String, includeIcon: Boolean): Map<String, Any?>? {
249278
val pInfo: PackageInfo = try {
250279
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
251-
pm.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
280+
pm.getPackageInfo(
281+
packageName,
282+
PackageManager.PackageInfoFlags.of(0)
283+
)
252284
} else {
253285
@Suppress("DEPRECATION")
254-
pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
286+
pm.getPackageInfo(packageName, 0)
255287
}
256288
} catch (_: Exception) {
257289
return null
@@ -260,7 +292,6 @@ class FlutterDeviceAppsAndroidPlugin : FlutterPlugin, MethodChannel.MethodCallHa
260292
val aInfo: ApplicationInfo = pInfo.applicationInfo ?: return null
261293

262294
val category: Int? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) aInfo.category else null
263-
val requestedPermissionsList: List<String>? = pInfo.requestedPermissions?.toList()
264295

265296
val isSystem = (aInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0
266297
val label = try {
@@ -300,8 +331,7 @@ class FlutterDeviceAppsAndroidPlugin : FlutterPlugin, MethodChannel.MethodCallHa
300331
"minSdkVersion" to aInfo.minSdkVersion,
301332
"enabled" to aInfo.enabled,
302333
"processName" to aInfo.processName,
303-
"installLocation" to pInfo.installLocation,
304-
"requestedPermissions" to requestedPermissionsList
334+
"installLocation" to pInfo.installLocation
305335
)
306336
}
307337

0 commit comments

Comments
 (0)