From dc1533b4ed678718bce820f61f1d34ac8bb28d79 Mon Sep 17 00:00:00 2001 From: Me7c7 Date: Sat, 16 Nov 2024 18:55:36 +0200 Subject: [PATCH] Huawei: Initial music managment support --- app/src/main/AndroidManifest.xml | 4 + .../DeviceSettingsPreferenceConst.java | 2 + .../DeviceSpecificSettingsFragment.java | 14 + .../musicmanager/MusicManagerActivity.java | 632 ++++++++++++++++++ .../adapter/MusicListAdapter.java | 85 +++ .../deviceevents/GBDeviceMusicData.java | 15 + .../deviceevents/GBDeviceMusicUpdate.java | 11 + .../gadgetbridge/devices/EventHandler.java | 7 +- .../devices/huawei/HuaweiCoordinator.java | 13 +- .../devices/huawei/HuaweiMusicUtils.java | 10 +- .../devices/huawei/HuaweiPacket.java | 8 + .../devices/huawei/packets/MusicControl.java | 207 +++++- .../gadgetbridge/impl/GBDeviceMusic.java | 33 + .../impl/GBDeviceMusicPlaylist.java | 41 ++ .../gadgetbridge/impl/GBDeviceService.java | 16 + .../gadgetbridge/model/DeviceService.java | 2 + .../service/AbstractDeviceSupport.java | 63 ++ .../service/DeviceCommunicationService.java | 11 + .../service/ServiceDeviceSupport.java | 17 + .../devices/huawei/HuaweiBRSupport.java | 11 + .../devices/huawei/HuaweiLESupport.java | 11 + .../devices/huawei/HuaweiMusicManager.java | 249 +++++++ .../devices/huawei/HuaweiSupportProvider.java | 9 + .../huawei/requests/GetMusicInfoParams.java | 2 +- .../devices/huawei/requests/GetMusicList.java | 45 ++ .../huawei/requests/GetMusicPlaylist.java | 39 ++ .../requests/GetMusicPlaylistMusics.java | 44 ++ .../huawei/requests/SendMusicOperation.java | 51 ++ .../main/res/layout/activity_musicmanager.xml | 92 +++ .../res/layout/item_musicmanager_song.xml | 63 ++ .../main/res/menu/musicmanager_context.xml | 12 + app/src/main/res/values/strings.xml | 13 + .../xml/devicesettings_musicmanagement.xml | 8 + 33 files changed, 1822 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/musicmanager/MusicManagerActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/MusicListAdapter.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicData.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceMusicUpdate.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusic.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceMusicPlaylist.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicList.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylist.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetMusicPlaylistMusics.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendMusicOperation.java create mode 100644 app/src/main/res/layout/activity_musicmanager.xml create mode 100644 app/src/main/res/layout/item_musicmanager_song.xml create mode 100644 app/src/main/res/menu/musicmanager_context.xml create mode 100644 app/src/main/res/xml/devicesettings_musicmanagement.xml 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