diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ffbee889b..b5391c011 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -199,6 +199,10 @@
android:label="@string/title_activity_appmanager"
android:launchMode="singleTop"
android:parentActivityName=".activities.ControlCenterv2" />
+
{
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java
new file mode 100644
index 000000000..cc7f821f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java
@@ -0,0 +1,632 @@
+package nodomain.freeyourgadget.gadgetbridge.activities.musicmanager;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.PopupMenu;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultCallback;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity;
+import nodomain.freeyourgadget.gadgetbridge.adapter.MusicListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.GridAutoFitLayoutManager;
+
+public class MusicManagerActivity extends AbstractGBActivity {
+ private static final Logger LOG = LoggerFactory.getLogger(MusicManagerActivity.class);
+
+ public static final String ACTION_MUSIC_DATA
+ = "nodomain.freeyourgadget.gadgetbridge.musicmanager.action.music_data";
+ public static final String ACTION_MUSIC_UPDATE
+ = "nodomain.freeyourgadget.gadgetbridge.musicmanager.action.music_update";
+
+ protected GBDevice mGBDevice = null;
+
+ private View loadingView = null;
+ private TextView musicDeviceInfo = null;
+
+ private final List allMusic = new ArrayList<>();
+
+ private final List musicList = new ArrayList<>();
+ private MusicListAdapter musicAdapter;
+
+ private final List playlists = new ArrayList<>();
+ private ArrayAdapter playlistAdapter;
+
+ private View playlistSpinnerLayout;
+ private Spinner playlistsSpinner;
+
+ private FloatingActionButton fabMusicUpload;
+ private FloatingActionButton fabMusicPlaylistAdd;
+
+ private int maxMusicCount = 0;
+ private int maxPlaylistCount = 0;
+
+ public GBDevice getGBDevice() {
+ return mGBDevice;
+ }
+
+ Handler loadingTimeout = new Handler();
+ Runnable loadingRunnable = new Runnable() {
+ @Override
+ public void run() {
+ GB.toast(getString(R.string.music_error), Toast.LENGTH_SHORT, GB.ERROR);
+ stopLoading();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_musicmanager);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
+ }
+ if (mGBDevice == null) {
+ throw new IllegalArgumentException("Must provide a device when invoking this activity");
+ }
+
+ fabMusicUpload = findViewById(R.id.fab_music_upload);
+ assert fabMusicUpload != null;
+ fabMusicUpload.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("audio/*");
+ openAudioActivityResultLauncher.launch(intent);
+ }
+ });
+
+ fabMusicPlaylistAdd = findViewById(R.id.fab_music_playlist_add);
+ assert fabMusicPlaylistAdd != null;
+ fabMusicPlaylistAdd.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ addMusicPlaylist();
+ }
+ });
+
+ hideActionButtons();
+
+ RecyclerView musicListView = findViewById(R.id.music_songs_list);
+ loadingView = findViewById(R.id.music_loading);
+
+ musicDeviceInfo = findViewById(R.id.music_device_info);
+
+ musicListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ if (dy > 0) {
+ hideActionButtons();
+ } else if (dy < 0) {
+ showActionButtons();
+ }
+ }
+ });
+ musicListView.setLayoutManager(new GridAutoFitLayoutManager(this, 300));
+
+ musicAdapter = new MusicListAdapter(
+ musicList,
+ new MusicListAdapter.onItemAction() {
+ @Override
+ public void onItemClick(View view, GBDeviceMusic music) {
+ openPopupMenu(view, music);
+ }
+
+ @Override
+ public boolean onItemLongClick(View view, GBDeviceMusic music) {
+ return false;
+ }
+ }
+ );
+ musicListView.setAdapter(musicAdapter);
+
+ playlistSpinnerLayout = findViewById(R.id.music_playlists_layout);
+
+ playlistsSpinner = findViewById(R.id.music_playlists);
+
+ ImageButton renamePlaylist = findViewById(R.id.music_playlist_rename);
+ assert renamePlaylist != null;
+ renamePlaylist.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ renameMusicPlaylist((GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem());
+ }
+ });
+
+ ImageButton deletePlaylist = findViewById(R.id.music_playlist_delete);
+ assert deletePlaylist != null;
+ deletePlaylist.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ deleteMusicPlaylist((GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem());
+ }
+ });
+
+
+ playlistsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
+ GBDeviceMusicPlaylist item = (GBDeviceMusicPlaylist) adapterView.getItemAtPosition(i);
+ if (item.getId() == 0) {
+ deletePlaylist.setVisibility(View.GONE);
+ renamePlaylist.setVisibility(View.GONE);
+
+ } else {
+ deletePlaylist.setVisibility(View.VISIBLE);
+ renamePlaylist.setVisibility(View.VISIBLE);
+ }
+ updateCurrentMusicList();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> adapterView) {
+
+ }
+ });
+
+ playlistAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, playlists);
+ initPlaylists();
+
+ playlistAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ playlistsSpinner.setAdapter(playlistAdapter);
+ }
+
+ private void hideActionButtons() {
+ fabMusicUpload.hide();
+ fabMusicPlaylistAdd.hide();
+
+ }
+
+ private void showActionButtons() {
+ fabMusicUpload.show();
+ if(maxPlaylistCount > 0) {
+ fabMusicPlaylistAdd.show();
+ }
+ }
+
+
+ private void startLoading(long timeout) {
+ hideActionButtons();
+ loadingView.setVisibility(View.VISIBLE);
+ if(timeout > 0) {
+ loadingTimeout.postDelayed(loadingRunnable, timeout);
+ }
+ }
+ private void startLoading() {
+ startLoading(4000);
+ }
+
+ private void stopLoading() {
+ loadingTimeout.removeCallbacks(loadingRunnable);
+ loadingView.setVisibility(View.GONE);
+ showActionButtons();
+ }
+
+ private void updateCurrentMusicList() {
+ GBDeviceMusicPlaylist current = (GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem();
+ musicList.clear();
+ if (current.getId() == 0) {
+ musicList.addAll(allMusic);
+ } else {
+ List filtered = allMusic.stream().filter(m -> current.getMusicIds().contains(m.getId())).collect(Collectors.toList());
+ musicList.addAll(filtered);
+ }
+ musicAdapter.notifyDataSetChanged();
+ }
+
+ private void initPlaylists() {
+ playlists.clear();
+ playlists.add(new GBDeviceMusicPlaylist(0,this.getString(R.string.music_all_songs),null));
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_MUSIC_DATA);
+ filter.addAction(ACTION_MUSIC_UPDATE);
+
+ LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
+
+ // Load music data without timeout
+ startLoading(0);
+ GBApplication.deviceService(mGBDevice).onMusicListReq();
+ }
+
+ @Override
+ public void onStop() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+ super.onStop();
+ }
+
+ ActivityResultLauncher openAudioActivityResultLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ new ActivityResultCallback() {
+ @Override
+ public void onActivityResult(ActivityResult result) {
+ if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
+ Intent startIntent = new Intent(MusicManagerActivity.this, FwAppInstallerActivity.class);
+ startIntent.setAction(Intent.ACTION_VIEW);
+ startIntent.setDataAndType(result.getData().getData(), null);
+ startActivity(startIntent);
+ }
+ }
+ });
+
+
+ public boolean openPopupMenu(View view, GBDeviceMusic music) {
+ PopupMenu popupMenu = new PopupMenu(this, view);
+ popupMenu.getMenuInflater().inflate(R.menu.musicmanager_context, popupMenu.getMenu());
+ Menu menu = popupMenu.getMenu();
+
+ if (playlists.size() <= 1) {
+ menu.removeItem(R.id.musicmanager_add_to_playlist);
+ }
+
+ GBDeviceMusicPlaylist current = (GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem();
+ musicList.clear();
+ if (current.getId() == 0) {
+ menu.removeItem(R.id.musicmanager_delete_from_playlist);
+ } else {
+ menu.removeItem(R.id.musicmanager_delete);
+ }
+
+ popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ return onPopupItemSelected(item, music);
+ }
+ }
+ );
+
+ popupMenu.show();
+ return true;
+ }
+
+ private boolean onPopupItemSelected(final MenuItem item, final GBDeviceMusic music) {
+ final int itemId = item.getItemId();
+ if (itemId == R.id.musicmanager_delete || itemId == R.id.musicmanager_delete_from_playlist) {
+ deleteMusicConfirm(music);
+ return true;
+ } else if (itemId == R.id.musicmanager_add_to_playlist) {
+ addMusicSongToPlaylist(music);
+ return true;
+ }
+ return false;
+ }
+
+ private void deleteMusicConfirm(final GBDeviceMusic music) {
+ new MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.Delete)
+ .setMessage(this.getString(R.string.music_delete_confirm_description, music.getTitle()))
+ .setIcon(R.drawable.ic_warning)
+ .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+ deleteMusicFromDevice((GBDeviceMusicPlaylist) playlistsSpinner.getSelectedItem(), music);
+ })
+ .setNegativeButton(android.R.string.no, null)
+ .show();
+ }
+
+ private void addPlaylistToDevice(final String playlistName) {
+ startLoading();
+ GBApplication.deviceService(mGBDevice).onMusicOperation(0, -1, playlistName, null);
+ }
+
+ private void deletePlaylistFromDevice(final GBDeviceMusicPlaylist playlist) {
+ startLoading();
+ GBApplication.deviceService(mGBDevice).onMusicOperation(1, playlist.getId(), null, null);
+ }
+
+ private void renamePlaylistOnDevice(final GBDeviceMusicPlaylist playlist, String newPlaylistName) {
+ startLoading();
+ GBApplication.deviceService(mGBDevice).onMusicOperation(2, playlist.getId(), newPlaylistName, null);
+ }
+
+ private void addMusicToDevicePlaylist(GBDeviceMusicPlaylist playlist, final GBDeviceMusic music) {
+ startLoading();
+ ArrayList list = new ArrayList<>();
+ list.add(music.getId());
+ GBApplication.deviceService(mGBDevice).onMusicOperation(3, playlist.getId(), null, list);
+ }
+
+ private void deleteMusicFromDevice(GBDeviceMusicPlaylist playlist, final GBDeviceMusic music) {
+ startLoading();
+ ArrayList list = new ArrayList<>();
+ list.add(music.getId());
+ GBApplication.deviceService(mGBDevice).onMusicOperation(4, playlist.getId(), null, list);
+ }
+
+ private void addMusicPlaylist() {
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+
+ FrameLayout container = new FrameLayout(this);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+ params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+ input.setLayoutParams(params);
+ container.addView(input);
+
+ new MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.music_new_playlist)
+ .setView(container)
+ .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+ String playlistName = input.getText().toString();
+ addPlaylistToDevice(playlistName);
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private void renameMusicPlaylist(GBDeviceMusicPlaylist playlist) {
+ if(playlist.getId() == 0)
+ return;
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setText(playlist.getName());
+
+ FrameLayout container = new FrameLayout(this);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+ params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+ input.setLayoutParams(params);
+ container.addView(input);
+
+ new MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.music_rename_playlist)
+ .setView(container)
+ .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+ String playlistName = input.getText().toString();
+ renamePlaylistOnDevice(playlist, playlistName);
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private void deleteMusicPlaylist(GBDeviceMusicPlaylist playlist) {
+ if(playlist.getId() == 0)
+ return;
+ new MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.Delete)
+ .setMessage(this.getString(R.string.music_delete_confirm_description, playlist.getName()))
+ .setIcon(R.drawable.ic_warning)
+ .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
+ deletePlaylistFromDevice(playlist);
+ })
+ .setNegativeButton(android.R.string.no, null)
+ .show();
+ }
+
+ private void addMusicSongToPlaylist(final GBDeviceMusic music) {
+ final Spinner dPlaylists = new Spinner(this);
+
+ List dialogPlaylists = new ArrayList<>();
+ for (GBDeviceMusicPlaylist playlist : playlists) {
+ if (playlist.getId() != 0) {
+ dialogPlaylists.add(playlist);
+ }
+ }
+
+ ArrayAdapter dialogPlaylistAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, dialogPlaylists);
+ dialogPlaylistAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ dPlaylists.setAdapter(dialogPlaylistAdapter);
+
+ FrameLayout container = new FrameLayout(this);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+ params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin);
+ dPlaylists.setLayoutParams(params);
+ container.addView(dPlaylists);
+
+ new MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.music_add_to_playlist)
+ .setView(container)
+ .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
+ GBDeviceMusicPlaylist playlist = (GBDeviceMusicPlaylist) dPlaylists.getSelectedItem();
+ addMusicToDevicePlaylist(playlist, music);
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private void startSyncFromDevice(Intent intent) {
+ String info = intent.getStringExtra("deviceInfo");
+ if (!TextUtils.isEmpty(info)) {
+ musicDeviceInfo.setText(info);
+ } else {
+ musicDeviceInfo.setVisibility(View.GONE);
+ }
+
+ maxMusicCount = intent.getIntExtra("maxMusicCount", 0);
+ maxPlaylistCount = intent.getIntExtra("maxPlaylistCount", 0);
+
+ // Hide playlist if device does not support it.
+ playlistSpinnerLayout.setVisibility(maxPlaylistCount>0?View.VISIBLE:View.GONE);
+
+ allMusic.clear();
+ musicList.clear();
+ initPlaylists();
+ }
+
+ private void musicListFromDevice(Intent intent) {
+ ArrayList list = (ArrayList) intent.getSerializableExtra("musicList");
+ if (list != null && !list.isEmpty()) {
+ allMusic.addAll(list);
+ }
+
+ ArrayList devicePlaylist = (ArrayList) intent.getSerializableExtra("musicPlaylist");
+ if (devicePlaylist != null && !devicePlaylist.isEmpty()) {
+ playlists.addAll(devicePlaylist);
+ playlistAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private void musicOperationResponse(Intent intent) {
+ int operation = intent.getIntExtra("operation", -1);
+ if (operation == 0) {
+ int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+ String playlistName = intent.getStringExtra("playlistName");
+
+ if (playlistIndex != -1 && !TextUtils.isEmpty(playlistName)) {
+ playlists.add(new GBDeviceMusicPlaylist(playlistIndex, playlistName, new ArrayList<>()));
+ playlistAdapter.notifyDataSetChanged();
+ }
+ } else if (operation == 1) {
+ int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+ if (playlistIndex != -1) {
+ for (Iterator iterator = playlists.iterator(); iterator.hasNext(); ) {
+ GBDeviceMusicPlaylist playlist = iterator.next();
+ if (playlist.getId() == playlistIndex) {
+ iterator.remove();
+ }
+ }
+ playlistAdapter.notifyDataSetChanged();
+ }
+ } else if (operation == 2) {
+ int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+ String playlistName = intent.getStringExtra("playlistName");
+ if (playlistIndex != -1 && !TextUtils.isEmpty(playlistName)) {
+ for (GBDeviceMusicPlaylist playlist : playlists) {
+ if (playlist.getId() == playlistIndex) {
+ playlist.setName(playlistName);
+ break;
+ }
+ }
+ playlistAdapter.notifyDataSetChanged();
+ }
+ } else if (operation == 3) {
+ int playlistIndex = intent.getIntExtra("playlistIndex", -1);
+ ArrayList ids = (ArrayList) intent.getSerializableExtra("musicIds");
+ if (playlistIndex != -1 && ids != null && !ids.isEmpty()) {
+ for (GBDeviceMusicPlaylist playlist : playlists) {
+ if (playlist.getId() == playlistIndex) {
+ ArrayList currentList = playlist.getMusicIds();
+ for (Integer id : ids) {
+ if (!currentList.contains(id))
+ currentList.add(id);
+ }
+ playlist.setMusicIds(currentList);
+ break;
+ }
+ }
+ playlistAdapter.notifyDataSetChanged();
+ updateCurrentMusicList();
+ }
+
+ } else if (operation == 4) {
+ ArrayList ids = (ArrayList) intent.getSerializableExtra("musicIds");
+ int playlistIndex = intent.getIntExtra("playlistIndex", 0);
+ if (ids != null && !ids.isEmpty()) {
+ if (playlistIndex == 0) {
+ for (Iterator iterator = musicList.iterator(); iterator.hasNext(); ) {
+ GBDeviceMusic music = iterator.next();
+ if (ids.contains(music.getId())) {
+ iterator.remove();
+ }
+ }
+ for (Iterator iterator = allMusic.iterator(); iterator.hasNext(); ) {
+ GBDeviceMusic music = iterator.next();
+ if (ids.contains(music.getId())) {
+ iterator.remove();
+ }
+ }
+ } else {
+ for (GBDeviceMusicPlaylist playlist : playlists) {
+ if (playlist.getId() == playlistIndex) {
+ ArrayList currentList = playlist.getMusicIds();
+ for (Integer id : ids) {
+ currentList.remove(id);
+ }
+ playlist.setMusicIds(currentList);
+ break;
+ }
+ }
+ }
+ playlistAdapter.notifyDataSetChanged();
+ updateCurrentMusicList();
+ }
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ switch (action) {
+ case ACTION_MUSIC_DATA: {
+ if (!intent.hasExtra("type"))
+ break;
+ int type = intent.getIntExtra("type", -1);
+
+ LOG.info("UPDATE type: {}", type);
+ if (type == 1) {
+ startSyncFromDevice(intent);
+ } else if (type == 2) {
+ LOG.info("got music list or playlist from device");
+ musicListFromDevice(intent);
+ } else if (type == 10) {
+ updateCurrentMusicList();
+ stopLoading();
+ }
+ break;
+ }
+ case ACTION_MUSIC_UPDATE: {
+ boolean success = intent.getBooleanExtra("success", false);
+ if (intent.hasExtra("operation") && success) {
+ musicOperationResponse(intent);
+ }
+ stopLoading();
+ break;
+ }
+ }
+ }
+ };
+
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java
new file mode 100644
index 000000000..a661906b6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java
@@ -0,0 +1,85 @@
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+
+public class MusicListAdapter extends RecyclerView.Adapter {
+
+ public interface onItemAction {
+ void onItemClick(View view, GBDeviceMusic music);
+ boolean onItemLongClick(View view, GBDeviceMusic music);
+ }
+
+ private final List musicList;
+ private final onItemAction callback;
+
+ public MusicListAdapter(List list, onItemAction callback) {
+ this.musicList = list;
+ this.callback = callback;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return musicList.get(position).getId();
+ }
+
+ @Override
+ public int getItemCount() {
+ return musicList.size();
+ }
+
+ @Override
+ public MusicListAdapter.MusicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_musicmanager_song, parent, false);
+ return new MusicViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final MusicListAdapter.MusicViewHolder holder, int position) {
+ final GBDeviceMusic music = musicList.get(position);
+
+ holder.musicTitle.setText(music.getTitle());
+ holder.musicArtist.setText(music.getArtist());
+
+ if(callback != null) {
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ callback.onItemClick(view, music);
+ }
+ });
+
+ holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ return callback.onItemLongClick(view, music);
+ }
+ });
+ }
+
+ }
+
+ public static class MusicViewHolder extends RecyclerView.ViewHolder {
+ final TextView musicArtist;
+ final TextView musicTitle;
+
+ MusicViewHolder(View itemView) {
+ super(itemView);
+ musicArtist = itemView.findViewById(R.id.item_details);
+ musicTitle = itemView.findViewById(R.id.item_name);
+ }
+
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java
new file mode 100644
index 000000000..9f7d268dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java
@@ -0,0 +1,15 @@
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
+
+public class GBDeviceMusicData extends GBDeviceEvent {
+ public int type = 0; // 1 - sync start, 2 - music list, 10 - end sync
+ public List list = null;
+ public List playlists = null;
+ public String deviceInfo = null;
+ public int maxMusicCount = 0;
+ public int maxPlaylistCount = 0;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java
new file mode 100644
index 000000000..000b92aee
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java
@@ -0,0 +1,11 @@
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+import java.util.ArrayList;
+
+public class GBDeviceMusicUpdate extends GBDeviceEvent {
+ public boolean success = false;
+ public int operation = -1;
+ public int playlistIndex = -1;
+ public String playlistName;
+ public ArrayList musicIds = null;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
index e11671d44..a9be0fbbc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
@@ -95,7 +95,7 @@ public interface EventHandler {
void onAppConfiguration(UUID appUuid, String config, Integer id);
- void onAppReorder(UUID uuids[]);
+ void onAppReorder(UUID[] uuids);
void onFetchRecordedData(int dataTypes);
@@ -154,4 +154,9 @@ public interface EventHandler {
void onSleepAsAndroidAction(String action, Bundle extras);
void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename);
+
+
+ void onMusicListReq();
+
+ void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java
index a78c16b0e..c11ee25f4 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java
@@ -305,6 +305,11 @@ public class HuaweiCoordinator {
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_contacts);
}
+ //Music
+ if (supportsMusicUploading() && getMusicInfoParams() != null && device.isConnected()) {
+ deviceSpecificSettings.addRootScreen(R.xml.devicesettings_musicmanagement);
+ }
+
// Time
if (supportsDateFormat()) {
final List dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME);
@@ -602,8 +607,6 @@ public class HuaweiCoordinator {
return false;
}
-
-
public boolean supportsCalendar() {
if (supportsExpandCapability())
return supportsExpandCapability(171) || supportsExpandCapability(184);
@@ -628,6 +631,12 @@ public class HuaweiCoordinator {
return false;
}
+ public boolean supportsMoreMusic() {
+ if (supportsExpandCapability())
+ return supportsExpandCapability(122);
+ return false;
+ }
+
public boolean supportsPromptPushMessage () {
// do not ask for capabilities under specific condition
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java
index a3cc2e3ea..31dfb78e7 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiMusicUtils.java
@@ -7,16 +7,16 @@ import java.util.List;
public class HuaweiMusicUtils {
public static class PageStruct {
- public short startIndex = 0;
- public short endIndex = 0;
+ public short startFrame = 0;
+ public short endFrame = 0;
public short count = 0;
public byte[] hashCode = null;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("PageStruct{");
- sb.append("startIndex=").append(startIndex);
- sb.append(", endIndex=").append(endIndex);
+ sb.append("startFrame=").append(startFrame);
+ sb.append(", endFrame=").append(endFrame);
sb.append(", count=").append(count);
sb.append(", hashCode=");
if (hashCode == null) sb.append("null");
@@ -68,7 +68,6 @@ public class HuaweiMusicUtils {
public int currentMusicCount = 0; // TODO: not sure
public int unknown = 0; // TODO: not sure
public List formatsRestrictions = null;
- public List pageStruct = null;
@Override
public String toString() {
@@ -80,7 +79,6 @@ public class HuaweiMusicUtils {
sb.append(", currentMusicCount=").append(currentMusicCount);
sb.append(", unknown=").append(unknown);
sb.append(", formatsRestrictions=").append(formatsRestrictions);
- sb.append(", pageStruct=").append(pageStruct);
sb.append('}');
return sb.toString();
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java
index 175a840ae..d0b124e57 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java
@@ -598,6 +598,14 @@ public class HuaweiPacket {
return new MusicControl.Control.Response(paramsProvider).fromPacket(this);
case MusicControl.MusicInfoParams.id:
return new MusicControl.MusicInfoParams.Response(paramsProvider).fromPacket(this);
+ case MusicControl.MusicList.id:
+ return new MusicControl.MusicList.Response(paramsProvider).fromPacket(this);
+ case MusicControl.MusicPlaylists.id:
+ return new MusicControl.MusicPlaylists.Response(paramsProvider).fromPacket(this);
+ case MusicControl.MusicPlaylistMusics.id:
+ return new MusicControl.MusicPlaylistMusics.Response(paramsProvider).fromPacket(this);
+ case MusicControl.MusicOperation.id:
+ return new MusicControl.MusicOperation.Response(paramsProvider).fromPacket(this);
case MusicControl.UploadMusicFileInfo.id:
return new MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest(paramsProvider).fromPacket(this);
case MusicControl.ExtendedMusicInfoParams.id:
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java
index 3560e58fd..d2d054b8c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/MusicControl.java
@@ -18,12 +18,14 @@ package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils.parseFormatBits;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiTLV;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
public class MusicControl {
public static final byte id = 0x25;
@@ -251,16 +253,16 @@ public class MusicControl {
public static class Response extends HuaweiPacket {
public HuaweiMusicUtils.MusicCapabilities params = new HuaweiMusicUtils.MusicCapabilities();
+ public int frameCount = 0;
+ public List pageStruct = null;
+
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws ParseException {
-
- //TODO: unknown TLV
-// if (this.tlv.contains(0x01))
-// LOG.info("Unknown: " + this.tlv.getShort(0x01));
+ this.frameCount = this.tlv.getAsInteger(0x01);
if (this.tlv.contains(0x02))
params.availableSpace = this.tlv.getAsInteger(0x02);
@@ -274,25 +276,214 @@ public class MusicControl {
params.currentMusicCount = this.tlv.getAsInteger(0x05);
if (this.tlv.contains(0x86)) {
- params.pageStruct = new ArrayList<>();
+ this.pageStruct = new ArrayList<>();
List subTlvs = this.tlv.getObject(0x86).getObjects(0x87);
for (HuaweiTLV subTlv : subTlvs) {
HuaweiMusicUtils.PageStruct pageStruct = new HuaweiMusicUtils.PageStruct();
if (subTlv.contains(0x08))
- pageStruct.startIndex = subTlv.getShort(0x08);
+ pageStruct.startFrame = subTlv.getShort(0x08);
if (subTlv.contains(0x09))
- pageStruct.endIndex = subTlv.getShort(0x09);
+ pageStruct.endFrame = subTlv.getShort(0x09);
if (subTlv.contains(0x0a))
pageStruct.count = subTlv.getShort(0x0a);
if (subTlv.contains(0x0b))
pageStruct.hashCode = subTlv.getBytes(0x0b);
- params.pageStruct.add(pageStruct);
+ this.pageStruct.add(pageStruct);
}
}
}
}
}
+ public static class MusicList {
+ public static final byte id = 0x05;
+
+ public static class Request extends HuaweiPacket {
+ public Request(ParamsProvider paramsProvider, short startFrame, short endIndex) {
+ super(paramsProvider);
+ this.serviceId = MusicControl.id;
+ this.commandId = id;
+ this.tlv = new HuaweiTLV()
+ .put(0x01, startFrame)
+ .put(0x04, endIndex);
+ }
+ }
+
+ public static class Response extends HuaweiPacket {
+
+ public short startFrame = 0;
+ public short endIndex = 0;
+
+ public List musicList;
+ public Response (ParamsProvider paramsProvider) {
+ super(paramsProvider);
+ }
+
+ @Override
+ public void parseTlv() throws HuaweiPacket.ParseException {
+ if(tlv.contains(0x1))
+ startFrame = tlv.getShort(0x1);
+ if(tlv.contains(0x4))
+ endIndex = tlv.getShort(0x4);
+ musicList = new ArrayList<>();
+ if(this.tlv.contains(0x82)) {
+ for (HuaweiTLV subTlv : this.tlv.getObject(0x82).getObjects(0x83)) {
+ int index = subTlv.getAsInteger(0x4);
+ String title = subTlv.getString(0x5);
+ String artist = subTlv.getString(0x6);
+ String fileName = subTlv.getString(0x7);
+ musicList.add(new GBDeviceMusic(index, title, artist, fileName));
+ }
+ }
+ }
+ }
+ }
+
+ public static class MusicPlaylists {
+ public static final byte id = 0x06;
+
+ public static class Request extends HuaweiPacket {
+ public Request(ParamsProvider paramsProvider) {
+ super(paramsProvider);
+ this.serviceId = MusicControl.id;
+ this.commandId = id;
+ this.tlv = new HuaweiTLV()
+ .put(0x01);
+ }
+ }
+
+ public static class Response extends HuaweiPacket {
+
+ public static class PlaylistData {
+ public int id;
+ public String name;
+ public int frameCount;
+ }
+
+ public List playlists = new ArrayList<>();
+
+ public Response (ParamsProvider paramsProvider) {
+ super(paramsProvider);
+ }
+
+ @Override
+ public void parseTlv() throws HuaweiPacket.ParseException {
+ if(this.tlv.contains(0x81)) {
+ for (HuaweiTLV subTlv : this.tlv.getObject(0x81).getObjects(0x82)) {
+ PlaylistData data = new PlaylistData();
+ data.id = subTlv.getAsInteger(0x3);
+ data.name = subTlv.getString(0x4);
+ data.frameCount = subTlv.getAsInteger(0x5);
+ playlists.add(data);
+ }
+ }
+ }
+ }
+ }
+
+ public static class MusicPlaylistMusics {
+ public static final byte id = 0x07;
+
+ public static class Request extends HuaweiPacket {
+ public Request(ParamsProvider paramsProvider, short playlist, short index) {
+ super(paramsProvider);
+ this.serviceId = MusicControl.id;
+ this.commandId = id;
+ this.tlv = new HuaweiTLV()
+ .put(0x01, playlist)
+ .put(0x02, index);
+ }
+ }
+
+ public static class Response extends HuaweiPacket {
+
+ public int id = -1;
+ public int index = -1;
+ public ArrayList musicIds = null;
+
+ public Response (ParamsProvider paramsProvider) {
+ super(paramsProvider);
+ }
+
+ @Override
+ public void parseTlv() throws HuaweiPacket.ParseException {
+ if(this.tlv.contains(0x1))
+ id = tlv.getAsInteger(0x1);
+ if(this.tlv.contains(0x2))
+ index = tlv.getAsInteger(0x2);
+
+ if(this.tlv.contains(0x3)) {
+ musicIds = new ArrayList<>();
+ ByteBuffer dt = ByteBuffer.wrap(this.tlv.getBytes(0x3));
+ while (dt.hasRemaining())
+ musicIds.add((int) dt.getShort());
+ }
+ }
+ }
+ }
+
+ public static class MusicOperation {
+ public static final byte id = 0x08;
+
+ public static class Request extends HuaweiPacket {
+ public Request(ParamsProvider paramsProvider, int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ super(paramsProvider);
+ this.serviceId = MusicControl.id;
+ this.commandId = id;
+ this.tlv = new HuaweiTLV()
+ .put(0x01, (byte)operation);
+
+ if(operation == 1 || operation == 2 || operation == 3 || operation == 4) {
+ this.tlv.put(0x02, (short)playlistIndex);
+ }
+ if (operation == 0 || operation == 2) {
+ this.tlv.put(0x03, playlistName);
+ }
+
+ if (operation == 3 || operation == 4) {
+ ByteBuffer ids = ByteBuffer.allocate(musicIds.size() * 2);
+ for (Integer id : musicIds) {
+ ids.putShort(id.shortValue());
+ }
+ this.tlv.put(0x04, ids.array());
+ }
+ }
+ }
+
+ public static class Response extends HuaweiPacket {
+
+ public int operation = -1;
+ public int playlistIndex = -1;
+ public String playlistName;
+ public ArrayList musicIds = null;
+ public int resultCode = -1;
+
+ public Response (ParamsProvider paramsProvider) {
+ super(paramsProvider);
+ }
+
+ @Override
+ public void parseTlv() throws HuaweiPacket.ParseException {
+ if(this.tlv.contains(0x7f))
+ resultCode = tlv.getInteger(0x7f);
+ if(this.tlv.contains(0x1))
+ operation = tlv.getByte(0x1);
+ if(this.tlv.contains(0x2))
+ playlistIndex = tlv.getAsInteger(0x2);
+ if(this.tlv.contains(0x3))
+ playlistName = tlv.getString(0x3);
+
+ if(this.tlv.contains(0x4)) {
+ musicIds = new ArrayList<>();
+ ByteBuffer dt = ByteBuffer.wrap(this.tlv.getBytes(0x4));
+ while (dt.hasRemaining())
+ musicIds.add((int) dt.getShort());
+ }
+ }
+ }
+ }
+
+
public static class ExtendedMusicInfoParams {
public static final byte id = 0x0d;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java
new file mode 100644
index 000000000..b15ce882b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import java.io.Serializable;
+
+public class GBDeviceMusic implements Serializable {
+ private final int id;
+ private final String title;
+ private final String artist;
+ private final String fileName;
+
+ public GBDeviceMusic(int id, String title, String artist, String fileName) {
+ this.id = id;
+ this.title = title;
+ this.artist = artist;
+ this.fileName = fileName;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getArtist() {
+ return artist;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java
new file mode 100644
index 000000000..a6e2f3ddd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java
@@ -0,0 +1,41 @@
+package nodomain.freeyourgadget.gadgetbridge.impl;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+public class GBDeviceMusicPlaylist implements Serializable {
+ private final int id;
+ private String name;
+ private ArrayList musicIds;
+
+ public GBDeviceMusicPlaylist(int id, String name, ArrayList musicIds) {
+ this.id = id;
+ this.name = name;
+ this.musicIds = musicIds;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public ArrayList getMusicIds() {
+ return musicIds;
+ }
+
+ public void setMusicIds(ArrayList musicIds) {
+ this.musicIds = musicIds;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
index bf5b5280f..0cf92e293 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
@@ -568,4 +568,20 @@ public class GBDeviceService implements DeviceService {
intent.putExtra(EXTRA_CAMERA_FILENAME, filename);
invokeService(intent);
}
+
+ @Override
+ public void onMusicListReq() {
+ Intent intent = createIntent().setAction(ACTION_REQUEST_MUSIC_LIST);
+ invokeService(intent);
+ }
+
+ @Override
+ public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ Intent intent = createIntent().setAction(ACTION_REQUEST_MUSIC_OPERATION);
+ intent.putExtra("operation", operation);
+ intent.putExtra("playlistIndex", playlistIndex);
+ intent.putExtra("playlistName", playlistName);
+ intent.putExtra("musicIds", musicIds);
+ invokeService(intent);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
index 79c40d8f5..20157a9b2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
@@ -80,6 +80,8 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_LED_COLOR = PREFIX + ".action.set_led_color";
String ACTION_POWER_OFF = PREFIX + ".action.power_off";
String ACTION_CAMERA_STATUS_CHANGE = PREFIX + ".action.camera_status_change";
+ String ACTION_REQUEST_MUSIC_LIST = PREFIX + ".action.request_music_list";
+ String ACTION_REQUEST_MUSIC_OPERATION = PREFIX + ".action.request_music_operation";
String ACTION_SLEEP_AS_ANDROID = ".action.sleep_as_android";
String EXTRA_SLEEP_AS_ANDROID_ACTION = "sleepasandroid_action";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
index ad7839803..a079eb84f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
@@ -63,6 +63,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.FindPhoneActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.activities.musicmanager.MusicManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
@@ -86,12 +87,16 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePref
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventWearState;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicData;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicUpdate;
import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevel;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
@@ -236,7 +241,12 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
handleGBDeviceEvent((GBDeviceEventWearState) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventSleepStateDetection) {
handleGBDeviceEvent((GBDeviceEventSleepStateDetection) deviceEvent);
+ } else if (deviceEvent instanceof GBDeviceMusicData) {
+ handleGBDeviceEvent((GBDeviceMusicData) deviceEvent);
+ } else if (deviceEvent instanceof GBDeviceMusicUpdate) {
+ handleGBDeviceEvent((GBDeviceMusicUpdate) deviceEvent);
}
+
}
private void handleGBDeviceEvent(GBDeviceEventSilentMode deviceEvent) {
@@ -751,6 +761,53 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
handleDeviceAction(actionOnUnwear, broadcastMessage);
}
+ private void handleGBDeviceEvent(GBDeviceMusicData deviceEvent) {
+ Context context = getContext();
+ LOG.info("Got event for ACTION_MUSIC_DATA");
+
+ Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_DATA);
+
+ intent.putExtra("type", deviceEvent.type);
+
+ if(deviceEvent.list != null) {
+ ArrayList list = new ArrayList<>(deviceEvent.list);
+ intent.putExtra("musicList", list);
+ }
+
+ if(deviceEvent.playlists != null) {
+ ArrayList list = new ArrayList<>(deviceEvent.playlists);
+ intent.putExtra("musicPlaylist", list);
+ }
+
+ if(!TextUtils.isEmpty(deviceEvent.deviceInfo)) {
+ intent.putExtra("deviceInfo", deviceEvent.deviceInfo);
+ }
+
+ if(deviceEvent.maxMusicCount > 0) {
+ intent.putExtra("maxMusicCount", deviceEvent.maxMusicCount);
+ }
+ if(deviceEvent.maxPlaylistCount > 0) {
+ intent.putExtra("maxPlaylistCount", deviceEvent.maxPlaylistCount);
+ }
+
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ }
+
+ private void handleGBDeviceEvent(GBDeviceMusicUpdate deviceEvent) {
+ Context context = getContext();
+ LOG.info("Got event for ACTION_MUSIC_UPDATE");
+
+ Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_UPDATE);
+
+ intent.putExtra("success", deviceEvent.success);
+ intent.putExtra("operation", deviceEvent.operation);
+ intent.putExtra("playlistIndex", deviceEvent.playlistIndex);
+ intent.putExtra("playlistName", deviceEvent.playlistName);
+ intent.putExtra("musicIds", deviceEvent.musicIds);
+
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ }
+
private StoreDataTask createStoreTask(String task, Context context, GBDeviceEventBatteryInfo deviceEvent) {
return new StoreDataTask(task, context, deviceEvent);
}
@@ -1233,4 +1290,10 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
@Override
public void onCameraStatusChange(GBDeviceEventCameraRemote.Event event, String filename) {}
+
+ @Override
+ public void onMusicListReq() {}
+
+ @Override
+ public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) {}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
index 944606dc0..44977652d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
@@ -87,6 +87,7 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.TinyWeatherForecastGe
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
import nodomain.freeyourgadget.gadgetbridge.externalevents.sleepasandroid.SleepAsAndroidReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@@ -1137,6 +1138,16 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
deviceSupport.onCameraStatusChange(event, filename);
break;
+ case ACTION_REQUEST_MUSIC_LIST:
+ deviceSupport.onMusicListReq();
+ break;
+ case ACTION_REQUEST_MUSIC_OPERATION:
+ int operation = intentCopy.getIntExtra("operation", -1);
+ int playlistIndex = intentCopy.getIntExtra("playlistIndex", -1);
+ String playlistName = intentCopy.getStringExtra("playlistName");
+ ArrayList musics = (ArrayList) intentCopy.getSerializableExtra("musicIds");
+ deviceSupport.onMusicOperation(operation, playlistIndex, playlistName, musics);
+ break;
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
index ad01d2cb8..407bcd6fc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
@@ -34,6 +34,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
@@ -524,4 +525,20 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onCameraStatusChange(event, filename);
}
+
+ @Override
+ public void onMusicListReq() {
+ if (checkBusy("music list request")) {
+ return;
+ }
+ delegate.onMusicListReq();
+ }
+
+ @Override
+ public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ if (checkBusy("music operation")) {
+ return;
+ }
+ delegate.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java
index 46988e060..67b216d22 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiBRSupport.java
@@ -30,6 +30,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
@@ -198,4 +199,14 @@ public class HuaweiBRSupport extends AbstractBTBRDeviceSupport {
public void onTestNewFunction() {
supportProvider.onTestNewFunction();
}
+
+ @Override
+ public void onMusicListReq() {
+ supportProvider.onMusicListReq();
+ }
+
+ @Override
+ public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java
index c25049cf7..15868167a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiLESupport.java
@@ -33,6 +33,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
@@ -214,4 +215,14 @@ public class HuaweiLESupport extends AbstractBTLEDeviceSupport {
public boolean getSendWriteRequestResponse() {
return false;
}
+
+ @Override
+ public void onMusicListReq() {
+ supportProvider.onMusicListReq();
+ }
+
+ @Override
+ public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java
index 69e670145..c948dfb68 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiMusicManager.java
@@ -1,11 +1,28 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
+import android.widget.Toast;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicData;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicUpdate;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicInfoParams;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicList;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicPlaylist;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicPlaylistMusics;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMusicOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendUploadMusicFileInfoResponse;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class HuaweiMusicManager {
static Logger LOG = LoggerFactory.getLogger(HuaweiMusicManager.class);
@@ -125,4 +142,236 @@ public class HuaweiMusicManager {
LOG.error("Could not send sendUploadMusicFileInfoResponse", e);
}
}
+
+ private boolean syncMusicData = false;
+ private int frameCount = 0;
+ private int endFrame = 65535;
+ private int currentFrame = 0;
+
+
+ public void startSyncMusicData() {
+ syncMusicData = true;
+ try {
+ GetMusicInfoParams getMusicInfoParams = new GetMusicInfoParams(this.support);
+ getMusicInfoParams.doPerform();
+ } catch (IOException e) {
+ LOG.error("Get music info: {}", e.getMessage());
+ syncMusicData = false;
+ }
+ }
+
+
+ private void syncMusicList() {
+ if (!syncMusicData) {
+ this.currentFrame = 0;
+ return;
+ }
+ int count = this.frameCount;
+ if (support.getHuaweiCoordinator().supportsMoreMusic()) {
+ count = Math.min(this.frameCount, 250);
+ }
+ if (this.currentFrame < count) {
+ try {
+ GetMusicList getMusicList = new GetMusicList(this.support, this.currentFrame, this.endFrame);
+ getMusicList.doPerform();
+ } catch (IOException e) {
+ LOG.error("Get music list: {}", e.getMessage());
+ endMusicListSync();
+ }
+ } else {
+ endMusicListSync();
+ }
+ }
+
+ private void endMusicListSync() {
+ this.currentFrame = 0;
+ try {
+ GetMusicPlaylist getMusicPlaylist = new GetMusicPlaylist(this.support);
+ getMusicPlaylist.doPerform();
+ } catch (IOException e) {
+ LOG.error("Get music playlist: {}", e.getMessage());
+ endMusicPlaylistSync();
+ }
+ }
+
+ private void endMusicPlaylistSync() {
+ this.currentPlaylistIndex = 0;
+ this.currentPlaylistFrame = 0;
+ tempPlaylistMusic.clear();
+
+ musicPlaylistMusicSync();
+ }
+
+ private final List devicePlaylists = new ArrayList<>();
+
+ private int currentPlaylistIndex = 0;
+ private int currentPlaylistFrame = 0;
+ private final List> tempPlaylistMusic = new ArrayList<>();
+
+ private void musicPlaylistMusicSync() {
+ if (this.currentPlaylistIndex < devicePlaylists.size()) {
+ MusicControl.MusicPlaylists.Response.PlaylistData playlist = devicePlaylists.get(this.currentPlaylistIndex);
+ syncPlaylistMusicsOne(playlist.id, playlist.frameCount);
+ } else {
+ musicPlaylistMusicDone();
+ }
+ }
+
+ private void syncPlaylistMusicsOne(int id, int frameCount) {
+ if (this.currentPlaylistFrame < frameCount) {
+ try {
+ GetMusicPlaylistMusics getMusicPlaylistMusics = new GetMusicPlaylistMusics(this.support, id, this.currentPlaylistFrame);
+ getMusicPlaylistMusics.doPerform();
+ } catch (IOException e) {
+ LOG.error("Get music playlist musics: {}", e.getMessage());
+ musicPlaylistMusicDone();
+ }
+ } else {
+ syncPlayListMusicIndexDone(id, frameCount);
+ }
+ }
+
+ public void syncNextPlaylistMusicIndex() {
+ this.currentPlaylistFrame++;
+ MusicControl.MusicPlaylists.Response.PlaylistData playlist = devicePlaylists.get(this.currentPlaylistIndex);
+ syncPlaylistMusicsOne(playlist.id, playlist.frameCount);
+ }
+
+ private void syncPlayListMusicIndexDone(int id, int frameCount) {
+ MusicControl.MusicPlaylists.Response.PlaylistData playlist = devicePlaylists.get(this.currentPlaylistIndex);
+
+ ArrayList musics = new ArrayList<>();
+ if (this.tempPlaylistMusic.size() == frameCount) {
+ for (int i = 0; i < frameCount; i++) {
+ musics.addAll(this.tempPlaylistMusic.get(i));
+ }
+ }
+
+ GBDeviceMusicPlaylist pl = new GBDeviceMusicPlaylist(playlist.id, playlist.name, musics);
+ List list = new ArrayList<>();
+ list.add(pl);
+ sendMusicPlaylist(list);
+ this.currentPlaylistIndex++;
+ this.currentPlaylistFrame = 0;
+ this.tempPlaylistMusic.clear();
+ musicPlaylistMusicSync();
+ }
+
+ private void musicPlaylistMusicDone() {
+ this.currentPlaylistIndex = 0;
+ this.currentPlaylistFrame = 0;
+ this.tempPlaylistMusic.clear();
+
+ this.syncMusicData = false;
+ sendMusicSyncDone();
+ }
+
+ public void onMusicMusicInfoParams(HuaweiMusicUtils.MusicCapabilities capabilities, int frameCount, List pageStruct) {
+ //TODO: research and use pageStruct. It may/should be used to retrieve music data from devices by pages.
+ // without it list can be incomplete, but I can't confirm this.
+ LOG.info("FrameCount: {}, pageStruct: {}", frameCount, pageStruct);
+ support.getHuaweiCoordinator().setMusicInfoParams(capabilities);
+ if(syncMusicData) {
+ this.frameCount = frameCount;
+ this.currentFrame = 0;
+ this.endFrame = 65535;
+ String formats = null;
+ if(capabilities.supportedFormats != null) {
+ formats = String.join(",", capabilities.supportedFormats);
+ }
+ int maxPlaylistCount = 0;
+ if(support.getCoordinator().getHuaweiCoordinator().getExtendedMusicInfoParams() != null) {
+ maxPlaylistCount = support.getCoordinator().getHuaweiCoordinator().getExtendedMusicInfoParams().maxPlaylistCount;
+ }
+ sendMusicSyncStart(support.getContext().getString(R.string.music_huawei_device_info, formats, capabilities.availableSpace), capabilities.maxMusicCount, maxPlaylistCount);
+ syncMusicList();
+ }
+ }
+
+ private void sendMusicSyncStart(final String info, int maxMusicCount, int maxPlaylistCount) {
+ final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+ musicListCmd.type = 1;
+ musicListCmd.deviceInfo = info;
+ musicListCmd.maxMusicCount = maxMusicCount;
+ musicListCmd.maxPlaylistCount = maxPlaylistCount;
+ support.evaluateGBDeviceEvent(musicListCmd);
+ }
+
+
+ private void sendMusicList(List list) {
+ final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+ musicListCmd.type = 2;
+ musicListCmd.list = list;
+ support.evaluateGBDeviceEvent(musicListCmd);
+ }
+
+ private void sendMusicPlaylist(List list) {
+ final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+ musicListCmd.type = 2;
+ musicListCmd.playlists = list;
+ support.evaluateGBDeviceEvent(musicListCmd);
+ }
+
+ private void sendMusicSyncDone() {
+ final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
+ musicListCmd.type = 10;
+ support.evaluateGBDeviceEvent(musicListCmd);
+ }
+
+ public void onMusicListResponse(int startFrame, int endFrame, List list) {
+ sendMusicList(list);
+ if (support.getHuaweiCoordinator().supportsMoreMusic() || !(endFrame == this.endFrame || list.size() == 1)) {
+ if (list.size() == 2) {
+ this.endFrame = list.get(1).getId();
+ }
+ this.currentFrame++;
+ syncMusicList();
+ return;
+ }
+ endMusicListSync();
+ }
+
+ public void onMusicPlaylistResponse(List playlists) {
+ this.devicePlaylists.clear();
+ for(MusicControl.MusicPlaylists.Response.PlaylistData pl: playlists) {
+ if(pl.id != 0) {
+ this.devicePlaylists.add(pl);
+ }
+ }
+ endMusicPlaylistSync();
+ }
+
+ public void onMusicPlaylistMusics(int id, int index, List musicIds) {
+ this.tempPlaylistMusic.add(musicIds);
+ syncNextPlaylistMusicIndex();
+ }
+
+ public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ LOG.info("music operation: {}", operation);
+ try {
+ SendMusicOperation sendMusicOperation = new SendMusicOperation(this.support, operation, playlistIndex, playlistName, musicIds);
+ sendMusicOperation.doPerform();
+ } catch (IOException e) {
+ LOG.error("SendMusicOperation: {}", e.getMessage());
+ }
+ }
+
+ public void onMusicOperationResponse(int resultCode, int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+
+ boolean success = true;
+ if (resultCode != 0x000186A0) {
+ GB.toast(support.getContext(), support.getContext().getString(R.string.music_error), Toast.LENGTH_SHORT, GB.ERROR);
+ success = false;
+ }
+
+ LOG.info("music operation response: {} {}", operation, success);
+ final GBDeviceMusicUpdate updateCmd = new GBDeviceMusicUpdate();
+ updateCmd.success = success;
+ updateCmd.operation = operation;
+ updateCmd.playlistIndex = playlistIndex;
+ updateCmd.playlistName = playlistName;
+ updateCmd.musicIds = musicIds;
+
+ support.evaluateGBDeviceEvent(updateCmd);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
index 735ebe5d6..14874ac95 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java
@@ -50,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicData;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
@@ -2529,4 +2530,12 @@ public class HuaweiSupportProvider {
callback
), true);
}
+
+ public void onMusicListReq() {
+ getHuaweiMusicManager().startSyncMusicData();
+ }
+
+ public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ getHuaweiMusicManager().onMusicOperation(operation, playlistIndex, playlistName, musicIds);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java
index 64292e3d5..2a1a6eb72 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicInfoParams.java
@@ -39,6 +39,6 @@ public class GetMusicInfoParams extends Request {
throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicInfoParams.Response.class);
MusicControl.MusicInfoParams.Response resp = (MusicControl.MusicInfoParams.Response)(receivedPacket);
- supportProvider.getHuaweiCoordinator().setMusicInfoParams(resp.params);
+ supportProvider.getHuaweiMusicManager().onMusicMusicInfoParams(resp.params, resp.frameCount, resp.pageStruct);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java
new file mode 100644
index 000000000..46281b04e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java
@@ -0,0 +1,45 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetMusicList extends Request {
+ private final Logger LOG = LoggerFactory.getLogger(GetMusicList.class);
+
+ private final int startFrame;
+ private final int endFrame;
+
+ public GetMusicList(HuaweiSupportProvider support, int startFrame, int endFrame) {
+ super(support);
+ this.serviceId = MusicControl.id;
+ this.commandId = MusicControl.MusicList.id;
+ this.startFrame = startFrame;
+ this.endFrame = endFrame;
+ }
+
+ @Override
+ protected List createRequest() throws Request.RequestCreationException {
+ try {
+ return new MusicControl.MusicList.Request(paramsProvider, (short) this.startFrame, (short) this.endFrame).serialize();
+ } catch (HuaweiPacket.CryptoException e) {
+ throw new Request.RequestCreationException(e);
+ }
+ }
+
+ @Override
+ protected void processResponse() throws Request.ResponseParseException {
+ LOG.info("MusicControl.MusicList processResponse");
+ if (!(receivedPacket instanceof MusicControl.MusicList.Response))
+ throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicList.Response.class);
+
+ MusicControl.MusicList.Response resp = (MusicControl.MusicList.Response) (receivedPacket);
+ supportProvider.getHuaweiMusicManager().onMusicListResponse(resp.startFrame, resp.endIndex, resp.musicList);
+
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java
new file mode 100644
index 000000000..9099a5d69
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetMusicPlaylist extends Request {
+ private final Logger LOG = LoggerFactory.getLogger(GetMusicPlaylist.class);
+
+ public GetMusicPlaylist(HuaweiSupportProvider support) {
+ super(support);
+ this.serviceId = MusicControl.id;
+ this.commandId = MusicControl.MusicPlaylists.id;
+ }
+
+ @Override
+ protected List createRequest() throws Request.RequestCreationException {
+ try {
+ return new MusicControl.MusicPlaylists.Request(paramsProvider).serialize();
+ } catch (HuaweiPacket.CryptoException e) {
+ throw new Request.RequestCreationException(e);
+ }
+ }
+
+ @Override
+ protected void processResponse() throws Request.ResponseParseException {
+ LOG.info("MusicControl.MusicPlaylists processResponse");
+ if (!(receivedPacket instanceof MusicControl.MusicPlaylists.Response))
+ throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicPlaylists.Response.class);
+
+ MusicControl.MusicPlaylists.Response resp = (MusicControl.MusicPlaylists.Response) (receivedPacket);
+ supportProvider.getHuaweiMusicManager().onMusicPlaylistResponse(resp.playlists);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java
new file mode 100644
index 000000000..c557b9068
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class GetMusicPlaylistMusics extends Request {
+ private final Logger LOG = LoggerFactory.getLogger(GetMusicPlaylistMusics.class);
+
+ private final int playlist;
+ private final int index;
+
+ public GetMusicPlaylistMusics(HuaweiSupportProvider support, int playlist, int index) {
+ super(support);
+ this.serviceId = MusicControl.id;
+ this.commandId = MusicControl.MusicPlaylistMusics.id;
+ this.playlist = playlist;
+ this.index = index;
+ }
+
+ @Override
+ protected List createRequest() throws Request.RequestCreationException {
+ try {
+ return new MusicControl.MusicPlaylistMusics.Request(paramsProvider, (short) playlist, (short) index).serialize();
+ } catch (HuaweiPacket.CryptoException e) {
+ throw new Request.RequestCreationException(e);
+ }
+ }
+
+ @Override
+ protected void processResponse() throws Request.ResponseParseException {
+ LOG.info("MusicControl.GetMusicPlaylistMusics processResponse");
+ if (!(receivedPacket instanceof MusicControl.MusicPlaylistMusics.Response))
+ throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicPlaylistMusics.Response.class);
+
+ MusicControl.MusicPlaylistMusics.Response resp = (MusicControl.MusicPlaylistMusics.Response) (receivedPacket);
+ supportProvider.getHuaweiMusicManager().onMusicPlaylistMusics(resp.id, resp.index, resp.musicIds);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java
new file mode 100644
index 000000000..1dca7adce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
+import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.MusicControl;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider;
+
+public class SendMusicOperation extends Request {
+ private static final Logger LOG = LoggerFactory.getLogger(SendMusicOperation.class);
+
+ private final int operation;
+ private final int playlistIndex;
+ private final String playlistName;
+ private final ArrayList musicIds;
+
+
+ public SendMusicOperation(HuaweiSupportProvider support, int operation, int playlistIndex, String playlistName, ArrayList musicIds) {
+ super(support);
+ this.serviceId = MusicControl.id;
+ this.commandId = MusicControl.MusicOperation.id;
+ this.operation = operation;
+ this.playlistIndex = playlistIndex;
+ this.playlistName = playlistName;
+ this.musicIds = musicIds;
+ }
+
+ @Override
+ protected List createRequest() throws Request.RequestCreationException {
+ try {
+ return new MusicControl.MusicOperation.Request(paramsProvider, operation, playlistIndex, playlistName, musicIds).serialize();
+ } catch (HuaweiPacket.CryptoException e) {
+ throw new Request.RequestCreationException(e);
+ }
+ }
+
+ @Override
+ protected void processResponse() throws ResponseTypeMismatchException {
+ LOG.debug("handle Music Operation");
+ if (!(receivedPacket instanceof MusicControl.MusicOperation.Response))
+ throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicOperation.Response.class);
+
+ MusicControl.MusicOperation.Response resp = (MusicControl.MusicOperation.Response) (receivedPacket);
+ supportProvider.getHuaweiMusicManager().onMusicOperationResponse(resp.resultCode, resp.operation, resp.playlistIndex, resp.playlistName, resp.musicIds);
+ }
+
+}
diff --git a/app/src/main/res/layout/activity_musicmanager.xml b/app/src/main/res/layout/activity_musicmanager.xml
new file mode 100644
index 000000000..9f8eeaa76
--- /dev/null
+++ b/app/src/main/res/layout/activity_musicmanager.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_musicmanager_song.xml b/app/src/main/res/layout/item_musicmanager_song.xml
new file mode 100644
index 000000000..3f91a581a
--- /dev/null
+++ b/app/src/main/res/layout/item_musicmanager_song.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/musicmanager_context.xml b/app/src/main/res/menu/musicmanager_context.xml
new file mode 100644
index 000000000..82263c5eb
--- /dev/null
+++ b/app/src/main/res/menu/musicmanager_context.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f8f910042..14d798665 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -191,6 +191,8 @@
Please connect AT LEAST ONE device you want to send the file to.
Please connect ONLY ONE device you want to send the file to.
Make sure that the device %s is connected
+
+ Music Manager
Settings
This feature requires the installation of a proprietary app
@@ -3398,4 +3400,15 @@
Minimum amount of steps that need to be taken during the threshold minutes
HRV monitoring
Automatically monitor heart rate variability throughout the day
+ Manage Music
+ Manage music on the watch
+ Are you sure you want to delete \'%1$s\'?
+ Add to playlist
+ Delete from playlist
+ Delete song
+ All songs
+ New playlist
+ Rename playlist
+ Error occurred
+ Supported formats: %1$s\nWatch storage: %2$d MB
diff --git a/app/src/main/res/xml/devicesettings_musicmanagement.xml b/app/src/main/res/xml/devicesettings_musicmanagement.xml
new file mode 100644
index 000000000..ad0d7d22e
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_musicmanagement.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file