Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8f3b580
Update README.md
KvRae Oct 8, 2025
4843be0
update protection dialog.
KhaledHawwas Oct 8, 2025
f2a54f0
fix app not blocking.
KhaledHawwas Oct 17, 2025
713a4ac
Merge branch 'mafazaa-org:dev' into dev
KvRae Oct 20, 2025
8319ebc
get vpn permission if it's revoked
KhaledHawwas Oct 23, 2025
f667dac
add script to block "background apps dialog".
KhaledHawwas Oct 23, 2025
e5c5943
fix crash of starting screenshot overlay.
KhaledHawwas Oct 23, 2025
1147cad
fix app update screen blocked.
KhaledHawwas Oct 23, 2025
d2eea40
fix our app is in the blocking list.
KhaledHawwas Oct 23, 2025
920f593
add manufacturer to log ui screen.
KhaledHawwas Oct 25, 2025
9e393c6
Merge branch 'dev' into hot-fix
KhaledHawwas Oct 25, 2025
165e87e
fix our app is in the blocking list.
KhaledHawwas Oct 25, 2025
c7ebf1e
add more details to log ui tree.
KhaledHawwas Oct 25, 2025
63964d9
fix dialog small button.
KhaledHawwas Oct 25, 2025
8780db5
Merge branch 'mafazaa-org:dev' into dev
KvRae Oct 25, 2025
4c1b63d
fix screen shot isn't delaying.
KhaledHawwas Oct 26, 2025
729eb91
convert ui tree logging to json.
KhaledHawwas Oct 27, 2025
20af8a9
feat: Add GitHub Action for Firebase App Distribution
KvRae Oct 28, 2025
f09142f
feat: Make gradlew executable in firebase-dist workflow
KvRae Oct 28, 2025
d0dfb63
feat: Decode google-services.json from base64
KvRae Oct 28, 2025
c318897
ci: exclude lintVitalRelease from release build
KvRae Oct 28, 2025
a5e12d5
feat: Use wildcard to find release APK
KvRae Oct 28, 2025
c223586
feat: Use specific APK path for Firebase Distribution
KvRae Oct 28, 2025
e081af9
feat: Improve APK path reference in Firebase distribution workflow
KvRae Oct 28, 2025
393fbd3
feat: Improve APK path reference in Firebase distribution workflow
KvRae Oct 28, 2025
149c38f
Enhancement/UI enhancement (#31)
KvRae Oct 25, 2025
6c02725
add more details to log ui tree.
KhaledHawwas Oct 25, 2025
00918a7
fix dialog small button.
KhaledHawwas Oct 25, 2025
04ec356
fix screen shot isn't delaying.
KhaledHawwas Oct 26, 2025
ccfa22e
convert ui tree logging to json.
KhaledHawwas Oct 27, 2025
c3a46e9
fix error with arabic letters in the log file.
KhaledHawwas Oct 28, 2025
3eb4858
Fix(CI): Use `testers` instead of `groups` in Firebase distribution w…
KvRae Oct 28, 2025
582ccdf
feat: Use tester groups in Firebase Distribution workflow
KvRae Oct 28, 2025
87a7c60
add more scripts.
KhaledHawwas Oct 28, 2025
a65c8db
feat: Build and upload Debug APK to Firebase
KvRae Nov 1, 2025
f9e698f
feat: Update firebase-dist.yml trigger
KvRae Nov 1, 2025
4724a04
Update README.md
KvRae Nov 1, 2025
c4e7303
feat: Run firebase-dist workflow on gh-action branch
KvRae Nov 1, 2025
1fb6e12
feat: Build and upload release APK to Firebase Distribution
KvRae Nov 1, 2025
badcb11
ci: Update firebase-dist workflow branches
KvRae Nov 1, 2025
5805d60
ci: Update firebase-dist workflow branches
KvRae Nov 1, 2025
e4fae2b
ci: Build and distribute APK instead of AAB with code version 11
KvRae Nov 1, 2025
13aab1b
chore: Update Firebase distribution workflow to use debug build with …
KvRae Nov 1, 2025
d177f73
Merge pull request #36 from KvRae/feature/gh-action
KvRae Nov 3, 2025
96a82dc
Update branch triggers in Firebase workflow
KvRae Nov 9, 2025
d37ee8c
Adding Admin permission / Report for validation features (#38)
KvRae Nov 9, 2025
c1bf5b9
Update branch triggers in firebase-dist.yml
KvRae Nov 9, 2025
906339c
Update Firebase App Distribution badge link
KvRae Nov 9, 2025
2597a57
Replace Firebase App Distribution badge with Test Build
KvRae Nov 9, 2025
68f5345
Fixing permission handling + ui flow (#40)
KvRae Nov 10, 2025
cf2fb80
add more scripts.
KhaledHawwas Nov 14, 2025
1008a04
Merge branch 'dev' of https://github.com/mafazaa-org/blocker-android …
KhaledHawwas Nov 14, 2025
f33aec3
Merge branch 'my-new-branch' into dev
KhaledHawwas Nov 14, 2025
c4a333b
fixing merge issues.
KhaledHawwas Nov 14, 2025
6535026
fixing merge issues.
KhaledHawwas Nov 15, 2025
e6eb137
cleanup
KhaledHawwas Nov 15, 2025
fb5fce1
Update Firebase App Distribution badge link
KvRae Nov 17, 2025
7532c36
v0.0.19
KhaledHawwas Dec 7, 2025
130f3eb
Merge branch 'test' of https://github.com/mafazaa-org/Ainaa-android i…
KhaledHawwas Dec 15, 2025
fb8b128
Merge branch 'dev' into test
KvRae Dec 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/firebase-dist.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Build & Upload Debug to Firebase App Distribution

on:
push:
branches:
- test
pull_request:
branches:
- dev
- test
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
# 1️⃣ Checkout code
- name: Checkout repository
uses: actions/checkout@v4

# 2️⃣ Set up JDK 17
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

# 3️⃣ Enable Gradle caching
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
cache-read-only: false

# 4️⃣ Inject google-services.json
- name: Create google-services.json
run: |
echo "${{ secrets.GOOGLE_SERVICES_JSON }}" | base64 --decode > app/google-services.json
echo "✅ google-services.json added successfully"

# 5️⃣ Make gradlew executable
- name: Make gradlew executable
run: chmod +x ./gradlew

# 6️⃣ Build Debug APK
- name: Build Debug APK
run: ./gradlew assembleDebug

# 7️⃣ Find the Debug APK
- name: Find Debug APK
id: find_apk
run: |
apk_path=$(find app/build/outputs/apk/debug -name "*.apk" | head -n 1)
echo "apk_path=$apk_path" >> $GITHUB_OUTPUT
echo "✅ Found Debug APK at: $apk_path"

# 8️⃣ Upload Debug APK to Firebase App Distribution
- name: Upload Debug APK to Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
groups: qa-testers
file: ${{ steps.find_apk.outputs.apk_path }}
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

[![Build & Upload Debug to Firebase App Distribution](https://github.com/mafazaa-org/Ainaa-android/actions/workflows/firebase-dist.yml/badge.svg?branch=feature%2Fgh-action)](https://github.com/mafazaa-org/Ainaa-android/actions/workflows/firebase-dist.yml)


</div>

Ainaa is an Android application built with Kotlin, following MVVM architecture and using dependency injection. It provides advanced features such as VPN, accessibility services, overlays, and integrates with Firebase via Google Services.
Expand Down
7 changes: 4 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ android {
applicationId = "com.mafazaa.ainaa"
minSdk = 26
targetSdk = 36
versionCode = 10
versionName = "v0.0.4"
versionCode = 19
versionName = "v0.0.19"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -46,7 +46,8 @@ android {
}

dependencies {
// Compose
testImplementation(libs.junit.junit)
// Compose
val composeBom = platform("androidx.compose:compose-bom:2024.09.02")
implementation(composeBom)
androidTestImplementation(composeBom)
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />

<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<!--For HTC devices-->
<action android:name="com.htc.intent.action.QUICKBOOT_POWEROFF" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
Expand Down
96 changes: 39 additions & 57 deletions app/src/main/java/com/mafazaa/ainaa/AppActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
import com.mafazaa.ainaa.DialogState.BlockApps
import com.mafazaa.ainaa.data.local.SharedPrefs
import com.mafazaa.ainaa.data.models.NetworkResult
import com.mafazaa.ainaa.domain.models.AppInfo
Expand All @@ -61,6 +62,7 @@ import com.mafazaa.ainaa.ui.dialog.BlockAppDialog
import com.mafazaa.ainaa.ui.dialog.ConfirmBlockedDialog
import com.mafazaa.ainaa.ui.dialog.EnableProtectionDialog
import com.mafazaa.ainaa.ui.dialog.HowItWorksDialog
import com.mafazaa.ainaa.ui.dialog.ManageKeywordsDialog
import com.mafazaa.ainaa.ui.dialog.PermissionDialog
import com.mafazaa.ainaa.ui.dialog.ReportProblemDialog
import com.mafazaa.ainaa.ui.protection.EnableProtectionScreen
Expand All @@ -74,7 +76,6 @@ import com.mafazaa.ainaa.utils.Constants.SUPPORT_URL
import com.mafazaa.ainaa.utils.MyLog
import com.mafazaa.ainaa.utils.getAllApps
import com.mafazaa.ainaa.utils.hasAccessibilityPermission
import com.mafazaa.ainaa.utils.hasAdminPermission
import com.mafazaa.ainaa.utils.hasNotificationPermission
import com.mafazaa.ainaa.utils.hasOverlayPermission
import com.mafazaa.ainaa.utils.hasUsageStatsPermission
Expand Down Expand Up @@ -103,6 +104,7 @@ sealed interface DialogState {
// Keeps Block Apps dialog open, and optionally shows a nested confirm dialog for a selected app
data class BlockApps(val confirmApp: AppInfo? = null) : DialogState
data object HowItWorks : DialogState
data object BlockWords : DialogState
data class EnableProtectionConfirm(val level: DnsProtectionLevel) :
DialogState
}
Expand All @@ -121,6 +123,7 @@ class AppActivity : ComponentActivity() {
private var overlayPermission by mutableStateOf(false)
private var usageStatsPermission by mutableStateOf(false)
private var accessibilityPermission by mutableStateOf(false)
private var permissionState by mutableStateOf<PermissionState?>(null)
private var notificationPermission by mutableStateOf(
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
)
Expand All @@ -136,13 +139,6 @@ class AppActivity : ComponentActivity() {
{
// Handle result if needed
}
private val permissionChain = listOf(
PermissionState.Accessibility,
PermissionState.Overlay,
PermissionState.Administrative,
PermissionState.Vpn,
PermissionState.Notification,
)

override fun onCreate(savedInstanceState: Bundle?) {
setLayoutDirection(window.decorView, ViewCompat.LAYOUT_DIRECTION_RTL)
Expand All @@ -157,6 +153,7 @@ class AppActivity : ComponentActivity() {
val viewModel: AppViewModel = getViewModel()
val sharedPrefs: SharedPrefs by inject(SharedPrefs::class.java)
viewModel.loadInstalledApps(getAllApps())
viewModel.loadBlockedWords()
MyLog.i(TAG, "Opening app")
//viewModel.handleUpdateStatus(this)
refreshPermissionState()
Expand Down Expand Up @@ -184,6 +181,7 @@ class AppActivity : ComponentActivity() {
) {
val snackbarHostState = remember { SnackbarHostState() }
val apps = viewModel.apps.collectAsState().value
val blockedWords = viewModel.blockedWords.collectAsState().value

// Centralized dialogs rendering
when (val d = dialogState) {
Expand Down Expand Up @@ -244,16 +242,16 @@ class AppActivity : ComponentActivity() {
onDismiss = { dialogState = null },
appStates = apps,
onBlockClick = { app ->
dialogState = DialogState.BlockApps(confirmApp = app)
dialogState = BlockApps(confirmApp = app)
}
)
if (d.confirmApp != null) {
ConfirmBlockedDialog(
app = d.confirmApp,
onDismiss = { dialogState = DialogState.BlockApps() },
onDismiss = { dialogState = BlockApps() },
onConfirm = {
viewModel.toggleAppSelection(it.packageName)
dialogState = DialogState.BlockApps()
dialogState = BlockApps()
}
)
}
Expand All @@ -273,27 +271,35 @@ class AppActivity : ComponentActivity() {
onConfirm = {
dialogState = null // Close the confirmation dialog
viewModel.saveLevel(d.level)

val nextPermission = findNextMissingPermission()
if (nextPermission == null) {
refreshPermissionState()
if (permissionState == null) {
// All permissions are already granted! Activate protection.
Toast.makeText(
context,
context.getString(R.string.protection_activated_text),
Toast.LENGTH_LONG
).show()
startAccessibilityService(MyAccessibilityService.ACTION_START_FOREGROUND)
startVpnService( MyAccessibilityService.ACTION_START_FOREGROUND)
startVpnService(MyVpnService.ACTION_START)
backStack.add(Screen.ProtectionActivated)
backStack.remove(Screen.EnableProtection)
} else {
dialogState = DialogState.Permission(nextPermission)
dialogState = DialogState.Permission(permissionState!!)
}
},
onDismiss = { dialogState = null }
)
}

DialogState.BlockWords -> {
ManageKeywordsDialog(
keywords = blockedWords.toSet(),
onDismiss = { dialogState = null },
onAddKeyword = { viewModel.addBlockedWord(it) },
onRemoveKeyword = {})

}

null -> {}
}

Expand Down Expand Up @@ -356,6 +362,7 @@ class AppActivity : ComponentActivity() {
onBlockAppClick = { dialogState = DialogState.BlockApps() },
onReportClick = { dialogState = DialogState.ReportProblem },
onConfirmProtectionClick = { dialogState = DialogState.HowItWorks },
onBlockWordClicked = { dialogState = DialogState.BlockWords },
onUpdateClick = { updateStatus ->
when (updateStatus) {
UpdateState.Downloaded -> {
Expand All @@ -378,15 +385,22 @@ class AppActivity : ComponentActivity() {
}

Screen.Support -> NavEntry(key) {
var isBlocking by remember {
mutableStateOf(MyAccessibilityService.isRunning)
}
SupportScreen(
onSupportClick = { openUrl(SUPPORT_URL) },
onJoinClick = { openUrl(JOIN_URL) },
onShareLogFile = { this@AppActivity.shareFile(viewModel.getLogFile()) },
onStopBlocking = { startAccessibilityService(MyAccessibilityService.ACTION_STOP) },
onStopBlocking = {
MyAccessibilityService.isRunning =
!MyAccessibilityService.isRunning
},
onOpenScreenShotWindow = {
viewModel.showScreenshotOverlay(true)

}
},
isBlocking = isBlocking
)
}

Expand Down Expand Up @@ -440,54 +454,22 @@ class AppActivity : ComponentActivity() {
if (!accessibilityPermission) {
accessibilityPermission = hasAccessibilityPermission()
}
permissionState = when {
!notificationPermission -> PermissionState.Notification
!vpnPermission -> PermissionState.Vpn
!overlayPermission -> PermissionState.Overlay
!accessibilityPermission -> PermissionState.Accessibility
else -> null
}

}

private fun findNextMissingPermission(): PermissionState? {
// Check permissions in the defined order
return permissionChain.firstOrNull { permission ->
!when (permission) {
PermissionState.Overlay -> hasOverlayPermission()
PermissionState.Accessibility -> hasAccessibilityPermission()
PermissionState.Administrative -> hasAdminPermission(adminReceiver)
PermissionState.Vpn -> hasVpnPermission()
PermissionState.Notification -> hasNotificationPermission()
PermissionState.Granted -> true
}
}
}

private fun checkAndContinuePermissionLoop() {
val nextPermission = findNextMissingPermission()
if (nextPermission != null) {
// If the user is still in the process and another permission is needed, show the next dialog.
// We only show it if a permission dialog isn't already showing.
if (dialogState == null) {
dialogState = DialogState.Permission(nextPermission)
}
} else {
// No more missing permissions!
// Check if the service isn't running yet, then activate.
if (!isServiceRunning(this, MyAccessibilityService::class.java)) {
Toast.makeText(
this,
getString(R.string.protection_activated_message)
.trimIndent(),
Toast.LENGTH_SHORT
).show()
startAccessibilityService(MyAccessibilityService.ACTION_START_FOREGROUND)
startVpnService(MyAccessibilityService.ACTION_START_FOREGROUND)
backStack.add(Screen.ProtectionActivated)
backStack.remove(Screen.EnableProtection)
}
}
}


override fun onResume() {
super.onResume()
refreshPermissionState()
checkAndContinuePermissionLoop()
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class RealFileRepo(val context: Context) : FileRepo {

override fun saveToLog(content: String, fileName: String) {
val logFile = getLogFile(fileName)
logFile.appendText(content)
logFile.appendText(content, Charsets.UTF_8)
}

override fun getUpdateFile(): File = File(
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/mafazaa/ainaa/data/local/SharedPrefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ class SharedPrefs(sharedPreferences: SharedPreferences) {
*/
var blockedApps by sharedPreferences.delegates.stringSet()

/**
* Set of blocked words.
*/
var blockedWords by sharedPreferences.delegates.stringSet()

var dnsProtectionLevel by sharedPreferences.delegates.protectionLevel()

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ sealed class BlockReason {
return "محاولة تعطيل التطبيق: \n$codeName"
}
}
data class BlockedWordDetected(val keyword: String, val sentence: String) : BlockReason() {
override fun getName(): String {
return "كلمة محظورة مكتشفة: \n$keyword"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.mafazaa.ainaa.domain.models

import com.google.gson.Gson

data class ScreenAnalysis(
/**
* the package name of the app currently in foreground
Expand All @@ -22,8 +24,11 @@ data class ScreenAnalysis(
*/
val isSettingsScreen: Boolean,
val root: ScreenNode,
val allTexts: List<String>
) {
fun toJson():String {
val gson = Gson()
return gson.toJson(this)
}
override fun toString(): String {
return "nodes:$nodesCount, app:$pkg, has our app name:$hasAppName,is a settings screen:$isSettingsScreen" +
"\n" + root.toString()
Expand Down
Loading
Loading