├── .gitignore ├── demo.gif ├── manifest.json ├── android-svc.sh ├── README.md └── android-svc-lib.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /.android-svc-cache 2 | /build 3 | wget-log 4 | /venv -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-vK/android-svc/HEAD/demo.gif -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "android-svc", 3 | "version": "0.2.3", 4 | "homepage": "https://github.com/T-vK/android-svc/", 5 | "maintainer": "@T-vK", 6 | "description": "Easy to use Android service wrapper", 7 | "arch": "all", 8 | "depends": ["bash", "git", "wget", "sed", "grep", "coreutils", "util-linux", "vim", "jq"], 9 | "files" :{ 10 | "android-svc.sh": "bin/android-svc", 11 | "android-svc-lib.sh": "lib/android-svc-lib.sh" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android-svc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #ANDROID_SVC_LIB_BEGIN 4 | if [ -f "${PREFIX}/lib/android-svc-lib.sh" ]; then 5 | source "${PREFIX}/lib/android-svc-lib.sh" 6 | elif [ -f "./android-svc-lib.sh" ]; then 7 | source ./android-svc-lib.sh 8 | else 9 | >&2 echo "ERROR: 'android-svc-lib.sh' is missing!" 10 | exit 1 11 | fi 12 | #ANDROID_SVC_LIB_END 13 | 14 | if [ "$1" == "help" ] || [ "$1" == "--help" ]; then 15 | echo "USAGE:" 16 | echo "" 17 | echo "android-svc [options] download" 18 | echo "Description: Enable offline usage by downloading required Android source code files for the current device." 19 | echo "Example: android-svc download" 20 | echo "" 21 | echo "android-svc [options] call 'SERVICE_PACKAGE_NAME.METHOD_NAME(arguments)'" 22 | echo "Description: Call a service method." 23 | echo "Example: android-svc call 'com.android.internal.telephony.ITelephony.dial(\"555-0199\")'" 24 | echo "" 25 | echo "android-svc [options] convert 'SERVICE_PACKAGE_NAME.METHOD_NAME(arguments)'" 26 | echo "Description: Convert a service method call to a bash command. THE RESULTING COMMAND WILL ONLY WORK FOR THE EXACT ANDROID VERSION OF YOUR DEVICE!" 27 | echo "Example: android-svc convert 'com.android.internal.telephony.ITelephony.dial(\"555-0199\")'" 28 | echo "" 29 | echo "android-svc [options] list-packages" 30 | echo "Description: List all service package names." 31 | echo "Example: android-svc list-packages" 32 | echo "" 33 | echo "android-svc [options] list-methods SERVICE_PACKAGE_NAME" 34 | echo "Description: List all methods for a service package." 35 | echo "Example: android-svc list-methods android.content.pm.IPackageManager" 36 | echo "" 37 | echo "android-svc [options] method-signature SERVICE_PACKAGE_NAME.METHOD_NAME" 38 | echo "Description: Get method-signature for a specific method." 39 | echo "Example: android-svc method-signature android.media.IAudioService.isMasterMute" 40 | echo "" 41 | echo "Supported options are --adb or --adb=" 42 | echo "(You only need this in order to use this from a Linux machine via ADB.)" 43 | echo "" 44 | echo "android-svc help" 45 | echo "Description: Print this message." 46 | echo "Example: android-svc help" 47 | exit 0 48 | elif [ "$1" == "version" ] || [ "$1" == "--version" ]; then 49 | echo "$g_ANDROID_SVC_LIB_VERSION" 50 | exit 0 51 | elif [[ "$1" == "--adb="* ]]; then 52 | SetShellType "adb" 53 | SetAdbDevice "$(echo "$1" | cut -d'=' -f2)" 54 | command="$2" 55 | commandParam1="$3" 56 | elif [ "$1" == "--adb" ]; then 57 | SetShellType "adb" 58 | command="$2" 59 | commandParam1="$3" 60 | else 61 | command="$1" 62 | commandParam1="$2" 63 | fi 64 | 65 | Init 66 | 67 | if [ "$command" == "call" ]; then 68 | CallServiceMethod "$commandParam1" 69 | elif [ "$command" == "convert" ]; then 70 | ConvertServiceCallToShellCommand "$commandParam1" 71 | elif [ "$command" == "list-packages" ]; then 72 | GetServicePackageNames 73 | elif [ "$command" == "list-methods" ]; then 74 | if [[ $commandParam1 == *"."* ]]; then # A service package name was given 75 | servicePackageName="$commandParam1" 76 | else # A service code name was given 77 | servicePackageName="$(GetServicePackageName "$l_serviceCodeName")" 78 | fi 79 | methodSignatures="$(GetMethodSignaturesForPackage "${servicePackageName}")" 80 | if test -t 1; then 81 | echo -e "$(AidlSyntaxHighlight "${methodSignatures}")" 82 | else 83 | echo "${methodSignatures}" 84 | fi 85 | elif [ "$command" == "method-signature" ]; then 86 | methodName=$(echo "$commandParam1" | rev | cut -d'.' -f 1 | rev) 87 | servicePackageName=$(echo "$commandParam1" | rev | cut -d"." -f2- | rev) 88 | methodSignature="$(GetMethodSignature "${servicePackageName}" "${methodName}")" 89 | if test -t 1; then 90 | echo -e "$(AidlSyntaxHighlight "${methodSignature}")" 91 | else 92 | echo "${methodSignature}" 93 | fi 94 | elif [ "$command" == "download" ]; then 95 | DownloadSourceFiles "${g_aidlFileList}" "${g_aidlFileCache}" 96 | else 97 | Exit 1 "Command not found!" 98 | fi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Commandline wrapper for Android's serivce utility 2 | 3 | ## About 4 | `android-svc` aims at making it easier to call service methods over ADB or using a terminal emulator on your Android device directly. 5 | Using Android's built-in `service` utility forces you to manually go through the Android source code and its AIDL files. 6 | You can for example simply call 7 | ``` Bash 8 | android-svc call 'android.content.pm.IPackageManager.getInstallerPackageName("com.nononsenseapps.feeder");' 9 | #or 10 | android-svc call 'package.getInstallerPackageName("com.nononsenseapps.feeder");' 11 | ``` 12 | instead of 13 | ``` Bash 14 | service call package 65 s16 'com.nononsenseapps.feeder' 15 | ``` 16 | which would have required you to understand how to get values like `65` or `s16` and write different code for every device and Android version you want to use it on. 17 | 18 | ## Demo 19 | ![demo](./demo.gif) 20 | 21 | ## Features 22 | 23 | - [x] Call service methods 24 | - [x] List available service packages 25 | - [x] List available methods for service packages 26 | - [x] Show method signatures (including data types for arguments and return values) 27 | - [x] Convert given service method calls into a bash commands 28 | - [x] Offline mode 29 | - [x] Works over ADB (from Linux host) 30 | - [x] Works directly on the phone (requires Termux) 31 | - [x] Supports the following data types: void, boolean, char, int, long, float, double, String 32 | 33 | ## Limitations 34 | 35 | - Requires root 36 | - I need help decoding/encoding all complex datatypes like arrays, Lists, ParceledListSlice etc. 37 | - String are decoded ignoring the first 8 bits of every char. This works fine as long as only UTF-8 characters are being used. When a string contains a UTF-16 character the decoded output will be incorrect. Any help properly decoding utf-16 hex code in bash would be appreciated. 38 | - String decoding is slow. Any help improving the performance would be apperciated. 39 | 40 | ## Disclaimer 41 | **Use at own risk!** 42 | - May call incorrect service methods on ROMs that have added/removed methods to/from the aidl files as they appear in AOSP. I recommend using LineageOS to reduce that risk. If you use another open source ROM, we can probably add support for detecting that by scanning its source code, just like I've done it for LineageOS. 43 | - Only tested with ARM64-based devices. 44 | 45 | ## How to install (in Termux) 46 | - Download the deb package from the [latest release](https://github.com/T-vK/android-svc/releases). 47 | - Install it using `apt install path/to/android-svc_x.x.x_all.deb` (replaceing x.x.x with the actual version) 48 | 49 | ## How to install (in Linux) 50 | - Download the standalone executable from the [latest release](https://github.com/T-vK/android-svc/releases). (It's the `android-svc` file.) 51 | - Make it executable by running `chmod +x ./android-svc`. 52 | - Optional: Add the containing folder to your PATH or copy android-svc into a folder that's in PATH already. 53 | Otherwise you'll have to use it like `path/to/android-svc help` instead of `android-svc help` 54 | 55 | Alternatively you can of course clone the repo with git and execute android-svc.sh directly. 56 | 57 | ## How to build 58 | If you want to build it yourself instead of using a release, you can do it like this: 59 | 60 | ``` 61 | git clone https://github.com/T-vK/android-svc.git 62 | cd ./android-svc 63 | ./build.sh 64 | ``` 65 | 66 | This will create a folder called 'build' in which you'll find the standalone executable and the deb package for Termux. 67 | 68 | 69 | ## How to use 70 | 71 | Run `android-svc help` to get the following help message: 72 | ``` Bash 73 | android-svc [options] download 74 | Description: Enable offline usage by downloading required Android source code files for the current device. 75 | Example: android-svc download 76 | 77 | android-svc [options] call 'SERVICE_PACKAGE_NAME.METHOD_NAME(arguments)' 78 | Description: Call a service method. 79 | Example: android-svc call 'com.android.internal.telephony.ITelephony.dial("555-0199")' 80 | 81 | android-svc [options] convert 'SERVICE_PACKAGE_NAME.METHOD_NAME(arguments)' 82 | Description: Convert a service method call to a bash command. THE RESULTING COMMAND WILL ONLY WORK FOR THE EXACT ANDROID VERSION OF YOUR DEVICE! 83 | Example: android-svc convert 'com.android.internal.telephony.ITelephony.dial("555-0199")' 84 | 85 | android-svc [options] list-packages 86 | Description: List all service package names. 87 | Example: android-svc list-packages 88 | 89 | android-svc [options] list-methods SERVICE_PACKAGE_NAME 90 | Description: List all methods for a service package. 91 | Example: android-svc list-methods android.content.pm.IPackageManager 92 | 93 | android-svc [options] method-signature SERVICE_PACKAGE_NAME.METHOD_NAME 94 | Description: Get method-signature for a specific method. 95 | Example: android-svc method-signature android.media.IAudioService.isMasterMute 96 | 97 | Supported options are --adb or --adb= 98 | (You only need this in order to use this from a Linux machine via ADB.) 99 | 100 | android-svc help 101 | Description: Print this message. 102 | Example: android-svc help 103 | ``` 104 | 105 | ## Examples with example output: 106 | 107 | ``` Bash 108 | # Example 1 (Enables offline usage): 109 | $ android-svc download 110 | Downloading 'core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl' 111 | Downloading 'core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl' 112 | Downloading 'core/java/android/accounts/IAccountAuthenticator.aidl' 113 | Downloading 'core/java/android/accounts/IAccountAuthenticatorResponse.aidl' 114 | Downloading 'core/java/android/accounts/IAccountManager.aidl' 115 | Downloading 'core/java/android/accounts/IAccountManagerResponse.aidl' 116 | ... 117 | 118 | 119 | 120 | # Example 2 (Find out which App installed another app): 121 | $ android-svc call 'android.content.pm.IPackageManager.getInstallerPackageName("com.nononsenseapps.feeder");' 122 | org.fdroid.fdroid.privileged 123 | 124 | 125 | 126 | # Example 3 (Find out if the master volume is muted): 127 | $ android-svc call 'android.media.IAudioService.isMasterMute();' 128 | false 129 | 130 | 131 | 132 | # Example 4 (Dial a given phone number): 133 | $ android-svc call 'com.android.internal.telephony.ITelephony.dial("555-0199")' 134 | 135 | 136 | 137 | # Example 5 (Convert the given package-method-call into a shell command (which only works for the exact same Android version): 138 | $ android-svc convert 'android.content.pm.IPackageManager.getInstallerPackageName("com.nononsenseapps.feeder");' 139 | service call package 65 s16 'com.nononsenseapps.feeder' 140 | 141 | 142 | 143 | # Example 6 (Get info about arguments and return data types about a given method from a given package): 144 | $ android-svc info android.content.pm.IPackageManager.getInstallerPackageName 145 | String getInstallerPackageName(in String packageName); 146 | 147 | 148 | 149 | # Example 7 (List all methods available for a given package): 150 | $ android-svc methods 'android.media.IAudioService' 151 | int trackPlayer(in PlayerBase.PlayerIdCard pic); 152 | oneway void playerAttributes(in int piid, in AudioAttributes attr); 153 | oneway void playerEvent(in int piid, in int event); 154 | oneway void releasePlayer(in int piid); 155 | oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,String callingPackage, String caller); 156 | void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage); 157 | void setStreamVolume(int streamType, int index, int flags, String callingPackage); 158 | boolean isStreamMute(int streamType); 159 | void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb); 160 | boolean isMasterMute(); 161 | void setMasterMute(boolean mute, int flags, String callingPackage, int userId); 162 | int getStreamVolume(int streamType); 163 | int getStreamMinVolume(int streamType); 164 | int getStreamMaxVolume(int streamType); 165 | int getLastAudibleStreamVolume(int streamType); 166 | void setMicrophoneMute(boolean on, String callingPackage, int userId); 167 | void setRingerModeExternal(int ringerMode, String caller); 168 | void setRingerModeInternal(int ringerMode, String caller); 169 | int getRingerModeExternal(); 170 | int getRingerModeInternal(); 171 | boolean isValidRingerMode(int ringerMode); 172 | void setVibrateSetting(int vibrateType, int vibrateSetting); 173 | int getVibrateSetting(int vibrateType); 174 | boolean shouldVibrate(int vibrateType); ROM, we can probably add support for detecting that by scanning its source code, just like I've done it for LineageOS. 175 | - Only tested with arm6 176 | void setMode(int mode, IBinder cb, String callingPackage); 177 | int getMode(); 178 | oneway void playSoundEffect(int effectType); 179 | oneway void playSoundEffectVolume(int effectType, float volume); 180 | boolean loadSoundEffects(); 181 | oneway void unloadSoundEffects(); 182 | oneway void reloadAudioSettings(); 183 | oneway void avrcpSupportsAbsoluteVolume(String address, boolean support); 184 | void setSpeakerphoneOn(boolean on); 185 | boolean isSpeakerphoneOn(); 186 | void setBluetoothScoOn(boolean on); 187 | void setBluetoothA2dpOn(boolean on); 188 | boolean isBluetoothA2dpOn(); 189 | int requestAudioFocus(in AudioAttributes aa, int durationHint, IBinder cb,IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,IAudioPolicyCallback pcb, int sdk); 190 | int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, in AudioAttributes aa,in String callingPackageName); 191 | void unregisterAudioFocusClient(String clientId); 192 | int getCurrentAudioFocus(); 193 | void startBluetoothSco(IBinder cb, int targetSdkVersion); 194 | void startBluetoothScoVirtualCall(IBinder cb); 195 | void stopBluetoothSco(IBinder cb); 196 | void forceVolumeControlStream(int streamType, IBinder cb); 197 | void setRingtonePlayer(IRingtonePlayer player); 198 | IRingtonePlayer getRingtonePlayer(); 199 | int getUiSoundsStreamType(); 200 | void setWiredDeviceConnectionState(int type, int state, String address, String name,String caller); 201 | int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile); 202 | void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device); 203 | AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer); 204 | boolean isCameraSoundForced(); 205 | void setVolumeController(in IVolumeController controller); 206 | void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible); 207 | boolean isStreamAffectedByRingerMode(int streamType); 208 | boolean isStreamAffectedByMute(int streamType); 209 | void disableSafeMediaVolume(String callingPackage); 210 | int setHdmiSystemAudioSupported(boolean on); 211 | boolean isHdmiSystemAudioSupported(); 212 | String registerAudioPolicy(in AudioPolicyConfig policyConfig,in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy,boolean isVolumeController); 213 | oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb); 214 | int addMixForPolicy(in AudioPolicyConfig policyConfig, in IAudioPolicyCallback pcb); 215 | int removeMixForPolicy(in AudioPolicyConfig policyConfig, in IAudioPolicyCallback pcb); 216 | int setFocusPropertiesForPolicy(int duckingBehavior, in IAudioPolicyCallback pcb); 217 | void setVolumePolicy(in VolumePolicy policy); 218 | void registerRecordingCallback(in IRecordingConfigDispatcher rcdb); 219 | oneway void unregisterRecordingCallback(in IRecordingConfigDispatcher rcdb); 220 | List getActiveRecordingConfigurations(); 221 | void registerPlaybackCallback(in IPlaybackConfigDispatcher pcdb); 222 | oneway void unregisterPlaybackCallback(in IPlaybackConfigDispatcher pcdb); 223 | List getActivePlaybackConfigurations(); 224 | void disableRingtoneSync(in int userId); 225 | int getFocusRampTimeMs(in int focusGain, in AudioAttributes attr); 226 | int dispatchFocusChange(in AudioFocusInfo afi, in int focusChange,in IAudioPolicyCallback pcb); 227 | oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio); 228 | int setBluetoothHearingAidDeviceConnectionState(in BluetoothDevice device,int state, boolean suppressNoisyIntent, int musicDevice); 229 | int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device,int state, int profile, boolean suppressNoisyIntent, int a2dpVolume); 230 | oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult,in IAudioPolicyCallback pcb); 231 | void registerAudioServerStateDispatcher(IAudioServerStateDispatcher asd); 232 | oneway void unregisterAudioServerStateDispatcher(IAudioServerStateDispatcher asd); 233 | boolean isAudioServerRunning(); 234 | ``` 235 | 236 | ## Further information 237 | In order to add support for certain binary data types, it will be necessary to create a custom build of Android's service utility. 238 | The official instructions on how to build that utility are incredibly time consuming and require downloading more than 600 GIGABYTES of source code. (If you read this in 2023 it has probably grown to >1TB of data.) 239 | I have thus been working on a minimal setup only requiring a couple of MB of storage and ended up with this: 240 | 241 | ``` Bash 242 | # Download required source code 243 | git clone https://android.googlesource.com/platform/superproject 244 | cd superproject 245 | git submodule init 246 | git submodule update frameworks/native 247 | git submodule update system/libbase 248 | git submodule update system/core 249 | git submodule update system/logging 250 | git submodule update bionic 251 | # Set required include directories in CPATH 252 | export CPATH="./frameworks/native/include:./system/libbase/include:./system/core/libcutils/include:./system/core/libutils/include:./system/logging/liblog/include:./system/core/libsystem/include:./frameworks/native/libs/binder/include:./bionic/libc/include" 253 | # Build 254 | g++ -DXP_UNIX -Wall -Werror -o service ./frameworks/native/cmds/service/service.cpp 255 | ``` 256 | 257 | (I'm not sure on how to cross compile yet.) 258 | 259 | ### Interesting code to further inspect in order to find the aidl files for packages that have missing data in `serivce list` 260 | (This is just a note for me) 261 | 262 | - https://github.com/aosp-mirror/platform_frameworks_base/blob/a4ddee215e41ea232340c14ef92d6e9f290e5174/core/java/android/content/Context.java#L4056 263 | - https://github.com/aosp-mirror/platform_frameworks_base/blob/master/media/java/android/media/IAudioService.aidl 264 | - https://android.googlesource.com/platform/frameworks/native/+/android-8.0.0_r36/services/audiomanager/IAudioManager.cpp 265 | - https://android.googlesource.com/platform/frameworks/native/+/android-8.0.0_r36/include/audiomanager/IAudioManager.h 266 | - https://github.com/aosp-mirror/platform_frameworks_base/blob/a4ddee215e41ea232340c14ef92d6e9f290e5174/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java#L66 267 | 268 | ## Credits 269 | Credits to @ktnr74 for his [get_android_service_call_numbers.sh](https://gist.github.com/ktnr74/ac6b34f11d1e781db089#file-get_android_service_call_numbers-sh) 270 | Credits to @bashenk for [his fork](https://gist.github.com/bashenk/b538ce0a60efe6a9c3b446683744d598#file-get_android_service_call_numbers-sh) of ktnr74's work, which adds some improvements. 271 | 272 | `android-svc` is based on that work, although most of the code has been rewritten and a lot of new features and fixes have been implemented. 273 | -------------------------------------------------------------------------------- /android-svc-lib.sh: -------------------------------------------------------------------------------- 1 | export g_ANDROID_SVC_LIB_VERSION=0.2.1 2 | export g_repoUrl="" 3 | export g_aidlFileList="" 4 | export g_shellType="" 5 | export g_adbSerial="" 6 | export g_fileCache="${HOME}/.android-svc-cache" 7 | 8 | export g_blue=$(printf '%s\n' '\033[0;34m' | sed -e 's/[\/&]/\\&/g') 9 | export g_red=$(printf '%s\n' '\033[0;31m' | sed -e 's/[\/&]/\\&/g') 10 | export g_green=$(printf '%s\n' '\033[0;32m' | sed -e 's/[\/&]/\\&/g') 11 | export g_yellow=$(printf '%s\n' '\033[0;33m' | sed -e 's/[\/&]/\\&/g') 12 | export g_nc=$(printf '%s\n' '\033[0m' | sed -e 's/[\/&]/\\&/g') # no color 13 | 14 | ### 15 | # Init g_repoUrl value 16 | # GLOBALS: 17 | # g_repoUrl will be filled by this method, and contains an url from https://github.com/aosp-mirror/platform_frameworks_base repository 18 | # RETURN: 19 | # nothing or exit the script with an error message 20 | Init () { 21 | g_repoUrl="${1-}" 22 | export g_aidlFileCache="${g_fileCache}/$(GetRomName)" 23 | if ! [ -n "$g_repoUrl" ]; then 24 | if [ -f "${g_fileCache}/REPO_URL" ]; then 25 | export g_repoUrl="$(cat "${g_fileCache}/REPO_URL")" 26 | else 27 | if [ -d "$g_aidlFileCache" ] && [ "$(ls -A "$g_aidlFileCache")" ]; then 28 | export g_repoUrl="OFFLINE_MODE" 29 | else 30 | export g_repoUrl="$(GetSourceRepoUrl)" 31 | fi 32 | fi 33 | fi 34 | [ -n "$g_repoUrl" ] || Exit 1 "Android source code repository URL was not provided and could not automatically be retrieved in Init" 35 | 36 | export g_aidlFileList="$(GetServiceAidlFileNames)" 37 | } 38 | 39 | DownloadSourceFiles () { 40 | l_sourceFileList="${1-}"; l_targetDir="${2-}" 41 | [ -n "$l_sourceFileList" ] || Exit 1 "Source file list was not provided in DownloadSourceFiles" 42 | [ -n "$l_targetDir" ] || Exit 1 "Target directory was not provided in DownloadSourceFiles" 43 | [ -n "$g_repoUrl" ] || Exit 1 "Android source code repository URL was empty in DownloadSourceFiles (Did you call Init first?)" 44 | 45 | while IFS= read -r aidlFile; do 46 | l_currentFile="${l_targetDir}/${aidlFile}" 47 | if ! [ -f "$l_currentFile" ]; then 48 | echo "Downloading '${aidlFile}'" 49 | mkdir -p "${l_currentFile%/*}" 50 | GetSourceFile "${aidlFile}" > "${l_currentFile}" 51 | else 52 | echo "Skipping '${aidlFile}' (already exists)" 53 | fi 54 | done <<< "$l_sourceFileList" 55 | echo "$g_repoUrl" > "${g_fileCache}/REPO_URL" 56 | } 57 | 58 | ### 59 | # Convert code like call to an service call syntax with argument 60 | # ARGUMENTS: 61 | # a call like 'com.android.internal.telephony.ITelephony.dial(\"555-0199\")'" 62 | # RETURN: 63 | # converted call ready to be pass to android shell 64 | ConvertServiceCallToShellCommand () { 65 | l_serviceCall="${1-}" 66 | [ -n "$l_serviceCall" ] || Exit 1 "Service call not provided in CallServiceMethod" 67 | 68 | l_fullCallPath=$(echo "$l_serviceCall" | cut -d'(' -f1) 69 | l_methodName=$(echo "$l_fullCallPath" | rev | cut -d'.' -f 1 | rev) 70 | l_serviceCodeName=$(echo "$l_fullCallPath" | rev | cut -d"." -f2- | rev) 71 | if [[ $l_serviceCodeName == *"."* ]]; then # A service package name was given 72 | l_servicePackageName="$l_serviceCodeName" 73 | l_serviceCodeName="$(GetServiceCodeName "$l_servicePackageName")" 74 | else # A service code name was given 75 | l_servicePackageName="$(GetServicePackageName "$l_serviceCodeName")" 76 | fi 77 | 78 | l_methodIndex="$(GetMethodIndex "${l_servicePackageName}" "${l_methodName}")" 79 | 80 | # Get data types for method parameters 81 | l_methodSignature="$(GetMethodSignature "$l_servicePackageName" "$l_methodName")" 82 | l_rawParamsDefiniton="$(echo "$l_methodSignature" | cut -d'(' -f2 | rev | cut -d')' -f2- | rev)" 83 | 84 | l_shellServiceCall="service call $l_serviceCodeName $l_methodIndex" 85 | 86 | if [ "$l_rawParamsDefiniton" != "" ]; then 87 | readarray -td, l_paramTypeArray <<<"$l_rawParamsDefiniton"; declare -p l_paramTypeArray > /dev/null 88 | for i in "${!l_paramTypeArray[@]}"; do 89 | l_paramTypeArray[i]=$(echo "${l_paramTypeArray[i]}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | rev | cut -d' ' -f2- | rev) 90 | #echo "Data type for argument $i: ${paramTypeArray[i]}" 91 | done 92 | 93 | # Get user given method arguments 94 | l_rawArgs=$(echo "$l_serviceCall" | cut -d'(' -f2 | rev | cut -d")" -f2- | rev) 95 | readarray -td, l_rawArgsArray <<<"$l_rawArgs"; declare -p l_rawArgsArray > /dev/null 96 | 97 | #echo "fullCallPath: $l_fullCallPath" 98 | #echo "serviceCodeName: $l_serviceCodeName" 99 | #echo "methodName: $l_methodName" 100 | #echo "rawArgs: $l_rawArgs" 101 | 102 | # Create "service call" from given input 103 | 104 | for i in "${!l_rawArgsArray[@]}"; do 105 | l_argument=$(echo "${l_rawArgsArray[i]}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | sed -e 's/^"//' -e 's/"$//') 106 | l_dataType=${l_paramTypeArray[i]} 107 | if [ "$l_dataType" == "boolean" ]; then 108 | if [ "$l_argument" == "true" ]; then 109 | l_argument=1 110 | elif [ "$l_argument" == "false" ]; then 111 | l_argument=0 112 | else 113 | Exit 1 "Parameter #$i of $l_methodName has to be of type '$l_dataType', but the value you provided was: '${l_argument}'!" 114 | fi 115 | elif [ "$l_dataType" == "int" ] && ! [[ $l_argument =~ ^\-?[0-9]+$ ]] ; then 116 | Exit 1 "Parameter #$i of $l_methodName has to be of type '$l_dataType', but the value you provided was: '${l_argument}'!" 117 | elif [ "$l_dataType" == "long" ] && ! [[ $l_argument =~ ^[0-9]+$ ]] ; then 118 | Exit 1 "Parameter #$i of $l_methodName has to be of type '$l_dataType', but the value you provided was: '${l_argument}'!" 119 | elif [ "$l_dataType" == "float" ] && ! [[ $l_argument =~ ^[0-9]+([.][0-9]+)?$ ]]; then 120 | Exit 1 "Parameter #$i of $l_methodName has to be of type '$l_dataType', but the value you provided was: '${l_argument}'!" 121 | elif [ "$l_dataType" == "double" ] && ! [[ $l_argument =~ ^[0-9]+([.][0-9]+)?$ ]]; then 122 | Exit 1 "Parameter #$i of $l_methodName has to be of type '$l_dataType', but the value you provided was: '${l_argument}'!" 123 | fi 124 | 125 | #echo "Index: '$i'" 126 | #echo "Data Type: '$dataType'" 127 | l_lowLevelDataType="$(ConvertDataType "${l_dataType}")" 128 | if [ "$l_lowLevelDataType" == "s16" ]; then 129 | l_shellServiceCall="${l_shellServiceCall} ${l_lowLevelDataType} '${l_argument}'" 130 | else 131 | l_shellServiceCall="${l_shellServiceCall} ${l_lowLevelDataType} ${l_argument}" 132 | fi 133 | #echo "Low Level Data Type: '$l_lowLevelDataType'" 134 | #echo "Argument: '$l_argument'" 135 | done 136 | fi 137 | 138 | #echo "Given call: $l_serviceCall" 139 | #echo "Method definition: $l_methodSignature" 140 | #echo "Shell command: $l_shellServiceCall" 141 | echo "$l_shellServiceCall" 142 | } 143 | 144 | CallServiceMethod () { 145 | l_serviceCall="${1-}" 146 | [ -n "$l_serviceCall" ] || Exit 1 "Service call not provided in CallServiceMethod" 147 | l_parcel="$(AndroidShell "$(ConvertServiceCallToShellCommand "${l_serviceCall}")")" 148 | 149 | l_fullCallPath=$(echo "$l_serviceCall" | cut -d'(' -f1) 150 | l_methodName=$(echo "$l_fullCallPath" | rev | cut -d'.' -f 1 | rev) 151 | l_serviceCodeName=$(echo "$l_fullCallPath" | rev | cut -d"." -f2- | rev) 152 | if [[ $l_serviceCodeName == *"."* ]]; then # A service package name was given 153 | l_servicePackageName="$l_serviceCodeName" 154 | l_serviceCodeName="$(GetServiceCodeName "$l_servicePackageName")" 155 | else # A service code name was given 156 | l_servicePackageName="$(GetServicePackageName "$l_serviceCodeName")" 157 | fi 158 | 159 | # Get data types for method parameters 160 | l_methodSignature="$(GetMethodSignature "$l_servicePackageName" "$l_methodName")" 161 | 162 | l_returnDataType="$(echo "$l_methodSignature" | cut -d'(' -f1 | rev | cut -d' ' -f2 | rev)" 163 | 164 | ParseParcel "${l_parcel}" "${l_returnDataType}" 165 | } 166 | 167 | ### 168 | # List available service package names from current or connected android devices 169 | # OUTPUTS: 170 | # services package names list 171 | GetServicePackageNames () { 172 | l_packageNames="$(AndroidShell 'service list' | grep -oP '(?<=\[).+(?=\])')" 173 | [ -n "$l_packageNames" ] || Exit 1 "Unable to find services in GetServicePackageNames" 174 | echo "$l_packageNames" 175 | } 176 | 177 | ### 178 | # Get service package name that implement a service name. 179 | # ARGUMENTS: 180 | # a service name like: input, gpu, display, batterystats... 181 | # OUTPUTS: 182 | # a service package name 183 | GetServicePackageName () { 184 | l_service="${1-}" 185 | [ -n "$l_service" ] || Exit 1 "Service name was not provided in GetServicePackageName" 186 | l_packageName=$(AndroidShell 'service list' | sed -e '1d' -e 's/[0-9]*[[:space:]]'"$l_service"': \[\(.*\)\]/\1/p' -e 'd') 187 | [ -n "$l_packageName" ] || Exit 1 "Unable to find service package name for '$l_service' in GetServicePackageName" 188 | echo "$l_packageName" 189 | } 190 | 191 | ### 192 | # get service name from a service package name. 193 | # ARGUMENTS: 194 | # a service name like: android.app.IUiModeManager, android.os.ISystemConfig, android.os.IPermissionController... 195 | # OUTPUTS: 196 | # a service name like: overlay, package, power.... 197 | GetServiceCodeName () { 198 | l_packageName="${1-}" 199 | [ -n "$l_packageName" ] || Exit 1 "Package name was not provided in GetServiceCodeName" 200 | l_codeName=$(AndroidShell 'service list' | sed -e '1d' -e 's/[0-9]*[[:space:]]\(.*\): \['"$l_packageName"'\]/\1/p' -e 'd') 201 | [ -n "$l_codeName" ] || Exit 1 "Unable to find service code name for '$l_packageName'" 202 | echo "$l_codeName" 203 | } 204 | 205 | GetMethodSignature () { 206 | l_packageName="${1-}"; l_methodName="${2-}" 207 | [ -n "$l_packageName" ] || Exit 1 "Package name was not provided in GetmethodSignature" 208 | [ -n "$l_methodName" ] || Exit 1 "Method name was not provided in GetmethodSignature" 209 | if [[ $l_packageName != *"."* ]]; then 210 | l_packageName="$(GetServicePackageName "$l_packageName")" 211 | fi 212 | l_methodSignature="$(GetMethodSignaturesForPackage "$l_packageName" | grep " ${l_methodName}(")" 213 | echo "${l_methodSignature}" 214 | } 215 | 216 | ### 217 | # List methods from a service package name. 218 | # the aidl file will be retieves from https://raw.githubusercontent.com/aosp-mirror/platform_frameworks_base/android-${version} 219 | # GLOBALS: 220 | # g_repoUrl filled by then Init method 221 | # g_aidlFileList list of aidl files filed by Init method 222 | # g_cacheDir if offline mode 223 | # ARGUMENTS: 224 | # a service name service name like: android.app.IUiModeManager, android.os.ISystemConfig, android.os.IPermissionController... 225 | # OUTPUTS: 226 | # list of aidl signatures. 227 | GetMethodSignaturesForPackage () { 228 | l_packageName="${1-}" 229 | [ -n "$l_packageName" ] || Exit 1 "Package name was not provided in GetMethodSignaturesForPackage" 230 | [ -n "$g_repoUrl" ] || Exit 1 "Android source code repository URL was empty in GetMethodSignaturesForPackage (Did you call Init first?)" 231 | [ -n "$g_aidlFileList" ] || Exit 1 "AIDL file list was empty in GetMethodSignaturesForPackage (Did you call Init first?)" 232 | 233 | l_packageFilePath="$(echo "$l_packageName" | tr '.' '/')\.aidl" 234 | l_servicePath="$(echo "$g_aidlFileList" | grep -m 1 "$l_packageFilePath\$")" 235 | 236 | if [ -f "${g_aidlFileCache}/${l_servicePath}" ]; then 237 | l_serviceSource="$(cat "${g_aidlFileCache}/${l_servicePath}")" 238 | else 239 | l_serviceSource="$(GetSourceFile "$l_servicePath")" 240 | fi 241 | 242 | echo "${l_serviceSource}" | sed -e '1,/interface/d' \ 243 | -e '/^$/d' \ 244 | -e 's/^[[:space:]]*//' \ 245 | -e '/^[^a-zA-Z]/d' \ 246 | -e '/^[^;]*$/{$!N}' \ 247 | -e '$d' \ 248 | -e 's/\(^\|\n\)[[:space:]]*\|\([[:space:]]\)\{2,\}/\2/g' | \ 249 | sed -e ':x;N;s/\([^;]\)\n/\1/;bx' | 250 | sed -e "s/ ,/, /g" | 251 | sed -E "s/,([^[:space:]])/, \1/g" 252 | } 253 | 254 | GetMethodIndex () { 255 | l_packageName="${1-}"; l_methodName="${2-}" 256 | [ -n "$l_packageName" ] || Exit 1 "Package name was not provided in GetmethodSignature" 257 | [ -n "$l_methodName" ] || Exit 1 "Method name was not provided in GetmethodSignature" 258 | l_methodindex="$(GetMethodSignaturesForPackage "$l_packageName" | cat -n | grep " ${l_methodName}(" | sed -e 's/^[[:space:]]*//' | cut -d$'\t' -f1)" 259 | echo "${l_methodindex}" 260 | } 261 | 262 | GetAndroidVersion () { 263 | l_androidVersion="$(AndroidShell 'getprop ro.build.version.release')" 264 | [ -n "$l_androidVersion" ] || Exit 1 "Failed to retrieve Android version in GetAndroidVersion" 265 | echo "${l_androidVersion}" 266 | } 267 | 268 | GetRomName () { 269 | l_lineageVersion="$(AndroidShell 'getprop ro.lineage.build.version')" 270 | if [ "$l_lineageVersion" != "" ]; then 271 | echo "lineage/${l_lineageVersion}" 272 | else 273 | l_androidVersion="$(GetAndroidVersion)" 274 | echo "stock/${l_androidVersion}" 275 | fi 276 | } 277 | 278 | GetSourceRepoUrl () { 279 | l_androidVersion="$(GetAndroidVersion)" 280 | l_lineageVersion="$(AndroidShell 'getprop ro.lineage.build.version')" 281 | if [ "${l_lineageVersion}" != "" ]; then 282 | #l_branches="$(git ls-remote https://github.com/LineageOS/android_frameworks_base.git | cut -d'/' -f3 | cut -d'^' -f1 | grep lineage-)" 283 | l_repoUrl="https://raw.githubusercontent.com/LineageOS/android_frameworks_base/lineage-${l_lineageVersion}" 284 | elif [ "${l_androidVersion}" != "" ]; then 285 | l_tag="$(git ls-remote --tags --sort="v:refname" https://android.googlesource.com/platform/frameworks/base.git | cut -d'/' -f3 | cut -d'^' -f1 | grep android-${l_androidVersion} | tail -1)" 286 | l_repoUrl="https://raw.githubusercontent.com/aosp-mirror/platform_frameworks_base/${l_tag}" 287 | fi 288 | l_repoStatus="$(curl -s -o /dev/null -w "%{http_code}" "${l_repoUrl}/Android.bp")" 289 | if [ "$l_repoStatus" != "200" ]; then 290 | Exit 1 "Failed to find a working Android source code repository for this Android version / ROM in GetSourceRepoUrl (HTTP status '${l_repoStatus}' for '${l_repoUrl}')" 291 | fi 292 | echo "$l_repoUrl" 293 | } 294 | 295 | ### 296 | # Get file content from repo 297 | # GLOBALS: 298 | # g_repoUrl filled by then Init method 299 | # ARGUMENTS: 300 | # $1 file to download 301 | # RETURN: 302 | # File content 303 | GetSourceFile () { 304 | l_file="${1-}" 305 | [ -n "$l_file" ] || Exit 1 "File was not provided in GetSourceFile" 306 | [ -n "$g_repoUrl" ] || Exit 1 "Android source code repository URL was empty in GetSourceFile (Did you call Init first?)" 307 | wget -qO - "$g_repoUrl/$l_file" 308 | } 309 | 310 | ### 311 | # list all aidl files 312 | # GLOBALS: 313 | # g_repoUrl filled by then Init method contains an url like https://raw.githubusercontent.com/aosp-mirror/platform_frameworks_base/android-11.0.0_r35 314 | # OUTPUTS: 315 | # path to all aidl files in the current branch 316 | GetServiceAidlFileNames () { 317 | [ -n "$g_repoUrl" ] || Exit 1 "Android source code repository URL was empty in GetServiceAidlFileNames (Did you call Init first?)" 318 | # l_githubUser should contains a gitHub user 319 | l_githubUser="$(echo "$g_repoUrl" | cut -d'/' -f 4)" 320 | # l_githubProject should contains the gitHub project name 321 | l_githubProject="$(echo "$g_repoUrl" | cut -d'/' -f 5)" 322 | # l_branch should contains android-XX.Y.Z-revision 323 | l_branch="$(echo "$g_repoUrl" | cut -d'/' -f 6)" 324 | # build github api to list all files in a branch 325 | l_recursiveFileTreeUrl="https://api.github.com/repos/${l_githubUser}/${l_githubProject}/git/trees/${l_branch}?recursive=1" 326 | 327 | 328 | if [ -d "$g_aidlFileCache" ] && [ "$(ls -A "$g_aidlFileCache")" ]; then 329 | find "$g_aidlFileCache" -type f -printf "%P\n" | sort -u 330 | return 331 | fi 332 | 333 | # Extract all .aidl file paths from the recursive file tree: 334 | curl -s "${l_recursiveFileTreeUrl}" | jq -r '.tree|map(.path|select(test("\\.aidl")))[]' | sort -u 335 | } 336 | 337 | ### 338 | # get android service datatype from aidl datatype. 339 | # ARGUMENTS: 340 | # $1 aidl parameter datatype (int, long, float...) 341 | # RETURN: 342 | # android service call datatype (i32, i64, f...) 343 | ConvertDataType () { 344 | # Note: I don't know if this conversion (especially for different architectures like arm64) would always be accurate! 345 | # Note 2: I have no clue how you could pass or convert Arrays, Lists, Objects, Maps, etc. 346 | #By default, AIDL supports the following data types: 347 | # 348 | # All primitive types in the Java programming language (such as int, long, char, boolean, and so on) 349 | # Arrays of primitive types such as int[] 350 | # String 351 | # CharSequence 352 | # List 353 | # 354 | # All elements in the List must be one of the supported data types in this list or one of the other AIDL-generated interfaces or parcelables you've declared. A List may optionally be used as a parameterized type class (for example, List). The actual concrete class that the other side receives is always an ArrayList, although the method is generated to use the List interface. 355 | # Map 356 | # 357 | # All elements in the Map must be one of the supported data types in this list or one of the other AIDL-generated interfaces or parcelables you've declared. Parameterized type maps, (such as those of the form Map) are not supported. The actual concrete class that the other side receives is always a HashMap, although the method is generated to use the Map interface. Consider using a Bundle as an alternative to Map. 358 | l_dataType="${1-}" 359 | [ -n "$l_dataType" ] || Exit 1 "Data type was not provided in ConvertDataType" 360 | if [ "$l_dataType" = "void" ] || [ "$l_dataType" = "oneway void" ]; then 361 | echo "" 362 | elif [ "$l_dataType" = "int" ] || [ "$l_dataType" = "in int" ] || [ "$l_dataType" = "boolean" ] || [ "$l_dataType" = "in boolean" ] || [ "$l_dataType" = "char" ] || [ "$l_dataType" = "in char" ]; then 363 | echo "i32" 364 | elif [ "$l_dataType" = "long" ] || [ "$l_dataType" = "in long" ]; then 365 | echo "i64" 366 | elif [ "$l_dataType" = "String" ] || [ "$l_dataType" = "in String" ]; then 367 | echo "s16" 368 | elif [ "$l_dataType" = "float" ] || [ "$l_dataType" = "in float" ]; then 369 | echo "f" 370 | elif [ "$l_dataType" = "double" ] || [ "$l_dataType" = "in double" ]; then 371 | echo "d" 372 | else 373 | echo "i64" 374 | fi 375 | } 376 | 377 | ### 378 | # Extract data from Parcel. 379 | # ARGUMENTS: 380 | # $1 parcel formated by service call (Hexa + text) 381 | # RETURN: 382 | # parcel hexadecimal data. 383 | GetParcelDataAsHex () { 384 | l_parcel="${1-}" 385 | [ -n "$l_parcel" ] || Exit 1 "Parcel not provided in GetParcelDataAsHex" 386 | if [ "$(echo "$l_parcel" | head -1)" == "Result: Parcel(" ]; then 387 | l_parcelHexData="$(echo "$l_parcel" | grep 0x | cut -d: -f2- | cut -d\' -f1 | tr -d [:space:])" 388 | else 389 | l_parcelHexData="$(echo "$l_parcel" | tr -d ' ' | cut -d'(' -f2- | cut -d"'" -f1 | tr -d '\n')" 390 | fi 391 | echo "$l_parcelHexData" 392 | } 393 | 394 | ### 395 | # TODO 396 | # ARGUMENTS: 397 | # $1 parcel formated by service call (Hexa + text) 398 | # $2 expected returned type (boolean, int, float, String, void) 399 | # RETURN: 400 | # 401 | ParseParcel () { 402 | l_parcel="${1-}"; l_dataType="${2-}" 403 | [ -n "$l_parcel" ] || Exit 1 "Parcel not provided in ParseParcel" 404 | [ -n "$l_dataType" ] || Exit 1 "Data type not provided in ParseParcel" 405 | l_hexData="$(GetParcelDataAsHex "$l_parcel")" 406 | if [ "$l_dataType" == "boolean" ]; then 407 | if [ "$l_hexData" == "0000000000000000" ]; then 408 | echo false 409 | elif [ "$l_hexData" == "0000000000000001" ]; then 410 | echo true 411 | else 412 | Exit 1 "Error trying to determine boolean value in ParseParcel (Got '$l_hexData')" 413 | fi 414 | elif [ "$l_dataType" == "int" ] || [ "$l_dataType" == "long" ]; then 415 | echo "$((0x${l_hexData}))" 416 | elif [ "$l_dataType" == "float" ]; then 417 | echo "$((0x${l_hexData}))" 418 | elif [ "$l_dataType" == "String" ]; then 419 | echo "$l_hexData" | fold -w8 | sed -E 's/(.{2})(.{2})(.{2})(.{2})/\4\3\2\1/' | xxd -r -p | tr -d '\0' 420 | elif [ "$l_dataType" == "void" ]; then 421 | : # Do nothing 422 | else 423 | echo "Decoding Parcels of type '${l_dataType}' is not yet supported!" 424 | echo "Any help with this would be appreciated!" 425 | echo "Here is the raw Parcel response:" 426 | echo "${l_parcel}" 427 | fi 428 | } 429 | 430 | ### 431 | # TODO 432 | # GLOBALS: 433 | # 434 | # ARGUMENTS: 435 | # $1 436 | # RETURN: 437 | # 438 | AidlSyntaxHighlight () { 439 | l_code="${1-}" 440 | [ -n "$l_code" ] || Exit 1 "No code was provided in AidlSyntaxHighlight" 441 | 442 | l_codeInput="$(echo "${l_code}" | sed -e "s/()/(a a)/g")" 443 | 444 | l_highlightedCode="$(echo -e "${l_codeInput}" | 445 | sed ' 446 | s/^ *\([^(,)]\+\) \+\([^ (,)]\+\)\([(,)]\)\(.*\)/\4\n{FUNCRET}\1{END} {FUNCNAME}\2{END}{SEP}\3{END}/; 447 | h; 448 | :a; { 449 | /^ *\([^(,)]\+\) \+\([^ (,)]\+\)\([(,)]\)\([^\n]*\)\n\(.*\)$/{ 450 | s//\4\n\5{PARTYPE}\1{END} {PARNAME}\2{END}{SEP}\3{END}/ 451 | b a 452 | } 453 | } 454 | s/^;\(.*\)/\1{SEP};{END}/ 455 | ' | sed -e '/^$/d' \ 456 | -e "s/,/, /g" \ 457 | -e "s/{PARTYPE}a{END} {PARNAME}a{END}//g" \ 458 | -e "s/{FUNCRET}/${g_blue}/g" \ 459 | -e "s/{FUNCNAME}/${g_red}/g" \ 460 | -e "s/{PARTYPE}/${g_blue}/g" \ 461 | -e "s/{PARNAME}/${g_green}/g" \ 462 | -e "s/{SEP}/${g_yellow}/g" \ 463 | -e "s/{END}/${g_nc}/g")" 464 | echo "${l_highlightedCode}" 465 | } 466 | 467 | ### 468 | # Set binary name to call to access android device. 469 | # get first android serial. 470 | # GLOBALS: 471 | # g_shellType "adb" or "" for on device call 472 | # g_adbSerial is modified to contains android serial id 473 | # ARGUMENTS: 474 | # $1 adb 475 | # RETURN: 476 | # nothing or exit the script with an error message 477 | SetShellType () { 478 | g_shellType="${1-}" 479 | [ -n "$g_shellType" ] || Exit 1 "Shell type was not provided in SetShellType" 480 | if [ "$g_shellType" == "adb" ]; then 481 | g_adbSerial="${g_adbSerial:-$(adb devices | sed -e '1d' -e '2s/[[:space:]].*//' -e 'q')}" 482 | fi 483 | } 484 | 485 | ### 486 | # Select a specific android device by serial id 487 | # ARGUMENTS: 488 | # $1 serial 489 | # RETURN: 490 | # nothing or exit the script with an error message 491 | SetAdbDevice () { 492 | g_adbSerial="${1-}" 493 | [ -n "$g_adbSerial" ] || Exit 1 "Adb device was not provided in SetAdbDevice" 494 | } 495 | 496 | ### 497 | # Execute an adb shell command. 498 | # GLOBALS: 499 | # g_shellType defined by SetShellType, can be adb or empty for on self device call 500 | # g_adbSerial android serial number defined by SetShellType or SetShellType 501 | # ARGUMENTS: 502 | # $1 command to execute 503 | # RETURN: 504 | # adb shell result 505 | AndroidShell () { 506 | if [ "$g_shellType" == "adb" ]; then 507 | adb ${g_adbSerial:+-s $g_adbSerial} shell -T -x "$1" | tr -d '\r' 508 | else 509 | su -c "$1" 510 | fi 511 | } 512 | 513 | ### 514 | # Assert needed binary are availables. 515 | # ARGUMENTS: 516 | # $# list of needed binary 517 | # RETURN: 518 | # nothing or exit the script with an error message 519 | AssertExecutablesAreAvailable () { 520 | l_bins= 521 | for b in "$@"; do [ -n "$(command -v "$b" 2>/dev/null)" ] || l_bins="${l_bins-}$b "; done 522 | if [ ${#l_bins} -eq 0 ]; then return 0; fi 523 | printf '%s\n' "Cannot find the following executables:" "$l_bins" 524 | Exit 1 "Exiting due to insufficient dependencies" 525 | } 526 | 527 | ### 528 | # Exit script with the given code and error message. 529 | # ARGUMENTS: 530 | # [$1] error code (Optional) 531 | # [$2] error message (Optional) 532 | # RETURN: 533 | # Exit the script 534 | Exit () { 535 | if [ $# -ge 1 ] && [ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null; then exitCode="$1"; shift; fi 536 | if [ $# -ge 1 ] ; then printf '%s\n' "$@" 1>&2; fi 537 | if (return 0 2>/dev/null); then # file was sourced rather than run 538 | kill -INT 0 # Ensures exiting of the script, even from nested subshells 539 | else 540 | exit "${exitCode:-1}" 541 | fi 542 | } 543 | 544 | AssertExecutablesAreAvailable 'bash' 'git' 'wget' 'tr' 'sed' 'cut' 'grep' 'head' 'tail' 'printf' 'cat' 'sort' 'rev' 'xxd' 'jq' 545 | --------------------------------------------------------------------------------