diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicfiles/MusicFilesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicfiles/MusicFilesActivity.java
index 037cd5eea..f3371cdd6 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicfiles/MusicFilesActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicfiles/MusicFilesActivity.java
@@ -16,205 +16,101 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.activities.musicfiles;
-import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast;
-
-import android.content.ComponentName;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaMetadataRetriever;
+import android.content.IntentFilter;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.view.MenuItem;
-import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.core.app.ActivityCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import org.json.JSONException;
-import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
+import java.util.Objects;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
-import nodomain.freeyourgadget.gadgetbridge.util.GB;
-import nodomain.freeyourgadget.internethelper.aidl.ftp.FtpEntry;
-import nodomain.freeyourgadget.internethelper.aidl.ftp.IFtpService;
-import nodomain.freeyourgadget.internethelper.aidl.ftp.IFtpCallback;
public class MusicFilesActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(MusicFilesActivity.class);
+ public static final String ACTION_FILES_LIST = "nodomain.freeyourgadget.gadgetbridge.activities.musicfiles.action_files_list";
+ public static final String ACTION_STARTED = "nodomain.freeyourgadget.gadgetbridge.activities.musicfiles.action_started";
+ public static final String ACTION_STATUS = "nodomain.freeyourgadget.gadgetbridge.activities.musicfiles.action_status";
+ public static final String ACTION_STOPPED = "nodomain.freeyourgadget.gadgetbridge.activities.musicfiles.action_stopped";
+
private GBDevice gbDevice;
- IFtpService iFtpService;
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ switch (Objects.requireNonNull(intent.getAction())) {
+ case ACTION_FILES_LIST:
+ return;
+ case ACTION_STARTED:
+ return;
+ case ACTION_STATUS:
+ return;
+ case ACTION_STOPPED:
+ finish();
+ return;
+ default:
+ LOG.warn("Unhandled intent action {}", intent.getAction());
+ }
+ }
+ };
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
setContentView(R.layout.activity_music_files);
+ if (!gbDevice.isInitialized()) {
+ new MaterialAlertDialogBuilder(this)
+ .setCancelable(false)
+ .setTitle("Device not initialized")
+ .setMessage("Please connect to the device")
+ .setIcon(R.drawable.ic_warning)
+ .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+ finish();
+ })
+ .show();
+ return;
+ }
+
+ final IntentFilter filterLocal = new IntentFilter();
+ filterLocal.addAction(ACTION_FILES_LIST);
+ filterLocal.addAction(ACTION_STARTED);
+ filterLocal.addAction(ACTION_STATUS);
+ filterLocal.addAction(ACTION_STOPPED);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
+
+ GBApplication.deviceService(gbDevice).onMusicFilesStart();
+
final ActivityResultLauncher activityResultLauncher = this.registerForActivityResult(
new ActivityResultContracts.GetMultipleContents(),
- urilist -> {
- final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
-
- LOG.info("Got {}", urilist);
-
- for (final Uri uri : urilist) {
- mediaMetadataRetriever.setDataSource(MusicFilesActivity.this, uri);
-
- final String title = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
- final String artist = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
- final String album = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
-
- long fileSize;
- try (AssetFileDescriptor fileDescriptor = getApplicationContext().getContentResolver().openAssetFileDescriptor(uri , "r")){
- fileSize = fileDescriptor.getLength();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- final JSONObject jsonObject = new JSONObject();
- try {
- jsonObject.put("title", title);
- jsonObject.put("album", album);
- jsonObject.put("artist", artist);
- jsonObject.put("size", fileSize);
- } catch (final JSONException e) {
- throw new RuntimeException(e);
- }
-
- final String md5;
- try (InputStream inputStream = getContentResolver().openInputStream(uri)) {
- md5 = FileUtils.md5sum(inputStream);
- } catch (final IOException e) {
- throw new RuntimeException(e);
- }
-
- grantUriPermission("nodomain.freeyourgadget.internethelper", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
- LOG.info("{}: {} {}", uri, md5, jsonObject);
-
- try {
- iFtpService.upload(ftpClient, uri.toString(), "/cenas.mp3");
- } catch (RemoteException e) {
- LOG.error("oops", e);
- }
-
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- mediaMetadataRetriever.close();
- }
- }
+ uris -> GBApplication.deviceService(gbDevice).onMusicFilesUpload(uris.toArray(new Uri[0]))
);
final FloatingActionButton fab = findViewById(R.id.fab);
- fab.setOnClickListener(v -> {
- try {
- if (initFtp()) {
- activityResultLauncher.launch("audio/*");
- }
- } catch (RemoteException e) {
- LOG.error("Failure", e);
- }
- });
+ fab.setOnClickListener(v -> activityResultLauncher.launch("audio/*"));
}
- String ftpClient;
- boolean ftpReady = false;
- final IFtpCallback.Stub cb = new IFtpCallback.Stub() {
- @Override
- public void onConnect(boolean success, String msg) throws RemoteException {
- LOG.info("onConnect {} {}", success, msg);
- if (success) {
- iFtpService.login(ftpClient, "gadgetbridge", "cenas123");
- }
- }
-
- @Override
- public void onLogin(boolean success, String msg) throws RemoteException {
- LOG.info("onLogin {} {}", success, msg);
- ftpReady = success;
- }
-
- @Override
- public void onList(String path, List entries) throws RemoteException {
- LOG.info("onList {}", path, entries);
- }
-
- @Override
- public void onUpload(String path, boolean success, String msg) throws RemoteException {
- LOG.info("onUpload");
- }
-
- @Override
- public void onDownload(String path, boolean success, String msg) throws RemoteException {
- LOG.info("onDownload");
- }
- };
-
- private boolean initFtp() throws RemoteException {
- if (ActivityCompat.checkSelfPermission(getApplicationContext(), "nodomain.freeyourgadget.internethelper.INTERNET") != PackageManager.PERMISSION_GRANTED) {
- LOG.error("No permission to access internet!");
- toast(this, "internet permission missing", Toast.LENGTH_SHORT, GB.ERROR);
- ActivityCompat.requestPermissions(this, new String[]{"nodomain.freeyourgadget.internethelper.INTERNET"}, 0);
- return false;
- }
-
- if (iFtpService == null) {
- LOG.info("connecting");
- ServiceConnection mConnection = new ServiceConnection() {
- // Called when the connection with the service is established.
- public void onServiceConnected(ComponentName className, IBinder service) {
- LOG.info("onServiceConnected");
-
- // Following the preceding example for an AIDL interface,
- // this gets an instance of the IRemoteInterface, which we can use to call on the service.
- iFtpService = IFtpService.Stub.asInterface(service);
- }
-
- // Called when the connection with the service disconnects unexpectedly.
- public void onServiceDisconnected(ComponentName className) {
- LOG.error("Service has unexpectedly disconnected");
- iFtpService = null;
- }
- };
- Intent intent = new Intent("nodomain.freeyourgadget.internethelper.FtpService");
- intent.setPackage("nodomain.freeyourgadget.internethelper");
- boolean res = this.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- if (res) {
- LOG.info("Bound to NetworkService");
- } else {
- LOG.warn("Could not bind to NetworkService");
- }
-
- return false;
- } else if (!ftpReady) {
- final int version = iFtpService.version();
- LOG.info("version = {}", version);
- ftpClient = iFtpService.createClient(cb);
- LOG.info("client = {}", ftpClient);
- iFtpService.connect(ftpClient, "10.0.1.49", 8710);
- }
-
- return ftpReady;
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/network/InternetHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/network/InternetHelper.java
new file mode 100644
index 000000000..0ba5bdd8b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/network/InternetHelper.java
@@ -0,0 +1,59 @@
+/* Copyright (C) 2023 José Rebelo
+
+ 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.network;
+
+import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.widget.Toast;
+
+import androidx.core.app.ActivityCompat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class InternetHelper {
+ private static final Logger LOG = LoggerFactory.getLogger(InternetHelper.class);
+
+ private final Context mContext;
+
+ public InternetHelper(final Context context) {
+ this.mContext = context;
+ }
+
+ public String getPackageName() {
+ return "nodomain.freeyourgadget.internethelper";
+ }
+
+ public String getPermission() {
+ return getPackageName() + ".INTERNET";
+ }
+
+ public boolean requestPermissions(final Activity activity) {
+ if (ActivityCompat.checkSelfPermission(mContext, getPermission()) != PackageManager.PERMISSION_GRANTED) {
+ LOG.warn("No permission to access internet, requesting");
+ ActivityCompat.requestPermissions(activity, new String[]{getPermission()}, 0);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMusicFilesCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMusicFilesCoordinator.java
index dd8e985b6..3af68e006 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMusicFilesCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/zeppos/ZeppOsMusicFilesCoordinator.java
@@ -16,5 +16,178 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nodomain.freeyourgadget.gadgetbridge.network.InternetHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+import nodomain.freeyourgadget.internethelper.aidl.ftp.FtpEntry;
+import nodomain.freeyourgadget.internethelper.aidl.ftp.IFtpCallback;
+import nodomain.freeyourgadget.internethelper.aidl.ftp.IFtpService;
+
public class ZeppOsMusicFilesCoordinator {
+ private static final Logger LOG = LoggerFactory.getLogger(ZeppOsMusicFilesCoordinator.class);
+
+ private final Context mContext;
+
+ private final Map filesToUpload = new HashMap<>();
+
+ private final InternetHelper internetHelper;
+
+ private IFtpService iFtpService;
+ private String ftpClient;
+ boolean ftpReady = false;
+
+ private final ServiceConnection mFtpConnection = new ServiceConnection() {
+ public void onServiceConnected(final ComponentName className, final IBinder service) {
+ LOG.info("onServiceConnected: {}", className);
+
+ iFtpService = IFtpService.Stub.asInterface(service);
+ }
+
+ public void onServiceDisconnected(final ComponentName className) {
+ LOG.error("Service has unexpectedly disconnected: {}", className);
+ iFtpService = null;
+ }
+ };
+
+ final IFtpCallback.Stub ftpCallback = new IFtpCallback.Stub() {
+ @Override
+ public void onConnect(boolean success, String msg) throws RemoteException {
+ LOG.info("onConnect {} {}", success, msg);
+ if (success) {
+ iFtpService.login(ftpClient, "gadgetbridge", "cenas123");
+ }
+ }
+
+ @Override
+ public void onLogin(boolean success, String msg) throws RemoteException {
+ LOG.info("onLogin {} {}", success, msg);
+ ftpReady = success;
+ }
+
+ @Override
+ public void onList(String path, List entries) throws RemoteException {
+ LOG.info("onList {}", path, entries);
+ }
+
+ @Override
+ public void onUpload(String path, boolean success, String msg) throws RemoteException {
+ LOG.info("onUpload");
+ }
+
+ @Override
+ public void onDownload(String path, boolean success, String msg) throws RemoteException {
+ LOG.info("onDownload");
+ }
+ };
+
+ public ZeppOsMusicFilesCoordinator(final Context context) {
+ this.mContext = context;
+ this.internetHelper = new InternetHelper(mContext);
+ }
+
+ public void initFtp() {
+ final Intent intent = new Intent("nodomain.freeyourgadget.internethelper.FtpService");
+ intent.setPackage(internetHelper.getPackageName());
+ boolean res = mContext.bindService(intent, mFtpConnection, Context.BIND_AUTO_CREATE);
+ if (res) {
+ LOG.info("Bound to FtpService");
+ } else {
+ LOG.warn("Could not bind to FtpService");
+ }
+ }
+
+ public void destroy() {
+ mContext.unbindService(mFtpConnection);
+ }
+
+ public void uploadFiles(final List uriList) {
+ if (iFtpService == null || ftpClient == null) {
+ LOG.error("No ftp client");
+ return;
+ }
+
+ final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
+
+ for (final Uri uri : uriList) {
+ mediaMetadataRetriever.setDataSource(mContext, uri);
+
+ final String title = StringUtils.ensureNotNull(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+ final String artist = StringUtils.ensureNotNull(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
+ final String album = StringUtils.ensureNotNull(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
+
+ long fileSize;
+ try (AssetFileDescriptor fileDescriptor = mContext.getContentResolver().openAssetFileDescriptor(uri, "r")) {
+ if (fileDescriptor == null) {
+ throw new IOException("Failed to get file descriptor for " + uri);
+ }
+
+ fileSize = fileDescriptor.getLength();
+ } catch (final IOException e) {
+ LOG.error("Failed to get file size for {}", uri, e);
+ continue;
+ }
+
+ final JSONObject jsonObject = new JSONObject();
+ try {
+ jsonObject.put("title", title);
+ jsonObject.put("album", album);
+ jsonObject.put("artist", artist);
+ jsonObject.put("size", fileSize);
+ } catch (final JSONException e) {
+ LOG.error("Failed to build json object - this should never happen...", e);
+ continue;
+ }
+
+ final String md5;
+ try (InputStream inputStream = mContext.getContentResolver().openInputStream(uri)) {
+ md5 = FileUtils.md5sum(inputStream);
+ } catch (final IOException e) {
+ LOG.error("Failed to compute md5 for {}", uri, e);
+ continue;
+ }
+
+ mContext.grantUriPermission(internetHelper.getPackageName(), uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ LOG.debug("Info for {}: md5={} jsonObj={}", uri, md5, jsonObject);
+
+ final String targetName = md5 + "." + FileUtils.getExtension(uri.toString());
+
+ filesToUpload.put(uri, "/" + targetName);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ mediaMetadataRetriever.close();
+ }
+
+ uploadNextFile();
+ }
+
+ public void uploadNextFile() {
+ //try {
+ // iFtpService.upload(ftpClient, uri.toString(), "/cenas.mp3");
+ //} catch (RemoteException e) {
+ // LOG.error("oops", e);
+ //}
+ }
}