diff options
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | README.md | 20 | ||||
| -rw-r--r-- | app/build.gradle.kts | 36 | ||||
| -rw-r--r-- | app/src/main/AndroidManifest.xml | 25 | ||||
| -rw-r--r-- | app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt | 121 | ||||
| -rw-r--r-- | app/src/main/java/com/arslaancodes/zwznfreefit/DeviceActivity.kt | 73 | ||||
| -rw-r--r-- | app/src/main/java/com/arslaancodes/zwznfreefit/MainActivity.kt | 75 | ||||
| -rw-r--r-- | app/src/main/res/layout/activity_device.xml | 49 | ||||
| -rw-r--r-- | app/src/main/res/layout/activity_main.xml | 28 | ||||
| -rw-r--r-- | app/src/main/res/values/strings.xml | 3 | ||||
| -rw-r--r-- | build.gradle.kts | 4 | ||||
| -rw-r--r-- | gradle-8.4-bin.zip | bin | 0 -> 130979468 bytes | |||
| -rw-r--r-- | gradle/libs.versions.toml | 15 | ||||
| -rw-r--r-- | gradle/wrapper/gradle-wrapper.jar | bin | 0 -> 63721 bytes | |||
| -rw-r--r-- | gradle/wrapper/gradle-wrapper.properties | 7 | ||||
| -rwxr-xr-x | gradlew | 249 | ||||
| -rw-r--r-- | gradlew.bat | 92 | ||||
| -rw-r--r-- | settings.gradle.kts | 16 |
18 files changed, 819 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f14eedc --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.gradle +build/ +local.properties +*.iml +gradle.properties +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..48c7483 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# zwzn-freefit-android + +Android companion app for zwzn/freefit watches. + +## Setup + +You need the Gradle wrapper JAR to build. Run once: +``` +gradle wrapper --gradle-version 8.4 +``` +Or if you installed gradle8 from AUR: +``` +gradle8 wrapper --gradle-version 8.4 +``` + +Then build and deploy to your phone: +``` +./gradlew assembleDebug +adb install app/build/outputs/apk/debug/app-debug.apk +``` diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..e79455f --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.arslaancodes.zwznfreefit" + compileSdk = 35 + + defaultConfig { + applicationId = "com.arslaancodes.zwznfreefit" + minSdk = 26 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..af11ef6 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + + <application + android:allowBackup="true" + android:label="zwzn-freefit" + android:theme="@style/Theme.AppCompat.NoActionBar"> + <activity + android:name=".MainActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".DeviceActivity" android:exported="true" android:theme="@style/Theme.AppCompat.NoActionBar"/> + </application> + +</manifest> diff --git a/app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt b/app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt new file mode 100644 index 0000000..c138ead --- /dev/null +++ b/app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt @@ -0,0 +1,121 @@ +package com.arslaancodes.zwznfreefit + +import android.bluetooth.* +import android.bluetooth.le.* +import android.content.Context +import android.os.ParcelUuid +import java.util.UUID + +val SERVICE_UUID = UUID.fromString("6E40FC00-B5A3-F393-E0A9-E50E24DCCA9E") +val WRITE_UUID = UUID.fromString("6E40FC20-B5A3-F393-E0A9-E50E24DCCA9E") +val NOTIFY_UUID = UUID.fromString("6E40FC21-B5A3-F393-E0A9-E50E24DCCA9E") + +class BleManager(private val context: Context) { + companion object { + lateinit var instance: BleManager + } + + init { + instance = this + } + + private val adapter = BluetoothAdapter.getDefaultAdapter() + private var gatt: BluetoothGatt? = null + private var onNotify: ((ByteArray) -> Unit)? = null + + fun scan(onFound: (BluetoothDevice) -> Unit) { + val scanner = adapter.bluetoothLeScanner + android.util.Log.d("BLE", "Starting scan, scanner=$scanner") + if (scanner == null) { + android.util.Log.e("BLE", "Scanner is null! Bluetooth off?") + return + } + val settings = ScanSettings.Builder().build() + scanner.startScan(null, settings, object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + android.util.Log.d("BLE", "Found: ${result.device.name} ${result.device.address}") + onFound(result.device) + } + override fun onScanFailed(errorCode: Int) { + android.util.Log.e("BLE", "Scan failed: $errorCode") + } + }) + } + + fun connect(device: BluetoothDevice, onConnected: (BluetoothGatt) -> Unit) { + gatt = device.connectGatt(context, false, object : BluetoothGattCallback() { + override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + g.discoverServices() + } + } + override fun onServicesDiscovered(g: BluetoothGatt, status: Int) { + val notifyChar = g.getService(SERVICE_UUID)?.getCharacteristic(NOTIFY_UUID) + if (notifyChar != null) { + g.setCharacteristicNotification(notifyChar, true) + val descriptor = notifyChar.getDescriptor( + UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") + ) + descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + g.writeDescriptor(descriptor) + } + onConnected(g) + } + override fun onCharacteristicChanged(g: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { + if (characteristic.uuid == NOTIFY_UUID) { + onNotify?.invoke(characteristic.value) + } + } + }) + } + + fun write(data: ByteArray) { + val char = gatt?.getService(SERVICE_UUID)?.getCharacteristic(WRITE_UUID) + char?.value = data + gatt?.writeCharacteristic(char) + } + + fun syncTime() { + val now = System.currentTimeMillis() / 1000 + val tzOffset = java.util.TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 1000 + + val packet = ByteArray(12) + packet[0] = 0x01 + packet[1] = (now shr 24).toByte() + packet[2] = (now shr 16).toByte() + packet[3] = (now shr 8).toByte() + packet[4] = now.toByte() + packet[5] = (tzOffset shr 24).toByte() + packet[6] = (tzOffset shr 16).toByte() + packet[7] = (tzOffset shr 8).toByte() + packet[8] = tzOffset.toByte() + packet[9] = 0x00 + packet[10] = 0x00 // language code, 0 = english + packet[11] = 0x00 + + write(packet) + } + + fun switchFindBand(flag: Int) { + // find/vibrate band + // these functions are usually named after their freefit/ferefit counterparts + val packet = ByteArray(2) + packet[0] = 0x51 // command + packet[1] = flag.toByte() // 0x01 = start, 0x00 = stop + write(packet) + } + + fun configRealTimeMeasure(type: Int, start: Boolean, onResult: (ByteArray) -> Unit) { + // measure heart rate/other stuff + // confirmed working types so far is just 0x00 for heart rate, will RE more later + // yet again, function named after freefit counterparts from RE'd protocol/decompiled APKs + // all code in this file is intellectual property of Arslaan Pathan, no pieces of decompiled freefit code have been used anywhere in the app + // freefit decompilation was merely used as a reference for reverse-engineering the protocol + onNotify = onResult + val packet = ByteArray(3) + packet[0] = 0x60.toByte() + packet[1] = type.toByte() + packet[2] = if (start) 0x01.toByte() else 0x00.toByte() + write(packet) + } +} diff --git a/app/src/main/java/com/arslaancodes/zwznfreefit/DeviceActivity.kt b/app/src/main/java/com/arslaancodes/zwznfreefit/DeviceActivity.kt new file mode 100644 index 0000000..38862bd --- /dev/null +++ b/app/src/main/java/com/arslaancodes/zwznfreefit/DeviceActivity.kt @@ -0,0 +1,73 @@ +package com.arslaancodes.zwznfreefit + +import android.bluetooth.BluetoothAdapter +import android.os.Bundle +import android.widget.TextView +import android.widget.Button +import androidx.appcompat.app.AppCompatActivity + +class DeviceActivity : AppCompatActivity() { + private lateinit var statusText: TextView + private val handler = android.os.Handler(android.os.Looper.getMainLooper()) + private val syncRunnable = object : Runnable { + override fun run() { + BleManager.instance.syncTime() + handler.postDelayed(this, 5 * 60 * 1000) + } + } + + override fun onResume() { + super.onResume() + handler.post(syncRunnable) + } + + override fun onPause() { + super.onPause() + handler.removeCallbacks(syncRunnable) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_device) + window.decorView.systemUiVisibility = ( + android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + or android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + ) + + val address = intent.getStringExtra("device_address") + val device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address) + + statusText = findViewById(R.id.deviceStatusText) + statusText.text = "Connected to: ${device.name ?: device.address}" + BleManager.instance.syncTime(); + findViewById<Button>(R.id.syncTimeButton).setOnClickListener { + BleManager.instance.syncTime() + } + + findViewById<Button>(R.id.findBandButton).setOnClickListener { + BleManager.instance.switchFindBand(0x01) + } + + findViewById<Button>(R.id.stopFindBandButton).setOnClickListener { + BleManager.instance.switchFindBand(0x00) + } + + findViewById<Button>(R.id.configRealTimeMeasureHeartRateButton).setOnClickListener { + BleManager.instance.configRealTimeMeasure(0x00, true, { data -> + runOnUiThread { + if (data[0] == 0x94.toByte()) { + val heartRate = data[1].toInt() and 0xFF + statusText.text = "Got generic measurement ${heartRate}" + } + if (data[0] == 0xE1.toByte()) { + val rate1 = data[1].toInt() and 0xFF + val rate2 = data[5].toInt() and 0xFF + val rate3 = data[6].toInt() and 0xFF + val rate4 = data[7].toInt() and 0xFF + statusText.text = "Got aggregate rates ${rate1}, ${rate2}, ${rate3}, ${rate4}" + } + } + }) + } + } +} diff --git a/app/src/main/java/com/arslaancodes/zwznfreefit/MainActivity.kt b/app/src/main/java/com/arslaancodes/zwznfreefit/MainActivity.kt new file mode 100644 index 0000000..59bff65 --- /dev/null +++ b/app/src/main/java/com/arslaancodes/zwznfreefit/MainActivity.kt @@ -0,0 +1,75 @@ +package com.arslaancodes.zwznfreefit + +import android.Manifest +import android.bluetooth.BluetoothDevice +import android.content.pm.PackageManager +import android.os.Bundle +import android.widget.* +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import android.content.Intent + +class MainActivity : AppCompatActivity() { + private lateinit var bleManager: BleManager + private lateinit var statusText: TextView + private lateinit var scanButton: Button + private lateinit var deviceList: ListView + + private val devices = mutableListOf<BluetoothDevice>() + private val deviceNames = mutableListOf<String>() + private lateinit var adapter: ArrayAdapter<String> + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + window.decorView.systemUiVisibility = ( + android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + or android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + ) + + bleManager = BleManager(this) + statusText = findViewById(R.id.statusText) + scanButton = findViewById(R.id.scanButton) + deviceList = findViewById(R.id.deviceList) + + adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, deviceNames) + deviceList.adapter = adapter + + deviceList.setOnItemClickListener { _, _, position, _ -> + val device = devices[position] + statusText.text = "Connecting to ${device.name ?: device.address}..." + bleManager.connect(device) { + runOnUiThread { + val intent = Intent(this, DeviceActivity::class.java) + intent.putExtra("device_address", device.address) + startActivity(intent) + } + } + } + + scanButton.setOnClickListener { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + arrayOf( + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.ACCESS_FINE_LOCATION + ), 1) + return@setOnClickListener + } + devices.clear() + deviceNames.clear() + adapter.notifyDataSetChanged() + statusText.text = "Scanning..." + bleManager.scan { device -> + runOnUiThread { + if (devices.none { it.address == device.address }) { + devices.add(device) + deviceNames.add(device.name ?: device.address) + adapter.notifyDataSetChanged() + } + } + } + } + } +} diff --git a/app/src/main/res/layout/activity_device.xml b/app/src/main/res/layout/activity_device.xml new file mode 100644 index 0000000..1364066 --- /dev/null +++ b/app/src/main/res/layout/activity_device.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="24dp" + android:background="#1e1e2f"> + + <TextView + android:id="@+id/deviceStatusText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="18sp"/> + + <Button + android:id="@+id/syncTimeButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="Sync Time (syncTime)"/> + + <Button + android:id="@+id/findBandButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="Find Watch (switchFindBand 0x01)"/> + + <Button + android:id="@+id/stopFindBandButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="Stop Finding Watch (switchFindBand 0x00)"/> + + <Button + android:id="@+id/configRealTimeMeasureHeartRateButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="Measure Heart Rate (configRealTimeMeasure type=0x00 start=false)"/> + + <Button + android:id="@+id/stopConfigRealTimeMeasureHeartRateButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="Stop Measuring Heart Rate (configRealTimeMeasure type=0x00 start=false)"/> +</LinearLayout> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e70aa4c --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#1e1e2f" + android:padding="24dp"> + + <Button + android:id="@+id/scanButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Scan"/> + + <TextView + android:id="@+id/statusText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text=""/> + + <ListView + android:id="@+id/deviceList" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="16dp"/> + +</LinearLayout> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..439573b --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">zwzn-freefit</string> +</resources> diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..7629126 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false +} diff --git a/gradle-8.4-bin.zip b/gradle-8.4-bin.zip Binary files differnew file mode 100644 index 0000000..abc45a6 --- /dev/null +++ b/gradle-8.4-bin.zip diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..d311b7f --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,15 @@ +[versions] +agp = "8.3.0" +kotlin = "1.9.22" +coreKtx = "1.12.0" +appcompat = "1.6.1" +material = "1.11.0" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 0000000..7f93135 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fa8f86 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..4ee4863 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "zwzn-freefit-android" +include(":app") |
