1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-16 10:00:08 +02:00
This commit is contained in:
José Rebelo 2023-08-22 17:34:30 +01:00
parent 99908fc894
commit c3ee43b8d3
3 changed files with 290 additions and 162 deletions

View File

@ -16,205 +16,101 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
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<String> 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<FtpEntry> 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

View File

@ -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 <http://www.gnu.org/licenses/>. */
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;
}
}

View File

@ -16,5 +16,178 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
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<Uri, String> 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<FtpEntry> 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<Uri> 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);
//}
}
}