diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 36cd717a12..9c9119cb0c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -267,6 +267,10 @@
android:label="@string/title_activity_appmanager"
android:launchMode="singleTop"
android:parentActivityName=".activities.ControlCenterv2" />
+
-
watchfaceDesignerActivity;
+ private Class extends Activity> appStoreActivity;
protected abstract List getSystemAppsInCategory();
@@ -424,12 +427,14 @@ public abstract class AbstractAppManagerFragment extends Fragment {
mGBDevice = ((AppManagerActivity) getActivity()).getGBDevice();
mCoordinator = mGBDevice.getDeviceCoordinator();
- final FloatingActionButton appListFab = ((FloatingActionButton) getActivity().findViewById(R.id.fab));
- final FloatingActionButton appListFabNew = ((FloatingActionButton) getActivity().findViewById(R.id.fab_new));
+ final FloatingActionButton appListFab = getActivity().findViewById(R.id.fab);
+ final FloatingActionButton appListFabNew = getActivity().findViewById(R.id.fab_new);
+ final FloatingActionButton appListFabStore = getActivity().findViewById(R.id.fab_store);
watchfaceDesignerActivity = mCoordinator.getWatchfaceDesignerActivity(mGBDevice);
+ appStoreActivity = mCoordinator.getAppStoreActivity(mGBDevice);
View rootView = inflater.inflate(R.layout.activity_appmanager, container, false);
- RecyclerView appListView = (RecyclerView) (rootView.findViewById(R.id.appListView));
+ RecyclerView appListView = rootView.findViewById(R.id.appListView);
appListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
@@ -437,6 +442,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
if (dy > 0) {
appListFab.hide();
appListFabNew.hide();
+ appListFabStore.hide();
} else if (dy < 0) {
if (mCoordinator.supportsFlashing(mGBDevice)) {
appListFab.show();
@@ -444,6 +450,11 @@ public abstract class AbstractAppManagerFragment extends Fragment {
if (watchfaceDesignerActivity != null) {
appListFabNew.show();
}
+ if (appStoreActivity != null && mGBDevice.getDeviceCoordinator() instanceof PebbleCoordinator) {
+ if (WebViewSingleton.getInstance().ensureInternetHelperBound()) {
+ appListFabStore.show();
+ }
+ }
}
}
});
@@ -462,17 +473,25 @@ public abstract class AbstractAppManagerFragment extends Fragment {
appManagementTouchHelper.attachToRecyclerView(appListView);
if ((watchfaceDesignerActivity != null) && (appListFabNew != null)) {
- appListFabNew.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent startIntent = new Intent(getContext(), watchfaceDesignerActivity);
- startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
- getContext().startActivity(startIntent);
- }
+ appListFabNew.setOnClickListener(v -> {
+ Intent startIntent = new Intent(getContext(), watchfaceDesignerActivity);
+ startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
+ getContext().startActivity(startIntent);
});
appListFabNew.show();
}
+ if (appStoreActivity != null && mGBDevice.getDeviceCoordinator() instanceof PebbleCoordinator) {
+ if (WebViewSingleton.getInstance().ensureInternetHelperBound()) {
+ appListFabStore.setOnClickListener(v -> {
+ Intent startIntent = new Intent(getContext(), appStoreActivity);
+ startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
+ getContext().startActivity(startIntent);
+ });
+ appListFabStore.show();
+ }
+ }
+
return rootView;
}
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
new file mode 100644
index 0000000000..f3bbbabe7d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/RebbleAppStoreActivity.kt
@@ -0,0 +1,297 @@
+/* Copyright (C) 2022-2024 Andreas Shimokawa, Daniel Dakhno, Gordon Williams, Arjan Schrijver
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.activities.appmanager
+
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.ServiceConnection
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.os.ParcelFileDescriptor
+import android.os.RemoteException
+import android.webkit.PermissionRequest
+import android.webkit.WebChromeClient
+import android.webkit.WebResourceRequest
+import android.webkit.WebResourceResponse
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import android.widget.Toast
+import androidx.core.net.toUri
+import nodomain.freeyourgadget.gadgetbridge.GBApplication
+import nodomain.freeyourgadget.gadgetbridge.R
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
+import nodomain.freeyourgadget.gadgetbridge.util.Capsule
+import nodomain.freeyourgadget.gadgetbridge.util.GB
+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 nodomain.freeyourgadget.internethelper.aidl.http.IHttpService
+import org.jsoup.Jsoup
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import java.io.FileOutputStream
+import java.io.OutputStream
+import java.nio.charset.Charset
+import java.util.concurrent.CountDownLatch
+import kotlin.concurrent.Volatile
+
+class RebbleAppStoreActivity : AbstractGBActivity() {
+ val LOG: Logger = LoggerFactory.getLogger(RebbleAppStoreActivity::class.java)
+ private var mGBDevice: GBDevice? = null
+ private var webView: WebView? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_banglejs_apps_management)
+
+ val intent1 = Intent("nodomain.freeyourgadget.internethelper.HttpService")
+ intent1.setPackage("nodomain.freeyourgadget.internethelper")
+ val res = applicationContext.bindService(intent1, mHttpConnection, BIND_AUTO_CREATE)
+ if (res) {
+ LOG.info("Bound to HttpService")
+ } else {
+ LOG.warn("Could not bind to HttpService")
+ }
+
+ val extras = intent.extras
+ if (extras != null) {
+ mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE)
+ }
+ requireNotNull(mGBDevice) { "Must provide a device when invoking this activity" }
+ initViews()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (webView != null) return // already set up
+ val commandFilter = IntentFilter()
+ commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED)
+ initViews()
+ }
+
+ override fun onDestroy() {
+ webView!!.destroy()
+ webView = null
+ super.onDestroy()
+ finish()
+ }
+
+ @Volatile
+ private var iHttpService: IHttpService? = null
+
+ private val mHttpConnection: ServiceConnection = object : ServiceConnection {
+ override fun onServiceConnected(className: ComponentName?, service: IBinder?) {
+ LOG.info("onServiceConnected: {}", className)
+ iHttpService = IHttpService.Stub.asInterface(service)
+ }
+
+ override fun onServiceDisconnected(className: ComponentName?) {
+ LOG.error("Service has unexpectedly disconnected: {}", className)
+ iHttpService = null
+ }
+ }
+
+ private fun isDownloadableWatchapp(url: String): Boolean {
+ val downloadExtensions = listOf(".pbw", ".zip")
+ return downloadExtensions.any { url.endsWith(it, ignoreCase = true) }
+ }
+
+ private fun downloadInstallWatchapp(url: Uri) {
+ val httpHeaders = HttpHeaders()
+ val httpGetRequest = HttpGetRequest(url.toString(), httpHeaders)
+ iHttpService!!.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("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)
+ }
+ override fun onException(message: String?) {
+ GB.toast("Download failed: $message", Toast.LENGTH_LONG, GB.ERROR)
+ }
+ })
+ }
+
+ // TODO: handle links like pebble://appstore/52b231c2b70e1c159500009b#
+ // so we don't have to use ?dev_settings=true
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private fun initViews() {
+ webView = findViewById(R.id.webview)
+ webView!!.webViewClient = WebViewClient()
+ val settings = webView!!.settings
+ settings.javaScriptEnabled = true
+ settings.loadWithOverviewMode = true
+ settings.useWideViewPort = true
+
+ val url = "https://apps.rebble.io/en_US/watchfaces?dev_settings=true"
+
+ webView!!.webViewClient = object : WebViewClient() {
+ override fun shouldOverrideUrlLoading(
+ wv: WebView,
+ request: WebResourceRequest
+ ): Boolean {
+ val requestUrl = request.url
+ // Check if the URL is a downloadable watchface/watchapp
+ if (isDownloadableWatchapp(requestUrl.toString())) {
+ downloadInstallWatchapp(requestUrl)
+ return true // Indicate that the URL loading is handled
+ }
+
+ val intent = Intent(Intent.ACTION_VIEW, requestUrl)
+ wv.context.startActivity(intent)
+ return true
+ }
+
+ override fun shouldInterceptRequest(
+ view: WebView?,
+ request: WebResourceRequest
+ ): WebResourceResponse? {
+ LOG.info("shouldIntercept {} {} {}", request.method, request.url, iHttpService != null)
+
+ if (!request.method.equals("get", ignoreCase = true)) {
+ return super.shouldInterceptRequest(view, request)
+ }
+ if (iHttpService == null) {
+ return super.shouldInterceptRequest(view, request)
+ }
+
+ val httpHeaders = HttpHeaders()
+ for (header in request.requestHeaders.entries) {
+ httpHeaders.addHeader(header.key, header.value)
+ }
+
+ val httpGetRequest = HttpGetRequest(request.url.toString(), httpHeaders)
+ val latch = CountDownLatch(1)
+ val internetResponseCapsule = Capsule()
+
+ try {
+ iHttpService!!.get(httpGetRequest, object : IHttpCallback.Stub() {
+ @Throws(RemoteException::class)
+ override fun onResponse(response: HttpResponse) {
+ // Extract headers
+ val contentType = response.headers["content-type"]?.split(";")?.get(0) ?: "text/html"
+ val contentEncoding = response.headers["content-encoding"] ?: "UTF-8"
+
+ // Retrieve original payload as InputStream
+ val inputStream = ParcelFileDescriptor.AutoCloseInputStream(response.body)
+
+ // Clean up malformed HTML from Rebble
+ if (contentType.equals("text/html", ignoreCase = true)) {
+ val rawHtml = inputStream.bufferedReader(Charset.forName(contentEncoding)).use { it.readText() }
+ val cleanedHtml = Jsoup.parse(rawHtml).html()
+ val internetResponse = WebResourceResponse(
+ contentType,
+ contentEncoding,
+ response.status,
+ "OK",
+ response.headers.toMap(),
+ cleanedHtml.byteInputStream()
+ )
+ internetResponseCapsule.set(internetResponse)
+ } else {
+ // If not text/html, return original response
+ val internetResponse = WebResourceResponse(
+ contentType,
+ contentEncoding,
+ response.status,
+ "OK",
+ response.headers.toMap(),
+ inputStream
+ )
+ internetResponseCapsule.set(internetResponse)
+ }
+ latch.countDown()
+ }
+
+ @Throws(RemoteException::class)
+ override fun onException(message: String?) {
+ throw RuntimeException(message)
+ }
+ })
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ }
+
+ try {
+ latch.await()
+ } catch (e: InterruptedException) {
+ throw RuntimeException(e)
+ }
+
+ return internetResponseCapsule.get()
+ }
+
+ override fun onReceivedError(
+ view: WebView,
+ errorCode: Int,
+ description: String?,
+ failingUrl: String?
+ ) {
+ GB.toast(
+ "Error: $description",
+ Toast.LENGTH_SHORT,
+ GB.ERROR
+ )
+ view.loadUrl("about:blank")
+ }
+ }
+
+ val mainLooper = Looper.getMainLooper()
+ Handler(mainLooper).postDelayed({
+ webView!!.loadUrl(url)
+ }, 100)
+
+ webView!!.webChromeClient = object : WebChromeClient() {
+ override fun onPermissionRequest(request: PermissionRequest) {
+ request.grant(request.resources)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index 7f6590b819..50f49553a4 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -580,6 +580,12 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return null;
}
+ @Nullable
+ @Override
+ public Class extends Activity> getAppStoreActivity(final GBDevice device) {
+ return null;
+ }
+
@Override
public int getBondingStyle() {
return BONDING_STYLE_ASK;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index 14807a2d5a..1337e3d377 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -616,6 +616,13 @@ public interface DeviceCoordinator {
*/
Class extends Activity> getWatchfaceDesignerActivity(GBDevice device);
+ /**
+ * Returns the Activity class that will be used to download apps/watchfaces.
+ *
+ * @return
+ */
+ Class extends Activity> getAppStoreActivity(GBDevice device);
+
/**
* Returns the device app cache directory.
*/
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java
index 8c8618e186..fc4cfc7546 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/banglejs/AppsManagementActivity.java
@@ -63,7 +63,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.Capsule;
-import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.internethelper.aidl.http.HttpGetRequest;
import nodomain.freeyourgadget.internethelper.aidl.http.HttpHeaders;
@@ -115,10 +114,6 @@ public class AppsManagementActivity extends AbstractGBActivity {
mCoordinator = mGBDevice.getDeviceCoordinator();
}
- private void toast(String data) {
- GB.toast(data, Toast.LENGTH_LONG, GB.INFO);
- }
-
@Override
protected void onPause() {
super.onPause();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
index ee2fd10b96..b3e29f0926 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java
@@ -38,6 +38,7 @@ import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.RebbleAppStoreActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
@@ -140,6 +141,11 @@ public class PebbleCoordinator extends AbstractBLClassicDeviceCoordinator {
return AppManagerActivity.class;
}
+ @Override
+ public Class extends Activity> getAppStoreActivity(final GBDevice device) {
+ return RebbleAppStoreActivity.class;
+ }
+
@Override
public File getAppCacheDir() throws IOException {
return PebbleUtils.getPbwCacheDir();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java
index 2ee48beaa9..dc90975304 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java
@@ -78,8 +78,8 @@ public class GBWebClient extends WebViewClient {
}
private WebResourceResponse mimicReply(Uri requestedUri) {
- if (requestedUri.getHost() != null && (StringUtils.indexOfAny(requestedUri.getHost(), AllowedDomains) != -1)) {
- if (WebViewSingleton.getInstance().internetHelperBound) {
+ if (requestedUri.getHost() != null && !requestedUri.toString().startsWith("file://")) {// && (StringUtils.indexOfAny(requestedUri.getHost(), AllowedDomains) != -1)) {
+ if (WebViewSingleton.getInstance().ensureInternetHelperBound()) {
LOG.debug("WEBVIEW forwarding request to the internet helper");
try {
return WebViewSingleton.getInstance().send(requestedUri);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java
index ca1ce6c165..f090a52f1b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java
@@ -107,6 +107,28 @@ public class WebViewSingleton {
}
}
+ public boolean ensureInternetHelperBound() {
+ if (contextWrapper != null && !internetHelperBound) {
+ String internetHelperPkg = "nodomain.freeyourgadget.internethelper";
+ String internetHelperCls = internetHelperPkg + ".HttpService";
+ try {
+ contextWrapper.getPackageManager().getApplicationInfo(internetHelperPkg, 0);
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(internetHelperPkg, internetHelperCls));
+
+ final Intent intent1 = new Intent("nodomain.freeyourgadget.internethelper.HttpService");
+ intent1.setPackage("nodomain.freeyourgadget.internethelper");
+ contextWrapper.getApplicationContext().bindService(intent1, internetHelperConnection, Context.BIND_AUTO_CREATE);
+ LOG.info("WEBVIEW: Internet helper bound successfully.");
+ } catch (PackageManager.NameNotFoundException e) {
+ LOG.info("WEBVIEW: Internet helper not installed, only mimicked HTTP requests will work.");
+ } catch (SecurityException e) {
+ LOG.info("WEBVIEW: Permission for internet helper not granted, only mimicked HTTP requests will work.");
+ }
+ }
+ return internetHelperBound;
+ }
+
public static WebViewSingleton getInstance() {
return instance;
}
@@ -188,24 +210,7 @@ public class WebViewSingleton {
webView.addJavascriptInterface(jsInterface, "GBjs");
webView.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500);
});
- if (contextWrapper != null && !internetHelperBound) {
- String internetHelperPkg = "nodomain.freeyourgadget.internethelper";
- String internetHelperCls = internetHelperPkg + ".HttpService";
- try {
- contextWrapper.getPackageManager().getApplicationInfo(internetHelperPkg, 0);
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(internetHelperPkg, internetHelperCls));
-
- final Intent intent1 = new Intent("nodomain.freeyourgadget.internethelper.HttpService");
- intent1.setPackage("nodomain.freeyourgadget.internethelper");
- contextWrapper.getApplicationContext().bindService(intent1, internetHelperConnection, Context.BIND_AUTO_CREATE);
- LOG.info("WEBVIEW: Internet helper bound successfully.");
- } catch (PackageManager.NameNotFoundException e) {
- LOG.info("WEBVIEW: Internet helper not installed, only mimicked HTTP requests will work.");
- } catch (SecurityException e) {
- LOG.info("WEBVIEW: Permission for internet helper not granted, only mimicked HTTP requests will work.");
- }
- }
+ ensureInternetHelperBound();
}
}
diff --git a/app/src/main/res/drawable/ic_store.xml b/app/src/main/res/drawable/ic_store.xml
new file mode 100644
index 0000000000..726c52ff8f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_store.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_fragmentappmanager.xml b/app/src/main/res/layout/activity_fragmentappmanager.xml
index c9fc33928b..6e4189efe3 100644
--- a/app/src/main/res/layout/activity_fragmentappmanager.xml
+++ b/app/src/main/res/layout/activity_fragmentappmanager.xml
@@ -51,4 +51,17 @@
android:visibility="invisible"
/>
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2d1572c5b6..0922fd6d09 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -4448,4 +4448,5 @@
Movement Evaluation
Send notification pictures
Send notification pictures for current device
+ Rebble app store