From c3ee43b8d3da46a3f22f1a247b5c09ef5cb1903d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Tue, 22 Aug 2023 17:34:30 +0100 Subject: [PATCH] wip --- .../musicfiles/MusicFilesActivity.java | 220 +++++------------- .../gadgetbridge/network/InternetHelper.java | 59 +++++ .../zeppos/ZeppOsMusicFilesCoordinator.java | 173 ++++++++++++++ 3 files changed, 290 insertions(+), 162 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/network/InternetHelper.java 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); + //} + } }