Huawei: Initial music managment support

This commit is contained in:
Me7c7 2024-11-16 18:55:36 +02:00 committed by José Rebelo
parent 1a3a7dec05
commit dc1533b4ed
33 changed files with 1822 additions and 18 deletions

View File

@ -199,6 +199,10 @@
android:label="@string/title_activity_appmanager"
android:launchMode="singleTop"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.musicmanager.MusicManagerActivity"
android:label="@string/title_activity_musicmanager"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.AppBlacklistActivity"
android:label="@string/title_activity_notification_management"

View File

@ -285,6 +285,8 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_CONTACTS = "pref_contacts";
public static final String PREF_WIDGETS = "pref_widgets";
public static final String PREF_MUSIC_MANAGEMENT = "pref_music_management";
public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled";
public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";
public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period";

View File

@ -70,6 +70,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
import nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications.AppSpecificNotificationSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst;
import nodomain.freeyourgadget.gadgetbridge.activities.musicmanager.MusicManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.widgets.WidgetScreensListActivity;
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
@ -1051,6 +1052,19 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
});
}
final Preference music_management = findPreference(PREF_MUSIC_MANAGEMENT);
if (music_management != null) {
music_management.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
final Intent intent = new Intent(getContext(), MusicManagerActivity.class);
intent.putExtra(GBDevice.EXTRA_DEVICE, device);
startActivity(intent);
return true;
}
});
}
final Preference widgets = findPreference(PREF_WIDGETS);
if (widgets != null) {
widgets.setOnPreferenceClickListener(preference -> {

View File

@ -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<GBDeviceMusic> allMusic = new ArrayList<>();
private final List<GBDeviceMusic> musicList = new ArrayList<>();
private MusicListAdapter musicAdapter;
private final List<GBDeviceMusicPlaylist> playlists = new ArrayList<>();
private ArrayAdapter<GBDeviceMusicPlaylist> 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<GBDeviceMusic> 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<Intent> openAudioActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@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<Integer> 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<Integer> 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<GBDeviceMusicPlaylist> dialogPlaylists = new ArrayList<>();
for (GBDeviceMusicPlaylist playlist : playlists) {
if (playlist.getId() != 0) {
dialogPlaylists.add(playlist);
}
}
ArrayAdapter<GBDeviceMusicPlaylist> 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<GBDeviceMusic> list = (ArrayList<GBDeviceMusic>) intent.getSerializableExtra("musicList");
if (list != null && !list.isEmpty()) {
allMusic.addAll(list);
}
ArrayList<GBDeviceMusicPlaylist> devicePlaylist = (ArrayList<GBDeviceMusicPlaylist>) 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<GBDeviceMusicPlaylist> 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<Integer> ids = (ArrayList<Integer>) intent.getSerializableExtra("musicIds");
if (playlistIndex != -1 && ids != null && !ids.isEmpty()) {
for (GBDeviceMusicPlaylist playlist : playlists) {
if (playlist.getId() == playlistIndex) {
ArrayList<Integer> 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<Integer> ids = (ArrayList<Integer>) intent.getSerializableExtra("musicIds");
int playlistIndex = intent.getIntExtra("playlistIndex", 0);
if (ids != null && !ids.isEmpty()) {
if (playlistIndex == 0) {
for (Iterator<GBDeviceMusic> iterator = musicList.iterator(); iterator.hasNext(); ) {
GBDeviceMusic music = iterator.next();
if (ids.contains(music.getId())) {
iterator.remove();
}
}
for (Iterator<GBDeviceMusic> 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<Integer> 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;
}
}
}
};
}

View File

@ -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<MusicListAdapter.MusicViewHolder> {
public interface onItemAction {
void onItemClick(View view, GBDeviceMusic music);
boolean onItemLongClick(View view, GBDeviceMusic music);
}
private final List<GBDeviceMusic> musicList;
private final onItemAction callback;
public MusicListAdapter(List<GBDeviceMusic> 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);
}
}
}

View File

@ -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<GBDeviceMusic> list = null;
public List<GBDeviceMusicPlaylist> playlists = null;
public String deviceInfo = null;
public int maxMusicCount = 0;
public int maxPlaylistCount = 0;
}

View File

@ -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<Integer> musicIds = null;
}

View File

@ -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<Integer> musicIds);
}

View File

@ -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<Integer> 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

View File

@ -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<FormatRestrictions> formatsRestrictions = null;
public List<PageStruct> 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();
}

View File

@ -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:

View File

@ -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<HuaweiMusicUtils.PageStruct> 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<HuaweiTLV> 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<GBDeviceMusic> 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<PlaylistData> 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<Integer> 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<Integer> 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<Integer> 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;

View File

@ -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;
}
}

View File

@ -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<Integer> musicIds;
public GBDeviceMusicPlaylist(int id, String name, ArrayList<Integer> 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<Integer> getMusicIds() {
return musicIds;
}
public void setMusicIds(ArrayList<Integer> musicIds) {
this.musicIds = musicIds;
}
@Override
public String toString() {
return name;
}
}

View File

@ -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<Integer> 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);
}
}

View File

@ -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";

View File

@ -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<GBDeviceMusic> list = new ArrayList<>(deviceEvent.list);
intent.putExtra("musicList", list);
}
if(deviceEvent.playlists != null) {
ArrayList<GBDeviceMusicPlaylist> 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<Integer> musicIds) {}
}

View File

@ -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<Integer> musics = (ArrayList<Integer>) intentCopy.getSerializableExtra("musicIds");
deviceSupport.onMusicOperation(operation, playlistIndex, playlistName, musics);
break;
}
}

View File

@ -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<Integer> musicIds) {
if (checkBusy("music operation")) {
return;
}
delegate.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
}
}

View File

@ -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<Integer> musicIds) {
supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
}
}

View File

@ -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<Integer> musicIds) {
supportProvider.onMusicOperation(operation, playlistIndex, playlistName, musicIds);
}
}

View File

@ -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<MusicControl.MusicPlaylists.Response.PlaylistData> devicePlaylists = new ArrayList<>();
private int currentPlaylistIndex = 0;
private int currentPlaylistFrame = 0;
private final List<List<Integer>> 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<Integer> 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<GBDeviceMusicPlaylist> 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<HuaweiMusicUtils.PageStruct> 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<GBDeviceMusic> list) {
final GBDeviceMusicData musicListCmd = new GBDeviceMusicData();
musicListCmd.type = 2;
musicListCmd.list = list;
support.evaluateGBDeviceEvent(musicListCmd);
}
private void sendMusicPlaylist(List<GBDeviceMusicPlaylist> 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<GBDeviceMusic> 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<MusicControl.MusicPlaylists.Response.PlaylistData> 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<Integer> musicIds) {
this.tempPlaylistMusic.add(musicIds);
syncNextPlaylistMusicIndex();
}
public void onMusicOperation(int operation, int playlistIndex, String playlistName, ArrayList<Integer> 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<Integer> 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);
}
}

View File

@ -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<Integer> musicIds) {
getHuaweiMusicManager().onMusicOperation(operation, playlistIndex, playlistName, musicIds);
}
}

View File

@ -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);
}
}

View File

@ -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<byte[]> 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);
}
}

View File

@ -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<byte[]> 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);
}
}

View File

@ -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<byte[]> 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);
}
}

View File

@ -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<Integer> musicIds;
public SendMusicOperation(HuaweiSupportProvider support, int operation, int playlistIndex, String playlistName, ArrayList<Integer> 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<byte[]> 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);
}
}

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<android.widget.RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.musicmanager.MusicManagerActivity">
<TextView
android:id="@+id/music_device_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:lineSpacingMultiplier="1.1"
android:text="" />
<LinearLayout
android:id="@+id/music_playlists_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/music_device_info"
android:orientation="horizontal">
<Spinner
android:id="@+id/music_playlists"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_weight="20" />
<ImageButton
android:id="@+id/music_playlist_rename"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_weight="1"
android:background="@null"
android:contentDescription="@string/music_rename_playlist"
app:srcCompat="@drawable/ic_edit"/>
<ImageButton
android:id="@+id/music_playlist_delete"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_weight="1"
android:background="@null"
android:contentDescription="@string/music_delete"
app:srcCompat="@drawable/ic_delete" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/music_songs_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/music_playlists_layout"
android:layout_centerHorizontal="true"
android:divider="@null" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_music_upload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:contentDescription="@string/music_new_playlist"
app:srcCompat="@android:drawable/stat_sys_upload" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_music_playlist_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="90dp"
app:srcCompat="@drawable/ic_add"
/>
<RelativeLayout
android:id="@+id/music_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#95000000"
android:gravity="center">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</android.widget.RelativeLayout>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/appmanager_item_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?android:attr/selectableItemBackground"
app:cardBackgroundColor="?attr/cardview_background_color"
app:cardElevation="3dp"
app:contentPadding="8dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:minHeight="60dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/candidate_item_device_image"
android:src="@drawable/ic_music"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@+id/item_image"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="Item Name"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/item_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Item Description"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/musicmanager_add_to_playlist"
android:title="@string/music_add_to_playlist"/>
<item
android:id="@+id/musicmanager_delete_from_playlist"
android:title="@string/music_delete_from_playlist"/>
<item
android:id="@+id/musicmanager_delete"
android:title="@string/music_delete"/>
</menu>

View File

@ -191,6 +191,8 @@
<string name="open_fw_installer_connect_minimum_one_device">Please connect AT LEAST ONE device you want to send the file to.</string>
<string name="open_fw_installer_connect_maximum_one_device">Please connect ONLY ONE device you want to send the file to.</string>
<string name="open_fw_installer_ensure_device_connected">Make sure that the device %s is connected</string>
<!-- Strings related to MusicManager -->
<string name="title_activity_musicmanager">Music Manager</string>
<!-- Strings related to Settings -->
<string name="title_activity_settings">Settings</string>
<string name="proprietary_app_warning">This feature requires the installation of a proprietary app</string>
@ -3398,4 +3400,15 @@
<string name="inactivity_warnings_minimum_steps_summary">Minimum amount of steps that need to be taken during the threshold minutes</string>
<string name="prefs_hrv_monitoring_title">HRV monitoring</string>
<string name="prefs_hrv_monitoring_description">Automatically monitor heart rate variability throughout the day</string>
<string name="pref_music_management_title">Manage Music</string>
<string name="pref_music_management_summary">Manage music on the watch</string>
<string name="music_delete_confirm_description">Are you sure you want to delete \'%1$s\'?</string>
<string name="music_add_to_playlist">Add to playlist</string>
<string name="music_delete_from_playlist">Delete from playlist</string>
<string name="music_delete">Delete song</string>
<string name="music_all_songs">All songs</string>
<string name="music_new_playlist">New playlist</string>
<string name="music_rename_playlist">Rename playlist</string>
<string name="music_error">Error occurred</string>
<string name="music_huawei_device_info">Supported formats: %1$s\nWatch storage: %2$d MB</string>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:icon="@drawable/ic_music_note"
android:key="pref_music_management"
android:summary="@string/pref_music_management_summary"
android:title="@string/pref_music_management_title" />
</androidx.preference.PreferenceScreen>