diff --git a/app/build.gradle b/app/build.gradle
index ee6e01089e..91290837ca 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -269,6 +269,7 @@ dependencies {
implementation 'com.github.wax911.android-emojify:gson:1.9.7'
implementation 'com.google.protobuf:protobuf-javalite:4.33.2'
implementation 'com.android.volley:volley:1.2.1'
+ implementation 'com.squareup.okhttp3:okhttp:5.2.1'
implementation 'org.msgpack:msgpack-core:0.9.10'
implementation 'com.github.ByteHamster:SearchPreference:2.7.3'
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
index c7369cbad4..4e07072c77 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
@@ -20,6 +20,7 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge;
+import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlarmManager;
@@ -101,6 +102,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
+import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.backup.PeriodicZipExporter;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
@@ -2273,6 +2275,10 @@ public class GBApplication extends Application {
return typedValue.data;
}
+ public static boolean hasDirectInternetAccess() {
+ return BuildConfig.INTERNET_ACCESS && PermissionsUtils.checkPermission(getContext(), Manifest.permission.INTERNET);
+ }
+
public static GBPrefs getPrefs() {
return prefs;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InternetHelperPreferencesActivity.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InternetHelperPreferencesActivity.kt
index c875fa27e3..d6ff329dbc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InternetHelperPreferencesActivity.kt
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InternetHelperPreferencesActivity.kt
@@ -33,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
+import nodomain.freeyourgadget.gadgetbridge.BuildConfig
import nodomain.freeyourgadget.gadgetbridge.R
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper
import nodomain.freeyourgadget.gadgetbridge.entities.URLFilterEntry
@@ -116,6 +117,8 @@ class InternetHelperPreferencesActivity : AbstractGBActivity() {
rootKey: String?
) {
setPreferencesFromResource(R.xml.internethelper_preferences, rootKey)
+ val unusedWarning = findPreference("pref_key_internethelper_unused")
+ unusedWarning?.isVisible = BuildConfig.INTERNET_ACCESS
val installWarning = findPreference("pref_key_internethelper_not_installed")
if (AndroidUtils.isPackageInstalled(PACKAGE_INTERNET_HELPER)) {
installWarning?.isVisible = false
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/RebbleAppStoreActivity.kt b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/RebbleAppStoreActivity.kt
index 691782f237..43f6f4accd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/RebbleAppStoreActivity.kt
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/RebbleAppStoreActivity.kt
@@ -30,6 +30,13 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.core.net.toUri
+import com.android.volley.Request
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.Volley
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import nodomain.freeyourgadget.gadgetbridge.GBApplication
import nodomain.freeyourgadget.gadgetbridge.R
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler
@@ -43,6 +50,7 @@ import nodomain.freeyourgadget.internethelper.aidl.http.HttpGetRequest
import nodomain.freeyourgadget.internethelper.aidl.http.HttpHeaders
import nodomain.freeyourgadget.internethelper.aidl.http.HttpResponse
import nodomain.freeyourgadget.internethelper.aidl.http.IHttpCallback
+import okhttp3.OkHttpClient
import org.json.JSONObject
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@@ -92,75 +100,157 @@ class RebbleAppStoreActivity : AbstractGBActivity() {
}
private fun downloadInstallWatchapp(url: Uri) {
- val httpHeaders = HttpHeaders()
- val httpGetRequest = HttpGetRequest(url.toString(), httpHeaders)
- InternetHelperSingleton.getHttpService()?.get(httpGetRequest, object : IHttpCallback.Stub() {
- override fun onResponse(response: HttpResponse) {
- val contentType = response.headers["content-type"]?.split(";")?.get(0)
- if (!contentType.equals("application/octet-stream") && !contentType.equals("application/zip")) {
- GB.toast(
- getString(R.string.rebble_appstore_download_failed_wrong_content_type, contentType),
- Toast.LENGTH_LONG, GB.ERROR
- )
- return
- }
- val inputStream = ParcelFileDescriptor.AutoCloseInputStream(response.body)
- val filename = url.lastPathSegment ?: "downloaded_file.pbw"
- val cacheDir = applicationContext.externalCacheDir ?: return
- val cacheFile = File(cacheDir, filename)
- val outputStream: OutputStream = FileOutputStream(cacheFile)
- val buffer = ByteArray(1024)
- var len: Int
- while (inputStream.read(buffer).also { len = it } > 0) {
- outputStream.write(buffer, 0, len)
- }
- outputStream.flush()
- outputStream.close()
- val installHandler: InstallHandler? = mGBDevice?.deviceCoordinator?.findInstallHandler(cacheFile.toUri(), applicationContext)
- if (installHandler == null) {
- GB.toast(getString(R.string.fwinstaller_file_not_compatible_to_device), Toast.LENGTH_LONG, GB.ERROR)
- return
- }
- val startIntent = Intent(applicationContext, installHandler.getInstallActivity())
- startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice)
- startIntent.action = Intent.ACTION_VIEW
- startIntent.setDataAndType(cacheFile.toUri(), null)
- startActivity(startIntent)
+ if (GBApplication.hasDirectInternetAccess()) {
+ downloadBinaryFile(url) { file ->
+ installFile(file)
}
+ } else {
+ val httpHeaders = HttpHeaders()
+ val httpGetRequest = HttpGetRequest(url.toString(), httpHeaders)
+ InternetHelperSingleton.getHttpService()
+ ?.get(httpGetRequest, object : IHttpCallback.Stub() {
+ override fun onResponse(response: HttpResponse) {
+ val contentType = response.headers["content-type"]?.split(";")?.get(0)
+ if (!contentType.equals("application/octet-stream") && !contentType.equals("application/zip")) {
+ GB.toast(
+ getString(
+ R.string.rebble_appstore_download_failed_wrong_content_type,
+ contentType
+ ),
+ Toast.LENGTH_LONG, GB.ERROR
+ )
+ return
+ }
+ val inputStream = ParcelFileDescriptor.AutoCloseInputStream(response.body)
+ val filename = url.lastPathSegment ?: "downloaded_file.pbw"
+ val cacheDir = applicationContext.externalCacheDir ?: return
+ val cacheFile = File(cacheDir, filename)
+ val outputStream: OutputStream = FileOutputStream(cacheFile)
+ val buffer = ByteArray(1024)
+ var len: Int
+ while (inputStream.read(buffer).also { len = it } > 0) {
+ outputStream.write(buffer, 0, len)
+ }
+ outputStream.flush()
+ outputStream.close()
+ installFile(cacheFile)
+ }
- override fun onException(message: String?) {
- GB.toast(getString(R.string.rebble_appstore_download_failed, message), Toast.LENGTH_LONG, GB.ERROR)
- }
- })
+ override fun onException(message: String?) {
+ GB.toast(
+ getString(R.string.rebble_appstore_download_failed, message),
+ Toast.LENGTH_LONG,
+ GB.ERROR
+ )
+ }
+ })
+ }
}
private fun downloadInstallWatchappById(storeId: String) {
val appUrl = "https://appstore-api.rebble.io/api/v1/apps/id/$storeId"
- val httpHeaders = HttpHeaders()
- val httpGetRequest = HttpGetRequest(appUrl, httpHeaders)
- InternetHelperSingleton.getHttpService()?.get(httpGetRequest, object : IHttpCallback.Stub() {
- override fun onResponse(response: HttpResponse) {
- val contentType = response.headers["content-type"]?.split(";")?.get(0)
- if (!contentType.equals("application/json")) {
+ if (GBApplication.hasDirectInternetAccess()) {
+ val requestQueue = Volley.newRequestQueue(this)
+ val jsonObjectRequest = JsonObjectRequest(
+ Request.Method.GET, appUrl, null,
+ { response ->
+ val dataArray = response.getJSONArray("data")
+ val firstAppObject = dataArray.getJSONObject(0)
+ val latestRelease = firstAppObject.getJSONObject("latest_release")
+ val pbwFile = latestRelease.getString("pbw_file")
+ downloadInstallWatchapp(pbwFile.toUri())
+ },
+ { error ->
GB.toast(
- getString(R.string.rebble_appstore_fetch_app_info_failed_content_type, contentType),
- Toast.LENGTH_LONG, GB.ERROR
+ getString(
+ R.string.rebble_appstore_fetching_download_file_failed,
+ error
+ ), Toast.LENGTH_LONG, GB.ERROR
)
- return
}
- val inputStream = ParcelFileDescriptor.AutoCloseInputStream(response.body)
- val responseBody = InputStreamReader(inputStream, StandardCharsets.UTF_8).readText()
- val jsonObject = JSONObject(responseBody)
- val dataArray = jsonObject.getJSONArray("data")
- val firstAppObject = dataArray.getJSONObject(0)
- val latestRelease = firstAppObject.getJSONObject("latest_release")
- val pbwFile = latestRelease.getString("pbw_file")
- downloadInstallWatchapp(pbwFile.toUri())
+ )
+ requestQueue.add(jsonObjectRequest)
+ } else {
+ val httpHeaders = HttpHeaders()
+ val httpGetRequest = HttpGetRequest(appUrl, httpHeaders)
+ InternetHelperSingleton.getHttpService()
+ ?.get(httpGetRequest, object : IHttpCallback.Stub() {
+ override fun onResponse(response: HttpResponse) {
+ val contentType = response.headers["content-type"]?.split(";")?.get(0)
+ if (!contentType.equals("application/json")) {
+ GB.toast(
+ getString(
+ R.string.rebble_appstore_fetch_app_info_failed_content_type,
+ contentType
+ ),
+ Toast.LENGTH_LONG, GB.ERROR
+ )
+ return
+ }
+ val inputStream = ParcelFileDescriptor.AutoCloseInputStream(response.body)
+ val responseBody =
+ InputStreamReader(inputStream, StandardCharsets.UTF_8).readText()
+ val jsonObject = JSONObject(responseBody)
+ val dataArray = jsonObject.getJSONArray("data")
+ val firstAppObject = dataArray.getJSONObject(0)
+ val latestRelease = firstAppObject.getJSONObject("latest_release")
+ val pbwFile = latestRelease.getString("pbw_file")
+ downloadInstallWatchapp(pbwFile.toUri())
+ }
+
+ override fun onException(message: String?) {
+ GB.toast(
+ getString(
+ R.string.rebble_appstore_fetching_download_file_failed,
+ message
+ ), Toast.LENGTH_LONG, GB.ERROR
+ )
+ }
+ })
+ }
+ }
+
+ fun downloadBinaryFile(url: Uri, onComplete: (File) -> Unit) {
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val client = OkHttpClient()
+ val request = okhttp3.Request.Builder().url(url.toString()).build()
+ val response = client.newCall(request).execute()
+
+ if (!response.isSuccessful) {
+ LOG.error("Downloading $url failed: ${response.code}")
+ return@launch
+ }
+
+ val filename = url.lastPathSegment ?: "downloaded_file.pbw"
+ val cacheDir = applicationContext.externalCacheDir
+ val cacheFile = File(cacheDir, filename)
+ val outputStream = FileOutputStream(cacheFile)
+
+ val inputStream = response.body.byteStream()
+ inputStream.copyTo(outputStream)
+ outputStream.flush()
+ outputStream.close()
+ inputStream.close()
+
+ onComplete(cacheFile)
+ } catch (e: Exception) {
+ LOG.error("Downloading $url failed: ", e)
}
- override fun onException(message: String?) {
- GB.toast(getString(R.string.rebble_appstore_fetching_download_file_failed, message), Toast.LENGTH_LONG, GB.ERROR)
- }
- })
+ }
+ }
+
+ fun installFile(file: File) {
+ val installHandler: InstallHandler? = mGBDevice?.deviceCoordinator?.findInstallHandler(file.toUri(), applicationContext)
+ if (installHandler == null) {
+ GB.toast(getString(R.string.fwinstaller_file_not_compatible_to_device), Toast.LENGTH_LONG, GB.ERROR)
+ return
+ }
+ val startIntent = Intent(applicationContext, installHandler.getInstallActivity())
+ startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice)
+ startIntent.action = Intent.ACTION_VIEW
+ startIntent.setDataAndType(file.toUri(), null)
+ startActivity(startIntent)
}
@SuppressLint("SetJavaScriptEnabled")
@@ -223,4 +313,4 @@ class RebbleAppStoreActivity : AbstractGBActivity() {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
index 5bdba5307e..4ad057d0ef 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/BangleJSCoordinator.java
@@ -40,7 +40,7 @@ import java.util.regex.Pattern;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
-import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
@@ -55,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.InternetHelperSingleton;
public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
@@ -168,12 +169,12 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public boolean supportsAppsManagement(final GBDevice device) {
- return true;
+ return GBApplication.hasDirectInternetAccess() || InternetHelperSingleton.INSTANCE.ensureInternetHelperBound();
}
@Override
public Class extends Activity> getAppsManagementActivity(final GBDevice device) {
- return AppsManagementActivity.class;
+ return supportsAppsManagement(device) ? AppsManagementActivity.class : null;
}
@Override
@@ -232,7 +233,7 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
settings.add(R.xml.devicesettings_header_connection);
settings.add(R.xml.devicesettings_high_mtu);
- if (BuildConfig.INTERNET_ACCESS)
+ if (GBApplication.hasDirectInternetAccess() || InternetHelperSingleton.INSTANCE.ensureInternetHelperBound())
settings.add(R.xml.devicesettings_device_internet_access);
settings.add(R.xml.devicesettings_banglejs_activity);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
index 2e86f72b68..40f1e9d38b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java
@@ -99,7 +99,6 @@ import java.util.SimpleTimeZone;
import de.greenrobot.dao.query.QueryBuilder;
import io.wax911.emojify.EmojiManager;
import io.wax911.emojify.parser.EmojiParserKt;
-import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity;
@@ -141,8 +140,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
-import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLESingleDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
@@ -152,6 +151,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
+import nodomain.freeyourgadget.gadgetbridge.util.InternetHelperSingleton;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -918,14 +918,14 @@ public class BangleJSDeviceSupport extends AbstractBTLESingleDeviceSupport {
}
final String id = _id;
- if (!BuildConfig.INTERNET_ACCESS) {
+ if (!GBApplication.hasDirectInternetAccess() && !InternetHelperSingleton.INSTANCE.ensureInternetHelperBound()) {
uartTxJSONError("http", "Internet access not enabled, check Gadgetbridge Device Settings", id);
return;
}
Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
if (! devicePrefs.getBoolean(PREF_DEVICE_INTERNET_ACCESS, false)) {
- uartTxJSONError("http", "Internet access not enabled in this Gadgetbridge build", id);
+ uartTxJSONError("http", "Internet access not enabled for this device", id);
return;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/webview/GBWebClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/webview/GBWebClient.java
index 220fe74978..d5a7470acd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/webview/GBWebClient.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/webview/GBWebClient.java
@@ -99,6 +99,12 @@ public class GBWebClient extends WebViewClient {
boolean matchFound = false;
List urlFilterEntries = DBHelper.getURLFilterEntries();
+ // Allow full access to internet when available
+ boolean directInternetAccess = GBApplication.hasDirectInternetAccess();
+ if (directInternetAccess && !locallySupported) {
+ return null;
+ }
+
// Handle local schemes locally
if (requestedUri.toString().startsWith("file://") || requestedUri.toString().startsWith("gadgetbridge://")) {
return null;
@@ -151,7 +157,7 @@ public class GBWebClient extends WebViewClient {
// Handle request
if (requestedUri.getHost() != null && urlIsAllowed) {
- if (!forceLocal && InternetHelperSingleton.INSTANCE.ensureInternetHelperBound()) {
+ if (!forceLocal && !directInternetAccess && InternetHelperSingleton.INSTANCE.ensureInternetHelperBound()) {
LOG.debug("WEBVIEW forwarding request to the internet helper");
try {
return InternetHelperSingleton.INSTANCE.send(requestedUri);
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 98c0a3cab5..f70a3cf4e1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -4485,4 +4485,6 @@
Fetching download file failed: %1$s
Internet helper add-on
Permit access to the Gadgetbridge-internethelper add-on app
+ Unused settings
+ The settings below are not used because direct internet access is available.
diff --git a/app/src/main/res/xml/internethelper_preferences.xml b/app/src/main/res/xml/internethelper_preferences.xml
index d0eb93a74f..b7709ea02e 100644
--- a/app/src/main/res/xml/internethelper_preferences.xml
+++ b/app/src/main/res/xml/internethelper_preferences.xml
@@ -2,6 +2,11 @@
+