├── app ├── .gitignore ├── src │ └── main │ │ ├── assets │ │ └── xposed_init │ │ ├── res │ │ ├── values │ │ │ ├── arrays.xml │ │ │ ├── defaults.xml │ │ │ ├── colors.xml │ │ │ └── strings.xml │ │ ├── values-night │ │ │ └── colors.xml │ │ ├── mipmap-anydpi-v26 │ │ │ └── icon.xml │ │ ├── layout │ │ │ ├── settings_activity.xml │ │ │ └── settings_activity_noxposed.xml │ │ ├── xml │ │ │ └── camera_preferences.xml │ │ ├── drawable │ │ │ └── icon_foreground.xml │ │ └── values-de │ │ │ └── strings.xml │ │ ├── java │ │ └── com │ │ │ └── programminghoch10 │ │ │ └── cameracontrol │ │ │ ├── OwnHook.java │ │ │ ├── SettingsActivity.java │ │ │ ├── PackageHook.java │ │ │ ├── CameraHook.java │ │ │ └── CameraManagerHook.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── .idea ├── .gitignore ├── vcs.xml ├── compiler.xml ├── runConfigurations.xml ├── misc.xml ├── gradle.xml └── jarRepositories.xml ├── screenshot.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .github └── workflows │ └── android-ci.yml ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "CameraControl" 2 | include ':app' 3 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.programminghoch10.cameracontrol.PackageHook -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programminghoch10/CameraControl/HEAD/screenshot.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programminghoch10/CameraControl/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFE0E0E0 4 | #FF202020 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jun 21 17:22:14 CEST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/defaults.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | true 5 | false 6 | false 7 | true 8 | false 9 | false 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/programminghoch10/cameracontrol/OwnHook.java: -------------------------------------------------------------------------------- 1 | package com.programminghoch10.cameracontrol; 2 | 3 | import de.robv.android.xposed.XposedHelpers; 4 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 5 | 6 | public class OwnHook { 7 | static void hook(XC_LoadPackage.LoadPackageParam lpparam) { 8 | XposedHelpers.setStaticBooleanField( 9 | XposedHelpers.findClass(BuildConfig.APPLICATION_ID + ".SettingsActivity", lpparam.classLoader), 10 | "xposedActive", true); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/android-ci.yml: -------------------------------------------------------------------------------- 1 | name: Android 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | app-build-debug: 7 | name: Build Debug APK 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - run: chmod +x gradlew 12 | - name: Run Gradle AssembleDebug 13 | run: ./gradlew assembleDebug 14 | - name: Upload Result 15 | uses: actions/upload-artifact@v2 16 | with: 17 | name: app-debug.apk 18 | path: app/build/outputs/apk/debug/app-debug.apk 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #FF202020 11 | #FFE0E0E0 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity_noxposed.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.3" 8 | 9 | defaultConfig { 10 | applicationId "com.programminghoch10.cameracontrol" 11 | minSdkVersion 27 12 | targetSdkVersion 30 13 | versionCode 2 14 | versionName "1.1" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation 'androidx.appcompat:appcompat:1.3.0' 31 | implementation 'androidx.preference:preference:1.1.1' 32 | compileOnly 'de.robv.android.xposed:api:82' 33 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/programminghoch10/cameracontrol/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.programminghoch10.cameracontrol; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | 7 | import androidx.appcompat.app.ActionBar; 8 | import androidx.appcompat.app.AppCompatActivity; 9 | import androidx.preference.PreferenceFragmentCompat; 10 | 11 | public class SettingsActivity extends AppCompatActivity { 12 | 13 | private static boolean xposedActive = false; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | ActionBar actionBar = getSupportActionBar(); 19 | if (actionBar != null) { 20 | actionBar.setDisplayHomeAsUpEnabled(true); 21 | } 22 | Log.d("CameraManager", "onCreate: xposedactive=" + xposedActive); 23 | if (!xposedActive) { 24 | setContentView(R.layout.settings_activity_noxposed); 25 | return; 26 | } 27 | setContentView(R.layout.settings_activity); 28 | if (savedInstanceState == null) { 29 | getSupportFragmentManager() 30 | .beginTransaction() 31 | .replace(R.id.settings, new SettingsFragment()) 32 | .commit(); 33 | } 34 | } 35 | 36 | public static class SettingsFragment extends PreferenceFragmentCompat { 37 | @SuppressLint("WorldReadableFiles") 38 | @Override 39 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 40 | getPreferenceManager().setSharedPreferencesMode(MODE_WORLD_READABLE); 41 | getPreferenceManager().setSharedPreferencesName("camera"); 42 | setPreferencesFromResource(R.xml.camera_preferences, rootKey); 43 | } 44 | 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/res/xml/camera_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 14 | 15 | 19 | 24 | 29 | 34 | 35 | 36 | 40 | 45 | 50 | 51 | 52 | 55 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CameraControl 3 | Next level camera management 4 | CameraControl Settings 5 | Welcome to CameraControl Settings.\n 6 | This configuration screen will allow you to manage access to the cameras.\n 7 | Please mind, that the settings will only be applied on those apps, 8 | which this module has been activated for 9 | and that a restart may be necessary to apply changes. \n 10 | A quick and easy way to apply changes is to force close the apps in their app info. 11 | XPosed is not available. \n 12 | Please ensure it\'s working and is activated for this module. \n 13 | If you think this is wrong, force close the app in the app settings or restart your device. 14 | 15 | Disable Cameras completely 16 | Disables Cameras completely for all selected apps. \ 17 | Those apps will not have access to any camera anymore and may fail when trying to open them. 18 | Faces 19 | Front 20 | Disable front facing cameras 21 | Back 22 | Disable back facing cameras 23 | External 24 | Disable external cameras 25 | Blocking 26 | Hide Cameras 27 | Hide Cameras by removing them from the list of available cameras when the app requests one. 28 | Block Camera Access 29 | Block access to a camera when the app attempts to open it. 30 | An app will usually not try to access cameras it does not know about, so hiding a camera should be enough. 31 | You can still use this for additional security, but it\'s not recommended to block access without hiding the cameras. 32 | Flash 33 | Hide Flash 34 | Try to hide the existence of the flash. This does not prevent the app from using the flash and might not work at all. 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Verwalten der Kameras auf Systemebene 4 | CameraControl Einstellungen 5 | Willkommen bei den CameraControl Einstellungen.\n 6 | Diese Einstellungen erlauben es dir den Zugriff auf die Kameras zu konfigurieren. \n 7 | Bitte beachte, dass die Einstellungen nur bei den Apps angewendet werden, 8 | für welche dieses Modul aktiviert wurde 9 | und dass ein Neustart eventuell nötig ist, um die Einstellungen anzuwenden. \n 10 | Ein einfacher Weg die Einstellungen anzuwenden, ist bei den Apps in den App-Einstellungen das Beenden zu erzwingen. 11 | XPosed ist nicht verfügbar. \n 12 | Bitte stelle sicher, dass es funktioniert und für dieses Modul aktiviert ist. \n 13 | Wenn du denkst dass dies nicht stimmt, erzwinge das Beenden der App in den App-Einstellungen oder starte dein Gerät neu. 14 | 15 | Kameras komplett deaktivieren 16 | Deaktiviere alle Kameras für ausgewählte apps. \ 17 | Diese Apps werden keinen Zugriff auf die Kamera mehr haben und es können Fehler auftreten wenn sie versuchen auf diese zuzugreifen. 18 | Seiten 19 | Vorne 20 | Deaktiviere Frontkameras 21 | Hinten 22 | Deaktiviere Rückkameras 23 | Extern 24 | Deaktiviere Externe Kameras 25 | Blockieren 26 | Kameras verstecken 27 | Verstecke die Kameras, indem sie von der Liste der Kameras entfernt werden, wenn die App eine anfordert. 28 | Kamerazugriff verhindern 29 | Verhindere den Kamerazugriff wenn die App versucht auf die Kamera zuzugreifen. 30 | Eine App wird normalerweise nicht versuchen auf Kameras zuzugreifen die sie nicht kennt, die Kameras zu verstecken sollte also reichen. 31 | Sie können dies für zusätzliche Sicherheit benutzen, aber es wird nicht empfohlen den Kamerazugriff zu verhindern ohne die Kameras zu verstecken. 32 | Blitz 33 | Blitz verstecken 34 | Versuche die Existenz des Blitzes zu verstecken. 35 | Dies verhindert nicht, dass eine App den Blitz benutzt und funktioniert eventuell nicht. 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/programminghoch10/cameracontrol/PackageHook.java: -------------------------------------------------------------------------------- 1 | package com.programminghoch10.cameracontrol; 2 | 3 | import android.content.SharedPreferences; 4 | import android.util.Log; 5 | 6 | import de.robv.android.xposed.IXposedHookLoadPackage; 7 | import de.robv.android.xposed.XSharedPreferences; 8 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 9 | 10 | public class PackageHook implements IXposedHookLoadPackage { 11 | 12 | private static XSharedPreferences getSharedPreferences() { 13 | XSharedPreferences sharedPreferences = new XSharedPreferences(BuildConfig.APPLICATION_ID, "camera"); 14 | if (!sharedPreferences.getFile().canRead()) sharedPreferences = null; 15 | if (sharedPreferences == null) { 16 | Log.e("CameraControl", "getSharedPreferences: failed to load SharedPreferences"); 17 | } 18 | return sharedPreferences; 19 | } 20 | 21 | @Override 22 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { 23 | if (lpparam.packageName.equals("android")) return; 24 | if (lpparam.packageName.equals(BuildConfig.APPLICATION_ID)) { 25 | OwnHook.hook(lpparam); 26 | return; 27 | } 28 | 29 | //Log.d("CameraControl", "handleLoadPackage: package="+lpparam.packageName); 30 | //XposedBridge.log("handleLoadPackage: package="+lpparam.packageName); 31 | 32 | XSharedPreferences sharedPreferences = getSharedPreferences(); 33 | if (sharedPreferences == null) return; 34 | CameraPreferences cameraPreferences = new CameraPreferences(sharedPreferences); 35 | 36 | if (sharedPreferences.getBoolean("disableCameraManager", false)) { 37 | CameraManagerHook.disableHook(lpparam); 38 | CameraHook.disableHook(lpparam); 39 | } else { 40 | CameraManagerHook.hook(lpparam, cameraPreferences); 41 | CameraHook.hook(lpparam, cameraPreferences); 42 | } 43 | } 44 | 45 | public static class CameraPreferences { 46 | boolean disableFrontFacing = true; 47 | boolean disableBackFacing = true; 48 | boolean disableExternal = true; 49 | boolean blockList = true; 50 | boolean blockAccess = true; 51 | boolean blockFlash = true; 52 | 53 | CameraPreferences(SharedPreferences sharedPreferences) { 54 | disableFrontFacing = sharedPreferences.getBoolean("disableFrontFacing", true); 55 | disableBackFacing = sharedPreferences.getBoolean("disableBackFacing", true); 56 | disableExternal = sharedPreferences.getBoolean("disableExternal", true); 57 | blockList = sharedPreferences.getBoolean("blockList", true); 58 | blockAccess = sharedPreferences.getBoolean("blockAccess", true); 59 | blockFlash = sharedPreferences.getBoolean("blockFlash", true); 60 | } 61 | 62 | CameraPreferences() { 63 | } 64 | 65 | void setAll(boolean state) { 66 | disableFrontFacing = state; 67 | disableBackFacing = state; 68 | disableExternal = state; 69 | blockList = state; 70 | blockAccess = state; 71 | blockFlash = state; 72 | } 73 | 74 | void disableAll() { 75 | setAll(true); 76 | } 77 | 78 | void enableAll() { 79 | setAll(false); 80 | } 81 | 82 | boolean blockLegacy() { 83 | return blockList || blockAccess; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CameraControl 2 | Next level camera management. 3 | 4 | CameraControl lets you manage access to your cameras depending on which side of the phone they face. 5 | 6 | Why? Because I hate it when Snapchat opens up and forces me to see my own depression. But completely disabling all the cameras for Snapchat would render the whole app useless. 7 | 8 | This was developed and tested on `LineageOS 17.1`, but should work on any android `>= 8.1`. 9 | 10 | ### How to install: 11 | 12 | 1. Install the module on a system with a running XPosed framework 13 | 1. Activate the module and select apps you want to limit camera access to. 14 | 15 | ### How to configure: 16 | 17 | - Route 1: 18 | - Go back to the module list 19 | - Long press on `CameraControl` and select App Info 20 | - On the bottom, click on `Advanced` 21 | - Click on `Additional settings in the app` 22 | - Route 2: 23 | - Go into device settings 24 | - Click on apps 25 | - Somehow tell the device to show all apps 26 | - Select `CameraControl` 27 | - On the bottom, click `Advanced` 28 | - Click on `Additional settings in the app` 29 | - Route 3: 30 | - Open up an ADB shell 31 | - Run command `am start-activity com.programminghoch10.cameracontrol/.SettingsActivity` 32 | 33 | Now configure away! 34 | 35 | ___Small intel:__ The configure activity is not accessible from the launcher, 36 | because LSPosed does not let modules hide their own launcher icons by default anymore. 37 | Defining a settings activity is an easy way to provide an accessible configuration interface, 38 | without forcing yet another launcher icon into the list. 39 | One does not have to open the menu often anyways._ 40 | 41 | ### What it looks like 42 | ![Screenshot](screenshot.png) 43 | 44 | ### How it works (technical stuff) 45 | 46 | Here is how this module prevents access and hides the cameras. 47 | 48 | * Camera2 API 49 | * Hiding cameras 50 | This module is hiding cameras by removing them from the results of the method `getCameraIdList`. For each camera on this list, it queries the `CameraCharacteristics` of that camera and removes it from the list if this camera meets the filter requirements. 51 | * Block camera access 52 | This module is blocking camera access by hooking method `openCamera`. Before the camera open call is sent to the system, this module checks if the camera shall be filtered and if so, prevents the system open call. 53 | * Hide flash 54 | This module hides the existence of the flash by hooking method `get` within the `CameraCharacteristics`. If the key for flash is queried, it returns `false` and if the key for autofocus capabilites is queried, it removes flash associated focus capabilites from the list. 55 | * Disable all cameras 56 | The module disables the whole Camera2 API by setting the testing flag `sCameraServiceDisabled` to `true`. This way the internal camera manager will not respond to calls anymore. 57 | * Camera API (deprecated) 58 | The old camera api is depricated since android 5 and most applications do not use it. 59 | * Hide / Block cameras 60 | This module does not support seperated hiding and blocking for the old camera api since those mechanisms are linked together. If one is selected, both mechanisms will be active. 61 | * Hide cameras 62 | The cameras are hidden by changing the result of the method `getNumberOfCameras`. The module checks each camera in the list for filter criteria and lowers the counter when a camera should be hidden. 63 | At the same time it saves a new mapping of camera indicies to be used by the blocking access hooks. 64 | * Block camera access 65 | To block the camera access the module hooks method `open`. Before relaying the system call, it checks the new camera mapping created whilst hiding the cameras and also additionally checks for the filter criteria. 66 | * Hide flash 67 | The module tries to hide the flash by overwriting the result of `getSupportedFlashModes`. 68 | It also prevents calls to `setFlashMode` in order to prevent the flash from being turned on. 69 | * Disable all cameras 70 | Disabling the camera api is not that easy, so instead we just activate full blocking capabilites. 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/programminghoch10/cameracontrol/CameraHook.java: -------------------------------------------------------------------------------- 1 | package com.programminghoch10.cameracontrol; 2 | 3 | import android.hardware.Camera; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import de.robv.android.xposed.XC_MethodHook; 9 | import de.robv.android.xposed.XC_MethodReplacement; 10 | import de.robv.android.xposed.XposedBridge; 11 | import de.robv.android.xposed.XposedHelpers; 12 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 13 | 14 | public class CameraHook { 15 | private static int realCameraCount = 0; 16 | 17 | static void disableHook(XC_LoadPackage.LoadPackageParam lpparam) { 18 | PackageHook.CameraPreferences cameraPreferences = new PackageHook.CameraPreferences(); 19 | cameraPreferences.disableAll(); 20 | hook(lpparam, cameraPreferences); 21 | } 22 | 23 | static void hook(XC_LoadPackage.LoadPackageParam lpparam, PackageHook.CameraPreferences cameraPreferences) { 24 | if (cameraPreferences.blockLegacy()) { 25 | XposedBridge.log("Hooking getNumberOfCameras"); 26 | XposedHelpers.findAndHookMethod(Camera.class, "getNumberOfCameras", new XC_MethodHook() { 27 | @Override 28 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 29 | realCameraCount = (int) param.getResult(); 30 | int availableCameras = getAvailableCamerasFromIdMap(generateIdMap(cameraPreferences)); 31 | param.setResult(availableCameras); 32 | } 33 | }); 34 | XposedBridge.log("Hooking shouldExposeAuxCamera"); 35 | XposedHelpers.findAndHookMethod(Camera.class, "shouldExposeAuxCamera", new XC_MethodHook() { 36 | @Override 37 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 38 | if (cameraPreferences.disableExternal) param.setResult(false); 39 | } 40 | }); 41 | XposedBridge.log("Hooking open"); 42 | XposedHelpers.findAndHookMethod(Camera.class, "open", int.class, new XC_MethodHook() { 43 | @Override 44 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 45 | Map map = generateIdMap(cameraPreferences); 46 | int requestedCameraId = (int) param.args[0]; 47 | if (!map.containsKey(requestedCameraId)) 48 | param.setThrowable(new RuntimeException()); 49 | Integer resultingCameraId = map.get(requestedCameraId); 50 | if (resultingCameraId == null) param.setThrowable(new RuntimeException()); 51 | param.args[0] = resultingCameraId; 52 | } 53 | }); 54 | XposedHelpers.findAndHookMethod(Camera.class, "open", new XC_MethodHook() { 55 | @Override 56 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 57 | if (cameraPreferences.disableBackFacing) param.setResult(null); 58 | } 59 | }); 60 | } 61 | if (cameraPreferences.blockFlash) { 62 | XposedBridge.log("Hooking setFlashMode"); 63 | XposedHelpers.findAndHookMethod(Camera.class, "setFlashMode", String.class, XC_MethodReplacement.DO_NOTHING); 64 | XposedBridge.log("Hooking getSupportedFlashModes"); 65 | XposedHelpers.findAndHookMethod(Camera.class, "getSupportedFlashModes", XC_MethodReplacement.returnConstant(null)); 66 | } 67 | } 68 | 69 | private static boolean shouldDisableCamera(Camera.CameraInfo cameraInfo, PackageHook.CameraPreferences cameraPreferences) { 70 | if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && cameraPreferences.disableFrontFacing) 71 | return true; 72 | if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK && cameraPreferences.disableBackFacing) 73 | return true; 74 | return false; 75 | } 76 | 77 | private static Map generateIdMap(PackageHook.CameraPreferences cameraPreferences) { 78 | Map map = new HashMap<>(); 79 | for (int i = 0; i < realCameraCount; i++) { 80 | Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); 81 | Camera.getCameraInfo(i, cameraInfo); 82 | boolean disableCamera = shouldDisableCamera(cameraInfo, cameraPreferences); 83 | map.put(i, disableCamera ? null : i); 84 | } 85 | return map; 86 | } 87 | 88 | private static int getAvailableCamerasFromIdMap(Map map) { 89 | int cameraCount = 0; 90 | for (Map.Entry entry : map.entrySet()) { 91 | if (entry.getValue() != null) cameraCount++; 92 | } 93 | return cameraCount; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/programminghoch10/cameracontrol/CameraManagerHook.java: -------------------------------------------------------------------------------- 1 | package com.programminghoch10.cameracontrol; 2 | 3 | import android.hardware.camera2.CameraAccessException; 4 | import android.hardware.camera2.CameraCharacteristics; 5 | import android.hardware.camera2.CameraDevice; 6 | import android.hardware.camera2.CameraManager; 7 | import android.os.Handler; 8 | import android.util.Log; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | import java.util.concurrent.Executor; 16 | 17 | import de.robv.android.xposed.XC_MethodHook; 18 | import de.robv.android.xposed.XC_MethodReplacement; 19 | import de.robv.android.xposed.XposedBridge; 20 | import de.robv.android.xposed.XposedHelpers; 21 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 22 | 23 | public class CameraManagerHook { 24 | 25 | static void disableHook(XC_LoadPackage.LoadPackageParam lpparam) { 26 | XposedBridge.log("Disabling CameraManager completely"); 27 | XposedHelpers.setStaticBooleanField( 28 | XposedHelpers.findClass(CameraManager.class.getName() + "$CameraManagerGlobal", lpparam.classLoader), 29 | "sCameraServiceDisabled", true 30 | ); 31 | } 32 | 33 | static void hook(XC_LoadPackage.LoadPackageParam lpparam, PackageHook.CameraPreferences cameraPreferences) { 34 | if (cameraPreferences.blockList) { 35 | XposedBridge.log("Hooking getCameraIdList"); 36 | XposedHelpers.findAndHookMethod(CameraManager.class, "getCameraIdList", new XC_MethodHook() { 37 | @Override 38 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 39 | String[] ids = (String[]) param.getResult(); 40 | CameraManager cameraManager = (CameraManager) param.thisObject; 41 | List cameras = new ArrayList<>(Arrays.asList(ids)); 42 | Iterator iterator = cameras.iterator(); 43 | while (iterator.hasNext()) { 44 | String id = iterator.next(); 45 | CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); 46 | if (disableCamera(characteristics, cameraPreferences)) 47 | iterator.remove(); 48 | } 49 | String[] camerasModified = cameras.toArray(new String[0]); 50 | param.setResult(camerasModified); 51 | } 52 | }); 53 | } 54 | if (cameraPreferences.blockAccess) { 55 | XposedBridge.log("Hooking openCamera"); 56 | Method openCamera = XposedHelpers.findMethodExact(CameraManager.class, "openCamera", 57 | String.class, CameraDevice.StateCallback.class, Handler.class); 58 | XposedBridge.hookMethod(openCamera, new XC_MethodHook() { 59 | @Override 60 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 61 | String cameraId = (String) param.args[0]; 62 | CameraManager cameraManager = (CameraManager) param.thisObject; 63 | CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); 64 | if (disableCamera(characteristics, cameraPreferences)) 65 | param.setThrowable(new CameraAccessException(CameraAccessException.CAMERA_DISABLED)); 66 | } 67 | }); 68 | } 69 | if (cameraPreferences.blockFlash) { 70 | XposedBridge.log("Hooking setTorchMode"); 71 | XposedHelpers.findAndHookMethod(CameraManager.class, "setTorchMode", String.class, boolean.class, XC_MethodReplacement.DO_NOTHING); 72 | XposedBridge.log("Hooking get"); 73 | XposedHelpers.findAndHookMethod(CameraCharacteristics.class, "get", CameraCharacteristics.Key.class, new XC_MethodHook() { 74 | @Override 75 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 76 | CameraCharacteristics.Key key = (CameraCharacteristics.Key) param.args[0]; 77 | Log.d("CameraControl", "beforeHookedMethod: get key " + key.getName()); 78 | if (key.getName().equals(CameraCharacteristics.FLASH_INFO_AVAILABLE.getName())) { 79 | Log.d("CameraControl", "beforeHookedMethod: PREVENT FLASH"); 80 | param.setResult(false); 81 | } 82 | } 83 | 84 | @Override 85 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 86 | CameraCharacteristics.Key key = (CameraCharacteristics.Key) param.args[0]; 87 | if (key.getName().equals(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES.getName())) { 88 | Log.d("CameraControl", "afterHookedMethod: filtering flash"); 89 | int[] methodResult = (int[]) param.getResult(); 90 | int[] filteredResult = 91 | Arrays.stream(methodResult).filter(u -> { 92 | switch (u) { 93 | case CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH: 94 | case CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH: 95 | case CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE: 96 | case CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH: 97 | return false; 98 | default: 99 | return true; 100 | } 101 | }).toArray(); 102 | param.setResult(filteredResult); 103 | } 104 | } 105 | }); 106 | XposedBridge.log("Hooking registerTorchCallback"); 107 | XposedHelpers.findAndHookMethod(CameraManager.class, "registerTorchCallback", 108 | CameraManager.TorchCallback.class, Executor.class, XC_MethodReplacement.DO_NOTHING); 109 | } 110 | } 111 | 112 | private static boolean disableCamera(CameraCharacteristics characteristics, PackageHook.CameraPreferences cameraPreferences) { 113 | if (cameraPreferences.disableFrontFacing 114 | && characteristics.get(CameraCharacteristics.LENS_FACING).equals(CameraCharacteristics.LENS_FACING_FRONT)) 115 | return true; 116 | if (cameraPreferences.disableBackFacing 117 | && characteristics.get(CameraCharacteristics.LENS_FACING).equals(CameraCharacteristics.LENS_FACING_BACK)) 118 | return true; 119 | if (cameraPreferences.disableExternal 120 | && characteristics.get(CameraCharacteristics.LENS_FACING).equals(CameraCharacteristics.LENS_FACING_EXTERNAL)) 121 | return true; 122 | return false; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | --------------------------------------------------------------------------------