Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
96 changes: 96 additions & 0 deletions .github/workflows/android-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Android Release

on:
push:
tags:
- "*.*.*"
workflow_dispatch:

permissions:
contents: write

concurrency:
group: android-release-${{ github.ref }}
cancel-in-progress: false

jobs:
build-and-release:
name: Build and publish Android release
runs-on: ubuntu-latest

steps:
- name: Check out source
uses: actions/checkout@v5

- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5

- name: Configure release signing
shell: bash
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
for value in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do
if [[ -z "${!value}" ]]; then
echo "Missing required GitHub Actions secret: $value"
exit 1
fi
done
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/android-release.keystore"

- name: Build release APK and AAB
env:
ANDROID_KEYSTORE_PATH: ${{ runner.temp }}/android-release.keystore
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
chmod +x ./gradlew
./gradlew assembleRelease bundleRelease

- name: Read and verify app version
id: version
shell: bash
run: |
APP_VERSION="$(jq -r '.elements[0].versionName' app/build/outputs/apk/release/output-metadata.json)"

if [[ -z "$APP_VERSION" || "$APP_VERSION" == "null" ]]; then
echo "Unable to read versionName from the release APK metadata."
exit 1
fi

if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "$APP_VERSION" ]]; then
echo "Tag '$GITHUB_REF_NAME' must match Android versionName '$APP_VERSION'."
exit 1
fi

echo "version=$APP_VERSION" >> "$GITHUB_OUTPUT"

- name: Prepare release files
shell: bash
env:
APP_VERSION: ${{ steps.version.outputs.version }}
run: |
mkdir release
cp app/build/outputs/apk/release/app-release.apk "release/MeshCentral-Agent-${APP_VERSION}.apk"
cp app/build/outputs/bundle/release/app-release.aab "release/MeshCentral-Agent-${APP_VERSION}.aab"

- name: Publish GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.version }}
target_commitish: ${{ github.sha }}
generate_release_notes: true
files: |
release/*.apk
release/*.aab
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
/.idea/assetWizardSettings.xml
.DS_Store
/build
/release
/captures
.externalNativeBuild
.cxx
local.properties
*.jks
*.keystore
73 changes: 65 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,68 @@
# MeshCentral Agent for Android
# MeshCentral Android Agent

This is the MeshCentral Agent for Android. It's a completely different code base from the agent used on Windows, Linux, macOS and FreeBSD. You can pair the agent to the server by scanning a QR code. Once paired, you can connect
to the server and see the device show up, see the battery state, device details and download pictures, audio and video files.
MeshCentral Android Agent connects an Android device to a
[MeshCentral](https://www.meshcentral.com) server for remote monitoring and
support. It is a native Kotlin application and is separate from the MeshCentral
agents used on Windows, Linux, macOS, and FreeBSD.

For more information, [visit MeshCentral.com](https://www.meshcentral.com).
Pair a device by scanning a MeshCentral QR code, opening an `mc://` pairing
link, or entering the link manually. After enrollment, the app maintains an
authenticated connection to the server and can:

## Social Media
[Reddit](https://www.reddit.com/r/MeshCentral/)
[Twitter](https://twitter.com/MeshCentral)
[BlogSpot](https://meshcentral2.blogspot.com/)
- Report device, network, storage, and battery information.
- Share the device screen after Android MediaProjection consent.
- Browse and transfer media and files available to the app.
- Receive server notifications and a limited set of console commands.
- Approve or reject MeshCentral push-based two-factor authentication requests.

Remote desktop is currently **view only**. The app can stream the display, but
it cannot tap, swipe, type, or otherwise control the device. Android displays a
foreground notification while screen sharing is active, and the user can deny
or stop capture at any time.

## Install

Install MeshAgent for Android from [Google Play](https://play.google.com/store/apps/details?id=com.meshcentral.agent2).

Or download the APK or AAB from the
[latest release](https://github.com/Ylianst/MeshCentralAndroidAgent/releases/latest).

## Requirements

- Android 6.0 (API 23) or later.
- A MeshCentral server configured to enroll Android agents.
- Android Studio or JDK 17 and an Android SDK for local development.

## Build

Open the repository in Android Studio, or build and test it from the repository
root:

```powershell
.\gradlew.bat assembleDebug testDebugUnitTest lintDebug
```

The debug APK is generated under `app/build/outputs/apk/debug/`. Production
releases require a dedicated signing keystore; see the release guide below.

## Documentation

- [Documentation site](https://ylianst.github.io/MeshCentralAndroidAgent/) -
published project documentation.
- [Documentation home](docs/index.md) - introduction, installation, key
capabilities, and links to all project resources.
- [Repository overview](docs/overview.md) - architecture, components, project
configuration, and development notes.
- [Remote desktop](docs/remote-desktop.md) - screen-capture flow, consent,
encoding, and Android platform limitations.
- [Tunnel authentication](docs/tunnel-authentication.md) - control-channel
authentication, certificate pinning, and relay tunnel trust.
- [Push two-factor authentication](docs/two-factor-authentication.md) - FCM
request validation, approval flow, and lifecycle behavior.
- [Creating a release](docs/releasing.md) - versioning, signing, GitHub Actions,
and publishing APK and AAB artifacts.

## Community

- [MeshCentral website](https://www.meshcentral.com)
- [MeshCentral subreddit](https://www.reddit.com/r/MeshCentral/)
3 changes: 2 additions & 1 deletion app/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/build
/build
/release
75 changes: 46 additions & 29 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.gms.google-services'
}

def releaseKeystorePath = System.getenv('ANDROID_KEYSTORE_PATH')
def releaseKeystorePassword = System.getenv('ANDROID_KEYSTORE_PASSWORD')
def releaseKeyAlias = System.getenv('ANDROID_KEY_ALIAS')
def releaseKeyPassword = System.getenv('ANDROID_KEY_PASSWORD')
def releaseSigningConfigured = releaseKeystorePath && releaseKeystorePassword && releaseKeyAlias && releaseKeyPassword

android {
signingConfigs {
release {
if (releaseSigningConfigured) {
storeFile file(releaseKeystorePath)
storePassword releaseKeystorePassword
keyAlias releaseKeyAlias
keyPassword releaseKeyPassword
}
}
}
defaultConfig {
compileSdk 34
targetSdk 34
compileSdk 37
targetSdk 37
applicationId "com.meshcentral.agent2"
minSdk 23
versionCode 28
versionName "1.0.21"
versionCode 30
versionName "1.0.23"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Expand All @@ -22,55 +37,57 @@ android {
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
//minifyEnabled true
minifyEnabled true

// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
//shrinkResources true
shrinkResources true

// Includes the default ProGuard rules files that are packaged with
// the Android Gradle plugin. To learn more, go to the section about
// R8 configuration files.
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
signingConfig releaseSigningConfigured ? signingConfigs.release : signingConfigs.debug

//debuggable true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
buildFeatures {
buildConfig true
}
kotlinOptions {
jvmTarget = '1.8'
lint {
// Trust managers implement the MeshCentral certificate-pinned handshake; the only
// remaining report is inside the Bouncy Castle crypto library, which we cannot annotate.
disable 'TrustAllX509TrustManager'
// Dependency upgrades are managed separately from Android SDK migrations.
disable 'GradleDependency'
// The agent intentionally requests full media access to enumerate device storage.
disable 'SelectedPhotoAccess'
}
packaging {
resources {
excludes += 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
namespace 'com.meshcentral.agent'
}

dependencies {
//noinspection GradleDependency
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.12.0'
//implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation 'com.budiyev.android:code-scanner:2.1.0'
implementation 'com.github.yuriy-budiyev:code-scanner:2.3.2'
implementation 'com.karumi:dexter:6.2.2'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.google.firebase:firebase-messaging:23.4.1'
implementation 'com.google.firebase:firebase-core:21.1.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'com.google.firebase:firebase-installations-ktx:17.2.0'
//implementation 'androidx.work:work-runtime-ktx:2.9.0'
//implementation 'org.webrtc:google-webrtc:1.0.32006'
//testImplementation 'junit:junit:4.13.1'
//androidTestImplementation 'androidx.test.ext:junit:1.1.2'
//androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
testImplementation 'junit:junit:4.13.2'
}
6 changes: 3 additions & 3 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
#-renamesourcefileattribute SourceFile


-keep class org.spongycastle.**
-dontwarn org.spongycastle.jce.provider.X509LDAPCertStoreSpi
-dontwarn org.spongycastle.x509.util.LDAPStoreHelper
-keep class org.bouncycastle.**
-dontwarn org.bouncycastle.jce.provider.X509LDAPCertStoreSpi
-dontwarn org.bouncycastle.x509.util.LDAPStoreHelper
Binary file removed app/release/app-release.aab
Binary file not shown.
Binary file removed app/release/app-release.apk
Binary file not shown.
20 changes: 0 additions & 20 deletions app/release/output-metadata.json

This file was deleted.

12 changes: 5 additions & 7 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,23 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove"/>

<application
android:allowBackup="true"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MeshCentralAgent"
android:requestLegacyExternalStorage="true">
android:theme="@style/Theme.MeshCentralAgent">
<!-- <activity android:name=".SettingsActivity" android:label="@string/title_activity_settings"></activity> -->

<meta-data
Expand All @@ -34,7 +33,6 @@

<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.MeshCentralAgent.NoActionBar"
android:exported="true">
<intent-filter>
Expand Down
Loading