mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-09-01 20:15:52 +02:00
wip
This commit is contained in:
parent
99908fc894
commit
c3ee43b8d3
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user