├── .gitignore ├── LICENSE ├── MANIFEST ├── README.md ├── demo ├── android_native_ui │ ├── assets │ │ └── image │ │ │ ├── icon.png │ │ │ ├── icon_transparent.png │ │ │ ├── splash.png │ │ │ └── wallpaper.jpg │ ├── buildozer.spec │ └── main.py └── kivy_ui │ ├── assets │ ├── audio │ │ └── Egedege.mp3 │ ├── image │ │ ├── ic_kvdroid.png │ │ ├── icon.png │ │ ├── icon_transparent.png │ │ ├── splash.png │ │ └── wallpaper.jpg │ └── json │ │ └── widget.json │ ├── behavior │ └── __init__.py │ ├── buildozer.spec │ ├── kvdroid.kv │ ├── main.py │ └── tools │ └── __init__.py ├── kvdroid ├── __init__.py ├── app.py ├── base.py ├── cast.py ├── event.py ├── jclass │ ├── __init__.py │ ├── android │ │ ├── R.py │ │ ├── __init__.py │ │ ├── app │ │ │ └── __init__.py │ │ ├── content │ │ │ ├── __init__.py │ │ │ ├── pm.py │ │ │ └── res.py │ │ ├── graphics │ │ │ ├── __init__.py │ │ │ └── drawable.py │ │ ├── hardware │ │ │ ├── __init__.py │ │ │ └── camera2 │ │ │ │ ├── __init__.py │ │ │ │ └── params.py │ │ ├── location │ │ │ └── __init__.py │ │ ├── media │ │ │ └── __init__.py │ │ ├── net │ │ │ ├── __init__.py │ │ │ └── wifi │ │ │ │ └── __init__.py │ │ ├── os │ │ │ ├── __init__.py │ │ │ └── ext.py │ │ ├── provider │ │ │ └── __init__.py │ │ ├── speech │ │ │ ├── __init__.py │ │ │ └── tts.py │ │ ├── support │ │ │ ├── __init__.py │ │ │ └── v4 │ │ │ │ ├── __init__.py │ │ │ │ └── app.py │ │ ├── telephony │ │ │ └── __init__.py │ │ ├── text │ │ │ ├── __init__.py │ │ │ └── format.py │ │ ├── util │ │ │ └── __init__.py │ │ ├── view │ │ │ └── __init__.py │ │ ├── webkit │ │ │ └── __init__.py │ │ └── widget │ │ │ └── __init__.py │ ├── androidx │ │ ├── __init__.py │ │ ├── activity │ │ │ ├── __init__.py │ │ │ └── result │ │ │ │ ├── __init__.py │ │ │ │ └── contract.py │ │ ├── appcompat │ │ │ ├── __init__.py │ │ │ └── app.py │ │ ├── browser │ │ │ ├── __init__.py │ │ │ └── customtabs.py │ │ └── core │ │ │ ├── __init__.py │ │ │ ├── app.py │ │ │ ├── content │ │ │ └── __init__.py │ │ │ └── view.py │ ├── java │ │ ├── __init__.py │ │ ├── io.py │ │ ├── lang.py │ │ ├── net.py │ │ ├── text.py │ │ └── util.py │ └── org │ │ ├── __init__.py │ │ ├── json.py │ │ └── kivy │ │ ├── __init__.py │ │ └── android │ │ └── __init__.py ├── jinterface │ ├── __init__.py │ ├── activity.py │ └── media.py ├── tools │ ├── __init__.py │ ├── appsource.py │ ├── audio.py │ ├── broadcast.py │ ├── call.py │ ├── camera.py │ ├── contact.py │ ├── darkmode.py │ ├── deviceinfo.py │ ├── email.py │ ├── font.py │ ├── graphics.py │ ├── iso.py │ ├── lang.py │ ├── metrics.py │ ├── network.py │ ├── notification.py │ ├── package.py │ ├── path.py │ ├── photo_picker.py │ ├── sms.py │ ├── uri.py │ └── webkit.py └── widget │ └── __init__.py ├── poetry.lock ├── pyproject.toml └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) CORPORATE_NAME 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | __init__.py 3 | kvdroid.py 4 | setup.py 5 | -------------------------------------------------------------------------------- /demo/android_native_ui/assets/image/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/android_native_ui/assets/image/icon.png -------------------------------------------------------------------------------- /demo/android_native_ui/assets/image/icon_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/android_native_ui/assets/image/icon_transparent.png -------------------------------------------------------------------------------- /demo/android_native_ui/assets/image/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/android_native_ui/assets/image/splash.png -------------------------------------------------------------------------------- /demo/android_native_ui/assets/image/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/android_native_ui/assets/image/wallpaper.jpg -------------------------------------------------------------------------------- /demo/android_native_ui/buildozer.spec: -------------------------------------------------------------------------------- 1 | [app] 2 | 3 | # (str) Title of your application 4 | title = KvDroid Native 5 | 6 | # (str) Package name 7 | package.name = kvdroid 8 | 9 | # (str) Package domain (needed for android/ios packaging) 10 | package.domain = org.kvdroid 11 | 12 | # (str) Source code where the main.py live 13 | source.dir = . 14 | 15 | # (list) Source files to include (let empty to include all the files) 16 | source.include_exts = py,png,jpg,kv,atlas 17 | 18 | # (list) List of inclusions using pattern matching 19 | #source.include_patterns = assets/*,images/*.png 20 | 21 | # (list) Source files to exclude (let empty to not exclude anything) 22 | #source.exclude_exts = spec 23 | 24 | # (list) List of directory to exclude (let empty to not exclude anything) 25 | #source.exclude_dirs = tests, bin, venv 26 | 27 | # (list) List of exclusions using pattern matching 28 | # Do not prefix with './' 29 | #source.exclude_patterns = license,images/*/*.jpg 30 | 31 | # (str) Application versioning (method 1) 32 | version = 0.1 33 | 34 | # (str) Application versioning (method 2) 35 | # version.regex = __version__ = ['"](.*)['"] 36 | # version.filename = %(source.dir)s/main.py 37 | 38 | # (list) Application requirements 39 | # comma separated e.g. requirements = sqlite3,kivy 40 | requirements = python3,https://github.com/kengoon/KvDroid/archive/refs/heads/master.zip 41 | 42 | # (str) Custom source folders for requirements 43 | # Sets custom source for any requirements with recipes 44 | # requirements.source.kivy = ../../kivy 45 | 46 | # (str) Presplash of the application 47 | presplash.filename = %(source.dir)s/assets/image/splash.png 48 | 49 | # (str) Icon of the application 50 | icon.filename = %(source.dir)s/assets/image/icon.png 51 | 52 | # (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) 53 | orientation = portrait 54 | 55 | # (list) List of service to declare 56 | #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY 57 | 58 | # 59 | # OSX Specific 60 | # 61 | 62 | # 63 | # author = © Copyright Info 64 | 65 | # change the major version of python used by the app 66 | osx.python_version = 3 67 | 68 | # Kivy version to use 69 | osx.kivy_version = 1.9.1 70 | 71 | # 72 | # Android specific 73 | # 74 | 75 | # (bool) Indicate if the application should be fullscreen or not 76 | fullscreen = 0 77 | 78 | # (string) Presplash background color (for android toolchain) 79 | # Supported formats are: #RRGGBB #AARRGGBB or one of the following names: 80 | # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, 81 | # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, 82 | # olive, purple, silver, teal. 83 | android.presplash_color = #FFFFFF 84 | 85 | # (string) Presplash animation using Lottie format. 86 | # see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/ 87 | # for general documentation. 88 | # Lottie files can be created using various tools, like Adobe After Effect or Synfig. 89 | #android.presplash_lottie = "path/to/lottie/file.json" 90 | 91 | # (str) Adaptive icon of the application (used if Android API level is 26+ at runtime) 92 | #icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png 93 | #icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png 94 | 95 | # (list) Permissions 96 | android.permissions = INTERNET 97 | 98 | # (list) features (adds uses-feature -tags to manifest) 99 | #android.features = android.hardware.usb.host 100 | 101 | # (int) Target Android API, should be as high as possible. 102 | android.api = 31 103 | 104 | # (int) Minimum API your APK / AAB will support. 105 | #android.minapi = 21 106 | 107 | # (int) Android SDK version to use 108 | #android.sdk = 20 109 | 110 | # (str) Android NDK version to use 111 | android.ndk = 23b 112 | 113 | # (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi. 114 | #android.ndk_api = 21 115 | 116 | # (bool) Use --private data storage (True) or --dir public storage (False) 117 | #android.private_storage = True 118 | 119 | # (str) Android NDK directory (if empty, it will be automatically downloaded.) 120 | #android.ndk_path = 121 | 122 | # (str) Android SDK directory (if empty, it will be automatically downloaded.) 123 | android.sdk_path = ~/Android/Sdk 124 | 125 | # (str) ANT directory (if empty, it will be automatically downloaded.) 126 | #android.ant_path = 127 | 128 | # (bool) If True, then skip trying to update the Android sdk 129 | # This can be useful to avoid excess Internet downloads or save time 130 | # when an update is due and you just want to test/build your package 131 | android.skip_update = False 132 | 133 | # (bool) If True, then automatically accept SDK license 134 | # agreements. This is intended for automation only. If set to False, 135 | # the default, you will be shown the license when first running 136 | # buildozer. 137 | android.accept_sdk_license = True 138 | 139 | # (str) Android entry point, default is ok for Kivy-based app 140 | #android.entrypoint = org.kivy.android.PythonActivity 141 | 142 | # (str) Full name including package path of the Java class that implements Android Activity 143 | # use that parameter together with android.entrypoint to set custom Java class instead of PythonActivity 144 | #android.activity_class_name = org.kivy.android.PythonActivity 145 | 146 | # (str) Extra xml to write directly inside the element of AndroidManifest.xml 147 | # use that parameter to provide a filename from where to load your custom XML code 148 | #android.extra_manifest_xml = ./src/android/extra_manifest.xml 149 | 150 | # (str) Extra xml to write directly inside the tag of AndroidManifest.xml 151 | # use that parameter to provide a filename from where to load your custom XML arguments: 152 | #android.extra_manifest_application_arguments = ./src/android/extra_manifest_application_arguments.xml 153 | 154 | # (str) Full name including package path of the Java class that implements Python Service 155 | # use that parameter to set custom Java class instead of PythonService 156 | #android.service_class_name = org.kivy.android.PythonService 157 | 158 | # (str) Android app theme, default is ok for Kivy-based app 159 | # android.apptheme = "@android:style/Theme.NoTitleBar" 160 | 161 | # (list) Pattern to whitelist for the whole project 162 | #android.whitelist = 163 | 164 | # (str) Path to a custom whitelist file 165 | #android.whitelist_src = 166 | 167 | # (str) Path to a custom blacklist file 168 | #android.blacklist_src = 169 | 170 | # (list) List of Java .jar files to add to the libs so that pyjnius can access 171 | # their classes. Don't add jars that you do not need, since extra jars can slow 172 | # down the build process. Allows wildcards matching, for example: 173 | # OUYA-ODK/libs/*.jar 174 | #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar 175 | 176 | # (list) List of Java files to add to the android project (can be java or a 177 | # directory containing the files) 178 | #android.add_src = 179 | 180 | # (list) Android AAR archives to add 181 | #android.add_aars = 182 | 183 | # (list) Put these files or directories in the apk assets directory. 184 | # Either form may be used, and assets need not be in 'source.include_exts'. 185 | # 1) android.add_assets = source_asset_relative_path 186 | # 2) android.add_assets = source_asset_path:destination_asset_relative_path 187 | #android.add_assets = 188 | 189 | # (list) Gradle dependencies to add 190 | android.gradle_dependencies = androidx.appcompat:appcompat:1.2.0, androidx.browser:browser:1.4.0 191 | 192 | # (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies' 193 | # contains an 'androidx' package, or any package from Kotlin source. 194 | # android.enable_androidx requires android.api >= 28 195 | android.enable_androidx = True 196 | 197 | # (list) add java compile options 198 | # this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option 199 | # see https://developer.android.com/studio/write/java8-support for further information 200 | # android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8" 201 | 202 | # (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies} 203 | # please enclose in double quotes 204 | # e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }" 205 | #android.add_gradle_repositories = 206 | 207 | # (list) packaging options to add 208 | # see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html 209 | # can be necessary to solve conflicts in gradle_dependencies 210 | # please enclose in double quotes 211 | # e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'" 212 | #android.add_packaging_options = 213 | 214 | # (list) Java classes to add as activities to the manifest. 215 | #android.add_activities = com.example.ExampleActivity 216 | 217 | # (str) OUYA Console category. Should be one of GAME or APP 218 | # If you leave this blank, OUYA support will not be enabled 219 | #android.ouya.category = GAME 220 | 221 | # (str) Filename of OUYA Console icon. It must be a 732x412 png image. 222 | #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png 223 | 224 | # (str) XML file to include as an intent filters in tag 225 | #android.manifest.intent_filters = 226 | 227 | # (str) launchMode to set for the main activity 228 | #android.manifest.launch_mode = standard 229 | 230 | # (list) Android additional libraries to copy into libs/armeabi 231 | #android.add_libs_armeabi = libs/android/*.so 232 | #android.add_libs_armeabi_v7a = libs/android-v7/*.so 233 | #android.add_libs_arm64_v8a = libs/android-v8/*.so 234 | #android.add_libs_x86 = libs/android-x86/*.so 235 | #android.add_libs_mips = libs/android-mips/*.so 236 | 237 | # (bool) Indicate whether the screen should stay on 238 | # Don't forget to add the WAKE_LOCK permission if you set this to True 239 | #android.wakelock = False 240 | 241 | # (list) Android application meta-data to set (key=value format) 242 | #android.meta_data = 243 | 244 | # (list) Android library project to add (will be added in the 245 | # project.properties automatically.) 246 | #android.library_references = 247 | 248 | # (list) Android shared libraries which will be added to AndroidManifest.xml using tag 249 | #android.uses_library = 250 | 251 | # (str) Android logcat filters to use 252 | android.logcat_filters = *:S python:D 253 | 254 | # (bool) Android logcat only display log for activity's pid 255 | #android.logcat_pid_only = False 256 | 257 | # (str) Android additional adb arguments 258 | #android.adb_args = -H host.docker.internal 259 | 260 | # (bool) Copy library instead of making a libpymodules.so 261 | #android.copy_libs = 1 262 | 263 | # (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 264 | # In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time. 265 | android.archs = arm64-v8a, armeabi-v7a 266 | 267 | # (int) overrides automatic versionCode computation (used in build.gradle) 268 | # this is not the same as app version and should only be edited if you know what you're doing 269 | # android.numeric_version = 1 270 | 271 | # (bool) enables Android auto backup feature (Android API >=23) 272 | android.allow_backup = True 273 | 274 | # (str) XML file for custom backup rules (see official auto backup documentation) 275 | # android.backup_rules = 276 | 277 | # (str) If you need to insert variables into your AndroidManifest.xml file, 278 | # you can do so with the manifestPlaceholders property. 279 | # This property takes a map of key-value pairs. (via a string) 280 | # Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] 281 | # android.manifest_placeholders = [:] 282 | 283 | # (bool) disables the compilation of py to pyc/pyo files when packaging 284 | # android.no-compile-pyo = True 285 | 286 | # (str) The format used to package the app for release mode (aab or apk). 287 | android.release_artifact = apk 288 | 289 | # 290 | # Python for android (p4a) specific 291 | # 292 | 293 | # (str) python-for-android URL to use for checkout 294 | #p4a.url = 295 | 296 | # (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy) 297 | #p4a.fork = kivy 298 | 299 | # (str) python-for-android branch to use, defaults to master 300 | p4a.branch = develop 301 | 302 | # (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch 303 | #p4a.commit = HEAD 304 | 305 | # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) 306 | #p4a.source_dir = 307 | 308 | # (str) The directory in which python-for-android should look for your own build recipes (if any) 309 | #p4a.local_recipes = 310 | 311 | # (str) Filename to the hook for p4a 312 | #p4a.hook = 313 | 314 | # (str) Bootstrap to use for android builds 315 | # p4a.bootstrap = sdl2 316 | 317 | # (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask) 318 | #p4a.port = 319 | 320 | # Control passing the --use-setup-py vs --ignore-setup-py to p4a 321 | # "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not 322 | # Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py 323 | # NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate 324 | # setup.py if you're using Poetry, but you need to add "toml" to source.include_exts. 325 | #p4a.setup_py = false 326 | 327 | # (str) extra command line arguments to pass when invoking pythonforandroid.toolchain 328 | #p4a.extra_args = 329 | 330 | 331 | # 332 | # iOS specific 333 | # 334 | 335 | # (str) Path to a custom kivy-ios folder 336 | #ios.kivy_ios_dir = ../kivy-ios 337 | # Alternately, specify the URL and branch of a git checkout: 338 | ios.kivy_ios_url = https://github.com/kivy/kivy-ios 339 | ios.kivy_ios_branch = master 340 | 341 | # Another platform dependency: ios-deploy 342 | # Uncomment to use a custom checkout 343 | #ios.ios_deploy_dir = ../ios_deploy 344 | # Or specify URL and branch 345 | ios.ios_deploy_url = https://github.com/phonegap/ios-deploy 346 | ios.ios_deploy_branch = 1.10.0 347 | 348 | # (bool) Whether or not to sign the code 349 | ios.codesign.allowed = false 350 | 351 | # (str) Name of the certificate to use for signing the debug version 352 | # Get a list of available identities: buildozer ios list_identities 353 | #ios.codesign.debug = "iPhone Developer: ()" 354 | 355 | # (str) The development team to use for signing the debug version 356 | #ios.codesign.development_team.debug = 357 | 358 | # (str) Name of the certificate to use for signing the release version 359 | #ios.codesign.release = %(ios.codesign.debug)s 360 | 361 | # (str) The development team to use for signing the release version 362 | #ios.codesign.development_team.release = 363 | 364 | # (str) URL pointing to .ipa file to be installed 365 | # This option should be defined along with `display_image_url` and `full_size_image_url` options. 366 | #ios.manifest.app_url = 367 | 368 | # (str) URL pointing to an icon (57x57px) to be displayed during download 369 | # This option should be defined along with `app_url` and `full_size_image_url` options. 370 | #ios.manifest.display_image_url = 371 | 372 | # (str) URL pointing to a large icon (512x512px) to be used by iTunes 373 | # This option should be defined along with `app_url` and `display_image_url` options. 374 | #ios.manifest.full_size_image_url = 375 | 376 | 377 | [buildozer] 378 | 379 | # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) 380 | log_level = 2 381 | 382 | # (int) Display warning if buildozer is run as root (0 = False, 1 = True) 383 | warn_on_root = 1 384 | 385 | # (str) Path to build artifact storage, absolute or relative to spec file 386 | # build_dir = ./.buildozer 387 | 388 | # (str) Path to build output (i.e. .apk, .aab, .ipa) storage 389 | # bin_dir = ./bin 390 | 391 | # ----------------------------------------------------------------------------- 392 | # List as sections 393 | # 394 | # You can define all the "list" as [section:key]. 395 | # Each line will be considered as a option to the list. 396 | # Let's take [app] / source.exclude_patterns. 397 | # Instead of doing: 398 | # 399 | #[app] 400 | #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* 401 | # 402 | # This can be translated into: 403 | # 404 | #[app:source.exclude_patterns] 405 | #license 406 | #data/audio/*.wav 407 | #data/images/original/* 408 | # 409 | 410 | 411 | # ----------------------------------------------------------------------------- 412 | # Profiles 413 | # 414 | # You can extend section / key with a profile 415 | # For example, you want to deploy a demo version of your application without 416 | # HD content. You could first change the title to add "(demo)" in the name 417 | # and extend the excluded directories to remove the HD content. 418 | # 419 | #[app@demo] 420 | #title = My Application (demo) 421 | # 422 | #[app:source.exclude_patterns@demo] 423 | #images/hd/* 424 | # 425 | # Then, invoke the command line with the "demo" profile: 426 | # 427 | #buildozer --profile demo android debug 428 | -------------------------------------------------------------------------------- /demo/android_native_ui/main.py: -------------------------------------------------------------------------------- 1 | from kvdroid import activity 2 | from android.runnable import run_on_ui_thread 3 | 4 | from kvdroid.app import App 5 | from kvdroid.jclass.android import RelativeLayout, ViewGroupLayoutParams, TextView 6 | from kvdroid.jclass.java import String 7 | 8 | 9 | class TestApp(App): 10 | 11 | def build_view(self): 12 | relative_layout = RelativeLayout(activity) 13 | layout_params = ViewGroupLayoutParams() 14 | text_view = TextView(activity) 15 | text_view.setText(String("Hello World")) 16 | relative_layout.addView(text_view, layout_params(layout_params.Wrap, layout_params.FILL_PARENT)) 17 | return relative_layout 18 | 19 | 20 | TestApp().run() 21 | -------------------------------------------------------------------------------- /demo/kivy_ui/assets/audio/Egedege.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/kivy_ui/assets/audio/Egedege.mp3 -------------------------------------------------------------------------------- /demo/kivy_ui/assets/image/ic_kvdroid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/kivy_ui/assets/image/ic_kvdroid.png -------------------------------------------------------------------------------- /demo/kivy_ui/assets/image/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/kivy_ui/assets/image/icon.png -------------------------------------------------------------------------------- /demo/kivy_ui/assets/image/icon_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/kivy_ui/assets/image/icon_transparent.png -------------------------------------------------------------------------------- /demo/kivy_ui/assets/image/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/kivy_ui/assets/image/splash.png -------------------------------------------------------------------------------- /demo/kivy_ui/assets/image/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/demo/kivy_ui/assets/image/wallpaper.jpg -------------------------------------------------------------------------------- /demo/kivy_ui/assets/json/widget.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "icon": "share", 4 | "text": "SHARE FILE", 5 | "exec": "from kvdroid.tools import share_file; share_file('assets/image/icon.png')" 6 | }, 7 | { 8 | "icon": "share-variant", 9 | "text": "SHARE TEXT", 10 | "exec": "from kvdroid.tools import share_text; share_text('Sent from KvDroid')" 11 | }, 12 | { 13 | "icon": "restart", 14 | "text": "RESTART APP", 15 | "exec": "from kvdroid.tools import restart_app; restart_app()" 16 | }, 17 | { 18 | "icon": "message-badge", 19 | "text": "TOAST", 20 | "exec": "from kvdroid.tools import toast; toast('Hello, Welcome to KvDroid')" 21 | }, 22 | { 23 | "icon": "cloud-download", 24 | "text": "DOWNLOAD MANAGER", 25 | "exec": "from kvdroid.tools import download_manager; download_manager('Downloading Egedege Song', 'Egedege ndi Igbo', 'https://bit.ly/3mHQdzZ')" 26 | }, 27 | { 28 | "icon": "select-color", 29 | "text": "CHANGE STATUSBAR COLOR", 30 | "exec": "from kvdroid.tools import change_statusbar_color; change_statusbar_color('#AA00FF', 'white')" 31 | }, 32 | { 33 | "icon": "format-color-fill", 34 | "text": "CHANGE NAVIGATION-BAR COLOR", 35 | "exec": "from kvdroid.tools import navbar_color; navbar_color('#AA00FF')" 36 | 37 | }, 38 | { 39 | "icon": "wallpaper", 40 | "text": "SET WALLPAPER", 41 | "exec": "from kvdroid.tools import set_wallpaper; set_wallpaper('assets/image/icon.png')" 42 | }, 43 | { 44 | "icon": "text-to-speech", 45 | "text": "TEXT TO SPEECH", 46 | "exec": "from kvdroid.tools import speech; speech('This is KV Droid application talking', 'en')" 47 | }, 48 | { 49 | "icon": "magnify-expand", 50 | "text": "IMMERSIVE MODE", 51 | "exec": "from kvdroid.tools import immersive_mode; immersive_mode()" 52 | }, 53 | { 54 | "icon": "launch", 55 | "text": "LAUNCH CHROME APPLICATION", 56 | "exec": "from kvdroid.tools import launch_app; launch_app('com.android.chrome')" 57 | }, 58 | { 59 | "icon": "details", 60 | "text": "GET CHROME DETAILS", 61 | "exec": "from kvdroid.tools import app_details; app_details('com.android.chrome')" 62 | }, 63 | { 64 | "icon": "music", 65 | "text": "PLAY LOCAL MUSIC", 66 | "exec": "from kvdroid.tools.audio import Player; Player().play('assets/audio/Egedege.mp3')" 67 | }, 68 | { 69 | "icon": "music-box", 70 | "text": "STREAM ONLINE MUSIC", 71 | "exec": "from kvdroid.tools.audio import Player; from kvdroid.tools import toast; toast('fetching music data'); Player().stream('https://bit.ly/3mHQdzZ')" 72 | }, 73 | { 74 | "icon": "phone", 75 | "text": "CALL 911", 76 | "exec": "from kvdroid.tools.call import make_call; make_call('911')" 77 | }, 78 | { 79 | "icon": "phone-dial", 80 | "text": "DIAL 911", 81 | "exec": "from kvdroid.tools.call import dial_call; dial_call('911')" 82 | }, 83 | { 84 | "icon": "contacts", 85 | "text": "READ MY CONTACT", 86 | "exec": "from kvdroid.tools.contact import get_contact_details; print(get_contact_details()); from kvdroid.tools import toast; toast('use adb logcat -s python to view your contact list, will still provide a more user friendly way in the near future')" 87 | }, 88 | { 89 | "icon": "web", 90 | "text": "OPEN CUSTOM-TAB WEBVIEW", 91 | "exec": "from kvdroid.tools.webkit import launch_url; launch_url('https://github.com/kvdroid/Kvdroid')" 92 | }, 93 | { 94 | "icon": "bell", 95 | "text": "NOTIFICATION", 96 | "exec": "from kvdroid.tools.notification import create_notification; from kvdroid.jclass.android import Color; from kvdroid.tools import get_resource; create_notification(small_icon=get_resource('drawable').ic_kvdroid, channel_id ='1', title='KvDroid', text='hello from kvdroid androidx notification', ids=1, channel_name='ch1', large_icon='assets/image/icon.png', big_picture='assets/image/icon.png', action_title1='CLICK', action_title2='PRESS', reply_title='REPLY', set_reply=True, expandable=True, set_large_icon=True, add_action_button1=True, add_action_button2=True, small_icon_color=Color().parseColor('#2962FF'))" 97 | } 98 | ] -------------------------------------------------------------------------------- /demo/kivy_ui/behavior/__init__.py: -------------------------------------------------------------------------------- 1 | from kivy.lang import Builder 2 | from kivy.properties import ( 3 | ColorProperty, 4 | ListProperty, 5 | NumericProperty, 6 | ReferenceListProperty, 7 | StringProperty, 8 | VariableListProperty, 9 | ) 10 | from kivy.uix.widget import Widget 11 | 12 | Builder.load_string( 13 | """ 14 | #:import RelativeLayout kivy.uix.relativelayout.RelativeLayout 15 | 16 | 17 | 18 | canvas.before: 19 | PushMatrix 20 | Rotate: 21 | angle: self.angle 22 | origin: self._background_origin 23 | Color: 24 | rgba: self.bg_color 25 | RoundedRectangle: 26 | group: "Background_instruction" 27 | size: self.size 28 | pos: self.pos if not isinstance(self, RelativeLayout) else (0, 0) 29 | # FIXME: Sometimes the radius has the value [], which get a 30 | # `GraphicException: 31 | # Invalid radius value, must be list of tuples/numerics` error` 32 | radius: root.radius if root.radius else [0, 0, 0, 0] 33 | source: root.background 34 | Color: 35 | rgba: self.line_color if self.line_color else (0, 0, 0, 0) 36 | Line: 37 | width: root.line_width 38 | rounded_rectangle: 39 | [ \ 40 | self.x, 41 | self.y, \ 42 | self.width, \ 43 | self.height, \ 44 | *self.radius, \ 45 | 100, \ 46 | ] 47 | PopMatrix 48 | """, 49 | filename="BackgroundLineBehavior.kv", 50 | ) 51 | 52 | 53 | class BackgroundLineBehavior(Widget): 54 | background = StringProperty() 55 | """ 56 | Background image path. 57 | 58 | :attr:`background` is a :class:`~kivy.properties.StringProperty` 59 | and defaults to `None`. 60 | """ 61 | 62 | radius = VariableListProperty([0], length=4) 63 | """ 64 | Canvas radius. 65 | 66 | .. code-block:: python 67 | 68 | # Top left corner slice. 69 | MDBoxLayout: 70 | md_bg_color: app.theme_cls.primary_color 71 | radius: [25, 0, 0, 0] 72 | 73 | :attr:`radius` is an :class:`~kivy.properties.VariableListProperty` 74 | and defaults to `[0, 0, 0, 0]`. 75 | """ 76 | 77 | bg_color = ColorProperty([0, 0, 0, 0]) 78 | """ 79 | The background color of the widget (:class:`~kivy.uix.widget.Widget`) 80 | that will be inherited from the :attr:`BackgroundColorBehavior` class. 81 | 82 | For example: 83 | 84 | .. code-block:: kv 85 | 86 | Widget: 87 | canvas: 88 | Color: 89 | rgba: 0, 1, 1, 1 90 | Rectangle: 91 | size: self.size 92 | pos: self.pos 93 | 94 | similar to code: 95 | 96 | .. code-block:: kv 97 | 98 | 99 | md_bg_color: 0, 1, 1, 1 100 | 101 | :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty` 102 | and defaults to `[1, 1, 1, 0]`. 103 | """ 104 | 105 | line_color = ColorProperty([0, 0, 0, 0]) 106 | """ 107 | If a custom value is specified for the `line_color parameter`, the border 108 | of the specified color will be used to border the widget: 109 | 110 | .. code-block:: kv 111 | 112 | MDBoxLayout: 113 | size_hint: .5, .2 114 | md_bg_color: 0, 1, 1, .5 115 | line_color: 0, 0, 1, 1 116 | radius: [24, ] 117 | 118 | .. versionadded:: 0.104.2 119 | 120 | :attr:`line_color` is an :class:`~kivy.properties.ColorProperty` 121 | and defaults to `[0, 0, 0, 0]`. 122 | """ 123 | 124 | line_width = NumericProperty(1) 125 | """ 126 | Border of the specified width will be used to border the widget. 127 | 128 | .. versionadded:: 1.0.0 129 | 130 | :attr:`line_width` is an :class:`~kivy.properties.NumericProperty` 131 | and defaults to `1`. 132 | """ 133 | 134 | angle = NumericProperty(0) 135 | background_origin = ListProperty(None) 136 | 137 | _background_x = NumericProperty(0) 138 | _background_y = NumericProperty(0) 139 | _background_origin = ReferenceListProperty( 140 | _background_x, 141 | _background_y, 142 | ) 143 | 144 | def __init__(self, **kwarg): 145 | super().__init__(**kwarg) 146 | self.bind(pos=self.update_background_origin) 147 | 148 | def update_background_origin(self, *_) -> None: 149 | if self.background_origin: 150 | self._background_origin = self.background_origin 151 | else: 152 | self._background_origin = self.center -------------------------------------------------------------------------------- /demo/kivy_ui/buildozer.spec: -------------------------------------------------------------------------------- 1 | [app] 2 | 3 | # (str) Title of your application 4 | title = KvDroid 5 | 6 | # (str) Package name 7 | package.name = kvdroid 8 | 9 | # (str) Package domain (needed for android/ios packaging) 10 | package.domain = org.android 11 | 12 | # (str) Source code where the main.py live 13 | source.dir = . 14 | 15 | # (list) Source files to include (let empty to include all the files) 16 | source.include_exts = py,png,jpg,kv,atlas,json,mp3,ttf 17 | 18 | # (list) List of inclusions using pattern matching 19 | #source.include_patterns = assets/*,images/*.png 20 | 21 | # (list) Source files to exclude (let empty to not exclude anything) 22 | #source.exclude_exts = spec 23 | 24 | # (list) List of directory to exclude (let empty to not exclude anything) 25 | #source.exclude_dirs = tests, bin, venv 26 | 27 | # (list) List of exclusions using pattern matching 28 | #source.exclude_patterns = license,images/*/*.jpg 29 | 30 | # (str) Application versioning (method 1) 31 | version = 0.1 32 | 33 | # (str) Application versioning (method 2) 34 | # version.regex = __version__ = ['"](.*)['"] 35 | # version.filename = %(source.dir)s/main.py 36 | 37 | # (list) Application requirements 38 | # comma separated e.g. requirements = sqlite3,kivy 39 | requirements = python3,kivy==master 40 | 41 | # (str) Custom source folders for requirements 42 | # Sets custom source for any requirements with recipes 43 | # requirements.source.kivy = ../../kivy 44 | 45 | # (str) Presplash of the application 46 | presplash.filename = %(source.dir)s/assets/image/splash.png 47 | 48 | # (str) Icon of the application 49 | icon.filename = %(source.dir)s/assets/image/icon.png 50 | 51 | # (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) 52 | orientation = all 53 | 54 | # (list) List of service to declare 55 | #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY 56 | 57 | # 58 | # OSX Specific 59 | # 60 | 61 | # 62 | # author = © Copyright Info 63 | 64 | # change the major version of python used by the app 65 | osx.python_version = 3 66 | 67 | # Kivy version to use 68 | osx.kivy_version = 1.9.1 69 | 70 | # 71 | # Android specific 72 | # 73 | 74 | # (bool) Indicate if the application should be fullscreen or not 75 | fullscreen = 0 76 | 77 | # (string) Presplash background color (for android toolchain) 78 | # Supported formats are: #RRGGBB #AARRGGBB or one of the following names: 79 | # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, 80 | # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, 81 | # olive, purple, silver, teal. 82 | android.presplash_color = #FFFFFF 83 | 84 | # (string) Presplash animation using Lottie format. 85 | # see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/ 86 | # for general documentation. 87 | # Lottie files can be created using various tools, like Adobe After Effect or Synfig. 88 | #android.presplash_lottie = "path/to/lottie/file.json" 89 | 90 | # (str) Adaptive icon of the application (used if Android API level is 26+ at runtime) 91 | #icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png 92 | #icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png 93 | 94 | # (list) Permissions 95 | android.permissions = INTERNET, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, CALL_PHONE, CALL_PRIVILEGED, READ_CONTACTS, WRITE_CONTACTS, SET_WALLPAPER, READ_SMS, READ_CALL_LOG, WRITE_CALL_LOG 96 | # (list) features (adds uses-feature -tags to manifest) 97 | #android.features = android.hardware.usb.host 98 | 99 | # (int) Target Android API, should be as high as possible. 100 | android.api = 31 101 | 102 | # (int) Minimum API your APK / AAB will support. 103 | #android.minapi = 21 104 | 105 | # (int) Android SDK version to use 106 | #android.sdk = 20 107 | 108 | # (str) Android NDK version to use 109 | #android.ndk = 19b 110 | 111 | # (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi. 112 | #android.ndk_api = 21 113 | 114 | # (bool) Use --private data storage (True) or --dir public storage (False) 115 | #android.private_storage = True 116 | 117 | # (str) Android NDK directory (if empty, it will be automatically downloaded.) 118 | #android.ndk_path = 119 | 120 | # (str) Android SDK directory (if empty, it will be automatically downloaded.) 121 | android.sdk_path = ~/Android/Sdk 122 | 123 | # (str) ANT directory (if empty, it will be automatically downloaded.) 124 | #android.ant_path = 125 | 126 | # (bool) If True, then skip trying to update the Android sdk 127 | # This can be useful to avoid excess Internet downloads or save time 128 | # when an update is due and you just want to test/build your package 129 | #android.skip_update = True 130 | 131 | # (bool) If True, then automatically accept SDK license 132 | # agreements. This is intended for automation only. If set to False, 133 | # the default, you will be shown the license when first running 134 | # buildozer. 135 | android.accept_sdk_license = False 136 | 137 | # (str) Android entry point, default is ok for Kivy-based app 138 | #android.entrypoint = org.kivy.android.PythonActivity 139 | 140 | # (str) Full name including package path of the Java class that implements Android Activity 141 | # use that parameter together with android.entrypoint to set custom Java class instead of PythonActivity 142 | #android.activity_class_name = org.kivy.android.PythonActivity 143 | 144 | # (str) Extra xml to write directly inside the element of AndroidManifest.xml 145 | # use that parameter to provide a filename from where to load your custom XML code 146 | #android.extra_manifest_xml = ./src/android/extra_manifest.xml 147 | 148 | # (str) Extra xml to write directly inside the tag of AndroidManifest.xml 149 | # use that parameter to provide a filename from where to load your custom XML arguments: 150 | #android.extra_manifest_application_arguments = ./src/android/extra_manifest_application_arguments.xml 151 | 152 | # (str) Full name including package path of the Java class that implements Python Service 153 | # use that parameter to set custom Java class instead of PythonService 154 | #android.service_class_name = org.kivy.android.PythonService 155 | 156 | # (str) Android app theme, default is ok for Kivy-based app 157 | # android.apptheme = "@android:style/Theme.NoTitleBar" 158 | 159 | # (list) Pattern to whitelist for the whole project 160 | #android.whitelist = 161 | 162 | # (str) Path to a custom whitelist file 163 | #android.whitelist_src = 164 | 165 | # (str) Path to a custom blacklist file 166 | #android.blacklist_src = 167 | 168 | # (list) List of Java .jar files to add to the libs so that pyjnius can access 169 | # their classes. Don't add jars that you do not need, since extra jars can slow 170 | # down the build process. Allows wildcards matching, for example: 171 | # OUYA-ODK/libs/*.jar 172 | # android.add_jars = jar/*.jar 173 | 174 | # (list) List of Java files to add to the android project (can be java or a 175 | # directory containing the files) 176 | #android.add_src = 177 | 178 | # (list) Android AAR archives to add 179 | #android.add_aars = 180 | 181 | # (list) Gradle dependencies to add 182 | android.gradle_dependencies = androidx.appcompat:appcompat:1.2.0, androidx.browser:browser:1.4.0 183 | 184 | # (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies' 185 | # contains an 'androidx' package, or any package from Kotlin source. 186 | # android.enable_androidx requires android.api >= 28 187 | android.enable_androidx = True 188 | 189 | # (list) add java compile options 190 | # this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option 191 | # see https://developer.android.com/studio/write/java8-support for further information 192 | # android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8" 193 | 194 | # (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies} 195 | # please enclose in double quotes 196 | # e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }" 197 | #android.add_gradle_repositories = 198 | 199 | # (list) packaging options to add 200 | # see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html 201 | # can be necessary to solve conflicts in gradle_dependencies 202 | # please enclose in double quotes 203 | # e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'" 204 | #android.add_packaging_options = 205 | 206 | # (list) Java classes to add as activities to the manifest. 207 | #android.add_activities = com.example.ExampleActivity 208 | 209 | # (str) OUYA Console category. Should be one of GAME or APP 210 | # If you leave this blank, OUYA support will not be enabled 211 | #android.ouya.category = GAME 212 | 213 | # (str) Filename of OUYA Console icon. It must be a 732x412 png image. 214 | #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png 215 | 216 | # (str) XML file to include as an intent filters in tag 217 | #android.manifest.intent_filters = 218 | 219 | # (str) launchMode to set for the main activity 220 | #android.manifest.launch_mode = standard 221 | 222 | # (list) Android additional libraries to copy into libs/armeabi 223 | #android.add_libs_armeabi = libs/android/*.so 224 | #android.add_libs_armeabi_v7a = libs/android-v7/*.so 225 | #android.add_libs_arm64_v8a = libs/android-v8/*.so 226 | #android.add_libs_x86 = libs/android-x86/*.so 227 | #android.add_libs_mips = libs/android-mips/*.so 228 | 229 | # (bool) Indicate whether the screen should stay on 230 | # Don't forget to add the WAKE_LOCK permission if you set this to True 231 | #android.wakelock = False 232 | 233 | # (list) Android application meta-data to set (key=value format) 234 | #android.meta_data = 235 | 236 | # (list) Android library project to add (will be added in the 237 | # project.properties automatically.) 238 | #android.library_references = 239 | 240 | # (list) Android shared libraries which will be added to AndroidManifest.xml using tag 241 | #android.uses_library = 242 | 243 | # (str) Android logcat filters to use 244 | android.logcat_filters = *:S python:D 245 | 246 | # (bool) Android logcat only display log for activity's pid 247 | #android.logcat_pid_only = False 248 | 249 | # (str) Android additional adb arguments 250 | #android.adb_args = -H host.docker.internal 251 | 252 | # (bool) Copy library instead of making a libpymodules.so 253 | #android.copy_libs = 1 254 | 255 | # (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 256 | # In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time. 257 | android.archs = armeabi-v7a, arm64-v8a, x86, x86_64 258 | 259 | # (int) overrides automatic versionCode computation (used in build.gradle) 260 | # this is not the same as app version and should only be edited if you know what you're doing 261 | # android.numeric_version = 1 262 | 263 | # (bool) enables Android auto backup feature (Android API >=23) 264 | android.allow_backup = True 265 | 266 | # (str) XML file for custom backup rules (see official auto backup documentation) 267 | # android.backup_rules = 268 | 269 | # (str) If you need to insert variables into your AndroidManifest.xml file, 270 | # you can do so with the manifestPlaceholders property. 271 | # This property takes a map of key-value pairs. (via a string) 272 | # Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] 273 | # android.manifest_placeholders = [:] 274 | 275 | # (bool) disables the compilation of py to pyc/pyo files when packaging 276 | # android.no-compile-pyo = True 277 | 278 | # (str) The format used to package the app for release mode (aab or apk). 279 | android.release_artifact = apk 280 | 281 | # 282 | # Python for android (p4a) specific 283 | # 284 | 285 | # (str) python-for-android URL to use for checkout 286 | #p4a.url = 287 | 288 | # (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy) 289 | #p4a.fork = kivy 290 | 291 | # (str) python-for-android branch to use, defaults to master 292 | p4a.branch = develop 293 | 294 | # (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch 295 | #p4a.commit = HEAD 296 | 297 | # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) 298 | #p4a.source_dir = 299 | 300 | # (str) The directory in which python-for-android should look for your own build recipes (if any) 301 | #p4a.local_recipes = 302 | 303 | # (str) Filename to the hook for p4a 304 | #p4a.hook = 305 | 306 | # (str) Bootstrap to use for android builds 307 | # p4a.bootstrap = sdl2 308 | 309 | # (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask) 310 | #p4a.port = 311 | 312 | # Control passing the --use-setup-py vs --ignore-setup-py to p4a 313 | # "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not 314 | # Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py 315 | # NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate 316 | # setup.py if you're using Poetry, but you need to add "toml" to source.include_exts. 317 | #p4a.setup_py = false 318 | 319 | # (str) extra command line arguments to pass when invoking pythonforandroid.toolchain 320 | #p4a.extra_args = 321 | 322 | 323 | # 324 | # iOS specific 325 | # 326 | 327 | # (str) Path to a custom kivy-ios folder 328 | #ios.kivy_ios_dir = ../kivy-ios 329 | # Alternately, specify the URL and branch of a git checkout: 330 | ios.kivy_ios_url = https://github.com/kivy/kivy-ios 331 | ios.kivy_ios_branch = master 332 | 333 | # Another platform dependency: ios-deploy 334 | # Uncomment to use a custom checkout 335 | #ios.ios_deploy_dir = ../ios_deploy 336 | # Or specify URL and branch 337 | ios.ios_deploy_url = https://github.com/phonegap/ios-deploy 338 | ios.ios_deploy_branch = 1.10.0 339 | 340 | # (bool) Whether or not to sign the code 341 | ios.codesign.allowed = false 342 | 343 | # (str) Name of the certificate to use for signing the debug version 344 | # Get a list of available identities: buildozer ios list_identities 345 | #ios.codesign.debug = "iPhone Developer: ()" 346 | 347 | # (str) The development team to use for signing the debug version 348 | #ios.codesign.development_team.debug = 349 | 350 | # (str) Name of the certificate to use for signing the release version 351 | #ios.codesign.release = %(ios.codesign.debug)s 352 | 353 | # (str) The development team to use for signing the release version 354 | #ios.codesign.development_team.release = 355 | 356 | # (str) URL pointing to .ipa file to be installed 357 | # This option should be defined along with `display_image_url` and `full_size_image_url` options. 358 | #ios.manifest.app_url = 359 | 360 | # (str) URL pointing to an icon (57x57px) to be displayed during download 361 | # This option should be defined along with `app_url` and `full_size_image_url` options. 362 | #ios.manifest.display_image_url = 363 | 364 | # (str) URL pointing to a large icon (512x512px) to be used by iTunes 365 | # This option should be defined along with `app_url` and `display_image_url` options. 366 | #ios.manifest.full_size_image_url = 367 | 368 | 369 | [buildozer] 370 | 371 | # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) 372 | log_level = 2 373 | 374 | # (int) Display warning if buildozer is run as root (0 = False, 1 = True) 375 | warn_on_root = 1 376 | 377 | # (str) Path to build artifact storage, absolute or relative to spec file 378 | # build_dir = ./.buildozer 379 | 380 | # (str) Path to build output (i.e. .apk, .aab, .ipa) storage 381 | # bin_dir = ./bin 382 | 383 | # ----------------------------------------------------------------------------- 384 | # List as sections 385 | # 386 | # You can define all the "list" as [section:key]. 387 | # Each line will be considered as a option to the list. 388 | # Let's take [app] / source.exclude_patterns. 389 | # Instead of doing: 390 | # 391 | #[app] 392 | #source.exclude_patterns = license,data/audio/*.wav,data/images/original/* 393 | # 394 | # This can be translated into: 395 | # 396 | #[app:source.exclude_patterns] 397 | #license 398 | #data/audio/*.wav 399 | #data/images/original/* 400 | # 401 | 402 | 403 | # ----------------------------------------------------------------------------- 404 | # Profiles 405 | # 406 | # You can extend section / key with a profile 407 | # For example, you want to deploy a demo version of your application without 408 | # HD content. You could first change the title to add "(demo)" in the name 409 | # and extend the excluded directories to remove the HD content. 410 | # 411 | #[app@demo] 412 | #title = My Application (demo) 413 | # 414 | #[app:source.exclude_patterns@demo] 415 | #images/hd/* 416 | # 417 | # Then, invoke the command line with the "demo" profile: 418 | # 419 | #buildozer --profile demo android debug 420 | -------------------------------------------------------------------------------- /demo/kivy_ui/kvdroid.kv: -------------------------------------------------------------------------------- 1 | #:import ScrollEffect kivy.effects.scroll.ScrollEffect 2 | #:import Window kivy.core.window.Window 3 | : 4 | radius: dp(10) 5 | padding: dp(100), dp(20) 6 | text_size: self.width, None 7 | markup: True 8 | halign: "center" 9 | line_color: 0, 0, 0, 1 10 | color: 0, 0, 0, 1 11 | on_release: app.execute_code(self.exec) 12 | 13 | BoxLayout: 14 | orientation: "vertical" 15 | spacing: "10dp" 16 | Label: 17 | text: "KVDROID" 18 | size_hint_y: None 19 | height: self.texture_size[1] 20 | font_size: "25sp" 21 | padding: "20dp", "20dp" 22 | color: 0, 0, 0, 1 23 | RecycleView: 24 | id: rv 25 | effect_cls: ScrollEffect 26 | scroll_distance: dp(1) 27 | viewclass: "LineButton" 28 | RecycleBoxLayout: 29 | orientation: "vertical" 30 | size_hint_y: None 31 | size: self.minimum_size 32 | default_size_hint: 1, None 33 | default_size: 0, dp(100) 34 | padding: dp(15) 35 | spacing: dp(15) 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demo/kivy_ui/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.environ["KIVY_NO_CONSOLELOG"] = "1" 3 | from kivy import Config 4 | 5 | Config.set("kivy", "exit_on_escape", "0") 6 | from kivy.uix.behaviors import ButtonBehavior 7 | from behavior import BackgroundLineBehavior 8 | from kivy.uix.label import Label 9 | from kivy.app import App 10 | from kivy.clock import Clock 11 | from tools import kvdroid_tools 12 | from kivy.core.window import Window 13 | from kivy.utils import get_color_from_hex 14 | from kivy import platform 15 | 16 | 17 | class LineButton(ButtonBehavior, BackgroundLineBehavior, Label): 18 | pass 19 | 20 | 21 | class KvDroid(App): 22 | icon = "assets/image/icon.png" 23 | 24 | def __init__(self, **kwargs): 25 | super().__init__(**kwargs) 26 | Window.clearcolor = get_color_from_hex("#FAFAFA") 27 | 28 | def on_start(self): 29 | if platform == "android": 30 | from android.permissions import Permission, request_permissions # NOQA 31 | request_permissions( 32 | [Permission.READ_EXTERNAL_STORAGE, 33 | Permission.WRITE_EXTERNAL_STORAGE, 34 | Permission.CALL_PHONE, 35 | Permission.CALL_PRIVILEGED, 36 | Permission.READ_CONTACTS, 37 | Permission.WRITE_CONTACTS, 38 | Permission.READ_SMS, 39 | Permission.WRITE_CALL_LOG, 40 | Permission.READ_CALL_LOG 41 | ] 42 | ) 43 | Clock.schedule_once(lambda *_: self.root.ids.rv.data.extend(kvdroid_tools), 3) 44 | 45 | @staticmethod 46 | def execute_code(code): 47 | exec(code) 48 | 49 | 50 | KvDroid().run() 51 | -------------------------------------------------------------------------------- /demo/kivy_ui/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from textwrap import dedent 2 | 3 | kvdroid_tools = [ 4 | { 5 | "text": "SHARE FILE", 6 | "exec": dedent( 7 | """ 8 | from kvdroid.tools import share_file 9 | 10 | share_file('assets/image/icon.png') 11 | """ 12 | ) 13 | }, 14 | { 15 | "text": "SHARE TEXT", 16 | "exec": dedent( 17 | """ 18 | from kvdroid.tools import share_text 19 | 20 | share_text('Sent from KvDroid') 21 | """ 22 | ) 23 | }, 24 | { 25 | "text": "RESTART APP", 26 | "exec": dedent( 27 | """ 28 | from kvdroid.tools import restart_app 29 | 30 | restart_app() 31 | """ 32 | ) 33 | }, 34 | { 35 | "text": "TOAST", 36 | "exec": dedent( 37 | """ 38 | from kvdroid.tools import toast 39 | 40 | toast('Hello, Welcome to KvDroid') 41 | """ 42 | ) 43 | }, 44 | { 45 | "text": "DOWNLOAD MANAGER", 46 | "exec": dedent( 47 | """ 48 | from kvdroid.tools import download_manager 49 | 50 | download_manager('Downloading Egedege Song', 'Egedege ndi Igbo', 'https://bit.ly/3mHQdzZ') 51 | """ 52 | ) 53 | }, 54 | { 55 | "text": "CHANGE STATUSBAR COLOR", 56 | "exec": dedent( 57 | """ 58 | from kvdroid.tools import change_statusbar_color 59 | 60 | change_statusbar_color('#AA00FF', 'white') 61 | """ 62 | ) 63 | }, 64 | { 65 | "text": "CHANGE NAVIGATION BAR COLOR", 66 | "exec": dedent( 67 | """ 68 | from kvdroid.tools import navbar_color 69 | 70 | navbar_color('#AA00FF') 71 | """ 72 | ) 73 | 74 | }, 75 | { 76 | "text": "SET WALLPAPER", 77 | "exec": dedent( 78 | """ 79 | from kvdroid.tools import set_wallpaper 80 | 81 | set_wallpaper('assets/image/icon.png') 82 | """ 83 | ) 84 | }, 85 | { 86 | "text": "TEXT TO SPEECH", 87 | "exec": dedent( 88 | """ 89 | from kvdroid.tools import speech 90 | 91 | speech('This is KV Droid application talking', 'en') 92 | """ 93 | ) 94 | }, 95 | { 96 | "text": "IMMERSIVE MODE", 97 | "exec": dedent( 98 | """ 99 | from kvdroid.tools import immersive_mode 100 | 101 | immersive_mode() 102 | """ 103 | ) 104 | }, 105 | { 106 | "text": "LAUNCH CHROME APPLICATION", 107 | "exec": dedent( 108 | """ 109 | from kvdroid.tools import launch_app 110 | 111 | launch_app('com.android.chrome') 112 | """ 113 | ) 114 | }, 115 | { 116 | "text": "GET CHROME DETAILS", 117 | "exec": dedent( 118 | """ 119 | from kvdroid.tools import app_details 120 | 121 | app_details('com.android.chrome') 122 | """ 123 | ) 124 | }, 125 | { 126 | "text": "PLAY LOCAL MUSIC", 127 | "exec": dedent( 128 | """ 129 | from kvdroid.tools.audio import Player 130 | 131 | Player().play('assets/audio/Egedege.mp3') 132 | """ 133 | ) 134 | }, 135 | { 136 | "text": "STREAM ONLINE MUSIC", 137 | "exec": dedent( 138 | """ 139 | from kvdroid.tools.audio import Player 140 | from kvdroid.tools import toast 141 | 142 | toast('fetching music data') 143 | Player().stream('https://bit.ly/3mHQdzZ') 144 | """ 145 | ) 146 | }, 147 | { 148 | "text": "CALL 911", 149 | "exec": dedent( 150 | """ 151 | from kvdroid.tools.call import make_call 152 | 153 | make_call('911') 154 | """ 155 | ) 156 | }, 157 | { 158 | "text": "DIAL 911", 159 | "exec": dedent( 160 | """ 161 | from kvdroid.tools.call import dial_call 162 | 163 | dial_call('911') 164 | """ 165 | ) 166 | }, 167 | { 168 | "text": "READ MY CONTACT", 169 | "exec": dedent( 170 | """ 171 | from kvdroid.tools.contact import get_contact_details 172 | print(get_contact_details()) 173 | 174 | from kvdroid.tools import toast 175 | toast( 176 | 'use adb logcat -s python to view your contact list, ' 177 | 'will still provide a more user friendly way in the near future' 178 | ) 179 | """ 180 | ) 181 | }, 182 | { 183 | "text": "OPEN CUSTOM TAB WEBVIEW", 184 | "exec": dedent( 185 | """ 186 | from kvdroid.tools.webkit import launch_url 187 | 188 | launch_url('https://github.com/kvdroid/Kvdroid') 189 | """ 190 | ) 191 | }, 192 | { 193 | "text": "NOTIFICATION", 194 | "exec": dedent( 195 | """ 196 | from kvdroid.tools.notification import create_notification 197 | from kvdroid.jclass.android import Color 198 | from kvdroid.tools import get_resource 199 | 200 | create_notification( 201 | small_icon=get_resource('drawable').ic_kvdroid, 202 | channel_id ='1', 203 | title='KvDroid', 204 | text='hello from kvdroid androidx notification', 205 | ids=1, 206 | channel_name='ch1', 207 | large_icon='assets/image/icon.png', 208 | big_picture='assets/image/icon.png', 209 | action_title1='CLICK', 210 | action_title2='PRESS', 211 | reply_title='REPLY', set_reply=True, 212 | expandable=True, set_large_icon=True, 213 | add_action_button1=True, 214 | add_action_button2=True, 215 | small_icon_color=Color().parseColor('#2962FF') 216 | ) 217 | """ 218 | ) 219 | }, 220 | { 221 | "text": "READ SMS", 222 | "exec": dedent( 223 | """ 224 | from kvdroid.tools.notification import create_notification 225 | from kvdroid.tools.sms import get_all_sms 226 | 227 | print(get_all_sms()) 228 | """ 229 | ) 230 | }, 231 | { 232 | "text": "READ CALL LOGS", 233 | "exec": dedent( 234 | """ 235 | from kvdroid.tools.notification import create_notification 236 | from kvdroid.tools.call import get_call_log 237 | 238 | print(get_call_log()) 239 | """ 240 | ) 241 | } 242 | ] 243 | -------------------------------------------------------------------------------- /kvdroid/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from os import environ 3 | from typing import Union 4 | 5 | from jnius import autoclass # NOQA 6 | 7 | 8 | def _get_platform(): 9 | # On Android sys.platform returns 'linux2', so prefer to check the 10 | # existence of environ variables set during Python initialization 11 | kivy_build = environ.get('KIVY_BUILD', '') 12 | if kivy_build in {'android', 'ios'}: 13 | return kivy_build 14 | elif 'P4A_BOOTSTRAP' in environ or 'ANDROID_ARGUMENT' in environ: 15 | return 'android' 16 | 17 | 18 | def get_hex_from_color(color: list): 19 | return "#" + "".join([f"{int(i * 255):02x}" for i in color]) 20 | 21 | 22 | def _convert_color(color: Union[str, list]): 23 | if isinstance(color, list): 24 | color = get_hex_from_color(color) 25 | return color 26 | 27 | 28 | platform = _get_platform() 29 | Logger = logging.getLogger('kivy') 30 | 31 | if platform != "android": 32 | raise ImportError("Kvdroid: Kvdroid is only callable from Android") 33 | from android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME # NOQA 34 | 35 | if 'PYTHON_SERVICE_ARGUMENT' in environ: 36 | PythonService = autoclass(SERVICE_CLASS_NAME) 37 | activity = PythonService.mService 38 | else: 39 | PythonActivity = autoclass(ACTIVITY_CLASS_NAME) 40 | activity = PythonActivity.mActivity 41 | 42 | packages = { 43 | "whatsapp": "com.whatsapp", 44 | "facebook": "com.facebook.katana", 45 | "facebookLite": "com.facebook.lite", 46 | "oldFacebook": "com.facebook.android", 47 | "linkedin": "com.linkedin.android", 48 | "fbMessenger": "com.facebook.orca", 49 | "fbMessengerLite": "com.facebook.mlite", 50 | "tiktok": "com.zhiliaoapp.musically", 51 | "tiktokLite": "com.zhiliaoapp.musically.go", 52 | "twitter": "com.twitter.android", 53 | "twitterLite": "com.twitter.android.lite", 54 | "telegram": "org.telegram.messenger", 55 | "telegramX": "org.thunderdog.challegram", 56 | "snapchat": "com.snapchat.android", 57 | "chrome": "com.android.chrome" 58 | } 59 | -------------------------------------------------------------------------------- /kvdroid/app.py: -------------------------------------------------------------------------------- 1 | from kvdroid.event import EventDispatcher 2 | from kvdroid.base import EventLoop 3 | from android.runnable import run_on_ui_thread 4 | from kvdroid import activity 5 | from kvdroid.jclass.android import RelativeLayout 6 | from kvdroid.jclass.android import ViewGroupLayoutParams 7 | from kvdroid import Logger 8 | 9 | 10 | class App(EventDispatcher): 11 | # Return the current running App instance 12 | _running_app = None 13 | 14 | def __init__(self, **kwargs): 15 | App._running_app = self 16 | super().__init__(**kwargs) 17 | self.register_event_type("on_pause") 18 | self.register_event_type("on_create") 19 | self.register_event_type("on_destroy") 20 | self.register_event_type("on_resume") 21 | self._eventloop = EventLoop() 22 | 23 | #: The *root* widget returned by the :meth:`build_view` 24 | self.root = None 25 | 26 | def build_view(self): 27 | """Initializes the application; it will be called only once. 28 | If this method returns a widget (tree), it will be used as the root 29 | widget and added to the window. 30 | 31 | :return: 32 | None or a root :class:`~android.widget.RelativeLayout` instance 33 | if no self.root exists.""" 34 | if not self.root: 35 | return RelativeLayout(activity) 36 | 37 | @run_on_ui_thread 38 | def add_content_view(self, layout): 39 | layout_params = ViewGroupLayoutParams() 40 | activity.addContentView( 41 | layout, 42 | layout_params(layout_params.MATCH_PARENT, layout_params.FILL_PARENT) 43 | ) 44 | return layout 45 | 46 | def on_create(self): 47 | """Event handler for the `on_create` event which is fired after 48 | initialization (after build() has been called) but before the 49 | application has started running. 50 | """ 51 | 52 | def on_pause(self): 53 | """Event handler called when Pause mode is requested""" 54 | 55 | def on_destroy(self): 56 | """Event handler for the `on_destroy` event which is fired when the 57 | application has finished running (i.e. the window is about to be 58 | closed). 59 | """ 60 | 61 | def on_resume(self): 62 | pass 63 | 64 | def run(self): 65 | self.root = self.build_view() 66 | if not self.root: 67 | Logger.critical("Application: No Layout was returned in build") 68 | return 69 | self.add_content_view(self.root) 70 | self._eventloop.status = "created" 71 | self.dispatch("on_create") 72 | self._eventloop.mainloop() 73 | 74 | @staticmethod 75 | def get_running_app(): 76 | """Return the currently running application instance. 77 | """ 78 | return App._running_app 79 | 80 | def stop(self): 81 | self.dispatch("on_destroy") 82 | self._eventloop.close() 83 | App._running_app = None 84 | -------------------------------------------------------------------------------- /kvdroid/base.py: -------------------------------------------------------------------------------- 1 | from kvdroid.event import EventDispatcher 2 | from kvdroid import activity 3 | 4 | 5 | class EventLoop(EventDispatcher): 6 | def __init__(self): 7 | super(EventLoop, self).__init__() 8 | from kvdroid.app import App 9 | self.app = App.get_running_app() 10 | self.quit = False 11 | self.status = "idle" 12 | self.resumed = False 13 | self.destroyed = False 14 | self.paused = False 15 | 16 | def mainloop(self): 17 | while not self.quit and self.status == "created": 18 | self.poll() 19 | 20 | def poll(self): 21 | if activity.isResumed() and not self.resumed: 22 | self.app.dispatch("on_resume") 23 | self.resumed = activity.resumed 24 | self.paused = False 25 | 26 | if activity.isDestroyed() and not self.destroyed: 27 | self.app.dispatch("on_destroy") 28 | self.destroyed = activity.destroyed 29 | self.resumed = False 30 | 31 | if not activity.hasWindowFocus() and not self.paused: 32 | self.app.dispatch("on_pause") 33 | self.paused = True 34 | self.resumed = False 35 | 36 | def close(self): 37 | self.quit = True 38 | self.status = "destroyed" 39 | -------------------------------------------------------------------------------- /kvdroid/cast.py: -------------------------------------------------------------------------------- 1 | from jnius import cast 2 | 3 | # experimental(castables not yet complete, still in hunt) 4 | castable_packages = { 5 | "parcelable": "android.os.Parcelable", 6 | "activity": "android.app.Activity", 7 | "context": "android.content.Context", 8 | "downloadManager": "android.app.DownloadManager", 9 | "charSequence": "java.lang.CharSequence", 10 | "bitmapDrawable": "android.graphics.drawable.BitmapDrawable", 11 | "adaptiveIconDrawable": "android.graphics.drawable.AdaptiveIconDrawable", 12 | "audioManager": "android.media.AudioManager", 13 | "uri": "android.net.Uri" 14 | } 15 | 16 | 17 | def cast_object(name: str, java_object: object): 18 | if name not in castable_packages: 19 | raise NameError("Java package name not in predefined castables") 20 | return cast(castable_packages[name], java_object) 21 | -------------------------------------------------------------------------------- /kvdroid/event.py: -------------------------------------------------------------------------------- 1 | # Borrowed from the logics and algorithms of kivy framework 2 | 3 | 4 | class EventDispatcher(object): 5 | __event_stack = {} 6 | 7 | def register_event_type(self, event_type): 8 | """Register an event type with the dispatcher. 9 | Registering event types allows the dispatcher to validate event handler 10 | names as they are attached and to search attached objects for suitable 11 | handlers. Each event type declaration must: 12 | 1. start with the prefix `on_`. 13 | 2. have a default handler in the class. 14 | """ 15 | 16 | if event_type[:3] != 'on_': 17 | raise Exception('A new event must start with "on_"') 18 | 19 | # Ensure that the user has at least declared the default handler 20 | if not hasattr(self, event_type): 21 | raise Exception( 22 | f'Missing default handler {event_type} in {self.__class__.__name__}') 23 | 24 | # Add the event type to the stack 25 | if event_type not in self.__event_stack: 26 | self.__event_stack[event_type] = [] 27 | 28 | def unregister_event_type(self, event_type): 29 | pass 30 | 31 | def bind(self, **kwargs): 32 | for key, value in kwargs.items(): 33 | assert callable(value), '{!r} is not callable'.format(value) 34 | if key[:3] == 'on_': 35 | observers: list = self.__event_stack.get(key) 36 | if observers is None: 37 | continue 38 | observers.append(value) 39 | 40 | def unbind(self, **kwargs): 41 | """Unbind event from callback functions with similar usage as""" 42 | 43 | for key, value in kwargs.items(): 44 | if key[:3] == 'on_': 45 | observers: list = self.__event_stack.get(key) 46 | if observers is None: 47 | continue 48 | observers.remove(value) 49 | 50 | def is_event_type(self, event_type): 51 | """Return True if the event_type is already registered. 52 | """ 53 | return event_type in self.__event_stack 54 | 55 | def dispatch(self, event_type, *args, **kwargs): 56 | """Dispatch an event across all the handlers added in bind/fbind(). 57 | As soon as a handler returns True, the dispatching stops. 58 | The function collects all the positional and keyword arguments and 59 | passes them on to the handlers. 60 | .. note:: 61 | The handlers are called in reverse order than they were registered 62 | with :meth:`bind`. 63 | :Parameters: 64 | `event_type`: str 65 | the event name to dispatch. 66 | .. versionchanged:: 1.9.0 67 | Keyword arguments collection and forwarding was added. Before, only 68 | positional arguments would be collected and forwarded. 69 | """ 70 | observers: list = self.__event_stack.get(event_type) 71 | observers.reverse() 72 | for callbacks in observers: 73 | callbacks(*args, **kwargs) 74 | 75 | handler = getattr(self, event_type) 76 | return handler(*args, **kwargs) 77 | -------------------------------------------------------------------------------- /kvdroid/jclass/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass, JavaException # NOQA 2 | 3 | 4 | def _class_call(cls, args: tuple, instantiate: bool): 5 | if not args: 6 | return cls() if instantiate else cls 7 | else: 8 | return cls(*args) 9 | 10 | 11 | def _browserx_except_cls_call(namespace: str, args: tuple, instantiate: bool): 12 | try: 13 | return _class_call(autoclass(namespace), args, instantiate) 14 | except JavaException as e: 15 | raise JavaException( 16 | f"{e}\nEnable androidx in your buildozer.spec file\nadd 'androidx.browser:browser:1.4.0' to " 17 | f"buildozer.spec file: android.gradle_dependencies" 18 | ) from e 19 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/R.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Dimen(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.R$dimen"), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/__init__.py: -------------------------------------------------------------------------------- 1 | from .content.res import * 2 | from .content import * 3 | from .speech.tts import * 4 | from .speech import * 5 | from .app import * 6 | from .graphics import * 7 | from .graphics.drawable import * 8 | from .location import * 9 | from .media import * 10 | from .net import * 11 | from .os import * 12 | from .os.ext import * 13 | from .provider import * 14 | from .view import * 15 | from .webkit import * 16 | from .widget import * 17 | from .net.wifi import * 18 | from .text.format import * 19 | from .hardware.camera2 import * 20 | from .hardware.camera2.params import * 21 | from .support.v4.app import * 22 | from .content.pm import * 23 | from .R import * 24 | from .telephony import * 25 | 26 | from jnius import autoclass 27 | from kvdroid.jclass import _class_call 28 | 29 | 30 | def Manifest(*args, instantiate: bool = False): 31 | return _class_call(autoclass("android.Manifest"), args, instantiate) 32 | 33 | 34 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/app/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Activity(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.app.Activity"), args, instantiate) 7 | 8 | 9 | def ActivityManager(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.app.ActivityManager"), args, instantiate) 11 | 12 | 13 | def AlarmManager(*args, instantiate: bool = False): 14 | return _class_call(autoclass("android.app.AlarmManager"), args, instantiate) 15 | 16 | 17 | def AlertDialog(*args, instantiate: bool = False): 18 | return _class_call(autoclass("android.app.AlertDialog"), args, instantiate) 19 | 20 | 21 | def Application(*args, instantiate: bool = False): 22 | return _class_call(autoclass("android.app.Application"), args, instantiate) 23 | 24 | 25 | def ApplicationInfo(*args, instantiate: bool = False): 26 | return _class_call(autoclass("android.content.pm.ApplicationInfo"), args, instantiate) 27 | 28 | 29 | def Fragment(*args, instantiate: bool = False): 30 | return _class_call(autoclass("android.app.Fragment"), args, instantiate) 31 | 32 | 33 | def FragmentManager(*args, instantiate: bool = False): 34 | return _class_call(autoclass("android.app.FragmentManager"), args, instantiate) 35 | 36 | 37 | def NotificationManager(*args, instantiate: bool = False): 38 | return _class_call(autoclass("android.app.NotificationManager"), args, instantiate) 39 | 40 | 41 | def Notification(*args, instantiate: bool = False): 42 | return _class_call(autoclass("android.app.Notification"), args, instantiate) 43 | 44 | 45 | if autoclass('android.os.Build$VERSION').SDK_INT >= 26: 46 | def NotificationChannel(*args, instantiate: bool = False): 47 | return _class_call(autoclass("android.app.NotificationChannel"), args, instantiate) 48 | 49 | 50 | def WallpaperManager(*args, instantiate: bool = False): 51 | return _class_call(autoclass('android.app.WallpaperManager'), args, instantiate) 52 | 53 | 54 | def Request(*args, instantiate: bool = False): 55 | return _class_call(autoclass("android.app.DownloadManager$Request"), args, instantiate) 56 | 57 | 58 | def PendingIntent(*args, instantiate: bool = False): 59 | return _class_call(autoclass("android.app.PendingIntent"), args, instantiate) 60 | 61 | 62 | def MemoryInfo(*args, instantiate: bool = False): 63 | return _class_call(autoclass('android.app.ActivityManager$MemoryInfo'), args, instantiate) 64 | 65 | 66 | def ComponentName(*args, instantiate: bool = False): 67 | return _class_call(autoclass("android.content.ComponentName"), args, instantiate) 68 | 69 | 70 | def ActivityInfo(*args, instantiate: bool = False): 71 | return _class_call(autoclass('android.content.pm.ActivityInfo'), args, instantiate) 72 | 73 | 74 | def PackageManager(*args, instantiate: bool = False): 75 | return _class_call(autoclass("android.content.pm.PackageManager"), args, instantiate) 76 | 77 | 78 | def Configuration(*args, instantiate: bool = False): 79 | return _class_call(autoclass("android.content.res.Configuration"), args, instantiate) 80 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/content/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Intent(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.content.Intent"), args, instantiate) 7 | 8 | 9 | def Context(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.content.Context"), args, instantiate) 11 | 12 | 13 | def IntentFilter(*args, instantiate: bool = False): 14 | return _class_call(autoclass("android.content.IntentFilter"), args, instantiate) 15 | 16 | 17 | def ContentValues(*args, instantiate: bool = False): 18 | return _class_call(autoclass('android.content.ContentValues'), args, instantiate) 19 | 20 | 21 | def ContentUris(*args, instantiate: bool = False): 22 | return _class_call(autoclass('android.content.ContentUris'), args, instantiate) 23 | 24 | 25 | def SharedPreferences(*args, instantiate: bool = False): 26 | return _class_call(autoclass('android.content.SharedPreferences'), args, instantiate) 27 | 28 | 29 | def BroadcastReceiver(*args, instantiate: bool = False): 30 | return _class_call(autoclass('android.content.BroadcastReceiver'), args, instantiate) 31 | 32 | 33 | def ContentResolver(*args, instantiate: bool = False): 34 | return _class_call(autoclass('android.content.ContentResolver'), args, instantiate) 35 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/content/pm.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def PackageManager(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.content.pm.PackageManager"), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/content/res.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Configuration(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.content.res.Configuration"), args, instantiate) 7 | 8 | 9 | def Resources(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.content.res.Resources"), args, instantiate) 11 | 12 | def TypedArray(*args, instantiate: bool = False): 13 | return _class_call(autoclass("android.content.res.TypedArray"), args, instantiate) 14 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/graphics/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Canvas(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.graphics.Canvas"), args, instantiate) 7 | 8 | 9 | def Color(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.graphics.Color"), args, instantiate) 11 | 12 | 13 | def Rect(*args, instantiate: bool = False): 14 | return _class_call(autoclass('android.graphics.Rect'), args, instantiate) 15 | 16 | 17 | def Bitmap(*args, instantiate: bool = False): 18 | return _class_call(autoclass('android.graphics.Bitmap'), args, instantiate) 19 | 20 | 21 | def BitmapFactory(*args, instantiate: bool = False): 22 | return _class_call(autoclass('android.graphics.BitmapFactory'), args, instantiate) 23 | 24 | 25 | def Config(*args, instantiate: bool = False): 26 | return _class_call(autoclass("android.graphics.Bitmap$Config"), args, instantiate) 27 | 28 | 29 | def CompressFormat(*args, instantiate: bool = False): 30 | return _class_call(autoclass("android.graphics.Bitmap$CompressFormat"), args, instantiate) 31 | 32 | 33 | def Point(*args, instantiate: bool = False): 34 | return _class_call(autoclass("android.graphics.Point"), args, instantiate) 35 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/graphics/drawable.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def BitmapDrawable(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.graphics.drawable.BitmapDrawable"), args, instantiate) 7 | 8 | 9 | def Drawable(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.graphics.drawable.Drawable"), args, instantiate) 11 | 12 | 13 | def AdaptiveIconDrawable(*args, instantiate: bool = False): 14 | return _class_call(autoclass('android.graphics.drawable.AdaptiveIconDrawable'), args, instantiate) 15 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/hardware/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Camera(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.hardware.Camera"), args, instantiate) 7 | 8 | 9 | def Sensor(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.hardware.Sensor"), args, instantiate) 11 | 12 | 13 | def SensorManager(*args, instantiate: bool = False): 14 | return _class_call(autoclass("android.hardware.SensorManager"), args, instantiate) -------------------------------------------------------------------------------- /kvdroid/jclass/android/hardware/camera2/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def CameraDevice(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.hardware.camera2.CameraDevice"), args, instantiate) 7 | 8 | 9 | def CameraCaptureSession(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.hardware.camera2.CameraCaptureSession"), args, instantiate) 11 | 12 | 13 | def CaptureRequest(*args, instantiate: bool = False): 14 | return _class_call(autoclass("android.hardware.camera2.CaptureRequest"), args, instantiate) 15 | 16 | 17 | def CaptureRequestBuilder(*args, instantiate: bool = False): 18 | return _class_call(autoclass("android.hardware.camera2.CaptureRequest$Builder"), args, instantiate) 19 | 20 | 21 | def CameraManager(*args, instantiate: bool = False): 22 | return _class_call(autoclass("android.hardware.camera2.CameraManager"), args, instantiate) 23 | 24 | 25 | def CameraCharacteristics(*args, instantiate: bool = False): 26 | return _class_call(autoclass("android.hardware.camera2.CameraCharacteristics"), args, instantiate) 27 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/hardware/camera2/params.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def StreamConfigurationMap(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.hardware.camera2.params.StreamConfigurationMap"), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/location/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def LocationManager(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.location.LocationManager"), args, instantiate) 7 | 8 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/media/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def ImageReader(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.media.ImageReader"), args, instantiate) 7 | 8 | 9 | def MediaPlayer(instantiate: bool=False, *args): 10 | return _class_call(autoclass('android.media.MediaPlayer'), args, instantiate) 11 | 12 | 13 | def AudioManager(instantiate: bool=False, *args): 14 | return _class_call(autoclass('android.media.AudioManager'), args, instantiate) 15 | 16 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/net/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Uri(*args, instantiate: bool = False): 6 | return _class_call(autoclass('android.net.Uri'), args, instantiate) 7 | 8 | 9 | def ConnectivityManager(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.net.ConnectivityManager"), args, instantiate) 11 | 12 | 13 | def NetworkInfo(*args, instantiate: bool = False): 14 | return _class_call(autoclass("android.net.NetworkInfo"), args, instantiate) 15 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/net/wifi/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def WifiManager(*args, instantiate: bool = False): 6 | return _class_call(autoclass('android.net.wifi.WifiManager'), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/os/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def AsyncTask(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.os.AsyncTask"), args, instantiate) 7 | 8 | 9 | def Environment(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.os.Environment"), args, instantiate) 11 | 12 | 13 | def BatteryManager(*args, instantiate: bool = False): 14 | return _class_call(autoclass("android.os.BatteryManager"), args, instantiate) 15 | 16 | 17 | def Build(*args, instantiate: bool = False): 18 | return _class_call(autoclass("android.os.Build"), args, instantiate) 19 | 20 | 21 | def Bundle(*args, instantiate: bool = False): 22 | return _class_call(autoclass("android.os.Bundle"), args, instantiate) 23 | 24 | 25 | def Debug(*args, instantiate: bool = False): 26 | return _class_call(autoclass("android.os.Debug"), args, instantiate) 27 | 28 | 29 | def FileUtils(*args, instantiate: bool = False): 30 | return _class_call(autoclass('android.os.FileUtils'), args, instantiate) 31 | 32 | 33 | def Handler(*args, instantiate: bool = False): 34 | return _class_call(autoclass("android.os.Handler"), args, instantiate) 35 | 36 | 37 | def HandlerThread(*args, instantiate: bool = False): 38 | return _class_call(autoclass("android.os.HandlerThread"), args, instantiate) 39 | 40 | 41 | def Looper(*args, instantiate: bool = False): 42 | return _class_call(autoclass("android.os.Looper"), args, instantiate) 43 | 44 | 45 | def Message(*args, instantiate: bool = False): 46 | return _class_call(autoclass("android.os.Message"), args, instantiate) 47 | 48 | 49 | def Parcelable(*args, instantiate: bool = False): 50 | return _class_call(autoclass("android.os.Parcelable"), args, instantiate) 51 | 52 | 53 | def PowerManager(*args, instantiate: bool = False): 54 | return _class_call(autoclass("android.os.PowerManager"), args, instantiate) 55 | 56 | 57 | def Process(*args, instantiate: bool = False): 58 | return _class_call(autoclass("android.os.Process"), args, instantiate) 59 | 60 | 61 | def Vibrator(*args, instantiate: bool = False): 62 | return _class_call(autoclass("android.os.Vibrator"), args, instantiate) 63 | 64 | 65 | def VERSION(*args, instantiate: bool = False): 66 | return _class_call(autoclass('android.os.Build$VERSION'), args, instantiate) 67 | 68 | 69 | def VERSION_CODES(*args, instantiate: bool = False): 70 | return _class_call(autoclass("android.os.Build$VERSION_CODES"), args, instantiate) 71 | 72 | 73 | def StrictMode(*args, instantiate: bool = False): 74 | return _class_call(autoclass('android.os.StrictMode'), args, instantiate) 75 | 76 | 77 | def StatFs(*args, instantiate: bool = False): 78 | return _class_call(autoclass("android.os.StatFs"), args, instantiate) 79 | 80 | 81 | def SystemClock(*args, instantiate: bool = False): 82 | return _class_call(autoclass("android.os.SystemClock"), args, instantiate) 83 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/os/ext.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | 3 | from kvdroid.jclass import _class_call 4 | 5 | 6 | def SdkExtensions(*args, instantiate: bool = False): 7 | return _class_call(autoclass('android.os.ext.SdkExtensions'), args, instantiate) 8 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/provider/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Settings(*args, instantiate: bool = False): 6 | return _class_call(autoclass('android.provider.Settings'), args, instantiate) 7 | 8 | 9 | def Contacts(*args, instantiate: bool = False): 10 | return _class_call(autoclass('android.provider.ContactsContract$Contacts'), args, instantiate) 11 | 12 | 13 | def DocumentsContract(*args, instantiate: bool = False): 14 | return _class_call(autoclass('android.provider.DocumentsContract'), args, instantiate) 15 | 16 | 17 | def Phone(*args, instantiate: bool = False): 18 | return _class_call( 19 | autoclass('android.provider.ContactsContract$CommonDataKinds$Phone'), 20 | args, instantiate) 21 | 22 | 23 | def MediaStore(*args, instantiate: bool = False): 24 | return _class_call(autoclass("android.provider.MediaStore"), args, instantiate) 25 | 26 | 27 | def MediaStoreFiles(*args, instantiate: bool = False): 28 | return _class_call(autoclass('android.provider.MediaStore$Files'), args, instantiate) 29 | 30 | 31 | def MediaStoreAudioMedia(*args, instantiate: bool = False): 32 | return _class_call(autoclass('android.provider.MediaStore$Audio$Media'), args, instantiate) 33 | 34 | 35 | def MediaStoreImagesMedia(*args, instantiate: bool = False): 36 | return _class_call(autoclass('android.provider.MediaStore$Images$Media'), args, instantiate) 37 | 38 | 39 | def MediaStoreVideoMedia(*args, instantiate: bool = False): 40 | return _class_call(autoclass('android.provider.MediaStore$Video$Media'), args, instantiate) 41 | 42 | 43 | def MediaStoreDownloads(*args, instantiate: bool = False): 44 | return _class_call(autoclass('android.provider.MediaStore$Downloads'), args, instantiate) 45 | 46 | 47 | def MediaStoreMediaColumns(*args, instantiate: bool = False): 48 | return _class_call(autoclass('android.provider.MediaStore$MediaColumns'), args, instantiate) 49 | 50 | 51 | def TelephonySms(*args, instantiate: bool = False): 52 | return _class_call(autoclass('android.provider.Telephony$Sms'), args, instantiate) 53 | 54 | 55 | def CallLogCalls(*args, instantiate: bool = False): 56 | return _class_call(autoclass('android.provider.CallLog$Calls'), args, instantiate) 57 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/speech/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/android/speech/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/android/speech/tts.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def TextToSpeech(*args, instantiate: bool = False): 6 | return _class_call(autoclass('android.speech.tts.TextToSpeech'), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/android/support/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/android/support/v4/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/android/support/v4/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/android/support/v4/app.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def ActivityCompat(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.support.v4.app.ActivityCompat"), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/telephony/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | def TelephonyManager(*args, instantiate: bool = False): 5 | return _class_call(autoclass("android.telephony.TelephonyManager"), args, instantiate) 6 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/text/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/android/text/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/android/text/format.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Formatter(*args, instantiate: bool = False): 6 | return _class_call(autoclass('android.text.format.Formatter'), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/util/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Size(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.util.Size"), args, instantiate) 7 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/view/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def View(*args, instantiate: bool = False): 6 | return _class_call(autoclass('android.view.View'), args, instantiate) 7 | 8 | 9 | def WindowManager(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.view.WindowManager"), args, instantiate) 11 | 12 | 13 | def WindowManagerLayoutParams(*args, instantiate: bool = False): 14 | return _class_call(autoclass("android.view.WindowManager$LayoutParams"), args, instantiate) 15 | 16 | 17 | def ViewGroupLayoutParams(*args, instantiate: bool = False): 18 | return _class_call(autoclass('android.view.ViewGroup$LayoutParams'), args, instantiate) 19 | 20 | 21 | def TextureView(*args, instantiate: bool = False): 22 | return _class_call(autoclass('android.view.TextureView'), args, instantiate) 23 | -------------------------------------------------------------------------------- /kvdroid/jclass/android/webkit/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def CookieManager(*args, instantiate: bool = False): 6 | return _class_call(autoclass("android.webkit.CookieManager"), args, instantiate) 7 | 8 | 9 | def URLUtil(*args, instantiate: bool = False): 10 | return _class_call(autoclass("android.webkit.URLUtil"), args, instantiate) 11 | 12 | 13 | def MimeTypeMap(*args, instantiate: bool = False): 14 | return _class_call(autoclass('android.webkit.MimeTypeMap'), args, instantiate) 15 | 16 | 17 | def WebView(*args, instantiate: bool = False): 18 | return _class_call(autoclass('android.webkit.WebView'), args, instantiate) -------------------------------------------------------------------------------- /kvdroid/jclass/android/widget/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Toast(*args, instantiate: bool = False): 6 | return _class_call(autoclass('android.widget.Toast'), args, instantiate) 7 | 8 | 9 | def RelativeLayout(*args, instantiate: bool = False): 10 | return _class_call(autoclass('android.widget.RelativeLayout'), args, instantiate) 11 | 12 | 13 | def LinearLayout(*args, instantiate: bool = False): 14 | return _class_call(autoclass('android.widget.LinearLayout'), args, instantiate) 15 | 16 | 17 | def TextView(*args, instantiate: bool = False): 18 | return _class_call(autoclass('android.widget.TextView'), args, instantiate) 19 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass, JavaException 2 | 3 | 4 | try: 5 | autoclass("androidx.core.app.ActivityCompat") 6 | except JavaException as e: 7 | raise JavaException( 8 | f"{e}\nadd androidx.appcompat:appcompat:1.4.2 to buildozer.spec file: android.gradle_dependencies") 9 | 10 | from .browser.customtabs import * 11 | from .core.app import * 12 | from .core.content import * 13 | from .activity import * 14 | from .appcompat.app import * 15 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/activity/__init__.py: -------------------------------------------------------------------------------- 1 | from .result import * 2 | from .result.contract import * 3 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/activity/result/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def PickVisualMediaRequestBuilder(*args, instantiate: bool = False): 6 | return _class_call( 7 | autoclass("androidx.activity.result.PickVisualMediaRequest$Builder"), 8 | args, 9 | instantiate 10 | ) 11 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/activity/result/contract.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def PickVisualMedia(*args, instantiate: bool = False): 6 | return _class_call( 7 | autoclass("androidx.activity.result.contract.ActivityResultContracts$PickVisualMedia"), 8 | args, 9 | instantiate 10 | ) 11 | 12 | 13 | def PickMultipleVisualMedia(*args, instantiate: bool = False): 14 | return _class_call( 15 | autoclass("androidx.activity.result.contract.ActivityResultContracts$PickMultipleVisualMedia"), 16 | args, 17 | instantiate 18 | ) 19 | 20 | 21 | def PickVisualMediaImageAndVideo(*args, instantiate: bool = False): 22 | return _class_call( 23 | autoclass("androidx.activity.result.contract.ActivityResultContracts$PickVisualMedia$ImageAndVideo"), 24 | args, 25 | instantiate 26 | ) 27 | 28 | 29 | def PickVisualMediaImageOnly(*args, instantiate: bool = False): 30 | return _class_call( 31 | autoclass("androidx.activity.result.contract.ActivityResultContracts$PickVisualMedia$ImageOnly"), 32 | args, 33 | instantiate 34 | ) 35 | 36 | 37 | def PickVisualMediaVideoOnly(*args, instantiate: bool = False): 38 | return _class_call( 39 | autoclass("androidx.activity.result.contract.ActivityResultContracts$PickVisualMedia$VideoOnly"), 40 | args, 41 | instantiate 42 | ) 43 | 44 | 45 | def PickVisualMediaSingleMimeType(*args, instantiate: bool = False): 46 | return _class_call( 47 | autoclass("androidx.activity.result.contract.ActivityResultContracts$PickVisualMedia$SingleMimeType"), 48 | args, 49 | instantiate 50 | ) 51 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/appcompat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/androidx/appcompat/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/appcompat/app.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def AppCompatActivity(*args, instantiate: bool = False): 6 | return _class_call( 7 | autoclass("androidx.appcompat.app.AppCompatActivity"), 8 | args, 9 | instantiate 10 | ) 11 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/browser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/androidx/browser/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/browser/customtabs.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass, JavaException 2 | 3 | from kvdroid.jclass import _browserx_except_cls_call 4 | 5 | 6 | def CustomTabsIntent(*args, instantiate: bool = False): 7 | return _browserx_except_cls_call( 8 | "androidx.browser.customtabs.CustomTabsIntent", args, instantiate) 9 | 10 | 11 | def CustomTabsIntentBuilder(*args, instantiate: bool = False): 12 | return _browserx_except_cls_call( 13 | "androidx.browser.customtabs.CustomTabsIntent$Builder", args, instantiate) 14 | 15 | 16 | def CustomTabColorSchemeParams(*args, instantiate: bool = False): 17 | return _browserx_except_cls_call( 18 | "androidx.browser.customtabs.CustomTabColorSchemeParams", args, instantiate) 19 | 20 | 21 | def CustomTabColorSchemeParamsBuilder(*args, instantiate: bool = False): 22 | return _browserx_except_cls_call( 23 | "androidx.browser.customtabs.CustomTabColorSchemeParams$Builder", args, instantiate) 24 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/androidx/core/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/core/app.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | if autoclass('android.os.Build$VERSION').SDK_INT >= 26: 5 | def NotificationManagerCompat(*args, instantiate: bool = False): 6 | return _class_call( 7 | autoclass("androidx.core.app.NotificationManagerCompat"), args, instantiate) 8 | 9 | 10 | def NotificationCompat(*args, instantiate: bool = False): 11 | return _class_call( 12 | autoclass("androidx.core.app.NotificationCompat"), args, instantiate) 13 | 14 | 15 | def NotificationCompatBigPictureStyle(*args, instantiate: bool = False): 16 | return _class_call( 17 | autoclass("androidx.core.app.NotificationCompat$BigPictureStyle"), args, instantiate) 18 | 19 | 20 | def NotificationCompatAction(*args, instantiate: bool = False): 21 | return _class_call( 22 | autoclass("androidx.core.app.NotificationCompat$Action"), args, instantiate) 23 | 24 | 25 | def NotificationCompatActionBuilder(*args, instantiate: bool = False): 26 | return _class_call( 27 | autoclass("androidx.core.app.NotificationCompat$Action$Builder"), args, instantiate) 28 | 29 | 30 | def NotificationCompatBuilder(*args, instantiate: bool = False): 31 | return _class_call( 32 | autoclass("androidx.core.app.NotificationCompat$Builder"), args, instantiate) 33 | 34 | 35 | def NotificationBigTextStyle(*args, instantiate: bool = False): 36 | return _class_call( 37 | autoclass("androidx.core.app.NotificationCompat$BigTextStyle"), args, instantiate) 38 | 39 | 40 | def RemoteInput(*args, instantiate: bool = False): 41 | return _class_call( 42 | autoclass("androidx.core.app.RemoteInput"), args, instantiate) 43 | 44 | 45 | def RemoteInputBuilder(*args, instantiate: bool = False): 46 | return _class_call( 47 | autoclass("androidx.core.app.RemoteInput$Builder"), args, instantiate) 48 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/core/content/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def ContextCompat(*args, instantiate: bool = False): 6 | return _class_call( 7 | autoclass("androidx.core.content.ContextCompat"), args, instantiate) 8 | 9 | 10 | def FileProvider(*args, instantiate: bool = False): 11 | return _class_call( 12 | autoclass("androidx.core.content.FileProvider"), args, instantiate) 13 | -------------------------------------------------------------------------------- /kvdroid/jclass/androidx/core/view.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def ViewCompat(*args, instantiate: bool = False): 6 | return _class_call(autoclass("androidx.core.view.ViewCompat"), args, instantiate) 7 | 8 | 9 | def WindowInsetsCompat(*args, instantiate: bool = False): 10 | return _class_call(autoclass("androidx.core.view.WindowInsetsCompat"), args, instantiate) 11 | 12 | 13 | def WindowInsetsCompatType(*args, instantiate: bool = False): 14 | return _class_call(autoclass("androidx.core.view.WindowInsetsCompat$Type"), args, instantiate) 15 | -------------------------------------------------------------------------------- /kvdroid/jclass/java/__init__.py: -------------------------------------------------------------------------------- 1 | from .io import * 2 | from .lang import * 3 | from .net import * 4 | from .util import * 5 | from .text import * 6 | -------------------------------------------------------------------------------- /kvdroid/jclass/java/io.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def File(*args, instantiate: bool = False): 6 | return _class_call(autoclass('java.io.File'), args, instantiate) 7 | 8 | 9 | def FileOutputStream(*args, instantiate: bool = False): 10 | return _class_call(autoclass('java.io.FileOutputStream'), args, instantiate) 11 | 12 | 13 | def FileInputStream(*args, instantiate: bool = False): 14 | return _class_call(autoclass('java.io.FileInputStream'), args, instantiate) 15 | 16 | 17 | def ByteArrayOutputStream(*args, instantiate: bool = False): 18 | return _class_call(autoclass('java.io.ByteArrayOutputStream'), args, instantiate) 19 | 20 | 21 | def InputStream(*args, instantiate: bool = False): 22 | return _class_call(autoclass('java.io.InputStream'), args, instantiate) 23 | 24 | 25 | def DataInputStream(*args, instantiate: bool = False): 26 | return _class_call(autoclass('java.io.DataInputStream'), args, instantiate) 27 | 28 | 29 | def OutputStream(*args, instantiate: bool = False): 30 | return _class_call(autoclass('java.io.OutputStream'), args, instantiate) 31 | 32 | -------------------------------------------------------------------------------- /kvdroid/jclass/java/lang.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Runtime(*args, instantiate: bool = False): 6 | return _class_call(autoclass('java.lang.Runtime'), args, instantiate) 7 | 8 | 9 | def String(*args, instantiate: bool = False): 10 | return _class_call(autoclass("java.lang.String"), args, instantiate) 11 | 12 | 13 | def StringBuffer(*args, instantiate: bool = False): 14 | return _class_call(autoclass("java.lang.StringBuffer"), args, instantiate) 15 | 16 | 17 | def StringBuilder(*args, instantiate: bool = False): 18 | return _class_call(autoclass("java.lang.StringBuilder"), args, instantiate) 19 | 20 | 21 | def System(*args, instantiate: bool = False): 22 | return _class_call(autoclass("java.lang.System"), args, instantiate) 23 | 24 | 25 | def Long(*args, instantiate: bool = False): 26 | return _class_call(autoclass("java.lang.Long"), args, instantiate) -------------------------------------------------------------------------------- /kvdroid/jclass/java/net.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def URLConnection(*args, instantiate: bool = False): 6 | return _class_call(autoclass("java.net.URLConnection"), args, instantiate) 7 | 8 | 9 | def HttpURLConnection(*args, instantiate: bool = False): 10 | return _class_call(autoclass("java.net.HttpURLConnection"), args, instantiate) 11 | 12 | 13 | def URL(*args, instantiate: bool = False): 14 | return _class_call(autoclass("java.net.URL"), args, instantiate) 15 | 16 | 17 | def Socket(*args, instantiate: bool = False): 18 | return _class_call(autoclass("java.net.Socket"), args, instantiate) 19 | 20 | 21 | def InetAddress(*args, instantiate: bool = False): 22 | return _class_call(autoclass("java.net.InetAddress"), args, instantiate) 23 | -------------------------------------------------------------------------------- /kvdroid/jclass/java/text.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def SimpleDateFormat(*args, instantiate: bool = False): 6 | return _class_call(autoclass('java.text.SimpleDateFormat'), args, instantiate) 7 | 8 | 9 | -------------------------------------------------------------------------------- /kvdroid/jclass/java/util.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def Locale(*args, instantiate: bool = False): 6 | return _class_call(autoclass('java.util.Locale'), args, instantiate) 7 | 8 | 9 | def Date(*args, instantiate: bool = False): 10 | return _class_call(autoclass('java.util.Date'), args, instantiate) 11 | 12 | 13 | def ArrayList(*args, instantiate: bool = False): 14 | return _class_call(autoclass('java.util.ArrayList'), args, instantiate) 15 | 16 | 17 | def HashMap(*args, instantiate: bool = False): 18 | return _class_call(autoclass('java.util.HashMap'), args, instantiate) -------------------------------------------------------------------------------- /kvdroid/jclass/org/__init__.py: -------------------------------------------------------------------------------- 1 | from .json import * 2 | from .kivy.android import * 3 | -------------------------------------------------------------------------------- /kvdroid/jclass/org/json.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | 4 | 5 | def JSONArray(*args, instantiate: bool = False): 6 | return _class_call(autoclass('org.json.JSONArray'), args, instantiate) 7 | 8 | 9 | def JSONObject(*args, instantiate: bool = False): 10 | return _class_call(autoclass('org.json.JSONObject'), args, instantiate) 11 | -------------------------------------------------------------------------------- /kvdroid/jclass/org/kivy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jclass/org/kivy/__init__.py -------------------------------------------------------------------------------- /kvdroid/jclass/org/kivy/android/__init__.py: -------------------------------------------------------------------------------- 1 | from jnius import autoclass 2 | from kvdroid.jclass import _class_call 3 | from android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME, JAVA_NAMESPACE # NOQA 4 | 5 | 6 | def PythonActivity(*args, instantiate: bool = False): 7 | return _class_call(autoclass(ACTIVITY_CLASS_NAME), args, instantiate) 8 | 9 | 10 | def PythonService(*args, instantiate: bool = False): 11 | return _class_call(autoclass(SERVICE_CLASS_NAME), args, instantiate) 12 | 13 | 14 | def GenericBroadcastReceiver(*args, instantiate: bool = False): 15 | return _class_call(autoclass(f"{JAVA_NAMESPACE}.GenericBroadcastReceiver"), args, instantiate) 16 | -------------------------------------------------------------------------------- /kvdroid/jinterface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/jinterface/__init__.py -------------------------------------------------------------------------------- /kvdroid/jinterface/activity.py: -------------------------------------------------------------------------------- 1 | from jnius import PythonJavaClass, java_method 2 | 3 | 4 | class ActivityResultCallback(PythonJavaClass): 5 | __javainterfaces__ = ["androidx/activity/result/ActivityResultCallback"] 6 | __javacontext__ = "app" 7 | 8 | def __init__(self, callback): 9 | super().__init__() 10 | self.callback = callback 11 | 12 | @java_method("(Ljava/lang/Object;)V") 13 | def onActivityResult(self, obj): 14 | self.callback(obj) 15 | -------------------------------------------------------------------------------- /kvdroid/jinterface/media.py: -------------------------------------------------------------------------------- 1 | from jnius import PythonJavaClass, java_method 2 | 3 | 4 | class OnAudioFocusChangeListener(PythonJavaClass): 5 | __javainterfaces__ = ["android/media/AudioManager$OnAudioFocusChangeListener"] 6 | __javacontext__ = "app" 7 | 8 | def __init__(self, callback, **kwargs): 9 | super(OnAudioFocusChangeListener, self).__init__(**kwargs) 10 | self.callback = callback 11 | 12 | @java_method("(I)V") 13 | def onAudioFocusChange(self, focus_change): 14 | self.callback(focus_change) 15 | -------------------------------------------------------------------------------- /kvdroid/tools/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from time import sleep 3 | from typing import Union 4 | from kvdroid import _convert_color 5 | from jnius import JavaException # NOQA 6 | from kvdroid import activity 7 | from kvdroid.jclass.androidx.core.view import ViewCompat, WindowInsetsCompatType 8 | from kvdroid.jclass.java import URL, Runtime, String 9 | from kvdroid.jclass.android import ( 10 | Intent, 11 | Context, 12 | Environment, 13 | Request, 14 | WallpaperManager, 15 | Color, 16 | BitmapFactory, 17 | Rect, 18 | URLUtil, 19 | VERSION, 20 | ComponentName, 21 | Toast, 22 | ) 23 | from android.runnable import run_on_ui_thread # NOQA 24 | 25 | 26 | def _android_version(): 27 | version = VERSION(instantiate=True) 28 | return version.RELEASE 29 | 30 | 31 | android_version = _android_version() 32 | 33 | 34 | @run_on_ui_thread 35 | def toast(text, length_long=False, gravity=0, y=0, x=0): 36 | """ 37 | Displays a toast. 38 | 39 | :param length_long: the amount of time (in seconds) that the toast is 40 | visible on the screen; 41 | :param text: text to be displayed in the toast; 42 | :param length_long: duration of the toast, if `True` the toast 43 | will last 2.3s but if it is `False` the toast will last 3.9s; 44 | :param gravity: refers to the toast position, if it is 80 the toast will 45 | be shown below, if it is 40 the toast will be displayed above; 46 | :param y: refers to the vertical position of the toast; 47 | :param x: refers to the horizontal position of the toast; 48 | 49 | Important: if only the text value is specified and the value of 50 | the `gravity`, `y`, `x` parameters is not specified, their values will 51 | be 0 which means that the toast will be shown in the center. 52 | """ 53 | 54 | duration = Toast().LENGTH_SHORT if length_long else Toast().LENGTH_LONG 55 | t = Toast().makeText(activity, String(text), duration) 56 | t.setGravity(gravity, x, y) 57 | t.show() 58 | 59 | 60 | def share_text(text, title='Share', chooser=False, app_package=None, call_playstore=True, error_msg=""): 61 | intent = Intent(Intent().ACTION_SEND) # a function call that returns a java class call 62 | from kvdroid.jclass.java import String 63 | intent.putExtra(Intent().EXTRA_TEXT, String(str(text))) 64 | intent.setType("text/plain") 65 | if app_package: 66 | from kvdroid import packages 67 | app_package = packages[app_package] if app_package in packages else None 68 | from jnius import JavaException # NOQA 69 | try: 70 | intent.setPackage(String(app_package)) 71 | except JavaException: 72 | if call_playstore: 73 | import webbrowser 74 | webbrowser.open(f"http://play.google.com/store/apps/details?id={app_package}") 75 | from kvdroid import Logger 76 | toast(error_msg) if error_msg else Logger.error("Kvdroid: Specified Application is unavailable") 77 | return 78 | from kvdroid import activity 79 | if chooser: 80 | chooser = Intent().createChooser(intent, String(title)) 81 | activity.startActivity(chooser) 82 | else: 83 | activity.startActivity(intent) 84 | 85 | 86 | def share_file(path, title='Share', chooser=True, app_package=None, call_playstore=True, error_msg=""): 87 | from kvdroid.jclass.java import String 88 | path = str(path) 89 | from kvdroid.jclass.android import VERSION 90 | if VERSION().SDK_INT >= 24: 91 | from kvdroid.jclass.android import StrictMode 92 | StrictMode().disableDeathOnFileUriExposure() 93 | shareIntent = Intent(Intent().ACTION_SEND) 94 | shareIntent.setType("*/*") 95 | from kvdroid.jclass.java import File 96 | imageFile = File(path) 97 | from kvdroid.jclass.android import Uri 98 | uri = Uri().fromFile(imageFile) 99 | from kvdroid.cast import cast_object 100 | parcelable = cast_object('parcelable', uri) 101 | shareIntent.putExtra(Intent().EXTRA_STREAM, parcelable) 102 | 103 | if app_package: 104 | from kvdroid import packages 105 | app_package = packages[app_package] if app_package in packages else None 106 | from jnius import JavaException 107 | try: 108 | shareIntent.setPackage(String(app_package)) 109 | except JavaException: 110 | if call_playstore: 111 | import webbrowser 112 | webbrowser.open(f"http://play.google.com/store/apps/details?id={app_package}") 113 | from kvdroid import Logger 114 | toast(error_msg) if error_msg else Logger.error("Kvdroid: Specified Application is unavailable") 115 | return 116 | 117 | if chooser: 118 | chooser = Intent().createChooser(shareIntent, String(title)) 119 | activity.startActivity(chooser) 120 | else: 121 | activity.startActivity(shareIntent) 122 | 123 | 124 | def mime_type(file_path): 125 | from kvdroid.jclass.java import URLConnection 126 | return URLConnection().guessContentTypeFromName(file_path) 127 | 128 | 129 | def get_resource(resource, activity_type=activity): 130 | from jnius import autoclass 131 | return autoclass(f"{activity_type.getPackageName()}.R${resource}") 132 | 133 | 134 | def restart_app(): 135 | from kvdroid.cast import cast_object 136 | currentActivity = cast_object('activity', activity) 137 | context = cast_object('context', currentActivity.getApplicationContext()) 138 | packageManager = context.getPackageManager() 139 | intent = packageManager.getLaunchIntentForPackage(context.getPackageName()) 140 | componentName = intent.getComponent() 141 | mainIntent = Intent().makeRestartActivityTask(componentName) 142 | context.startActivity(mainIntent) 143 | Runtime().getRuntime().exit(0) 144 | 145 | 146 | def download_manager(title, description, url, folder=None, file_name=None): 147 | from kvdroid.jclass.android import Uri 148 | uri = Uri().parse(str(url)) 149 | from kvdroid.cast import cast_object 150 | dm = cast_object("downloadManager", activity.getSystemService(Context().DOWNLOAD_SERVICE)) 151 | request = Request(uri) 152 | request.setTitle(str(title)) 153 | request.setDescription(str(description)) 154 | request.setNotificationVisibility(Request().VISIBILITY_VISIBLE_NOTIFY_COMPLETED) 155 | if folder and file_name: 156 | request.setDestinationInExternalPublicDir(str(folder), str(file_name)) 157 | else: 158 | conn = URL(url).openConnection() 159 | with contextlib.suppress(JavaException): 160 | conn.getContent() 161 | url = conn.getURL().toString() 162 | print(url) 163 | request.setDestinationInExternalPublicDir( 164 | Environment().DIRECTORY_DOWNLOADS, URLUtil().guessFileName(url, None, None) 165 | ) 166 | dm.enqueue(request) 167 | 168 | 169 | @run_on_ui_thread 170 | def change_statusbar_color(color: Union[str, list], text_color): 171 | color = _convert_color(color) 172 | window = activity.getWindow() 173 | if str(text_color) == "black": 174 | from kvdroid.jclass.android import View 175 | window.getDecorView().setSystemUiVisibility(View().SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) 176 | elif str(text_color) == "white": 177 | window.getDecorView().setSystemUiVisibility(0) 178 | else: 179 | raise TypeError("Available options are ['white','black'] for StatusBar text color") 180 | from kvdroid.jclass.android import WindowManagerLayoutParams 181 | window.clearFlags(WindowManagerLayoutParams().FLAG_TRANSLUCENT_STATUS) 182 | window.addFlags(WindowManagerLayoutParams().FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) 183 | window.setStatusBarColor(Color().parseColor(color)) 184 | 185 | 186 | @run_on_ui_thread 187 | def navbar_color(color: Union[str, list]): 188 | color = _convert_color(color) 189 | window = activity.getWindow() 190 | window.setNavigationBarColor(Color().parseColor(color)) 191 | 192 | 193 | def set_wallpaper(path_to_image): 194 | from kvdroid.cast import cast_object 195 | context = cast_object('context', activity.getApplicationContext()) 196 | bitmap = BitmapFactory().decodeFile(path_to_image) 197 | manager = WallpaperManager().getInstance(context) 198 | return manager.setBitmap(bitmap) 199 | 200 | 201 | def speech(text: str, lang: str): 202 | from kvdroid.jclass.android import TextToSpeech 203 | tts = TextToSpeech(activity, None) 204 | retries = 0 205 | from kvdroid.jclass.java import Locale 206 | tts.setLanguage(Locale(lang)) 207 | speak_status = tts.speak(text, TextToSpeech().QUEUE_FLUSH, None) 208 | while retries < 100 and speak_status == -1: 209 | sleep(0.1) 210 | retries += 1 211 | speak_status = tts.speak( 212 | text, TextToSpeech().QUEUE_FLUSH, None 213 | ) 214 | return speak_status 215 | 216 | 217 | def keyboard_height(): 218 | 219 | try: 220 | rect = Rect(instantiate=True) 221 | activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect) 222 | rect.top = 0 223 | return activity.getWindowManager().getDefaultDisplay().getHeight() - ( 224 | rect.bottom - rect.top 225 | ) 226 | except JavaException as e: 227 | print(e) 228 | return 0 229 | 230 | 231 | def check_keyboad_visibility_and_get_height(): 232 | """ 233 | https://developer.android.com/develop/ui/views/layout/sw-keyboard 234 | """ 235 | 236 | view = activity.getWindow().getDecorView() 237 | insets = ViewCompat().getRootWindowInsets(view) 238 | ime_visible = insets.isVisible(WindowInsetsCompatType().ime()) 239 | ime_height = insets.getInsets(WindowInsetsCompatType().ime()).bottom 240 | return ime_visible, ime_height 241 | 242 | 243 | @run_on_ui_thread 244 | def immersive_mode(status='enable'): 245 | window = activity.getWindow() 246 | from kvdroid.jclass.android import View 247 | View = View() 248 | if status == "disable": 249 | return window.getDecorView().setSystemUiVisibility( 250 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 251 | | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 252 | | View.SYSTEM_UI_FLAG_VISIBLE) 253 | else: 254 | return window.getDecorView().setSystemUiVisibility( 255 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 256 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 257 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 258 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 259 | | View.SYSTEM_UI_FLAG_FULLSCREEN 260 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) 261 | 262 | 263 | def launch_app_activity(app_package, app_activity): 264 | if int(android_version.split(".")[0]) <= 12: 265 | intent = Intent(Intent().ACTION_VIEW) 266 | intent.setClassName(app_package, app_activity) 267 | else: 268 | intent = Intent(Intent().ACTION_MAIN) 269 | intent.setFlags(Intent().FLAG_ACTIVITY_NEW_TASK | Intent().FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) 270 | component_name = ComponentName(app_package, app_activity, instantiate=True) 271 | intent.setComponent(component_name) 272 | 273 | return activity.startActivity(intent) 274 | 275 | 276 | def launch_app(app_package): 277 | intent = activity.getPackageManager().getLaunchIntentForPackage(app_package) 278 | activity.startActivity(intent) 279 | 280 | 281 | def app_details(app_package): 282 | from kvdroid.jclass.android import Settings 283 | intent = Intent(Settings().ACTION_APPLICATION_DETAILS_SETTINGS) 284 | from kvdroid.jclass.android import Uri 285 | uri = Uri().parse(f"package:{app_package}") 286 | intent.setData(uri) 287 | activity.startActivity(intent) 288 | 289 | 290 | def set_orientation(mode="user"): 291 | ''' 292 | This function is adapted from the Pykivdroid project (https://github.com/Sahil-pixel/Pykivdroid). 293 | ''' 294 | from kvdroid.jclass.android import ActivityInfo 295 | options = { 296 | 'portrait': ActivityInfo().SCREEN_ORIENTATION_PORTRAIT, 297 | 'landscape': ActivityInfo().SCREEN_ORIENTATION_LANDSCAPE, 298 | 'behind': ActivityInfo().SCREEN_ORIENTATION_BEHIND, 299 | 'full_sensor': ActivityInfo().SCREEN_ORIENTATION_FULL_SENSOR, 300 | 'full_user': ActivityInfo().SCREEN_ORIENTATION_FULL_USER, 301 | 'locked': ActivityInfo().SCREEN_ORIENTATION_LOCKED, 302 | 'no_sensor': ActivityInfo().SCREEN_ORIENTATION_NOSENSOR, 303 | 'user': ActivityInfo().SCREEN_ORIENTATION_USER, 304 | 'user_portrait': ActivityInfo().SCREEN_ORIENTATION_USER_PORTRAIT, 305 | 'user_landscape': ActivityInfo().SCREEN_ORIENTATION_USER_LANDSCAPE, 306 | 'unspecified': ActivityInfo().SCREEN_ORIENTATION_UNSPECIFIED, 307 | 'sensor_portrait': ActivityInfo().SCREEN_ORIENTATION_SENSOR_PORTRAIT, 308 | 'sensor_landscape': ActivityInfo().SCREEN_ORIENTATION_SENSOR_LANDSCAPE, 309 | 'sensor': ActivityInfo().SCREEN_ORIENTATION_SENSOR, 310 | 'reverse_portrait': ActivityInfo().SCREEN_ORIENTATION_REVERSE_PORTRAIT, 311 | 'reverse_landscape': ActivityInfo().SCREEN_ORIENTATION_REVERSE_LANDSCAPE, 312 | } 313 | with contextlib.suppress(JavaException): 314 | if mode in options: 315 | activity.setRequestedOrientation(options[mode]) 316 | -------------------------------------------------------------------------------- /kvdroid/tools/appsource.py: -------------------------------------------------------------------------------- 1 | from kvdroid import activity 2 | 3 | 4 | def app_source(): 5 | packageName = activity.getPackageName() 6 | installer = activity.getPackageManager().getInstallerPackageName(packageName) 7 | return "playstore" if installer == "com.android.vending" else "unknown" 8 | 9 | 10 | def app_info(info: str): 11 | infos = { 12 | "name": activity.getApplicationInfo().loadLabel(activity.getPackageManager()), 13 | "package": activity.getApplicationContext().getPackageName(), 14 | "version_name": activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionName, 15 | "version_code": activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode 16 | } 17 | return infos.get(info) 18 | 19 | 20 | def app_dirs(directory: str, slash: bool = False): 21 | dirs = { 22 | "files": activity.getFilesDir().getAbsolutePath(), 23 | "cache": activity.getCacheDir().getAbsolutePath(), 24 | "app": f"{activity.getFilesDir().getAbsolutePath()}/app", 25 | "ext_files": activity.getExternalFilesDir(None).getAbsolutePath(), 26 | "ext_cache": activity.getExternalCacheDir().getAbsolutePath(), 27 | "data": activity.getFilesDir().getParent(), 28 | } 29 | return f'{dirs[directory]} {"/" if slash else ""}' if directory in dirs else None 30 | -------------------------------------------------------------------------------- /kvdroid/tools/audio.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from typing import Callable 3 | from jnius import JavaException 4 | from kvdroid.jclass.android import MediaPlayer, AudioManager 5 | from kvdroid.cast import cast_object 6 | from kvdroid import activity 7 | from kvdroid.jclass.android import Context 8 | 9 | 10 | class Player: 11 | def __init__(self, on_audio_focus_change: Callable = None): 12 | self.on_audio_focus_change = on_audio_focus_change 13 | self.mPlayer = None 14 | self.content = None 15 | 16 | def raw(self): 17 | return self.mPlayer 18 | 19 | def play(self, content: str): 20 | if not self.mPlayer: 21 | self.mPlayer = MediaPlayer(instantiate=True) 22 | self.content = content 23 | self.mPlayer.stop() 24 | self.mPlayer.reset() 25 | self.mPlayer.setDataSource(self.content) 26 | self.mPlayer.prepare() 27 | self.mPlayer.start() 28 | 29 | def pause(self): 30 | self.mPlayer.pause() 31 | 32 | def resume(self): 33 | self.mPlayer.start() 34 | 35 | def stop(self): 36 | self.mPlayer.stop() 37 | 38 | def stream(self, content: str, on_load_finish: Callable = lambda: None): 39 | Thread(target=self._stream, args=(content, on_load_finish)).start() 40 | 41 | def _stream(self, content: str, on_load_finish: Callable): 42 | if not self.mPlayer: 43 | self.mPlayer = MediaPlayer(instantiate=True) 44 | try: 45 | from kivy.clock import mainthread # NOQA 46 | @mainthread # NOQA 47 | def load_finish(): 48 | on_load_finish() 49 | except ImportError: 50 | from android.runnable import run_on_ui_thread # NOQA 51 | @run_on_ui_thread # NOQA 52 | def load_finish(): 53 | on_load_finish() 54 | self.content = content 55 | self.mPlayer.setAudioStreamType(AudioManager().STREAM_MUSIC) 56 | self.mPlayer.stop() 57 | self.mPlayer.reset() 58 | self.mPlayer.setDataSource(self.content) 59 | self.mPlayer.prepare() 60 | self.mPlayer.start() 61 | load_finish() 62 | 63 | def get_duration(self): 64 | if self.content: 65 | return self.mPlayer.getDuration() 66 | 67 | def current_position(self): 68 | if self.content: 69 | return self.mPlayer.getCurrentPosition() 70 | 71 | def seek(self, value: int): 72 | try: 73 | self.mPlayer.seekTo(value * 1000) 74 | except JavaException: 75 | pass 76 | 77 | def do_loop(self, loop=False): 78 | if not loop: 79 | self.mPlayer.setLooping(False) 80 | else: 81 | self.mPlayer.setLooping(True) 82 | 83 | def is_playing(self): 84 | return self.mPlayer.isPlaying() 85 | 86 | def release_media_player(self): 87 | if not self.mPlayer: 88 | return 89 | self.mPlayer.release() 90 | self.mPlayer = None 91 | 92 | def _on_audio_focus_change(self, focus_change): 93 | if focus_change == AudioManager().AUDIOFOCUS_LOSS: 94 | self.stop() 95 | elif focus_change == AudioManager().AUDIOFOCUS_LOSS_TRANSIENT: 96 | self.pause() 97 | elif focus_change == AudioManager().AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 98 | self.mPlayer.setVolume(0.2, 0.2) 99 | elif focus_change == AudioManager().AUDIOFOCUS_GAIN: 100 | self.mPlayer.setVolume(1.0, 1.0) 101 | self.resume() 102 | 103 | def user_defined_focus_change(self, focus_change): 104 | if focus_change == AudioManager().AUDIOFOCUS_LOSS: 105 | self.on_audio_focus_change("AUDIOFOCUS_LOSS") 106 | elif focus_change == AudioManager().AUDIOFOCUS_LOSS_TRANSIENT: 107 | self.on_audio_focus_change("AUDIOFOCUS_LOSS_TRANSIENT") 108 | elif focus_change == AudioManager().AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 109 | self.on_audio_focus_change("AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK") 110 | elif focus_change == AudioManager().AUDIOFOCUS_GAIN: 111 | self.on_audio_focus_change("AUDIOFOCUS_GAIN") 112 | 113 | def request_audio_focus(self): 114 | focusChangeListener = OnAudioFocusChangeListener( 115 | callback=self.user_defined_focus_change or self._on_audio_focus_change) 116 | am = cast_object("audioManager", activity.getSystemService(Context().AUDIO_SERVICE)) 117 | result = am.requestAudioFocus(focusChangeListener, AudioManager().STREAM_MUSIC, AudioManager().AUDIOFOCUS_GAIN) 118 | if result == AudioManager().AUDIOFOCUS_REQUEST_GRANTED: 119 | return True 120 | return False 121 | -------------------------------------------------------------------------------- /kvdroid/tools/broadcast.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # Broadcast receiver bridge 3 | 4 | from jnius import autoclass, PythonJavaClass, java_method # NOQA 5 | from android.config import JNI_NAMESPACE # NOQA 6 | from kvdroid.jclass.org import GenericBroadcastReceiver 7 | from kvdroid.jclass.android import IntentFilter, HandlerThread, Intent, Handler 8 | from kvdroid import activity 9 | 10 | 11 | class BroadcastReceiver(object): 12 | 13 | class Callback(PythonJavaClass): 14 | __javainterfaces__ = [f'{JNI_NAMESPACE}/GenericBroadcastReceiverCallback'] 15 | __javacontext__ = 'app' 16 | 17 | def __init__(self, callback, *args, **kwargs): 18 | self.callback = callback 19 | PythonJavaClass.__init__(self, *args, **kwargs) 20 | 21 | @java_method('(Landroid/content/Context;Landroid/content/Intent;)V') 22 | def onReceive(self, context, intent): 23 | self.callback(context, intent) 24 | 25 | def __init__(self, callback, actions=None, categories=None, use_intent_action=True): 26 | super().__init__() 27 | self.handler = None 28 | self.callback = callback 29 | 30 | if not actions and not categories: 31 | raise ValueError('You need to define at least actions or categories') 32 | 33 | def _expand_partial_name(partial_name): 34 | if '.' in partial_name: 35 | return partial_name # Its actually a full dotted name 36 | name = 'ACTION_{}'.format(partial_name.upper()) 37 | if not hasattr(Intent(), name): 38 | raise AttributeError('The intent {} doesnt exist'.format(name)) 39 | return getattr(Intent(), name) 40 | 41 | if use_intent_action: 42 | # resolve actions/categories first 43 | resolved_actions = [_expand_partial_name(x) for x in actions or []] 44 | resolved_categories = [_expand_partial_name(x) for x in categories or []] 45 | else: 46 | resolved_actions = actions 47 | resolved_categories = categories 48 | 49 | # resolve android API 50 | 51 | # create a thread for handling events from the receiver 52 | self.handler_thread = HandlerThread('handlerthread') 53 | 54 | # create a listener 55 | self.listener = BroadcastReceiver.Callback(self.callback) 56 | self.receiver = GenericBroadcastReceiver(self.listener) 57 | self.receiver_filter = IntentFilter(instantiate=True) 58 | for x in resolved_actions: 59 | self.receiver_filter.addAction(x) 60 | for x in resolved_categories: 61 | self.receiver_filter.addCategory(x) 62 | 63 | def start(self): 64 | self.handler_thread.start() 65 | self.handler = Handler(self.handler_thread.getLooper()) 66 | self.context.registerReceiver( 67 | self.receiver, self.receiver_filter, None, self.handler) 68 | 69 | def stop(self): 70 | self.context.unregisterReceiver(self.receiver) 71 | self.handler_thread.quit() 72 | 73 | @property 74 | def context(self): 75 | return activity 76 | -------------------------------------------------------------------------------- /kvdroid/tools/call.py: -------------------------------------------------------------------------------- 1 | from kvdroid.jclass.android import Uri, Intent, CallLogCalls 2 | from kvdroid import activity 3 | from datetime import datetime 4 | 5 | get_date = datetime.fromtimestamp 6 | 7 | 8 | def make_call(tel): 9 | intent = Intent(Intent().ACTION_CALL, Uri().parse(f"tel:{tel}")) 10 | activity.startActivity(intent) 11 | 12 | 13 | def dial_call(tel): 14 | intent = Intent(Intent().ACTION_DIAL, Uri().parse(f"tel:{tel}")) 15 | activity.startActivity(intent) 16 | 17 | 18 | def get_call_log(content_resolver=activity.getContentResolver()): 19 | # sourcery skip: use-named-expression 20 | Calls = CallLogCalls() 21 | cursor = content_resolver.query(Calls.CONTENT_URI, None, None, None, None) 22 | if cursor: 23 | total_log = cursor.getCount() 24 | call_logs = [] 25 | call_logs_type = {1: "incoming", 2: "outgoing", 3: "missed", 4: "voicemail", 5: "rejected", 6: "blocked"} 26 | try: 27 | while cursor.moveToNext(): 28 | call_logs.append({ 29 | "date": get_date(float(cursor.getString(cursor.getColumnIndexOrThrow(Calls.DATE))) / 1000), 30 | "name": cursor.getString(cursor.getColumnIndexOrThrow(Calls.CACHED_NAME)), 31 | "number": cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER)), 32 | "duration": cursor.getString(cursor.getColumnIndexOrThrow(Calls.DURATION)), 33 | "type": call_logs_type[cursor.getInt(cursor.getColumnIndexOrThrow(Calls.TYPE))] 34 | }) 35 | finally: 36 | cursor.close() 37 | return total_log, call_logs 38 | return 39 | -------------------------------------------------------------------------------- /kvdroid/tools/camera.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/tools/camera.py -------------------------------------------------------------------------------- /kvdroid/tools/contact.py: -------------------------------------------------------------------------------- 1 | from kvdroid import activity 2 | 3 | 4 | def get_contact_details(option: str = "phone_book"): 5 | from kvdroid.jclass.android import Phone 6 | Phone = Phone() 7 | """ 8 | option accepts this values : "phone_book", "mobile_no", "names" 9 | 10 | :param option: str: used to determine the return value 11 | :return: value 12 | """ 13 | value = None 14 | PROJECTION = ["contact_id", "display_name", Phone.NUMBER] 15 | cr = activity.getContentResolver() 16 | cursor = cr.query(Phone.CONTENT_URI, PROJECTION, None, None, "display_name" + " ASC") 17 | mobile_no_set: list = [] 18 | phone_book: dict = {} 19 | if cursor: 20 | try: 21 | name_index: int = cursor.getColumnIndex("display_name") 22 | number_index: int = cursor.getColumnIndex(Phone.NUMBER) 23 | 24 | while cursor.moveToNext(): 25 | name = cursor.getString(name_index) 26 | number = cursor.getString(number_index) 27 | number = number.replace(" ", "") 28 | if number not in mobile_no_set: 29 | if name in phone_book: 30 | phone_book[name].append(number) 31 | else: 32 | phone_book[name] = [number] 33 | mobile_no_set.append(number) 34 | finally: 35 | cursor.close() 36 | 37 | if option == "mobile_no": 38 | value = mobile_no_set 39 | elif option == "names": 40 | value = list(phone_book.keys()) 41 | elif option == "phone_book": 42 | value = phone_book 43 | else: 44 | raise TypeError("available options are ['names', 'mobile_no', 'phone_book'] for get_contact_details") 45 | return value 46 | -------------------------------------------------------------------------------- /kvdroid/tools/darkmode.py: -------------------------------------------------------------------------------- 1 | from kvdroid import activity 2 | 3 | 4 | def dark_mode(): 5 | from kvdroid.jclass.android import Configuration 6 | Configuration = Configuration() 7 | night_mode_flags = activity.getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK 8 | if night_mode_flags == Configuration.UI_MODE_NIGHT_YES: 9 | return True 10 | elif night_mode_flags in [ 11 | Configuration.UI_MODE_NIGHT_NO, 12 | Configuration.UI_MODE_NIGHT_UNDEFINED, 13 | ]: 14 | return False 15 | -------------------------------------------------------------------------------- /kvdroid/tools/deviceinfo.py: -------------------------------------------------------------------------------- 1 | from kvdroid.jclass.android.app import MemoryInfo 2 | from kvdroid.jclass.android import IntentFilter, Intent 3 | from kvdroid.jclass.android import StatFs 4 | from kvdroid.jclass.java import Runtime 5 | from kvdroid import activity 6 | 7 | 8 | def device_info(text:str="", convert=False): 9 | from kvdroid.jclass.android import Context, Build, BatteryManager, VERSION, Environment 10 | Environment = Environment() 11 | VERSION = VERSION() 12 | Build = Build() 13 | BatteryManager = BatteryManager() 14 | Context = Context() 15 | bm = activity.getSystemService(Context.BATTERY_SERVICE) 16 | count = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER) 17 | cap = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) 18 | intent = activity.registerReceiver(None, IntentFilter(Intent().ACTION_BATTERY_CHANGED)) 19 | 20 | def convert_bytes(num): 21 | step_unit = 1000.0 # 1024 bad the size 22 | for x in ['bytes', 'KB', 'MB', 'GB', 'TB']: 23 | if num < step_unit: 24 | return "%3.1f %s" % (num, x) 25 | num /= step_unit 26 | 27 | def avail_mem(): 28 | stat = StatFs(Environment.getDataDirectory().getPath()) 29 | bytesAvailable = stat.getBlockSize() * stat.getAvailableBlocks() 30 | if convert: 31 | return convert_bytes(bytesAvailable) 32 | else: 33 | return bytesAvailable 34 | 35 | def total_mem(): 36 | stat = StatFs(Environment.getDataDirectory().getPath()) 37 | bytesAvailable = stat.getBlockSize() * stat.getBlockCount() 38 | if convert: 39 | return convert_bytes(bytesAvailable) 40 | else: 41 | return bytesAvailable 42 | 43 | def used_mem(): 44 | stat = StatFs(Environment.getDataDirectory().getPath()) 45 | total = stat.getBlockSize() * stat.getBlockCount() 46 | avail = stat.getBlockSize() * stat.getAvailableBlocks() 47 | if convert: 48 | return convert_bytes(total - avail) 49 | else: 50 | return total - avail 51 | 52 | def avail_ram(): 53 | memInfo = MemoryInfo(instantiate=True) 54 | service = activity.getSystemService(Context.ACTIVITY_SERVICE) 55 | service.getMemoryInfo(memInfo) 56 | if convert: 57 | return convert_bytes(memInfo.availMem) 58 | else: 59 | return memInfo.availMem 60 | 61 | def total_ram(): 62 | memInfo = MemoryInfo(instantiate=True) 63 | service = activity.getSystemService(Context.ACTIVITY_SERVICE) 64 | service.getMemoryInfo(memInfo) 65 | if convert: 66 | return convert_bytes(memInfo.totalMem) 67 | else: 68 | return memInfo.totalMem 69 | 70 | def used_ram(): 71 | memInfo = MemoryInfo(instantiate=True) 72 | service = activity.getSystemService(Context.ACTIVITY_SERVICE) 73 | service.getMemoryInfo(memInfo) 74 | if convert: 75 | return convert_bytes(memInfo.totalMem - memInfo.availMem) 76 | else: 77 | return memInfo.totalMem - memInfo.availMem 78 | 79 | def bat_health(): 80 | context = activity.getApplicationContext() 81 | intent_filter = IntentFilter(Intent().ACTION_BATTERY_CHANGED) 82 | intent = context.registerReceiver(None, intent_filter) 83 | health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, -1) 84 | if health == BatteryManager.BATTERY_HEALTH_GOOD: 85 | return "Good" 86 | elif health == BatteryManager.BATTERY_HEALTH_OVERHEAT: 87 | return "Overheated" 88 | elif health == BatteryManager.BATTERY_HEALTH_DEAD: 89 | return "Dead" 90 | elif health == BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: 91 | return "Over voltage" 92 | elif health == BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE: 93 | return "Unspecified" 94 | else: 95 | return "Unknown" 96 | 97 | 98 | def bat_status(): 99 | context = activity.getApplicationContext() 100 | intent_filter = IntentFilter(Intent().ACTION_BATTERY_CHANGED) 101 | intent = context.registerReceiver(None, intent_filter) 102 | status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1) 103 | if status == BatteryManager.BATTERY_STATUS_CHARGING: 104 | return "Charging" 105 | elif status == BatteryManager.BATTERY_STATUS_DISCHARGING: 106 | return "Discharging" 107 | elif status == BatteryManager.BATTERY_STATUS_FULL: 108 | return "Full" 109 | elif status == BatteryManager.BATTERY_STATUS_NOT_CHARGING: 110 | return "Not charging" 111 | else: 112 | return "Unknown" 113 | 114 | 115 | 116 | infos = { 117 | 'model': Build.MODEL, 118 | 'brand': Build.BRAND, 119 | 'manufacturer': Build.MANUFACTURER, 120 | 'version': VERSION.RELEASE, 121 | 'sdk': VERSION.SDK, 122 | 'product': Build.PRODUCT, 123 | 'base': VERSION.BASE_OS, 124 | 'rom': VERSION.INCREMENTAL, 125 | 'security': VERSION.SECURITY_PATCH, 126 | 'hardware': Build.HARDWARE, 127 | 'tags': Build.TAGS, 128 | 'sdk_int': VERSION.SDK_INT, 129 | 'cpu_abi': Build.CPU_ABI, 130 | 'cpu_cores': Runtime().getRuntime().availableProcessors(), 131 | 'avail_mem': avail_mem(), 132 | 'total_mem': total_mem(), 133 | 'used_mem': used_mem(), 134 | 'avail_ram': avail_ram(), 135 | 'total_ram': total_ram(), 136 | 'used_ram': used_ram(), 137 | 'bat_level': bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY), 138 | 'bat_capacity': round((count / cap) * 100), 139 | 'bat_tempeture': intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) / 10, 140 | 'bat_voltage': float(intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0) * 0.001), 141 | 'bat_health' : bat_health(), 142 | 'bat_status' : bat_status(), 143 | 'bat_technology': intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY) 144 | } 145 | 146 | if text: 147 | if text in infos.keys(): 148 | return infos[text] 149 | else: 150 | raise KeyError(f"Invalid key. Expected one of {list(infos.keys())}") 151 | else: 152 | return infos 153 | -------------------------------------------------------------------------------- /kvdroid/tools/email.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from kvdroid.tools import toast 4 | from kvdroid.cast import cast_object 5 | 6 | from kvdroid import activity 7 | from kvdroid.jclass.java import File, String 8 | from kvdroid.jclass.android import Intent, VERSION 9 | from kvdroid.jclass.androidx import FileProvider 10 | from kvdroid.tools.uri import grant_uri_permission 11 | 12 | context = activity.getApplicationContext() 13 | 14 | 15 | def send_email(recipient: List[str], subject: str, body: str, file_path: str = None, 16 | create_chooser: bool = False, mime_type: str = "message/rfc822", 17 | authority: str = f"{context.getPackageName()}.fileprovider") -> None: 18 | """ 19 | This method will open any default or selected email app and sends a body of message 20 | with or without an attachment depending on the chosen argument 21 | 22 | :param authority: file provider authority name 23 | :param recipient: email address of the receiver 24 | :param subject: subject of the email 25 | :param body: body of the email 26 | :param file_path: file path of the attachment to be sent (default is None) 27 | :param create_chooser: whether to create an option to choose a specific app for sending email 28 | :param mime_type: body text type (defaults to "text/plain") 29 | :rtype: None 30 | """ 31 | if VERSION().SDK_INT >= 24: 32 | from kvdroid.jclass.android import StrictMode 33 | StrictMode().disableDeathOnFileUriExposure() 34 | uri = None 35 | email_intent = Intent(Intent().ACTION_SEND) 36 | email_intent.setType(mime_type) 37 | email_intent.putExtra(Intent().EXTRA_EMAIL, recipient) 38 | email_intent.putExtra(Intent().EXTRA_SUBJECT, String(subject)) 39 | email_intent.putExtra(Intent().EXTRA_TEXT, String(body)) 40 | email_intent.addFlags(Intent().FLAG_GRANT_READ_URI_PERMISSION | Intent().FLAG_GRANT_WRITE_URI_PERMISSION) 41 | if file_path: 42 | file = File(file_path) 43 | if not file.exists() or not file.canRead(): 44 | toast(f"{file_path} does not exist") 45 | return 46 | uri = FileProvider().getUriForFile(activity, authority, file) 47 | parcelable = cast_object('parcelable', uri) 48 | email_intent.setDataAndType(uri, activity.getContentResolver().getType(uri)) 49 | email_intent.putExtra(Intent().EXTRA_STREAM, parcelable) 50 | if create_chooser: 51 | chooser_intent = Intent().createChooser( 52 | email_intent, cast_object("charSequence", String("Pick an Email Provider"))) 53 | if file_path: 54 | grant_uri_permission(chooser_intent, uri, 55 | Intent().FLAG_GRANT_WRITE_URI_PERMISSION | 56 | Intent().FLAG_GRANT_READ_URI_PERMISSION) 57 | activity.startActivity(chooser_intent) 58 | else: 59 | email_intent.setClassName('com.google.android.gm', 'com.google.android.gm.ComposeActivityGmailExternal') 60 | if file_path: 61 | context.grantUriPermission(String('com.google.android.gm'), uri, 62 | Intent().FLAG_GRANT_WRITE_URI_PERMISSION | 63 | Intent().FLAG_GRANT_READ_URI_PERMISSION) 64 | activity.startActivity(email_intent) 65 | -------------------------------------------------------------------------------- /kvdroid/tools/font.py: -------------------------------------------------------------------------------- 1 | """ 2 | from kivy.app import App 3 | from kivy.properties import StringProperty 4 | from kivy.uix.boxlayout import BoxLayout 5 | from kivy.uix.screenmanager import Screen 6 | from kivy.uix.recycleview import RecycleView 7 | from kivy.lang import Builder 8 | from kvdroid.jclass.java import Locale 9 | from kvdroid.tools.font import system_font 10 | 11 | Builder.load_string("" 12 | : 13 | RCList: 14 | id: rclist 15 | size_hint: 1,1 16 | 17 | : 18 | viewclass: 'XBox' 19 | RecycleBoxLayout: 20 | spacing: dp(10) 21 | #default_size: dp(100), dp(100) 22 | default_size_hint: 1, None 23 | size_hint_y: None 24 | height: self.minimum_height 25 | orientation: 'vertical' 26 | 27 | : 28 | Button: 29 | text: root.name 30 | font_size: "22sp" 31 | Button: 32 | text: root.native 33 | font_name: root.font_name 34 | font_size: "22sp" 35 | on_press: print(root.font_name) 36 | "") 37 | 38 | class XBox(BoxLayout): 39 | name = StringProperty() 40 | native = StringProperty() 41 | font_name = StringProperty() 42 | 43 | class RCList(RecycleView): 44 | def __init__(self, **kwargs): 45 | super(RCList, self).__init__(**kwargs) 46 | self.data = [] 47 | 48 | class MainApp(Screen): 49 | def __init__(self, **kwargs): 50 | super().__init__(**kwargs) 51 | for locale in Locale().getAvailableLocales(): 52 | try: 53 | item = {"name": locale.getDisplayLanguage(Locale("en")), 54 | "native": locale.getDisplayLanguage(Locale(locale.getLanguage())), 55 | "font_name": system_font(locale.getLanguage())} 56 | if system_font(locale.getLanguage()) != "Roboto" and not item in self.ids.rclist.data: 57 | self.ids.rclist.data.append(item) 58 | except: 59 | print(locale.getLanguage(), locale.getDisplayLanguage()) 60 | 61 | class Test(App): 62 | def build(self): 63 | return MainApp() 64 | 65 | Test().run() 66 | """ 67 | 68 | import os 69 | from kvdroid.tools.iso import iso_codes 70 | from kivy.core.text import LabelBase 71 | from kvdroid.tools.lang import device_lang 72 | from kivy.utils import platform 73 | import xml.etree.ElementTree as ET 74 | 75 | FONT_PATH = os.path.join("/", "system", "fonts/") 76 | FONT_XML_PATHS = [os.path.join("/", "system", "etc", "fonts.xml"), os.path.join("/", "system", "etc", "system_fonts.xml")] 77 | FONT_DICT = {} 78 | 79 | def system_font(language=None): 80 | if not language: 81 | language = device_lang() 82 | else: 83 | language = language.split("-")[0] 84 | if language.lower() in iso_codes.keys(): 85 | if iso_codes[language.lower()] in FONT_DICT.keys(): 86 | return FONT_DICT[iso_codes[language.lower()]] 87 | else: 88 | return "Roboto" 89 | else: 90 | raise ValueError( 91 | "The language definition must be in iso639-1 or iso639-2 code formats such as 'en' or 'eng'") 92 | 93 | def is_font_exist(font): 94 | if os.path.isfile(os.path.join(FONT_PATH, font)): 95 | return font 96 | 97 | def register_font(lang, name, font): 98 | if not lang in FONT_DICT.keys(): 99 | LabelBase.register(name= name, 100 | fn_regular=FONT_PATH + font, 101 | fn_bold= None, 102 | fn_italic= None, 103 | fn_bolditalic= None) 104 | FONT_DICT[lang] = name 105 | 106 | if platform == "android": 107 | for font_xml_path in FONT_XML_PATHS: 108 | if os.path.exists(font_xml_path): 109 | tree = ET.parse(font_xml_path) 110 | root = tree.getroot() 111 | lang_families = [item for item in root.findall("family") if item and 'lang' in item.attrib] 112 | for family in lang_families: 113 | font_elements = family.findall("font") 114 | if font_elements: 115 | font_name = font_elements[0].text.strip() 116 | if is_font_exist(font_name): 117 | name = font_name.split(".")[0].split("-")[0] 118 | lang_code = family.attrib["lang"].split(",") 119 | if len(lang_code) >= 2: 120 | for lang in lang_code: 121 | lang_code = lang.split("-")[-1].strip() 122 | register_font(lang_code,name,font_name) 123 | else: 124 | if lang_code[0] == "ja": 125 | lang_code = "Jpan" 126 | elif lang_code[0] == "ko": 127 | lang_code = "Kore" 128 | elif lang_code[0] == "zh": 129 | lang_code = "Hant" 130 | else: 131 | lang_code = lang_code[0].split("-")[-1].strip() 132 | register_font(lang_code,name,font_name) 133 | 134 | 135 | #print(system_font("ar")) 136 | -------------------------------------------------------------------------------- /kvdroid/tools/graphics.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from kvdroid.cast import cast_object 4 | from kvdroid.jclass.android import Bitmap, CompressFormat, Config, Canvas, AdaptiveIconDrawable, BitmapDrawable 5 | from kvdroid.jclass.java import InputStream 6 | from kvdroid.jclass.java import FileOutputStream 7 | from kvdroid import activity 8 | from kvdroid.jclass.android import BitmapFactory 9 | BitmapFactory = BitmapFactory() 10 | 11 | 12 | def save_drawable(drawable, path, name): 13 | if isinstance(drawable, AdaptiveIconDrawable()): 14 | drawable = cast_object("adaptiveIconDrawable", drawable) 15 | else: 16 | drawable = cast_object("bitmapDrawable", drawable) 17 | 18 | height = drawable.getIntrinsicHeight() if drawable.getIntrinsicHeight() > 0 else 1 19 | width = drawable.getIntrinsicWidth() if drawable.getIntrinsicWidth() > 0 else 1 20 | if drawable.isFilterBitmap(): 21 | bitmap = drawable.getBitmap() 22 | else: 23 | bitmap = Bitmap().createBitmap(width, height, Config().ARGB_8888) 24 | canvas = Canvas(bitmap) 25 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()) 26 | drawable.draw(canvas) 27 | out = FileOutputStream(path + name + ".png") 28 | bitmap.compress(CompressFormat().PNG, 90, out) 29 | return path + name + ".png" 30 | 31 | 32 | def bitmap_to_drawable(bitmap: Union[int, str, InputStream()]): 33 | if isinstance(bitmap, int): 34 | bitmap = BitmapFactory.decodeResource(activity.getResources(), bitmap) 35 | elif isinstance(bitmap, str): 36 | bitmap = BitmapFactory.decodeFile(bitmap) 37 | else: 38 | bitmap = BitmapFactory.decodeStream(bitmap) 39 | return BitmapDrawable(activity.getResources(), bitmap) 40 | -------------------------------------------------------------------------------- /kvdroid/tools/iso.py: -------------------------------------------------------------------------------- 1 | iso_codes = { 2 | 'aa': 'Latn', 3 | 'aar': 'Latn', 4 | 'ab': 'Cyrl', 5 | 'abk': 'Cyrl', 6 | 'abq': 'Cyrl', 7 | 'abr': 'Latn', 8 | 'ace': 'Latn', 9 | 'ach': 'Latn', 10 | 'ada': 'Latn', 11 | 'ady': 'Cyrl', 12 | 'aeb': 'Arab', 13 | 'af': 'Latn', 14 | 'afr': 'Latn', 15 | 'agq': 'Latn', 16 | 'aii': 'Syrc', 17 | 'ak': 'Latn', 18 | 'aka': 'Latn', 19 | 'akz': 'Latn', 20 | 'ale': 'Latn', 21 | 'aln': 'Latn', 22 | 'alt': 'Cyrl', 23 | 'am': 'Ethi', 24 | 'amh': 'Ethi', 25 | 'amo': 'Latn', 26 | 'an': 'Latn', 27 | 'anp': 'Deva', 28 | 'aoz': 'Latn', 29 | 'apu': 'Latn', 30 | 'ar': 'Arab', 31 | 'ara': 'Arab', 32 | 'arg': 'Latn', 33 | 'arn': 'Latn', 34 | 'aro': 'Latn', 35 | 'arp': 'Latn', 36 | 'arq': 'Arab', 37 | 'ars': 'Arab', 38 | 'ary': 'Arab', 39 | 'arz': 'Arab', 40 | 'as': 'Beng', 41 | 'asa': 'Latn', 42 | 'asm': 'Beng', 43 | 'ast': 'Latn', 44 | 'atj': 'Latn', 45 | 'av': 'Cyrl', 46 | 'ava': 'Cyrl', 47 | 'awa': 'Deva', 48 | 'ay': 'Latn', 49 | 'aym': 'Latn', 50 | 'az': 'Latn', 51 | 'aze': 'Latn', 52 | 'ba': 'Cyrl', 53 | 'bak': 'Cyrl', 54 | 'bal': 'Arab', 55 | 'bam': 'Latn', 56 | 'ban': 'Latn', 57 | 'bap': 'Deva', 58 | 'bar': 'Latn', 59 | 'bas': 'Latn', 60 | 'bax': 'Bamu', 61 | 'bbc': 'Latn', 62 | 'bbj': 'Latn', 63 | 'bci': 'Latn', 64 | 'be': 'Cyrl', 65 | 'bej': 'Arab', 66 | 'bel': 'Cyrl', 67 | 'bem': 'Latn', 68 | 'ben': 'Beng', 69 | 'bew': 'Latn', 70 | 'bez': 'Latn', 71 | 'bfd': 'Latn', 72 | 'bfq': 'Taml', 73 | 'bft': 'Arab', 74 | 'bfy': 'Deva', 75 | 'bg': 'Cyrl', 76 | 'bgc': 'Deva', 77 | 'bgn': 'Arab', 78 | 'bgx': 'Grek', 79 | 'bhb': 'Deva', 80 | 'bhi': 'Deva', 81 | 'bho': 'Deva', 82 | 'bi': 'Latn', 83 | 'bik': 'Latn', 84 | 'bin': 'Latn', 85 | 'bis': 'Latn', 86 | 'bjj': 'Deva', 87 | 'bjn': 'Latn', 88 | 'bkm': 'Latn', 89 | 'bku': 'Latn', 90 | 'bla': 'Cans', 91 | 'blt': 'Tavt', 92 | 'bm': 'Latn', 93 | 'bmq': 'Latn', 94 | 'bn': 'Beng', 95 | 'bo': 'Tibt', 96 | 'bod': 'Tibt', 97 | 'bos': 'Latn', 98 | 'bpy': 'Beng', 99 | 'bqi': 'Arab', 100 | 'bqv': 'Latn', 101 | 'br': 'Latn', 102 | 'bra': 'Deva', 103 | 'bre': 'Latn', 104 | 'brh': 'Arab', 105 | 'brx': 'Deva', 106 | 'bs': 'Latn', 107 | 'bss': 'Latn', 108 | 'bto': 'Latn', 109 | 'btv': 'Deva', 110 | 'bua': 'Cyrl', 111 | 'buc': 'Latn', 112 | 'bug': 'Latn', 113 | 'bul': 'Cyrl', 114 | 'bum': 'Latn', 115 | 'bvb': 'Latn', 116 | 'bxr': 'Cyrl', 117 | 'byn': 'Ethi', 118 | 'byv': 'Latn', 119 | 'bze': 'Latn', 120 | 'bzx': 'Latn', 121 | 'ca': 'Latn', 122 | 'cad': 'Latn', 123 | 'car': 'Latn', 124 | 'cat': 'Latn', 125 | 'cay': 'Latn', 126 | 'cch': 'Latn', 127 | 'ccp': 'Cakm', 128 | 'ce': 'Cyrl', 129 | 'ceb': 'Latn', 130 | 'ces': 'Latn', 131 | 'cgg': 'Latn', 132 | 'ch': 'Latn', 133 | 'cha': 'Latn', 134 | 'che': 'Cyrl', 135 | 'chk': 'Latn', 136 | 'chm': 'Cyrl', 137 | 'cho': 'Latn', 138 | 'chp': 'Latn', 139 | 'chr': 'Cher', 140 | 'chv': 'Cyrl', 141 | 'chy': 'Latn', 142 | 'cic': 'Latn', 143 | 'ciw': 'Latn', 144 | 'cja': 'Arab', 145 | 'cjm': 'Cham', 146 | 'cjs': 'Cyrl', 147 | 'ckb': 'Arab', 148 | 'ckt': 'Cyrl', 149 | 'co': 'Latn', 150 | 'cor': 'Latn', 151 | 'cos': 'Latn', 152 | 'cps': 'Latn', 153 | 'cr': 'Latn', 154 | 'cre': 'Latn', 155 | 'crh': 'Cyrl', 156 | 'crj': 'Latn', 157 | 'crk': 'Latn', 158 | 'crl': 'Latn', 159 | 'crm': 'Cans', 160 | 'crs': 'Latn', 161 | 'cs': 'Latn', 162 | 'csw': 'Cans', 163 | 'ctd': 'Latn', 164 | 'cv': 'Cyrl', 165 | 'cy': 'Latn', 166 | 'cym': 'Latn', 167 | 'da': 'Latn', 168 | 'dak': 'Latn', 169 | 'dan': 'Latn', 170 | 'dar': 'Cyrl', 171 | 'dav': 'Latn', 172 | 'dcc': 'Arab', 173 | 'de': 'Latn', 174 | 'del': 'Latn', 175 | 'den': 'Latn', 176 | 'deu': 'Latn', 177 | 'dgr': 'Latn', 178 | 'din': 'Latn', 179 | 'div': 'Thaa', 180 | 'dje': 'Latn', 181 | 'dng': 'Cyrl', 182 | 'dnj': 'Latn', 183 | 'doi': 'Arab', 184 | 'dsb': 'Latn', 185 | 'dtm': 'Latn', 186 | 'dtp': 'Latn', 187 | 'dty': 'Deva', 188 | 'dua': 'Latn', 189 | 'dv': 'Thaa', 190 | 'dyo': 'Latn', 191 | 'dyu': 'Latn', 192 | 'dz': 'Tibt', 193 | 'dzo': 'Tibt', 194 | 'ebu': 'Latn', 195 | 'ee': 'Latn', 196 | 'efi': 'Latn', 197 | 'egl': 'Latn', 198 | 'eka': 'Latn', 199 | 'eky': 'Kali', 200 | 'el': 'Grek', 201 | 'ell': 'Grek', 202 | 'en': 'Latn', 203 | 'eng': 'Latn', 204 | 'eo': 'Latn', 205 | 'epo': 'Latn', 206 | 'es': 'Latn', 207 | 'ess': 'Latn', 208 | 'est': 'Latn', 209 | 'esu': 'Latn', 210 | 'et': 'Latn', 211 | 'eu': 'Latn', 212 | 'eus': 'Latn', 213 | 'evn': 'Cyrl', 214 | 'ewe': 'Latn', 215 | 'ewo': 'Latn', 216 | 'ext': 'Latn', 217 | 'fa': 'Arab', 218 | 'fan': 'Latn', 219 | 'fao': 'Latn', 220 | 'fas': 'Arab', 221 | 'ff': 'Latn', 222 | 'ffm': 'Latn', 223 | 'fi': 'Latn', 224 | 'fia': 'Arab', 225 | 'fij': 'Latn', 226 | 'fil': 'Latn', 227 | 'fin': 'Latn', 228 | 'fit': 'Latn', 229 | 'fj': 'Latn', 230 | 'fkv': 'Latn', 231 | 'fo': 'Latn', 232 | 'fon': 'Latn', 233 | 'fr': 'Latn', 234 | 'fra': 'Latn', 235 | 'frc': 'Latn', 236 | 'frp': 'Latn', 237 | 'frr': 'Latn', 238 | 'frs': 'Latn', 239 | 'fry': 'Latn', 240 | 'fud': 'Latn', 241 | 'ful': 'Latn', 242 | 'fuq': 'Latn', 243 | 'fur': 'Latn', 244 | 'fuv': 'Latn', 245 | 'fvr': 'Latn', 246 | 'fy': 'Latn', 247 | 'ga': 'Latn', 248 | 'gaa': 'Latn', 249 | 'gag': 'Latn', 250 | 'gan': 'Hans', 251 | 'gay': 'Latn', 252 | 'gba': 'Latn', 253 | 'gbm': 'Deva', 254 | 'gbz': 'Arab', 255 | 'gcr': 'Latn', 256 | 'gd': 'Latn', 257 | 'gil': 'Latn', 258 | 'gjk': 'Arab', 259 | 'gju': 'Arab', 260 | 'gl': 'Latn', 261 | 'gla': 'Latn', 262 | 'gld': 'Cyrl', 263 | 'gle': 'Latn', 264 | 'glg': 'Latn', 265 | 'glk': 'Arab', 266 | 'glv': 'Latn', 267 | 'gn': 'Latn', 268 | 'gom': 'Deva', 269 | 'gon': 'Telu', 270 | 'gor': 'Latn', 271 | 'gos': 'Latn', 272 | 'grb': 'Latn', 273 | 'grn': 'Latn', 274 | 'grt': 'Beng', 275 | 'gsw': 'Latn', 276 | 'gu': 'Gujr', 277 | 'gub': 'Latn', 278 | 'guc': 'Latn', 279 | 'guj': 'Gujr', 280 | 'gur': 'Latn', 281 | 'guz': 'Latn', 282 | 'gv': 'Latn', 283 | 'gvr': 'Deva', 284 | 'gwi': 'Latn', 285 | 'ha': 'Latn', 286 | 'hai': 'Latn', 287 | 'hak': 'Hans', 288 | 'hat': 'Latn', 289 | 'hau': 'Latn', 290 | 'haw': 'Latn', 291 | 'haz': 'Arab', 292 | 'hdn': 'Latn', 293 | 'he': 'Hebr', 294 | 'heb': 'Hebr', 295 | 'her': 'Latn', 296 | 'hi': 'Deva', 297 | 'hif': 'Latn', 298 | 'hil': 'Latn', 299 | 'hin': 'Deva', 300 | 'hmd': 'Plrd', 301 | 'hmn': 'Latn', 302 | 'hmo': 'Latn', 303 | 'hnd': 'Arab', 304 | 'hne': 'Deva', 305 | 'hnj': 'Laoo', 306 | 'hnn': 'Latn', 307 | 'hno': 'Arab', 308 | 'ho': 'Latn', 309 | 'hoc': 'Deva', 310 | 'hoj': 'Deva', 311 | 'hop': 'Latn', 312 | 'hr': 'Latn', 313 | 'hrv': 'Latn', 314 | 'hsb': 'Latn', 315 | 'hsn': 'Hans', 316 | 'ht': 'Latn', 317 | 'hu': 'Latn', 318 | 'hun': 'Latn', 319 | 'hup': 'Latn', 320 | 'hy': 'Armn', 321 | 'hye': 'Armn', 322 | 'hz': 'Latn', 323 | 'iba': 'Latn', 324 | 'ibb': 'Latn', 325 | 'ibo': 'Latn', 326 | 'id': 'Latn', 327 | 'ife': 'Latn', 328 | 'ig': 'Latn', 329 | 'ii': 'Yiii', 330 | 'iii': 'Yiii', 331 | 'ik': 'Latn', 332 | 'ikt': 'Latn', 333 | 'iku': 'Latn', 334 | 'ilo': 'Latn', 335 | 'ind': 'Latn', 336 | 'inh': 'Cyrl', 337 | 'ipk': 'Latn', 338 | 'is': 'Latn', 339 | 'isl': 'Latn', 340 | 'it': 'Latn', 341 | 'ita': 'Latn', 342 | 'iu': 'Latn', 343 | 'izh': 'Latn', 344 | 'ja': 'Jpan', 345 | 'jam': 'Latn', 346 | 'jav': 'Latn', 347 | 'jgo': 'Latn', 348 | 'jmc': 'Latn', 349 | 'jml': 'Deva', 350 | 'jpn': 'Jpan', 351 | 'jpr': 'Hebr', 352 | 'jrb': 'Hebr', 353 | 'jv': 'Latn', 354 | 'ka': 'Geor', 355 | 'kaa': 'Cyrl', 356 | 'kab': 'Latn', 357 | 'kac': 'Latn', 358 | 'kaj': 'Latn', 359 | 'kal': 'Latn', 360 | 'kam': 'Latn', 361 | 'kan': 'Knda', 362 | 'kao': 'Latn', 363 | 'kas': 'Deva', 364 | 'kat': 'Geor', 365 | 'kau': 'Latn', 366 | 'kaz': 'Cyrl', 367 | 'kbd': 'Cyrl', 368 | 'kca': 'Cyrl', 369 | 'kcg': 'Latn', 370 | 'kck': 'Latn', 371 | 'kde': 'Latn', 372 | 'kdt': 'Thai', 373 | 'kea': 'Latn', 374 | 'kek': 'Latn', 375 | 'kfo': 'Latn', 376 | 'kfr': 'Deva', 377 | 'kfy': 'Deva', 378 | 'kg': 'Latn', 379 | 'kge': 'Latn', 380 | 'kgp': 'Latn', 381 | 'kha': 'Latn', 382 | 'khb': 'Talu', 383 | 'khk': 'Cyrl', 384 | 'khm': 'Khmr', 385 | 'khn': 'Deva', 386 | 'khq': 'Latn', 387 | 'kht': 'Mymr', 388 | 'khw': 'Arab', 389 | 'ki': 'Latn', 390 | 'kik': 'Latn', 391 | 'kin': 'Latn', 392 | 'kio': 'Latn', 393 | 'kir': 'Latn', 394 | 'kiu': 'Latn', 395 | 'kj': 'Latn', 396 | 'kjg': 'Laoo', 397 | 'kjh': 'Cyrl', 398 | 'kk': 'Cyrl', 399 | 'kkj': 'Latn', 400 | 'kl': 'Latn', 401 | 'kln': 'Latn', 402 | 'km': 'Khmr', 403 | 'kmb': 'Latn', 404 | 'kn': 'Knda', 405 | 'ko': 'Kore', 406 | 'koi': 'Cyrl', 407 | 'kok': 'Deva', 408 | 'kom': 'Cyrl', 409 | 'kon': 'Latn', 410 | 'kor': 'Kore', 411 | 'kos': 'Latn', 412 | 'kpe': 'Latn', 413 | 'kpv': 'Cyrl', 414 | 'kpy': 'Cyrl', 415 | 'kr': 'Latn', 416 | 'krc': 'Cyrl', 417 | 'kri': 'Latn', 418 | 'krj': 'Latn', 419 | 'krl': 'Latn', 420 | 'kru': 'Deva', 421 | 'ks': 'Deva', 422 | 'ksb': 'Latn', 423 | 'ksf': 'Latn', 424 | 'ksh': 'Latn', 425 | 'ku': 'Latn', 426 | 'kua': 'Latn', 427 | 'kum': 'Cyrl', 428 | 'kur': 'Latn', 429 | 'kut': 'Latn', 430 | 'kv': 'Cyrl', 431 | 'kvr': 'Latn', 432 | 'kvx': 'Arab', 433 | 'kw': 'Latn', 434 | 'kxm': 'Thai', 435 | 'kxp': 'Arab', 436 | 'ky': 'Latn', 437 | 'kyu': 'Kali', 438 | 'la': 'Latn', 439 | 'lad': 'Hebr', 440 | 'lag': 'Latn', 441 | 'lah': 'Arab', 442 | 'laj': 'Latn', 443 | 'lam': 'Latn', 444 | 'lao': 'Laoo', 445 | 'lat': 'Latn', 446 | 'lav': 'Latn', 447 | 'lb': 'Latn', 448 | 'lbe': 'Cyrl', 449 | 'lbw': 'Latn', 450 | 'lcp': 'Thai', 451 | 'lep': 'Lepc', 452 | 'lez': 'Cyrl', 453 | 'lg': 'Latn', 454 | 'li': 'Latn', 455 | 'lif': 'Limb', 456 | 'lij': 'Latn', 457 | 'lim': 'Latn', 458 | 'lin': 'Latn', 459 | 'lis': 'Lisu', 460 | 'lit': 'Latn', 461 | 'liv': 'Latn', 462 | 'ljp': 'Latn', 463 | 'lki': 'Arab', 464 | 'lkt': 'Latn', 465 | 'lmn': 'Telu', 466 | 'lmo': 'Latn', 467 | 'ln': 'Latn', 468 | 'lo': 'Laoo', 469 | 'lol': 'Latn', 470 | 'loz': 'Latn', 471 | 'lrc': 'Arab', 472 | 'lt': 'Latn', 473 | 'ltg': 'Latn', 474 | 'ltz': 'Latn', 475 | 'lu': 'Latn', 476 | 'lua': 'Latn', 477 | 'lub': 'Latn', 478 | 'lug': 'Latn', 479 | 'lun': 'Latn', 480 | 'luo': 'Latn', 481 | 'lus': 'Beng', 482 | 'lut': 'Latn', 483 | 'luy': 'Latn', 484 | 'luz': 'Arab', 485 | 'lv': 'Latn', 486 | 'lwl': 'Thai', 487 | 'lzz': 'Latn', 488 | 'mad': 'Latn', 489 | 'maf': 'Latn', 490 | 'mag': 'Deva', 491 | 'mah': 'Latn', 492 | 'mai': 'Deva', 493 | 'mak': 'Latn', 494 | 'mal': 'Mlym', 495 | 'man': 'Nkoo', 496 | 'mar': 'Deva', 497 | 'mas': 'Latn', 498 | 'maz': 'Latn', 499 | 'mdf': 'Cyrl', 500 | 'mdh': 'Latn', 501 | 'mdr': 'Latn', 502 | 'mdt': 'Latn', 503 | 'men': 'Latn', 504 | 'mer': 'Latn', 505 | 'mfa': 'Arab', 506 | 'mfe': 'Latn', 507 | 'mg': 'Latn', 508 | 'mgh': 'Latn', 509 | 'mgo': 'Latn', 510 | 'mgp': 'Deva', 511 | 'mgy': 'Latn', 512 | 'mh': 'Latn', 513 | 'mhr': 'Cyrl', 514 | 'mi': 'Latn', 515 | 'mic': 'Latn', 516 | 'min': 'Latn', 517 | 'mk': 'Cyrl', 518 | 'mkd': 'Cyrl', 519 | 'ml': 'Mlym', 520 | 'mlg': 'Latn', 521 | 'mls': 'Latn', 522 | 'mlt': 'Latn', 523 | 'mn': 'Cyrl', 524 | 'mni': 'Beng', 525 | 'mns': 'Cyrl', 526 | 'mnw': 'Mymr', 527 | 'moe': 'Latn', 528 | 'moh': 'Latn', 529 | 'mon': 'Cyrl', 530 | 'mos': 'Latn', 531 | 'mr': 'Deva', 532 | 'mrd': 'Deva', 533 | 'mri': 'Latn', 534 | 'mrj': 'Cyrl', 535 | 'mro': 'Latn', 536 | 'ms': 'Latn', 537 | 'msa': 'Latn', 538 | 'mt': 'Latn', 539 | 'mtr': 'Deva', 540 | 'mua': 'Latn', 541 | 'mus': 'Latn', 542 | 'mvy': 'Arab', 543 | 'mwk': 'Latn', 544 | 'mwl': 'Latn', 545 | 'mwr': 'Deva', 546 | 'mwv': 'Latn', 547 | 'mxc': 'Latn', 548 | 'my': 'Mymr', 549 | 'mya': 'Mymr', 550 | 'myv': 'Cyrl', 551 | 'myx': 'Latn', 552 | 'mzn': 'Arab', 553 | 'na': 'Latn', 554 | 'nan': 'Hans', 555 | 'nap': 'Latn', 556 | 'naq': 'Latn', 557 | 'nau': 'Latn', 558 | 'nav': 'Latn', 559 | 'nb': 'Latn', 560 | 'nbl': 'Latn', 561 | 'nch': 'Latn', 562 | 'nd': 'Latn', 563 | 'ndc': 'Latn', 564 | 'nde': 'Latn', 565 | 'ndl': 'Latn', 566 | 'ndo': 'Latn', 567 | 'nds': 'Latn', 568 | 'ne': 'Deva', 569 | 'nep': 'Deva', 570 | 'new': 'Deva', 571 | 'ng': 'Latn', 572 | 'ngl': 'Latn', 573 | 'nhe': 'Latn', 574 | 'nhw': 'Latn', 575 | 'nia': 'Latn', 576 | 'nij': 'Latn', 577 | 'nio': 'Cyrl', 578 | 'niu': 'Latn', 579 | 'njo': 'Latn', 580 | 'nl': 'Latn', 581 | 'nld': 'Latn', 582 | 'nmg': 'Latn', 583 | 'nn': 'Latn', 584 | 'nnh': 'Latn', 585 | 'nno': 'Latn', 586 | 'no': 'Latn', 587 | 'nob': 'Latn', 588 | 'nod': 'Lana', 589 | 'noe': 'Deva', 590 | 'nog': 'Cyrl', 591 | 'non': 'Runr', 592 | 'nor': 'Latn', 593 | 'nqo': 'Nkoo', 594 | 'nr': 'Latn', 595 | 'nsk': 'Cans', 596 | 'nso': 'Latn', 597 | 'nus': 'Latn', 598 | 'nv': 'Latn', 599 | 'nxq': 'Latn', 600 | 'ny': 'Latn', 601 | 'nya': 'Latn', 602 | 'nym': 'Latn', 603 | 'nyn': 'Latn', 604 | 'nyo': 'Latn', 605 | 'nzi': 'Latn', 606 | 'oc': 'Latn', 607 | 'oci': 'Latn', 608 | 'oj': 'Latn', 609 | 'oji': 'Latn', 610 | 'olo': 'Latn', 611 | 'om': 'Latn', 612 | 'or': 'Orya', 613 | 'ori': 'Orya', 614 | 'orm': 'Latn', 615 | 'os': 'Cyrl', 616 | 'osa': 'Osge', 617 | 'oss': 'Cyrl', 618 | 'pa': 'Guru', 619 | 'pag': 'Latn', 620 | 'pam': 'Latn', 621 | 'pan': 'Guru', 622 | 'pap': 'Latn', 623 | 'pau': 'Latn', 624 | 'pcd': 'Latn', 625 | 'pcm': 'Latn', 626 | 'pdc': 'Latn', 627 | 'pdt': 'Latn', 628 | 'pfl': 'Latn', 629 | 'pko': 'Latn', 630 | 'pl': 'Latn', 631 | 'pms': 'Latn', 632 | 'pnt': 'Latn', 633 | 'pol': 'Latn', 634 | 'pon': 'Latn', 635 | 'por': 'Latn', 636 | 'prd': 'Arab', 637 | 'prs': 'Arab', 638 | 'ps': 'Arab', 639 | 'pt': 'Latn', 640 | 'pus': 'Arab', 641 | 'puu': 'Latn', 642 | 'qu': 'Latn', 643 | 'quc': 'Latn', 644 | 'que': 'Latn', 645 | 'qug': 'Latn', 646 | 'quz': 'Latn', 647 | 'raj': 'Deva', 648 | 'rap': 'Latn', 649 | 'rar': 'Latn', 650 | 'rcf': 'Latn', 651 | 'rej': 'Latn', 652 | 'rgn': 'Latn', 653 | 'ria': 'Latn', 654 | 'rif': 'Tfng', 655 | 'rjs': 'Deva', 656 | 'rkt': 'Beng', 657 | 'rm': 'Latn', 658 | 'rmf': 'Latn', 659 | 'rmn': 'Latn', 660 | 'rmo': 'Latn', 661 | 'rmt': 'Arab', 662 | 'rmu': 'Latn', 663 | 'rmy': 'Latn', 664 | 'rn': 'Latn', 665 | 'rng': 'Latn', 666 | 'ro': 'Latn', 667 | 'rob': 'Latn', 668 | 'rof': 'Latn', 669 | 'roh': 'Latn', 670 | 'rom': 'Latn', 671 | 'ron': 'Latn', 672 | 'rtm': 'Latn', 673 | 'ru': 'Cyrl', 674 | 'rue': 'Cyrl', 675 | 'rug': 'Latn', 676 | 'run': 'Latn', 677 | 'rup': 'Latn', 678 | 'rus': 'Cyrl', 679 | 'rw': 'Latn', 680 | 'rwk': 'Latn', 681 | 'ryu': 'Kana', 682 | 'sa': 'Deva', 683 | 'sad': 'Latn', 684 | 'saf': 'Latn', 685 | 'sag': 'Latn', 686 | 'sah': 'Cyrl', 687 | 'san': 'Deva', 688 | 'saq': 'Latn', 689 | 'sas': 'Latn', 690 | 'sat': 'Olck', 691 | 'saz': 'Saur', 692 | 'sbp': 'Latn', 693 | 'sc': 'Latn', 694 | 'sck': 'Deva', 695 | 'scn': 'Latn', 696 | 'sco': 'Latn', 697 | 'scs': 'Latn', 698 | 'sd': 'Deva', 699 | 'sdc': 'Latn', 700 | 'sdh': 'Arab', 701 | 'se': 'Latn', 702 | 'see': 'Latn', 703 | 'sef': 'Latn', 704 | 'seh': 'Latn', 705 | 'sei': 'Latn', 706 | 'sel': 'Cyrl', 707 | 'ses': 'Latn', 708 | 'sg': 'Latn', 709 | 'sgs': 'Latn', 710 | 'shi': 'Tfng', 711 | 'shn': 'Mymr', 712 | 'si': 'Sinh', 713 | 'sid': 'Latn', 714 | 'sin': 'Sinh', 715 | 'sjd': 'Cyrl', 716 | 'sje': 'Latn', 717 | 'sjt': 'Cyrl', 718 | 'sk': 'Latn', 719 | 'skr': 'Arab', 720 | 'sl': 'Latn', 721 | 'sli': 'Latn', 722 | 'slk': 'Latn', 723 | 'slv': 'Latn', 724 | 'sly': 'Latn', 725 | 'sm': 'Latn', 726 | 'sma': 'Latn', 727 | 'sme': 'Latn', 728 | 'smj': 'Latn', 729 | 'smn': 'Latn', 730 | 'smo': 'Latn', 731 | 'sms': 'Latn', 732 | 'sn': 'Latn', 733 | 'sna': 'Latn', 734 | 'snd': 'Deva', 735 | 'snk': 'Latn', 736 | 'so': 'Latn', 737 | 'som': 'Latn', 738 | 'sot': 'Latn', 739 | 'sou': 'Thai', 740 | 'spa': 'Latn', 741 | 'sq': 'Latn', 742 | 'sqi': 'Latn', 743 | 'sr': 'Latn', 744 | 'srb': 'Latn', 745 | 'srd': 'Latn', 746 | 'srn': 'Latn', 747 | 'srp': 'Latn', 748 | 'srr': 'Latn', 749 | 'srs': 'Latn', 750 | 'srx': 'Deva', 751 | 'ss': 'Latn', 752 | 'ssw': 'Latn', 753 | 'ssy': 'Latn', 754 | 'st': 'Latn', 755 | 'sto': 'Latn', 756 | 'stq': 'Latn', 757 | 'su': 'Latn', 758 | 'suk': 'Latn', 759 | 'sun': 'Latn', 760 | 'sus': 'Latn', 761 | 'sv': 'Latn', 762 | 'sw': 'Latn', 763 | 'swa': 'Latn', 764 | 'swb': 'Arab', 765 | 'swe': 'Latn', 766 | 'swg': 'Latn', 767 | 'swv': 'Deva', 768 | 'sxn': 'Latn', 769 | 'syi': 'Latn', 770 | 'syl': 'Beng', 771 | 'syr': 'Syrc', 772 | 'szl': 'Latn', 773 | 'ta': 'Taml', 774 | 'tab': 'Cyrl', 775 | 'tah': 'Latn', 776 | 'taj': 'Deva', 777 | 'tam': 'Taml', 778 | 'tat': 'Cyrl', 779 | 'tau': 'Latn', 780 | 'tbw': 'Latn', 781 | 'tcy': 'Knda', 782 | 'tdd': 'Tale', 783 | 'tdg': 'Deva', 784 | 'tdh': 'Deva', 785 | 'te': 'Telu', 786 | 'tel': 'Telu', 787 | 'tem': 'Latn', 788 | 'teo': 'Latn', 789 | 'ter': 'Latn', 790 | 'tet': 'Latn', 791 | 'tg': 'Latn', 792 | 'tgk': 'Latn', 793 | 'tgl': 'Latn', 794 | 'th': 'Thai', 795 | 'tha': 'Thai', 796 | 'thl': 'Deva', 797 | 'thq': 'Deva', 798 | 'thr': 'Deva', 799 | 'ti': 'Ethi', 800 | 'tig': 'Ethi', 801 | 'tir': 'Ethi', 802 | 'tiv': 'Latn', 803 | 'tk': 'Latn', 804 | 'tkl': 'Latn', 805 | 'tkr': 'Latn', 806 | 'tkt': 'Deva', 807 | 'tku': 'Latn', 808 | 'tl': 'Latn', 809 | 'tlh': 'Piqd', 810 | 'tli': 'Latn', 811 | 'tly': 'Latn', 812 | 'tmh': 'Latn', 813 | 'tn': 'Latn', 814 | 'to': 'Latn', 815 | 'tog': 'Latn', 816 | 'ton': 'Latn', 817 | 'tpi': 'Latn', 818 | 'tr': 'Latn', 819 | 'tru': 'Latn', 820 | 'trv': 'Latn', 821 | 'trw': 'Arab', 822 | 'ts': 'Latn', 823 | 'tsd': 'Grek', 824 | 'tsg': 'Latn', 825 | 'tsi': 'Latn', 826 | 'tsj': 'Tibt', 827 | 'tsn': 'Latn', 828 | 'tso': 'Latn', 829 | 'tt': 'Cyrl', 830 | 'ttj': 'Latn', 831 | 'tts': 'Thai', 832 | 'ttt': 'Latn', 833 | 'tuk': 'Latn', 834 | 'tum': 'Latn', 835 | 'tur': 'Latn', 836 | 'tuv': 'Latn', 837 | 'tvl': 'Latn', 838 | 'twq': 'Latn', 839 | 'ty': 'Latn', 840 | 'tyv': 'Cyrl', 841 | 'tzm': 'Tfng', 842 | 'ude': 'Cyrl', 843 | 'udm': 'Cyrl', 844 | 'ug': 'Cyrl', 845 | 'uig': 'Cyrl', 846 | 'uk': 'Cyrl', 847 | 'ukr': 'Cyrl', 848 | 'uli': 'Latn', 849 | 'umb': 'Latn', 850 | 'unr': 'Deva', 851 | 'unx': 'Deva', 852 | 'ur': 'Arab', 853 | 'urd': 'Arab', 854 | 'uz': 'Latn', 855 | 'uzb': 'Latn', 856 | 'vai': 'Vaii', 857 | 've': 'Latn', 858 | 'vec': 'Latn', 859 | 'ven': 'Latn', 860 | 'vep': 'Latn', 861 | 'vi': 'Latn', 862 | 'vic': 'Latn', 863 | 'vie': 'Latn', 864 | 'vls': 'Latn', 865 | 'vmf': 'Latn', 866 | 'vmw': 'Latn', 867 | 'vot': 'Latn', 868 | 'vro': 'Latn', 869 | 'vun': 'Latn', 870 | 'wa': 'Latn', 871 | 'wae': 'Latn', 872 | 'wal': 'Ethi', 873 | 'war': 'Latn', 874 | 'was': 'Latn', 875 | 'wbp': 'Latn', 876 | 'wbq': 'Telu', 877 | 'wbr': 'Deva', 878 | 'wln': 'Latn', 879 | 'wls': 'Latn', 880 | 'wni': 'Arab', 881 | 'wo': 'Latn', 882 | 'wol': 'Latn', 883 | 'wtm': 'Deva', 884 | 'wuu': 'Hans', 885 | 'xal': 'Cyrl', 886 | 'xav': 'Latn', 887 | 'xh': 'Latn', 888 | 'xho': 'Latn', 889 | 'xmf': 'Geor', 890 | 'xnr': 'Deva', 891 | 'xog': 'Latn', 892 | 'xsr': 'Deva', 893 | 'xwo': 'Mong', 894 | 'yao': 'Latn', 895 | 'yap': 'Latn', 896 | 'yav': 'Latn', 897 | 'ybb': 'Latn', 898 | 'yi': 'Hebr', 899 | 'yid': 'Hebr', 900 | 'yo': 'Latn', 901 | 'yor': 'Latn', 902 | 'yrk': 'Cyrl', 903 | 'yrl': 'Latn', 904 | 'yua': 'Latn', 905 | 'yue': 'Hant', 906 | 'za': 'Latn', 907 | 'zag': 'Latn', 908 | 'zap': 'Latn', 909 | 'zdj': 'Arab', 910 | 'zea': 'Latn', 911 | 'zgh': 'Tfng', 912 | 'zh': 'Hant', 913 | 'zha': 'Latn', 914 | 'zho': 'Hant', 915 | 'zmi': 'Latn', 916 | 'zu': 'Latn', 917 | 'zul': 'Latn', 918 | 'zun': 'Latn', 919 | 'zza': 'Latn'} 920 | -------------------------------------------------------------------------------- /kvdroid/tools/lang.py: -------------------------------------------------------------------------------- 1 | from kvdroid.jclass.java.util import Locale 2 | 3 | 4 | def device_lang(option="Language", display_lang=None): 5 | locale = Locale().getDefault() 6 | options = { 7 | "Language": locale.getLanguage(), 8 | "ISO3Language": locale.getISO3Language(), 9 | "Country": locale.getCountry(), 10 | "ISO3Country": locale.getISO3Country(), 11 | "DisplayCountry": locale.getDisplayCountry(Locale(str(display_lang))), 12 | "DisplayName": locale.getDisplayName(Locale(str(display_lang))), 13 | "String": locale.toString(), 14 | "DisplayLanguage": locale.getDisplayLanguage(Locale(str(display_lang))), 15 | "LanguageTag": locale.toLanguageTag()} 16 | 17 | if option not in options.keys(): 18 | raise ValueError(f"Invalid option. Expected one of: {list(options.keys())}") 19 | else: 20 | return options[option] 21 | 22 | 23 | def supported_languages(): 24 | langs = [] 25 | for locale in Locale().getAvailableLocales(): 26 | if locale.getLanguage() not in langs: 27 | langs.append(locale.getLanguage()) 28 | return langs 29 | -------------------------------------------------------------------------------- /kvdroid/tools/metrics.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from kvdroid import activity 4 | from kvdroid.jclass.android.graphics import Point 5 | 6 | 7 | class Metrics(object): 8 | config = activity.getResources().getConfiguration() 9 | metric = activity.getResources().getDisplayMetrics() 10 | 11 | def height_dp(self): 12 | return self.config.screenHeightDp 13 | 14 | def width_dp(self): 15 | return self.config.screenWidthDp 16 | 17 | def height_px(self): 18 | return self.metric.heightPixels 19 | 20 | def width_px(self): 21 | return self.metric.widthPixels 22 | 23 | def orientation(self): 24 | if self.config.orientation == 1: 25 | return "portrait" 26 | else: 27 | return "landscape" 28 | 29 | @staticmethod 30 | def resolution(): 31 | point = Point(instantiate=True) 32 | activity.getWindowManager().getDefaultDisplay().getRealSize(point) 33 | size = re.findall(r"\d+", point.toString()) 34 | return size[1] + "x" + size[0] 35 | -------------------------------------------------------------------------------- /kvdroid/tools/network.py: -------------------------------------------------------------------------------- 1 | from kvdroid.jclass.android import Activity 2 | from kvdroid import activity 3 | from jnius import JavaException, cast 4 | from kvdroid.jclass.android import WifiManager, Formatter, Context 5 | 6 | 7 | def network_status() -> bool: 8 | """ 9 | Checks if Mobile data or Wi-Fi network is connected 10 | 11 | :rtype: bool 12 | :return: network status 13 | """ 14 | return bool(wifi_status() or mobile_status()) 15 | 16 | 17 | def wifi_status() -> bool: 18 | """ 19 | Checks if th phone Wi-Fi is connected to any network 20 | 21 | :rtype: bool 22 | :return: Wi-Fi status 23 | """ 24 | from kvdroid.jclass.android import ConnectivityManager 25 | ConnectivityManager = ConnectivityManager() 26 | con_mgr = activity.getSystemService(Activity().CONNECTIVITY_SERVICE) 27 | try: 28 | return con_mgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnectedOrConnecting() 29 | except JavaException: 30 | return False 31 | 32 | 33 | def mobile_status() -> bool: 34 | """ 35 | Checks if Mobile data is connected 36 | 37 | :rtype: bool 38 | :return: Mobile data status 39 | """ 40 | from kvdroid.jclass.android import ConnectivityManager 41 | ConnectivityManager = ConnectivityManager() 42 | con_mgr = activity.getSystemService(Activity().CONNECTIVITY_SERVICE) 43 | try: 44 | return con_mgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).isConnectedOrConnecting() 45 | except JavaException: 46 | return False 47 | 48 | 49 | def get_wifi_ip_address() -> str: 50 | """ 51 | Gets the Wi-Fi IP Address. But when the Wi-Fi is off, it 52 | returns a string value of "0.0.0.0" 53 | 54 | :rtype: str 55 | :return: Wi-Fi Ip Address 56 | """ 57 | formatter = Formatter() 58 | context = activity.getApplicationContext() 59 | wifi_manager = cast(WifiManager(), context.getSystemService(Context().WIFI_SERVICE)) 60 | return formatter.formatIpAddress(wifi_manager.getConnectionInfo().getIpAddress()) 61 | 62 | 63 | def network_latency() -> float: 64 | import time 65 | from kvdroid.jclass.java import InetAddress 66 | InetAddress = InetAddress() 67 | def ping(host): 68 | start_time = time.time() 69 | address = InetAddress.getByName(host) 70 | if address.isReachable(1000): 71 | end_time = time.time() 72 | return (end_time - start_time) * 1000 73 | else: 74 | return None 75 | return ping('google.com') 76 | 77 | 78 | def get_wifi_signal() -> float: 79 | wm = activity.getSystemService(activity.WIFI_SERVICE) 80 | wifi_info = wm.getConnectionInfo() 81 | signal_strength = wifi_info.getRssi() 82 | if signal_strength < -100: 83 | signal_percent = 0.0 84 | else: 85 | signal_percent = 100.0 * (signal_strength + 100) / 200.0 86 | return signal_percent -------------------------------------------------------------------------------- /kvdroid/tools/package.py: -------------------------------------------------------------------------------- 1 | from kvdroid import activity 2 | from kvdroid.jclass.android import ApplicationInfo, PackageManager, ComponentName, VERSION 3 | from kvdroid.jclass.java import File 4 | 5 | 6 | def all_packages(): 7 | pkgAppsList = activity.getPackageManager().getInstalledPackages(0) 8 | package_list = [] 9 | for i in range(pkgAppsList.size()): 10 | package = pkgAppsList.get(i).applicationInfo 11 | package_list.append(package.packageName) 12 | return package_list 13 | 14 | 15 | def all_main_activities(): 16 | from kvdroid.jclass.android import Intent 17 | mainIntent = Intent(Intent().ACTION_MAIN) 18 | mainIntent.addCategory(Intent().CATEGORY_LAUNCHER) 19 | pkgAppsList = activity.getPackageManager().queryIntentActivities(mainIntent, 0) 20 | activity_list = [] 21 | for i in range(pkgAppsList.size()): 22 | package = pkgAppsList.get(i).activityInfo 23 | activity_list.append({package.packageName: package.name}) 24 | return activity_list 25 | 26 | 27 | def is_system_package(package): 28 | pManager = activity.getPackageManager() 29 | package = pManager.getApplicationInfo( 30 | package, PackageManager().GET_META_DATA) 31 | if (package.flags & (ApplicationInfo().FLAG_SYSTEM | ApplicationInfo().FLAG_UPDATED_SYSTEM_APP)) != 0: 32 | return True 33 | else: 34 | return False 35 | 36 | 37 | def is_package_enabled(package): 38 | pManager = activity.getPackageManager() 39 | return pManager.getApplicationInfo(package, 0).enabled 40 | 41 | 42 | def is_package_installed(package): 43 | pManager = activity.getPackageManager() 44 | try: 45 | pManager.getApplicationInfo(package, 0) 46 | return True 47 | except: 48 | return False 49 | 50 | 51 | def package_source(package): 52 | installer = activity.getPackageManager().getInstallerPackageName(package) 53 | if installer == "com.android.vending": 54 | return "playstore" 55 | else: 56 | return "unknown" 57 | 58 | 59 | def package_info(package): 60 | pManager = activity.getPackageManager() 61 | appInfo = pManager.getPackageInfo(package, 0) 62 | installTime = appInfo.firstInstallTime 63 | updateTime = appInfo.lastUpdateTime 64 | if VERSION().SDK_INT >= 28: 65 | versionCode = appInfo.getLongVersionCode() 66 | versionName = appInfo.versionName 67 | targetSdkVersion = appInfo.applicationInfo.targetSdkVersion 68 | minSdkVersion = appInfo.applicationInfo.minSdkVersion 69 | enabled = is_package_enabled(package) 70 | application = pManager.getApplicationInfo( 71 | package, PackageManager().GET_META_DATA) 72 | applicationName=pManager.getApplicationLabel(pManager.getApplicationInfo(package, pManager.GET_META_DATA)) 73 | size = File(application.publicSourceDir).length() 74 | loadLabel = application.loadLabel(pManager) 75 | loadIcon = application.loadIcon(pManager) 76 | packageName = application.packageName 77 | sourceDir = application.sourceDir 78 | dataDir = application.dataDir 79 | processName = application.processName 80 | publicSourceDir = application.publicSourceDir 81 | sharedLibraryFiles = application.sharedLibraryFiles 82 | packagePerms = pManager.getPackageInfo( 83 | packageName, PackageManager().GET_PERMISSIONS) 84 | requestedPermissions = packagePerms.requestedPermissions 85 | permissions = [] 86 | if requestedPermissions != None: 87 | for i in range(len(requestedPermissions)): 88 | permissions.append(requestedPermissions[i]) 89 | activities = [] 90 | activity_list = pManager.getPackageInfo( 91 | packageName, PackageManager().GET_ACTIVITIES).activities 92 | if activity_list: 93 | for act in activity_list: 94 | activities.append(act.name) 95 | infos = {"packageName": packageName, 96 | "applicationName":applicationName, 97 | "loadLabel": loadLabel, 98 | "loadIcon": loadIcon, 99 | "sourceDir": sourceDir, 100 | "dataDir": dataDir, 101 | "processName": processName, 102 | "publicSourceDir": publicSourceDir, 103 | "sharedLibraryFiles": sharedLibraryFiles, 104 | "installTime": installTime, 105 | "updateTime": updateTime, 106 | "versionName": versionName, 107 | "versionCode": versionCode, 108 | "targetSdkVersion": targetSdkVersion, 109 | "minSdkVersion": minSdkVersion, 110 | "permissions": permissions, 111 | "activities": activities, 112 | "enabled": enabled, 113 | "size": size 114 | } 115 | return infos 116 | 117 | 118 | def is_activity_exported(package, act): 119 | component = ComponentName(package, act) 120 | activityInfo = activity.getPackageManager().getActivityInfo( 121 | component, PackageManager().MATCH_DEFAULT_ONLY) 122 | if activityInfo != None and activityInfo.exported: 123 | return True 124 | else: 125 | return False 126 | 127 | 128 | def activity_info(package, act): 129 | pManager = activity.getPackageManager() 130 | component = ComponentName(package, act) 131 | activityInfo = activity.getPackageManager().getActivityInfo( 132 | component, PackageManager().GET_META_DATA) 133 | loadLabel = activityInfo.loadLabel(pManager) 134 | loadIcon = activityInfo.loadIcon(pManager) 135 | exported = is_activity_exported(package,act) 136 | infos = { 137 | "loadLabel": loadLabel, 138 | "loadIcon": loadIcon, 139 | "exported": exported 140 | } 141 | return infos 142 | -------------------------------------------------------------------------------- /kvdroid/tools/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | from jnius import cast 3 | from kvdroid.jclass.android import Context, VERSION 4 | from kvdroid.jclass.android.os import Environment 5 | from kvdroid import activity 6 | 7 | 8 | def sdcard(directory: str = "", slash: bool = False): 9 | dirs = { 10 | "alarm": Environment().DIRECTORY_ALARMS, 11 | "dcim": Environment().DIRECTORY_DCIM, 12 | "download": Environment().DIRECTORY_DOWNLOADS, 13 | "documents": Environment().DIRECTORY_DOCUMENTS, 14 | "movies": Environment().DIRECTORY_MOVIES, 15 | "music": Environment().DIRECTORY_MUSIC, 16 | "notifications": Environment().DIRECTORY_NOTIFICATIONS, 17 | "pictures": Environment().DIRECTORY_PICTURES, 18 | "podcasts": Environment().DIRECTORY_PODCASTS, 19 | "ringtones": Environment().DIRECTORY_RINGTONES 20 | } 21 | if not directory: 22 | return Environment().getExternalStorageDirectory().getAbsolutePath() 23 | else: 24 | if directory in dirs.keys(): 25 | return Environment().getExternalStoragePublicDirectory(dirs[directory]).toString() + ("/" if slash else "") 26 | else: 27 | return None 28 | 29 | 30 | def external_sdcard(slash: bool = False): 31 | try: 32 | return os.path.join("/storage", os.listdir("/storage")[1]) + ("/" if slash else "") 33 | except Exception: 34 | return None 35 | 36 | 37 | def get_storage_volumes(): 38 | path = [] 39 | context = activity.getApplicationContext() 40 | storage_manager = cast( 41 | "android.os.storage.StorageManager", 42 | context.getSystemService(Context().STORAGE_SERVICE), 43 | ) 44 | 45 | if storage_manager is not None: 46 | if VERSION().SDK_INT() >= 24: 47 | storage_volumes = storage_manager.getStorageVolumes() 48 | for storage_volume in storage_volumes: 49 | if storage_volume.isRemovable(): 50 | try: 51 | directory = storage_volume.getDirectory() 52 | except AttributeError: 53 | directory = storage_volume.getPathFile() 54 | path.append(directory.getAbsolutePath()) 55 | else: 56 | storage_volumes = storage_manager.getVolumeList() 57 | for storage_volume in storage_volumes: 58 | if storage_volume.isRemovable(): 59 | path.append(storage_volume.getPath()) 60 | 61 | return path if len(path) > 1 else path[0] 62 | -------------------------------------------------------------------------------- /kvdroid/tools/photo_picker.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from typing import Callable 3 | 4 | from jnius import JavaException 5 | 6 | from kvdroid import activity 7 | from kvdroid.jclass.android import Intent, Activity, VERSION_CODES, VERSION, SdkExtensions 8 | from kvdroid.jclass.androidx import ( 9 | PickVisualMedia, 10 | PickMultipleVisualMedia, 11 | PickVisualMediaImageOnly, 12 | PickVisualMediaRequestBuilder, 13 | PickVisualMediaVideoOnly, 14 | PickVisualMediaImageAndVideo, 15 | PickVisualMediaSingleMimeType 16 | ) 17 | from kvdroid.jclass.android import MediaStore 18 | from kvdroid.jinterface.activity import ActivityResultCallback 19 | from android.runnable import run_on_ui_thread # noqa 20 | from android import activity as act # noqa 21 | from kvdroid.tools.uri import resolve_uri 22 | 23 | 24 | def _register_picker(multiple: bool, callback): 25 | return activity.registerForActivityResult( 26 | PickMultipleVisualMedia(instantiate=True) if multiple else PickVisualMedia(instantiate=True), 27 | ActivityResultCallback(callback) 28 | ) 29 | 30 | 31 | @run_on_ui_thread 32 | def pick_image_only(multiple: bool, callback): 33 | pick_media = _register_picker(multiple, callback) 34 | builder = PickVisualMediaRequestBuilder(instantiate=True) 35 | builder.setMediaType(PickVisualMediaImageOnly().INSTANCE) 36 | pick_media.launch(builder.build()) 37 | 38 | 39 | @run_on_ui_thread 40 | def pick_video_only(multiple: bool, callback): 41 | pick_media = _register_picker(multiple, callback) 42 | builder = PickVisualMediaRequestBuilder(instantiate=True) 43 | builder.setMediaType(PickVisualMediaVideoOnly().INSTANCE) 44 | pick_media.launch(builder.build()) 45 | 46 | 47 | @run_on_ui_thread 48 | def pick_image_and_video(multiple: bool, callback): 49 | pick_media = _register_picker(multiple, callback) 50 | builder = PickVisualMediaRequestBuilder(instantiate=True) 51 | builder.setMediaType(PickVisualMediaImageAndVideo().INSTANCE) 52 | pick_media.launch(builder.build()) 53 | 54 | 55 | @run_on_ui_thread 56 | def pick_single_mimetype(multiple: bool, mimetype: str, callback): 57 | pick_media = _register_picker(multiple, callback) 58 | builder = PickVisualMediaRequestBuilder(instantiate=True) 59 | builder.setMediaType(PickVisualMediaSingleMimeType(mimetype)) 60 | pick_media.launch(builder.build()) 61 | 62 | 63 | @run_on_ui_thread 64 | def persist_background_permission(uri): 65 | flag = Intent().FLAG_GRANT_READ_URI_PERMISSION 66 | activity.context.contentResolver.takePersistableUriPermission(uri, flag) 67 | 68 | 69 | _selection_single_code = None 70 | _selection_multiple_code = None 71 | _callback: Callable = lambda *_: None 72 | 73 | 74 | def get_pick_images_max_limit(): 75 | if VERSION().SDK_INT >= 33: 76 | return MediaStore().getPickImagesMaxLimit() 77 | if VERSION().SDK_INT >= 30: 78 | if SdkExtensions().getExtensionVersion(VERSION_CODES().R) >= 2: 79 | return MediaStore().getPickImagesMaxLimit() 80 | return 100 81 | 82 | 83 | def is_photo_picker_available(): 84 | if VERSION().SDK_INT >= 33: 85 | return True 86 | if VERSION().SDK_INT >= 30: 87 | if SdkExtensions().getExtensionVersion(VERSION_CODES().R) >= 2: 88 | return True 89 | return False 90 | 91 | 92 | def action_pick_image(callback, pick_max=get_pick_images_max_limit(), multiple: bool = False): 93 | global _selection_single_code, _selection_multiple_code, _callback 94 | _selection_single_code = randint(12345, 654321) 95 | _selection_multiple_code = randint(654321, 754321) 96 | _callback = callback 97 | if is_photo_picker_available(): 98 | intent = Intent(MediaStore().ACTION_PICK_IMAGES) 99 | if multiple: 100 | intent.putExtra(MediaStore().EXTRA_PICK_IMAGES_MAX, pick_max) 101 | activity.startActivityForResult(intent, _selection_multiple_code if multiple else _selection_single_code) 102 | else: 103 | raise JavaException( 104 | "Photo picker is not available on this android device. " 105 | "Possibly the android version is 10 or below or it's an Android Go device. " 106 | "Use 'chooser' instead from 'androidstorage4kivy' package" 107 | ) 108 | 109 | 110 | def _on_activity_result(request_code, result_code, data): 111 | if request_code not in (_selection_single_code, _selection_multiple_code): 112 | return 113 | 114 | if result_code != Activity().RESULT_OK: 115 | return 116 | 117 | if request_code == _selection_multiple_code: 118 | # Process multiple URI if multiple files selected 119 | selection = [ 120 | resolve_uri( 121 | data.getClipData().getItemAt(count).getUri() 122 | ) for count in range(data.getClipData().getItemCount()) 123 | ] 124 | _callback(selection) 125 | else: 126 | _callback(resolve_uri(data.getData())) 127 | 128 | 129 | act.bind(on_activity_result=_on_activity_result) 130 | -------------------------------------------------------------------------------- /kvdroid/tools/sms.py: -------------------------------------------------------------------------------- 1 | from kvdroid import activity 2 | from kvdroid.jclass.android import TelephonySms 3 | from datetime import datetime 4 | 5 | get_date = datetime.fromtimestamp 6 | 7 | 8 | def get_all_sms(content_resolver=activity.getContentResolver()): 9 | # sourcery skip: use-named-expression 10 | Sms = TelephonySms() 11 | cursor = content_resolver.query(Sms.CONTENT_URI, None, None, None, None) 12 | if cursor: 13 | total_sms = cursor.getCount() 14 | messages = [] 15 | sms_types = {0: "all", 1: "inbox", 2: "sent", 3: "draft", 4: "outbox", 5: "failed", 6: "queued"} 16 | try: 17 | while cursor.moveToNext(): 18 | messages.append({ 19 | "date": get_date(float(cursor.getString(cursor.getColumnIndexOrThrow("date"))) / 1000), 20 | "number": cursor.getString(cursor.getColumnIndexOrThrow("address")), 21 | "body": cursor.getString(cursor.getColumnIndexOrThrow("body")), 22 | "type": sms_types[cursor.getInt(cursor.getColumnIndexOrThrow("type"))] 23 | }) 24 | finally: 25 | cursor.close() 26 | return total_sms, messages 27 | return 28 | -------------------------------------------------------------------------------- /kvdroid/tools/uri.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | 3 | from jnius import JavaException 4 | 5 | from kvdroid.jclass.android import PackageManager, Environment, DocumentsContract, ContentUris, Uri, Intent, \ 6 | MediaStoreAudioMedia, MediaStoreImagesMedia, MediaStoreVideoMedia, MediaStoreFiles 7 | from kvdroid.jclass.java import Long 8 | from kvdroid.tools.path import sdcard, get_storage_volumes 9 | from kvdroid import activity 10 | 11 | context = activity.getApplicationContext() 12 | 13 | 14 | def grant_uri_permission(intent, uri, permissions): 15 | packageManager = context.getPackageManager() 16 | activities = packageManager.queryIntentActivities(intent, PackageManager().MATCH_DEFAULT_ONLY) 17 | activities = activities.toArray() 18 | for resolveInfo in activities: 19 | packageName = resolveInfo.activityInfo.packageName 20 | context.grantUriPermission(packageName, uri, permissions) 21 | 22 | 23 | def revoke_uri_permission(uri, permissions): 24 | context.revokeUriPermission(uri, permissions) 25 | 26 | 27 | def resolve_uri(uri): 28 | """ 29 | Resolve URI input from ``android.app.Activity.onActivityResult()``. 30 | """ 31 | 32 | uri_authority = uri.getAuthority() 33 | uri_scheme = uri.getScheme().lower() 34 | 35 | path = None 36 | file_name = None 37 | selection = None 38 | downloads = None 39 | 40 | # This does not allow file selected from google photos or gallery 41 | # or even any other file explorer to work 42 | # not a document URI, nothing to convert from 43 | # if not DocumentsContract.isDocumentUri(mActivity, uri): 44 | # return path 45 | 46 | if uri_authority == 'com.android.externalstorage.documents': 47 | return handle_external_documents(uri) 48 | 49 | # in case a user selects a file from 'Downloads' section 50 | # note: this won't be triggered if a user selects a path directly 51 | # e.g.: Phone -> Download -> 52 | elif uri_authority == 'com.android.providers.downloads.documents': 53 | path = downloads = handle_downloads_documents(uri) 54 | 55 | elif uri_authority == 'com.android.providers.media.documents': 56 | file_name, selection, uri = handle_media_documents(uri) 57 | 58 | # parse content:// scheme to path 59 | if uri_scheme == 'content' and not downloads: 60 | try: 61 | path = parse_content( 62 | uri=uri, projection=['_data'], selection=selection, 63 | selection_args=file_name, sort_order=None 64 | ) 65 | except JavaException: # handles array error for selection_args 66 | path = parse_content( 67 | uri=uri, projection=['_data'], selection=selection, 68 | selection_args=[file_name], sort_order=None 69 | ) 70 | 71 | # nothing to parse, file:// will return a proper path 72 | elif uri_scheme == 'file': 73 | path = uri.getPath() 74 | 75 | return path 76 | 77 | 78 | def handle_downloads_documents(uri): 79 | """ 80 | Selection from the system filechooser when using ``Downloads`` 81 | option from menu. Might not work all the time due to: 82 | 83 | 1) invalid URI: 84 | 85 | jnius.jnius.JavaException: 86 | JVM exception occurred: Unknown URI: 87 | content://downloads/public_downloads/1034 88 | 89 | 2) missing URI / android permissions 90 | 91 | jnius.jnius.JavaException: 92 | JVM exception occurred: 93 | Permission Denial: reading 94 | com.android.providers.downloads.DownloadProvider uri 95 | content://downloads/all_downloads/1034 from pid=2532, uid=10455 96 | requires android.permission.ACCESS_ALL_DOWNLOADS, 97 | or grantUriPermission() 98 | 99 | Workaround: 100 | Selecting path from ``Phone`` -> ``Download`` -> ```` 101 | (or ``Internal storage``) manually. 102 | """ 103 | 104 | try: 105 | download_dir = Environment().getExternalStoragePublicDirectory( 106 | Environment().DIRECTORY_DOWNLOADS 107 | ).getPath() 108 | path = parse_content( 109 | uri=uri, 110 | projection=["_display_name"], 111 | selection=None, 112 | selection_args=None, 113 | sort_order=None, 114 | ) 115 | return join(download_dir, path) 116 | 117 | except Exception: 118 | import traceback 119 | traceback.print_exc() 120 | 121 | # known locations, differ between machines 122 | downloads = [ 123 | 'content://downloads/public_downloads', 124 | 'content://downloads/my_downloads', 125 | 126 | # all_downloads requires separate permission 127 | # android.permission.ACCESS_ALL_DOWNLOADS 128 | 'content://downloads/all_downloads' 129 | ] 130 | 131 | file_id = DocumentsContract().getDocumentId(uri) 132 | try_uris = [ 133 | ContentUris().withAppendedId( 134 | Uri().parse(down), Long().valueOf(file_id) 135 | ) 136 | for down in downloads 137 | ] 138 | 139 | # try all known Download folder uris 140 | # and handle JavaExceptions due to different locations 141 | # for content:// downloads or missing permission 142 | path = None 143 | for down in try_uris: 144 | try: 145 | path = parse_content( 146 | uri=down, projection=['_data'], 147 | selection=None, 148 | selection_args=None, 149 | sort_order=None 150 | ) 151 | 152 | except JavaException: 153 | import traceback 154 | traceback.print_exc() 155 | 156 | # we got a path, ignore the rest 157 | if path: 158 | break 159 | 160 | # alternative approach to Downloads by joining 161 | # all data items from Activity result 162 | if not path: 163 | for down in try_uris: 164 | try: 165 | path = parse_content( 166 | uri=down, projection=None, 167 | selection=None, 168 | selection_args=None, 169 | sort_order=None, 170 | index_all=True 171 | ) 172 | 173 | except JavaException: 174 | import traceback 175 | traceback.print_exc() 176 | 177 | # we got a path, ignore the rest 178 | if path: 179 | break 180 | return path 181 | 182 | 183 | def handle_external_documents(uri): 184 | """ 185 | Selection from the system filechooser when using ``Phone`` 186 | or ``Internal storage`` or ``SD card`` option from menu. 187 | """ 188 | 189 | file_id = DocumentsContract().getDocumentId(uri) 190 | file_type, file_name = file_id.split(':') 191 | 192 | primary_storage = sdcard() 193 | sdcard_storage = get_storage_volumes() 194 | 195 | if type(sdcard_storage) == list: 196 | sdcard_storage = sdcard_storage[0] 197 | 198 | directory = primary_storage 199 | 200 | if file_type == "primary": 201 | directory = primary_storage 202 | elif file_type == "home": 203 | directory = join(primary_storage, Environment().DIRECTORY_DOCUMENTS) 204 | elif sdcard_storage and file_type in sdcard_storage: 205 | directory = sdcard_storage 206 | 207 | return join(directory, file_name) 208 | 209 | 210 | def handle_media_documents(uri): 211 | """ 212 | Selection from the system filechooser when using ``Images`` 213 | or ``Videos`` or ``Audio`` option from menu. 214 | """ 215 | 216 | file_id = DocumentsContract().getDocumentId(uri) 217 | file_type, file_name = file_id.split(':') 218 | selection = '_id=?' 219 | 220 | if file_type == 'image': 221 | uri = MediaStoreImagesMedia().EXTERNAL_CONTENT_URI 222 | elif file_type == 'video': 223 | uri = MediaStoreVideoMedia().EXTERNAL_CONTENT_URI 224 | elif file_type == 'audio': 225 | uri = MediaStoreAudioMedia().EXTERNAL_CONTENT_URI 226 | 227 | # Other file type was selected (probably in the Documents folder) 228 | else: 229 | uri = MediaStoreFiles().getContentUri("external") 230 | 231 | return file_name, selection, uri 232 | 233 | 234 | def parse_content( 235 | uri, projection, selection, selection_args, sort_order, 236 | index_all=False 237 | ): 238 | """ 239 | Parser for ``content://`` URI returned by some Android resources. 240 | """ 241 | 242 | result = None 243 | resolver = activity.getContentResolver() 244 | read = Intent().FLAG_GRANT_READ_URI_PERMISSION 245 | write = Intent().FLAG_GRANT_READ_URI_PERMISSION 246 | persist = Intent().FLAG_GRANT_READ_URI_PERMISSION 247 | 248 | # grant permission for our activity 249 | activity.grantUriPermission( 250 | activity.getPackageName(), 251 | uri, 252 | read | write | persist 253 | ) 254 | 255 | if not index_all: 256 | cursor = resolver.query( 257 | uri, projection, selection, 258 | selection_args, sort_order 259 | ) 260 | 261 | idx = cursor.getColumnIndex(projection[0]) 262 | if idx != -1 and cursor.moveToFirst(): 263 | result = cursor.getString(idx) 264 | else: 265 | result = [] 266 | cursor = resolver.query( 267 | uri, projection, selection, 268 | selection_args, sort_order 269 | ) 270 | while cursor.moveToNext(): 271 | for idx in range(cursor.getColumnCount()): 272 | result.append(cursor.getString(idx)) 273 | result = '/'.join(result) 274 | return result 275 | -------------------------------------------------------------------------------- /kvdroid/tools/webkit.py: -------------------------------------------------------------------------------- 1 | from kvdroid.jclass.android.webkit import CookieManager 2 | 3 | 4 | def get_cookies(site_name: str): 5 | cookieManager = CookieManager().getInstance() 6 | return cookieManager.getCookie(site_name) 7 | 8 | 9 | def launch_url(url: str, color="#FFFFFF", color_scheme="system"): 10 | from kvdroid.jclass.android.graphics import Color 11 | from kvdroid.jclass.androidx.browser.customtabs import CustomTabColorSchemeParamsBuilder 12 | from kvdroid.jclass.androidx.browser.customtabs import CustomTabsIntentBuilder, CustomTabsIntent 13 | from kvdroid import activity 14 | from kvdroid.jclass.android.net import Uri 15 | CustomTabsIntent = CustomTabsIntent() 16 | schemes = { 17 | "system": CustomTabsIntent.COLOR_SCHEME_SYSTEM, 18 | "light": CustomTabsIntent.COLOR_SCHEME_LIGHT, 19 | "dark": CustomTabsIntent.COLOR_SCHEME_DARK 20 | } 21 | color_int = Color().parseColor(color) 22 | default_color = CustomTabColorSchemeParamsBuilder(instantiate=True).setToolbarColor(color_int).build() 23 | builder = CustomTabsIntentBuilder(instantiate=True) 24 | if color_scheme not in schemes: 25 | builder.setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM) 26 | else: 27 | builder.setColorScheme(schemes[color_scheme]) 28 | builder.setDefaultColorSchemeParams(default_color) 29 | custom_tabs_intent = builder.build() 30 | custom_tabs_intent.intent.setPackage("com.android.chrome") 31 | custom_tabs_intent.launchUrl(activity, Uri().parse(url)) 32 | -------------------------------------------------------------------------------- /kvdroid/widget/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvdroid/Kvdroid/f59bc17d57b8ea0b8d9a5f0ea8c9875d53262ff9/kvdroid/widget/__init__.py -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "pyjnius" 5 | version = "1.6.1" 6 | description = "A Python module to access Java classes as Python classes using JNI." 7 | optional = false 8 | python-versions = "*" 9 | files = [ 10 | {file = "pyjnius-1.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff6d71b847620d6a7de33a8acaa53671003c235dc881413d3faa11bc8e075c40"}, 11 | {file = "pyjnius-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:061dd84a28695e750c809d2979fe3dabf9d4407b2240c2a06289755da3524bd9"}, 12 | {file = "pyjnius-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be215ef25bf2e5484e00ba27e7a7256bbedee1ad0e3213d3cffb748b8abffe20"}, 13 | {file = "pyjnius-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d790bf3a5d30aa25757b98bc841a7bcb87c752ad6bea85a439f1582f884fa01"}, 14 | {file = "pyjnius-1.6.1-cp310-cp310-win32.whl", hash = "sha256:cc33abad31b7dd0035b12adc9e02674d1dd299adc6b2028bdc7b998684109158"}, 15 | {file = "pyjnius-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:4021629d8d53e615b244666a92e733cbcfaa82955bee4887df42282c8a4aa46e"}, 16 | {file = "pyjnius-1.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2fc1ff1dc5dc93ee0adcb0a3f9194d9a824aeacefb0ee738c6d121964c6585f2"}, 17 | {file = "pyjnius-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4359f42d907f8b971a676f2d1406ce41b1ba3c810c5770d640037d4addc5176e"}, 18 | {file = "pyjnius-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88dcb79d0ebc80ab74c1e6d37a0ffc9139521ff12eff1d5b2219be199f65536a"}, 19 | {file = "pyjnius-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a32df745d40fd80c0c6e6c12417fa86d435ac23c1236d030365cababcf9981d"}, 20 | {file = "pyjnius-1.6.1-cp311-cp311-win32.whl", hash = "sha256:c55837b1eaef929c2b1721388bdb51290893766e11442ed611776701c9266f15"}, 21 | {file = "pyjnius-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:97b8c8faf16a4f1ac82cc89ca59c65a43d6a796b92884540c5627db2e54a962a"}, 22 | {file = "pyjnius-1.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8e77043544febb3a37e5498477755195d7538b47b63b1d62a9ee7060ff0d8697"}, 23 | {file = "pyjnius-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9f3775bda5275773b3e27a682220ddb8a9c649ab097c85798a366e7d4b8b709b"}, 24 | {file = "pyjnius-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5cb3fb92898d6f2a1eeff348b71e80c001ef8594682273d45ca7b629877efda"}, 25 | {file = "pyjnius-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a90b68fb53c4f9a0d1919903deed3bf5def72c355e38c5c07f545e72001d1c4c"}, 26 | {file = "pyjnius-1.6.1-cp312-cp312-win32.whl", hash = "sha256:7199809fd3114db4942e560ac000ed8ecc4cdf2e115ef551012cd3d0b1af3a0a"}, 27 | {file = "pyjnius-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:0e0da04b08c61c2b59cd1670d743c7867f31d6c6a45c8ca5f90657da88d48fd9"}, 28 | {file = "pyjnius-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab4c0ff95e09b971bf233542bd350303a39306170f3b3f6cc979cc98abb96ae2"}, 29 | {file = "pyjnius-1.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:761f174b2d4f452f6b7d0c7011959f806bc7af78ec934ca64c0d15d3aac88984"}, 30 | {file = "pyjnius-1.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54593442f9703836a045a172041bb7c56fbd96d0dca5bab9e77573e78fb7c08c"}, 31 | {file = "pyjnius-1.6.1-cp37-cp37m-win32.whl", hash = "sha256:0df35edecabd4889c84053476ffeb569fc25365905c2044f19522c0f2e557075"}, 32 | {file = "pyjnius-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eecfd0b9c5e03d05433877e6e3243c145e029b5d62bc37eb799798de4950308e"}, 33 | {file = "pyjnius-1.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a13cf7fff029cb54f8dfb2605b2f427a3ced0783abe8dc66a35d62d8d2baaac7"}, 34 | {file = "pyjnius-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6f4ab7609d68872092083740bb3ce00b8e0c93fc0b5066a6e4a93dab7e43320d"}, 35 | {file = "pyjnius-1.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b15a05b3dc1c261761a2cff8d8f02f8f3b4cfea3a250b85e455c53b55cbf2bd"}, 36 | {file = "pyjnius-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361c26a86dbd06e21e24c987fe1ea9bc7ea69f9459f76104ed4265dc04a6774c"}, 37 | {file = "pyjnius-1.6.1-cp38-cp38-win32.whl", hash = "sha256:60669194242013455cc54a49c165f230e240c5334b7aec29471fd9be53eb01e8"}, 38 | {file = "pyjnius-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:4da5ce988706d48ec844d45a9d84a47c717d40e5eff03f757678838fd71a75e8"}, 39 | {file = "pyjnius-1.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b9cd218a3d94f33f2b05176458412edeae5b674a4c3fc58db00ab81746a025e2"}, 40 | {file = "pyjnius-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a56bee308898feb06bbb1b81feeabed7c9fc85a4d92ee92585fabc5895a29c11"}, 41 | {file = "pyjnius-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fa140e1a5dae2658b170e39325b43275852aa263e94b87c89748d07ca5c57"}, 42 | {file = "pyjnius-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3ee60508991069f45ba75d65e4a6650c05739c64943bf3702485672f4d8f22"}, 43 | {file = "pyjnius-1.6.1-cp39-cp39-win32.whl", hash = "sha256:ed5e19b216e01fb511a75b1ce97b48f710552f7f1269f1c0d85fef84cb6935bd"}, 44 | {file = "pyjnius-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:e20a49820ca0b0ebef03b710ecc6f03e91847c7b08612fc87f88293b7f73e797"}, 45 | {file = "pyjnius-1.6.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:26893e4ef5a5d21393ceab61d03ad02d03cf8d4c3177c262d48853186b5512df"}, 46 | {file = "pyjnius-1.6.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:133ddedf0a88dae1b11a0e4ad36336794e38f190bde7c73f7621cbd21ad22700"}, 47 | {file = "pyjnius-1.6.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55820ca521795454afa1f855ecad28648f440dcf70a4f188adffedb35c50e920"}, 48 | {file = "pyjnius-1.6.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9d75c37de19b43a7b33b3f965bd5206c6d5b9999de85d40797a4e46e48cd717"}, 49 | {file = "pyjnius-1.6.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5d761e1c500fe777e184ec88826535099b6fb8c2f3aedfe4cf7c3c0a009f4f60"}, 50 | {file = "pyjnius-1.6.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445e8df7906ecebfc0ae273e5657a004a1ff229131439aed0a70087234aad082"}, 51 | {file = "pyjnius-1.6.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:095b9289805ee58c92a54225d1d8678e3226dd2bd077e86bb9b33191ee316573"}, 52 | {file = "pyjnius-1.6.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:aac6abc5deec53f4b434358267126da78bebd43c1a442ba4f65db98a3bf5064b"}, 53 | {file = "pyjnius-1.6.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2477b8d302e52bfdbbfd43354fb35741fbca1b55914a1377803a8079953eefc"}, 54 | {file = "pyjnius-1.6.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eb16f9583cba038898bff21846034766c3882066747b8f2f1cbbf3871fb2e5d"}, 55 | {file = "pyjnius-1.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e92fc8ca421cfa9e2f3ba1ad6b450c6ec47d7b2e1f7c349143511d40fbf3ed3"}, 56 | {file = "pyjnius-1.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b7c2eb6b17d009939c580a98b2abf70aebea0d25c74517cc738ecf6be4263bc4"}, 57 | {file = "pyjnius-1.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cf08dc6579a14b212b2d4f51a6cb6fcfd7154f305672db6d1ae42b07e520dd45"}, 58 | {file = "pyjnius-1.6.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a09aac5b2dd9b6448cd8233a2c072da3616048525977ae683f943834ce553"}, 59 | {file = "pyjnius-1.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06621ba1e21e7c97b70c6861a3885780e0ebe95cecd111d53da5c6ce70bcd60d"}, 60 | {file = "pyjnius-1.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:edaf12697c7d0efbb2060f2d9b2a64bacb81846cdc0a0a33551990a5123d8ecb"}, 61 | {file = "pyjnius-1.6.1.tar.gz", hash = "sha256:d2a7ece6ed79bf1d7f97a4f6d61302d9f1d7652182a3e4c8a69dbaef31396e60"}, 62 | ] 63 | 64 | [package.extras] 65 | ci = ["coveralls", "pytest-rerunfailures"] 66 | dev = ["pycodestyle", "pytest", "pytest-cov"] 67 | 68 | [[package]] 69 | name = "setuptools" 70 | version = "77.0.3" 71 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 72 | optional = false 73 | python-versions = ">=3.9" 74 | files = [ 75 | {file = "setuptools-77.0.3-py3-none-any.whl", hash = "sha256:67122e78221da5cf550ddd04cf8742c8fe12094483749a792d56cd669d6cf58c"}, 76 | {file = "setuptools-77.0.3.tar.gz", hash = "sha256:583b361c8da8de57403743e756609670de6fb2345920e36dc5c2d914c319c945"}, 77 | ] 78 | 79 | [package.extras] 80 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] 81 | core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] 82 | cover = ["pytest-cov"] 83 | doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] 84 | enabler = ["pytest-enabler (>=2.2)"] 85 | test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] 86 | type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] 87 | 88 | [metadata] 89 | lock-version = "2.0" 90 | python-versions = "^3.9" 91 | content-hash = "6f242146ea16db5ac1b9166867768399afc230782b1050b24d6b7bb22bb54056" 92 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "kvdroid" 3 | version = "0.4.0" 4 | description ="A re-implementation of android java API in python with easy access to some Android functionalitylike Notification,Reading of Contacts, accessing Webview Cookies, etc..." 5 | authors = ["Yunus Ceyhan ", "Kenechukwu Akubue "] 6 | readme = "README.md" 7 | keywords = ['Android', 'Androidx', 'Python', 'Kivy', 'KivyMD', 'KvDroid'] 8 | license = "MIT" 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.9" 12 | pyjnius = "^1.6.1" 13 | 14 | 15 | [tool.poetry.group.dev.dependencies] 16 | setuptools = "^77.0.3" 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | from os import path 4 | 5 | this_directory = path.abspath(path.dirname(__file__)) 6 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: 7 | long_description = f.read() 8 | 9 | setup( 10 | name='kvdroid', 11 | packages=find_packages( 12 | include=["kvdroid", "kvdroid.*"] 13 | ), 14 | version='0.3.0', 15 | description='A re-implementation of android java API in python with easy access to some Android functionality ' 16 | 'like Notification,Reading of Contacts, accessing Webview Cookies, etc...', 17 | long_description=long_description, 18 | long_description_content_type='text/markdown', 19 | author='Yunus Ceyhan', 20 | author_email='yunus.ceyhn@gmail.com', 21 | url='https://github.com/kvdroid/Kvdroid', 22 | keywords=['Android', 'Androidx', 'Python', 'Kivy', 'KivyMD', "KvDroid"], 23 | install_requires=["pyjnius"], 24 | classifiers=[], 25 | ) 26 | --------------------------------------------------------------------------------