Take direct internet access into account

This commit is contained in:
Arjan Schrijver
2025-12-25 14:45:08 +01:00
committed by Arjan Schrijver
parent d2cd47231a
commit 528bb8d362
9 changed files with 183 additions and 69 deletions
+1
View File
@@ -269,6 +269,7 @@ dependencies {
implementation 'com.github.wax911.android-emojify:gson:1.9.7' implementation 'com.github.wax911.android-emojify:gson:1.9.7'
implementation 'com.google.protobuf:protobuf-javalite:4.33.2' implementation 'com.google.protobuf:protobuf-javalite:4.33.2'
implementation 'com.android.volley:volley:1.2.1' 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 'org.msgpack:msgpack-core:0.9.10'
implementation 'com.github.ByteHamster:SearchPreference:2.7.3' implementation 'com.github.ByteHamster:SearchPreference:2.7.3'
@@ -20,6 +20,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */ along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge; package nodomain.freeyourgadget.gadgetbridge;
import android.Manifest;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.AlarmManager; 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.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.backup.PeriodicZipExporter; import nodomain.freeyourgadget.gadgetbridge.util.backup.PeriodicZipExporter;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs; import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
@@ -2273,6 +2275,10 @@ public class GBApplication extends Application {
return typedValue.data; return typedValue.data;
} }
public static boolean hasDirectInternetAccess() {
return BuildConfig.INTERNET_ACCESS && PermissionsUtils.checkPermission(getContext(), Manifest.permission.INTERNET);
}
public static GBPrefs getPrefs() { public static GBPrefs getPrefs() {
return prefs; return prefs;
} }
@@ -33,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import nodomain.freeyourgadget.gadgetbridge.BuildConfig
import nodomain.freeyourgadget.gadgetbridge.R import nodomain.freeyourgadget.gadgetbridge.R
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper import nodomain.freeyourgadget.gadgetbridge.database.DBHelper
import nodomain.freeyourgadget.gadgetbridge.entities.URLFilterEntry import nodomain.freeyourgadget.gadgetbridge.entities.URLFilterEntry
@@ -116,6 +117,8 @@ class InternetHelperPreferencesActivity : AbstractGBActivity() {
rootKey: String? rootKey: String?
) { ) {
setPreferencesFromResource(R.xml.internethelper_preferences, rootKey) setPreferencesFromResource(R.xml.internethelper_preferences, rootKey)
val unusedWarning = findPreference<Preference>("pref_key_internethelper_unused")
unusedWarning?.isVisible = BuildConfig.INTERNET_ACCESS
val installWarning = findPreference<Preference>("pref_key_internethelper_not_installed") val installWarning = findPreference<Preference>("pref_key_internethelper_not_installed")
if (AndroidUtils.isPackageInstalled(PACKAGE_INTERNET_HELPER)) { if (AndroidUtils.isPackageInstalled(PACKAGE_INTERNET_HELPER)) {
installWarning?.isVisible = false installWarning?.isVisible = false
@@ -30,6 +30,13 @@ import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.Toast import android.widget.Toast
import androidx.core.net.toUri 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.R
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler 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.HttpHeaders
import nodomain.freeyourgadget.internethelper.aidl.http.HttpResponse import nodomain.freeyourgadget.internethelper.aidl.http.HttpResponse
import nodomain.freeyourgadget.internethelper.aidl.http.IHttpCallback import nodomain.freeyourgadget.internethelper.aidl.http.IHttpCallback
import okhttp3.OkHttpClient
import org.json.JSONObject import org.json.JSONObject
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -92,75 +100,157 @@ class RebbleAppStoreActivity : AbstractGBActivity() {
} }
private fun downloadInstallWatchapp(url: Uri) { private fun downloadInstallWatchapp(url: Uri) {
val httpHeaders = HttpHeaders() if (GBApplication.hasDirectInternetAccess()) {
val httpGetRequest = HttpGetRequest(url.toString(), httpHeaders) downloadBinaryFile(url) { file ->
InternetHelperSingleton.getHttpService()?.get(httpGetRequest, object : IHttpCallback.Stub() { installFile(file)
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)
} }
} 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?) { override fun onException(message: String?) {
GB.toast(getString(R.string.rebble_appstore_download_failed, message), Toast.LENGTH_LONG, GB.ERROR) GB.toast(
} getString(R.string.rebble_appstore_download_failed, message),
}) Toast.LENGTH_LONG,
GB.ERROR
)
}
})
}
} }
private fun downloadInstallWatchappById(storeId: String) { private fun downloadInstallWatchappById(storeId: String) {
val appUrl = "https://appstore-api.rebble.io/api/v1/apps/id/$storeId" val appUrl = "https://appstore-api.rebble.io/api/v1/apps/id/$storeId"
val httpHeaders = HttpHeaders() if (GBApplication.hasDirectInternetAccess()) {
val httpGetRequest = HttpGetRequest(appUrl, httpHeaders) val requestQueue = Volley.newRequestQueue(this)
InternetHelperSingleton.getHttpService()?.get(httpGetRequest, object : IHttpCallback.Stub() { val jsonObjectRequest = JsonObjectRequest(
override fun onResponse(response: HttpResponse) { Request.Method.GET, appUrl, null,
val contentType = response.headers["content-type"]?.split(";")?.get(0) { response ->
if (!contentType.equals("application/json")) { 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( GB.toast(
getString(R.string.rebble_appstore_fetch_app_info_failed_content_type, contentType), getString(
Toast.LENGTH_LONG, GB.ERROR 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() requestQueue.add(jsonObjectRequest)
val jsonObject = JSONObject(responseBody) } else {
val dataArray = jsonObject.getJSONArray("data") val httpHeaders = HttpHeaders()
val firstAppObject = dataArray.getJSONObject(0) val httpGetRequest = HttpGetRequest(appUrl, httpHeaders)
val latestRelease = firstAppObject.getJSONObject("latest_release") InternetHelperSingleton.getHttpService()
val pbwFile = latestRelease.getString("pbw_file") ?.get(httpGetRequest, object : IHttpCallback.Stub() {
downloadInstallWatchapp(pbwFile.toUri()) 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") @SuppressLint("SetJavaScriptEnabled")
@@ -223,4 +313,4 @@ class RebbleAppStoreActivity : AbstractGBActivity() {
} }
} }
} }
} }
@@ -40,7 +40,7 @@ import java.util.regex.Pattern;
import de.greenrobot.dao.AbstractDao; import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property; import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator; 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.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.InternetHelperSingleton;
public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator { public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
@@ -168,12 +169,12 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
@Override @Override
public boolean supportsAppsManagement(final GBDevice device) { public boolean supportsAppsManagement(final GBDevice device) {
return true; return GBApplication.hasDirectInternetAccess() || InternetHelperSingleton.INSTANCE.ensureInternetHelperBound();
} }
@Override @Override
public Class<? extends Activity> getAppsManagementActivity(final GBDevice device) { public Class<? extends Activity> getAppsManagementActivity(final GBDevice device) {
return AppsManagementActivity.class; return supportsAppsManagement(device) ? AppsManagementActivity.class : null;
} }
@Override @Override
@@ -232,7 +233,7 @@ public class BangleJSCoordinator extends AbstractBLEDeviceCoordinator {
settings.add(R.xml.devicesettings_header_connection); settings.add(R.xml.devicesettings_header_connection);
settings.add(R.xml.devicesettings_high_mtu); 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_device_internet_access);
settings.add(R.xml.devicesettings_banglejs_activity); settings.add(R.xml.devicesettings_banglejs_activity);
@@ -99,7 +99,6 @@ import java.util.SimpleTimeZone;
import de.greenrobot.dao.query.QueryBuilder; import de.greenrobot.dao.query.QueryBuilder;
import io.wax911.emojify.EmojiManager; import io.wax911.emojify.EmojiManager;
import io.wax911.emojify.parser.EmojiParserKt; import io.wax911.emojify.parser.EmojiParserKt;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity; 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.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender; import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLESingleDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLESingleDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; 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.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.InternetHelperSingleton;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager; import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -918,14 +918,14 @@ public class BangleJSDeviceSupport extends AbstractBTLESingleDeviceSupport {
} }
final String id = _id; 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); uartTxJSONError("http", "Internet access not enabled, check Gadgetbridge Device Settings", id);
return; return;
} }
Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())); Prefs devicePrefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
if (! devicePrefs.getBoolean(PREF_DEVICE_INTERNET_ACCESS, false)) { 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; return;
} }
@@ -99,6 +99,12 @@ public class GBWebClient extends WebViewClient {
boolean matchFound = false; boolean matchFound = false;
List<URLFilterEntry> urlFilterEntries = DBHelper.getURLFilterEntries(); List<URLFilterEntry> urlFilterEntries = DBHelper.getURLFilterEntries();
// Allow full access to internet when available
boolean directInternetAccess = GBApplication.hasDirectInternetAccess();
if (directInternetAccess && !locallySupported) {
return null;
}
// Handle local schemes locally // Handle local schemes locally
if (requestedUri.toString().startsWith("file://") || requestedUri.toString().startsWith("gadgetbridge://")) { if (requestedUri.toString().startsWith("file://") || requestedUri.toString().startsWith("gadgetbridge://")) {
return null; return null;
@@ -151,7 +157,7 @@ public class GBWebClient extends WebViewClient {
// Handle request // Handle request
if (requestedUri.getHost() != null && urlIsAllowed) { 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"); LOG.debug("WEBVIEW forwarding request to the internet helper");
try { try {
return InternetHelperSingleton.INSTANCE.send(requestedUri); return InternetHelperSingleton.INSTANCE.send(requestedUri);
+2
View File
@@ -4485,4 +4485,6 @@
<string name="rebble_appstore_fetching_download_file_failed">Fetching download file failed: %1$s</string> <string name="rebble_appstore_fetching_download_file_failed">Fetching download file failed: %1$s</string>
<string name="internet_helper_permission_title">Internet helper add-on</string> <string name="internet_helper_permission_title">Internet helper add-on</string>
<string name="internet_helper_permission_summary">Permit access to the Gadgetbridge-internethelper add-on app</string> <string name="internet_helper_permission_summary">Permit access to the Gadgetbridge-internethelper add-on app</string>
<string name="internet_helper_pref_unused_settings_title">Unused settings</string>
<string name="internet_helper_pref_unused_settings_summary">The settings below are not used because direct internet access is available.</string>
</resources> </resources>
@@ -2,6 +2,11 @@
<PreferenceScreen <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
android:icon="@drawable/ic_warning"
android:key="pref_key_internethelper_unused"
android:title="@string/internet_helper_pref_unused_settings_title"
android:summary="@string/internet_helper_pref_unused_settings_summary" />
<Preference <Preference
android:icon="@drawable/ic_warning" android:icon="@drawable/ic_warning"
android:key="pref_key_internethelper_not_installed" android:key="pref_key_internethelper_not_installed"