1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 12:26:48 +01:00

Huawei: Initial music upload support

This commit is contained in:
Me7c7 2024-10-19 19:20:12 +03:00 committed by José Rebelo
parent 86cb08c0fb
commit 48717aaf42
12 changed files with 873 additions and 38 deletions

View File

@ -74,6 +74,10 @@ public class HuaweiCoordinator {
private App.AppDeviceParams appDeviceParams;
private HuaweiMusicUtils.MusicCapabilities musicDeviceParams = null;
private HuaweiMusicUtils.MusicCapabilities musicExtendedDeviceParams = null;
private final HuaweiCoordinatorSupplier parent;
private boolean transactionCrypted=true;
@ -492,6 +496,8 @@ public class HuaweiCoordinator {
public boolean supportsAppParams(){ return supportsCommandForService(0x2a, 0x06);}
public boolean supportsMusicUploading(){ return supportsCommandForService(0x25, 0x04);}
public boolean supportsWeather() {
return supportsCommandForService(0x0f, 0x01);
}
@ -712,6 +718,23 @@ public class HuaweiCoordinator {
return appDeviceParams;
}
public void setExtendedMusicInfoParams(HuaweiMusicUtils.MusicCapabilities musicDeviceParams) {
LOG.info(musicDeviceParams.toString());
this.musicExtendedDeviceParams = musicDeviceParams;
}
public HuaweiMusicUtils.MusicCapabilities getExtendedMusicInfoParams() {
return musicExtendedDeviceParams;
}
public void setMusicInfoParams(HuaweiMusicUtils.MusicCapabilities musicDeviceParams) {
LOG.info(musicDeviceParams.toString());
this.musicDeviceParams = musicDeviceParams;
}
public HuaweiMusicUtils.MusicCapabilities getMusicInfoParams() {
return musicDeviceParams;
}
public Class<? extends Activity> getAppManagerActivity() {
return AppManagerActivity.class;
}

View File

@ -19,9 +19,13 @@ package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
@ -31,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiAppManager;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiFwHelper;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiMusicManager;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiWatchfaceManager;
public class HuaweiInstallHandler implements InstallHandler {
@ -46,6 +51,59 @@ public class HuaweiInstallHandler implements InstallHandler {
this.helper = new HuaweiFwHelper(uri, context);
}
private HuaweiMusicUtils.FormatRestrictions getRestriction(HuaweiMusicUtils.MusicCapabilities capabilities, String ext) {
List<HuaweiMusicUtils.FormatRestrictions> restrictions = capabilities.formatsRestrictions;
if(restrictions == null)
return null;
for(HuaweiMusicUtils.FormatRestrictions r: restrictions) {
if(ext.equals(r.getName())) {
return r;
}
}
return null;
}
//TODO: add proper checks
private boolean checkMediaCompatibility(HuaweiMusicUtils.MusicCapabilities capabilities, HuaweiMusicManager.AudioInfo currentMusicInfo) {
if(capabilities == null) {
LOG.error("No media info from device");
return false;
}
String ext = currentMusicInfo.getExtension();
List<String> supportedFormats = capabilities.supportedFormats;
if(supportedFormats == null) {
LOG.error("Format not supported {}", ext);
return false;
}
if(!supportedFormats.contains(ext)) {
LOG.error("Format not supported {}", ext);
return false;
}
HuaweiMusicUtils.FormatRestrictions restrictions = getRestriction(capabilities, ext);
if(restrictions == null) {
LOG.info("no restriction for: {}", ext);
return true;
}
LOG.info("bitrate {}", restrictions.bitrate);
LOG.info("channels {}", restrictions.channels);
LOG.info("musicEncode {}", restrictions.musicEncode);
LOG.info("sampleRate {}", restrictions.sampleRate);
LOG.info("unknownBitrate {}", restrictions.unknownBitrate);
if(currentMusicInfo.getChannels() > restrictions.channels) {
LOG.error("Not supported channels couunt {} > {}", currentMusicInfo.getChannels(), restrictions.channels);
return false;
}
//TODO: check other restrictions.
return true;
}
@Override
public void validateInstallation(InstallActivity installActivity, GBDevice device) {
@ -57,7 +115,7 @@ public class HuaweiInstallHandler implements InstallHandler {
return;
}
if(helper.isWatchface()) {
if (helper.isWatchface()) {
final HuaweiCoordinatorSupplier huaweiCoordinatorSupplier = (HuaweiCoordinatorSupplier) coordinator;
HuaweiWatchfaceManager.WatchfaceDescription description = helper.getWatchfaceDescription();
@ -71,7 +129,6 @@ public class HuaweiInstallHandler implements InstallHandler {
GenericItem installItem = new GenericItem();
if (helper.getPreviewBitmap() != null) {
installItem.setPreview(helper.getPreviewBitmap());
}
@ -79,16 +136,16 @@ public class HuaweiInstallHandler implements InstallHandler {
installItem.setName(description.title);
installActivity.setInstallItem(installItem);
if (device.isBusy()) {
LOG.error("Firmware cannot be installed (device busy)");
installActivity.setInfoText("Firmware cannot be installed (device busy)");
LOG.error("Watchface cannot be installed (device busy)");
installActivity.setInfoText("Watchface cannot be installed (device busy)");
installActivity.setInfoText(device.getBusyTask());
installActivity.setInstallEnabled(false);
return;
}
if (!device.isConnected()) {
LOG.error("Firmware cannot be installed (not connected or wrong device)");
installActivity.setInfoText("Firmware cannot be installed (not connected or wrong device)");
LOG.error("Watchface cannot be installed (not connected or wrong device)");
installActivity.setInfoText("Watchface cannot be installed (not connected or wrong device)");
installActivity.setInstallEnabled(false);
return;
}
@ -106,7 +163,7 @@ public class HuaweiInstallHandler implements InstallHandler {
installItem.setIcon(R.drawable.ic_watchface);
installActivity.setInfoText(context.getString(R.string.watchface_install_info, installItem.getName(), description.version, description.author));
LOG.debug("Initialized HuaweiInstallHandler");
LOG.debug("Initialized HuaweiInstallHandler: Watchface");
} else if (helper.isAPP()) {
final HuaweiAppManager.AppConfig config = helper.getAppConfig();
@ -123,7 +180,7 @@ public class HuaweiInstallHandler implements InstallHandler {
installItem.setName(config.bundleName);
installActivity.setInstallItem(installItem);
if (device.isBusy()) {
LOG.error("Firmware cannot be installed (device busy)");
LOG.error("App cannot be installed (device busy)");
installActivity.setInfoText("Firmware cannot be installed (device busy)");
installActivity.setInfoText(device.getBusyTask());
installActivity.setInstallEnabled(false);
@ -131,7 +188,7 @@ public class HuaweiInstallHandler implements InstallHandler {
}
if (!device.isConnected()) {
LOG.error("Firmware cannot be installed (not connected or wrong device)");
LOG.error("App cannot be installed (not connected or wrong device)");
installActivity.setInfoText("Firmware cannot be installed (not connected or wrong device)");
installActivity.setInstallEnabled(false);
return;
@ -149,7 +206,55 @@ public class HuaweiInstallHandler implements InstallHandler {
installActivity.setInfoText(context.getString(R.string.app_install_info, installItem.getName(), config.version, config.vendor));
LOG.debug("Initialized HuaweiInstallHandler");
LOG.debug("Initialized HuaweiInstallHandler: App");
} else if (helper.isMusic()) {
final HuaweiCoordinatorSupplier huaweiCoordinatorSupplier = (HuaweiCoordinatorSupplier) coordinator;
HuaweiMusicUtils.MusicCapabilities capabilities = huaweiCoordinatorSupplier.getHuaweiCoordinator().getExtendedMusicInfoParams();
if(capabilities == null) {
capabilities = huaweiCoordinatorSupplier.getHuaweiCoordinator().getMusicInfoParams();
}
HuaweiMusicManager.AudioInfo currentMusicInfo = helper.getMusicInfo();
boolean isMediaCompatible = checkMediaCompatibility(capabilities, currentMusicInfo);
this.valid = isMediaCompatible && !TextUtils.isEmpty(helper.getMusicInfo().getFileName()) && !TextUtils.isEmpty(helper.getMusicInfo().getArtist()) && !TextUtils.isEmpty(helper.getMusicInfo().getTitle());
installActivity.setInstallEnabled(true);
GenericItem installItem = new GenericItem();
installItem.setName(helper.getFileName());
installActivity.setInstallItem(installItem);
if (device.isBusy()) {
LOG.error("Music cannot be uploaded (device busy)");
installActivity.setInfoText("Music cannot be uploaded (device busy)");
installActivity.setInfoText(device.getBusyTask());
installActivity.setInstallEnabled(false);
return;
}
if (!device.isConnected()) {
LOG.error("Music cannot be uploaded (not connected or wrong device)");
installActivity.setInfoText("Music cannot be uploaded (not connected or wrong device)");
installActivity.setInstallEnabled(false);
return;
}
if (!this.valid) {
LOG.error("Music cannot be uploaded");
installActivity.setInfoText("Music cannot be uploaded");
installActivity.setInstallEnabled(false);
return;
}
installItem.setDetails(helper.getMusicInfo().getFileName());
installItem.setIcon(R.drawable.ic_music_note);
installActivity.setInfoText(context.getString(R.string.app_install_info, helper.getMusicInfo().getFileName(), helper.getMusicInfo().getTitle(), helper.getMusicInfo().getArtist()));
LOG.debug("Initialized HuaweiInstallHandler: Music");
}
}

View File

@ -0,0 +1,131 @@
package nodomain.freeyourgadget.gadgetbridge.devices.huawei;
import java.util.ArrayList;
import java.util.List;
public class HuaweiMusicUtils {
public static class PageStruct {
public short startIndex = 0;
public short endIndex = 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(", count=").append(count);
sb.append(", hashCode=");
if (hashCode == null) sb.append("null");
else {
sb.append('[');
for (int i = 0; i < hashCode.length; ++i)
sb.append(i == 0 ? "" : ", ").append(hashCode[i]);
sb.append(']');
}
sb.append('}');
return sb.toString();
}
}
public static class FormatRestrictions {
public byte formatIdx = -1;
public int sampleRate = -1; // TODO: not sure
public byte musicEncode = -1; // TODO: not sure
public short bitrate = -1;
public byte channels = -1;
public short unknownBitrate = -1; // TODO: not sure
// TODO: I am not sure about this. Most of formats unknown for me.
private static final String[] formats = {"mp3", "wav", "aac", "sbc", "msbc", "hwa", "cvsd", "pcm", "ape", "m4a", "flac", "opus", "ogg", "butt", "amr", "imy"};
public String getName() {
return (formatIdx >= 0 && formatIdx < formats.length)?formats[formatIdx]:null;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("FormatRestrictions{");
sb.append("formatIdx=").append(formatIdx);
sb.append(", sampleRate=").append(sampleRate);
sb.append(", musicEncode=").append(musicEncode);
sb.append(", bitrate=").append(bitrate);
sb.append(", channels=").append(channels);
sb.append(", unknownBitrate=").append(unknownBitrate);
sb.append('}');
return sb.toString();
}
}
public static class MusicCapabilities {
public short availableSpace = 0;
public List<String> supportedFormats = null;
public short maxMusicCount = 0;
public short maxPlaylistCount = 0;
public short currentMusicCount = 0; // TODO: not sure
public byte unknown = 0; // TODO: not sure
public List<FormatRestrictions> formatsRestrictions = null;
public List<PageStruct> pageStruct = null;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("MusicCapabilities{");
sb.append("availableSpace=").append(availableSpace);
sb.append(", supportedFormats=").append(supportedFormats);
sb.append(", maxMusicCount=").append(maxMusicCount);
sb.append(", maxPlaylistCount=").append(maxPlaylistCount);
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();
}
}
// TODO: I am not sure about this. Most of formats unknown for me.
private static final String[] formatsByte0 = {"mp3", "wav", "aac", "sbc", "msbc", "hwa", "cvsd"};
private static final String[] formatsByte1 = {"pcm", "ape", "m4a", "flac", "opus", "ogg", "butt"};
private static final String[] formatsByte2 = {"amr", "imy"};
public static void parseNext(int dt, String[] info, List<String> res) {
for (byte k = 0; k < info.length; k++) {
if ((dt & (1 << k)) != 0) {
res.add(info[k]);
}
}
}
public static List<String> parseFormats(List<Integer> data) {
List<String> res = new ArrayList<>();
if (data.size() >= 1) {
parseNext(data.get(0), formatsByte0, res);
}
if (data.size() >= 2) {
parseNext(data.get(1), formatsByte1, res);
}
if (data.size() >= 3) {
parseNext(data.get(2), formatsByte2, res);
}
return res;
}
public static List<String> parseFormatBits(byte[] formatBits) {
if (formatBits.length == 0)
return null;
List<Integer> toDecode = new ArrayList<>();
for (byte formatBit : formatBits) {
int dt = formatBit & 0xFF;
toDecode.add(dt);
if ((dt & 128) == 0) break;
}
return parseFormats(toDecode);
}
}

View File

@ -594,6 +594,12 @@ public class HuaweiPacket {
return new MusicControl.MusicInfo.Response(paramsProvider).fromPacket(this);
case MusicControl.Control.id:
return new MusicControl.Control.Response(paramsProvider).fromPacket(this);
case MusicControl.MusicInfoParams.id:
return new MusicControl.MusicInfoParams.Response(paramsProvider).fromPacket(this);
case MusicControl.UploadMusicFileInfo.id:
return new MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest(paramsProvider).fromPacket(this);
case MusicControl.ExtendedMusicInfoParams.id:
return new MusicControl.ExtendedMusicInfoParams.Response(paramsProvider).fromPacket(this);
default:
this.isEncrypted = this.attemptDecrypt(); // Helps with debugging
return this;

View File

@ -16,12 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets;
import static nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiMusicUtils.parseFormatBits;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
public class MusicControl {
public static final byte id = 0x25;
// TODO: should this be in HuaweiConstants?
public static final int successValue = 0x000186A0;
@ -189,4 +199,167 @@ public class MusicControl {
}
}
}
public static class UploadMusicFileInfo {
public static final int id = 0x09;
public static class UploadMusicFileInfoRequest extends HuaweiPacket {
public short songIndex;
public String songFileName;
public UploadMusicFileInfoRequest(ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws ParseException {
this.songIndex = this.tlv.getShort(0x01);
this.songFileName = this.tlv.getString(0x02);
}
}
public static class UploadMusicFileInfoResponse extends HuaweiPacket {
public UploadMusicFileInfoResponse(ParamsProvider paramsProvider, short songIndex, String songName, String songArtist) {
super(paramsProvider);
this.serviceId = MusicControl.id;
this.commandId = id;
this.tlv = new HuaweiTLV().put(0x01, songIndex).put(0x03, songName).put(0x04, songArtist);
this.complete = true;
}
}
}
public static class MusicInfoParams {
public static final byte id = 0x04;
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)
.put(0x02)
.put(0x03);
this.complete = true;
}
}
public static class Response extends HuaweiPacket {
public HuaweiMusicUtils.MusicCapabilities params = new HuaweiMusicUtils.MusicCapabilities();
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));
if (this.tlv.contains(0x02))
params.availableSpace = this.tlv.getShort(0x02);
if (this.tlv.contains(0x03)) {
byte[] formatBits = this.tlv.getBytes(0x03);
params.supportedFormats = parseFormatBits(formatBits);
}
if (this.tlv.contains(0x04))
params.maxMusicCount = this.tlv.getShort(0x04);
if (this.tlv.contains(0x05))
params.currentMusicCount = this.tlv.getByte(0x05);
if (this.tlv.contains(0x86)) {
params.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);
if (subTlv.contains(0x09))
pageStruct.endIndex = 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);
}
}
}
}
}
public static class ExtendedMusicInfoParams {
public static final byte id = 0x0d;
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)
.put(0x02)
.put(0x03)
.put(0x04)
.put(0x05);
this.complete = true;
}
}
public static class Response extends HuaweiPacket {
public HuaweiMusicUtils.MusicCapabilities params = new HuaweiMusicUtils.MusicCapabilities();
public Response(ParamsProvider paramsProvider) {
super(paramsProvider);
}
@Override
public void parseTlv() throws ParseException {
if (this.tlv.contains(0x01))
params.availableSpace = this.tlv.getShort(0x01);
if (this.tlv.contains(0x02)) {
byte[] formatBits = this.tlv.getBytes(0x02);
params.supportedFormats = parseFormatBits(formatBits);
}
if (this.tlv.contains(0x03))
params.maxMusicCount = this.tlv.getShort(0x03);
if (this.tlv.contains(0x04))
params.maxPlaylistCount = this.tlv.getShort(0x04);
if (this.tlv.contains(0x05))
params.unknown = this.tlv.getByte(0x05);
if (this.tlv.contains(0x86)) {
params.formatsRestrictions = new ArrayList<>();
List<HuaweiTLV> subTlvs = this.tlv.getObject(0x86).getObjects(0x87);
for (HuaweiTLV subTlv : subTlvs) {
HuaweiMusicUtils.FormatRestrictions restriction = new HuaweiMusicUtils.FormatRestrictions();
if (subTlv.contains(0x08))
restriction.formatIdx = subTlv.getByte(0x08);
if (subTlv.contains(0x09))
restriction.sampleRate = subTlv.getInteger(0x09);
if (subTlv.contains(0x0a))
restriction.musicEncode = subTlv.getByte(0x0a);
if (subTlv.contains(0x0b))
restriction.bitrate = subTlv.getShort(0x0b);
if (subTlv.contains(0x0c))
restriction.channels = subTlv.getByte(0x0c);
if (subTlv.contains(0x0d))
restriction.unknownBitrate = subTlv.getShort(0x0b);
params.formatsRestrictions.add(restriction);
}
}
}
}
}
}

View File

@ -343,6 +343,12 @@ public class AsynchronousResponse {
this.support.sendSetMusic();
}, 100);
}
} else if (response.commandId == MusicControl.UploadMusicFileInfo.id) {
if (!(response instanceof MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest))
throw new Request.ResponseTypeMismatchException(response, MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest.class);
MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest resp = (MusicControl.UploadMusicFileInfo.UploadMusicFileInfoRequest) response;
support.getHuaweiMusicManager().uploadMusicInfo(resp.songIndex, resp.songFileName);
}
}
}

View File

@ -17,10 +17,17 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaExtractor;
import android.media.MediaMetadataRetriever;
import android.media.MediaFormat;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -51,8 +58,11 @@ public class HuaweiFwHelper {
Bitmap previewBitmap;
HuaweiWatchfaceManager.WatchfaceDescription watchfaceDescription;
HuaweiAppManager.AppConfig appConfig;
HuaweiMusicManager.AudioInfo musicInfo;
Context mContext;
public HuaweiFwHelper(final Uri uri, final Context context) {
this.uri = uri;
@ -69,7 +79,9 @@ public class HuaweiFwHelper {
}
private void parseFile() {
if(parseAsApp()) {
if (parseAsMusic()) {
fileType = FileUpload.Filetype.music;
} else if (parseAsApp()) {
assert appConfig.bundleName != null;
fileType = FileUpload.Filetype.app;
} else if (parseAsWatchFace()) {
@ -79,31 +91,136 @@ public class HuaweiFwHelper {
}
}
private String getNameWithoutExtension(String fileName) {
return fileName.indexOf(".") > 0?fileName.substring(0, fileName.lastIndexOf(".")): fileName;
}
private String getExtension(String fileName) {
if (TextUtils.isEmpty(fileName)) {
return null;
}
int lastIndexOf = fileName.lastIndexOf(".");
if (lastIndexOf >= 0 && lastIndexOf + 1 < fileName.length()) {
return fileName.substring(lastIndexOf + 1);
}
return null;
}
private HuaweiMusicManager.AudioInfo getAudioInfo(Uri selectedUri) throws IOException {
ContentResolver contentResolver = mContext.getContentResolver();
String[] filePathColumn = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
Cursor cursor = contentResolver.query(selectedUri, filePathColumn, null, null, null);
if(cursor == null)
return null;
cursor.moveToFirst();
int fileNameIndex = cursor.getColumnIndex(filePathColumn[0]);
String fileName = cursor.getString(fileNameIndex);
int fileSizeIndex = cursor.getColumnIndex(filePathColumn[1]);
long fileSize = cursor.getLong(fileSizeIndex);
cursor.close();
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(mContext, selectedUri);
String title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
String artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
if(TextUtils.isEmpty(title)) {
title = getNameWithoutExtension(fileName);
}
if(TextUtils.isEmpty(artist)) {
artist = "Unknown";
}
MediaExtractor mex = new MediaExtractor();
mex.setDataSource(mContext, selectedUri, null);
MediaFormat mf = mex.getTrackFormat(0);
int bitrate = -1; // TODO: calculate or get bitrate
int sampleRate = mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
int channels = mf.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
long duration = mf.getLong(MediaFormat.KEY_DURATION);
LOG.info("bitRate: " + bitrate);
LOG.info("sampleRate: " + sampleRate);
LOG.info("channelCount: " + channels);
LOG.info("duration: " + duration);
String extension = getExtension(fileName);
if(!TextUtils.isEmpty(extension)) {
extension = extension.toLowerCase();
}
HuaweiMusicManager.AudioInfo audioInfo = new HuaweiMusicManager.AudioInfo(fileName, fileSize, title, artist, extension);
audioInfo.setCharacteristics(duration, sampleRate, bitrate, (byte) channels);
return audioInfo;
}
private byte[] getFileData(UriHelper uriHelper) throws IOException {
InputStream inputStream = uriHelper.openInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[1000];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
inputStream.close();
return buffer.toByteArray();
}
boolean parseAsMusic() {
try {
final UriHelper uriHelper = UriHelper.get(uri, this.mContext);
String mimeType = mContext.getContentResolver().getType(uri);
LOG.info("File mime type: {}", mimeType);
if(mimeType == null || !mimeType.startsWith("audio/"))
return false;
musicInfo = getAudioInfo(uri);
if(musicInfo == null)
return false;
musicInfo.setMimeType(mimeType);
byte[] musicData = getFileData(uriHelper);
fileName = musicInfo.getFileName();
fw = musicData;
fileSize = fw.length;
return true;
} catch (FileNotFoundException e) {
LOG.error("Music: File was not found {}", e.getMessage());
} catch (IOException e) {
LOG.error("Music: General IO error occurred {}", e.getMessage());
} catch (Exception e) {
LOG.error("Music: Unknown error occurred", e);
}
return false;
}
boolean parseAsApp() {
try {
final UriHelper uriHelper = UriHelper.get(uri, this.mContext);
InputStream inputStream = uriHelper.openInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[4];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
byte[] appData = buffer.toByteArray();
inputStream.close();
byte[] appData = getFileData(uriHelper);
HuaweiBinAppParser app = new HuaweiBinAppParser();
app.parseData(appData);
byte[] config = app.getEntryContent("config.json");
if(config == null)
if (config == null)
return false;
appConfig = new HuaweiAppManager.AppConfig(new String(config));
fileName = app.getPackageName() + "_INSTALL"; //TODO: INSTALL or UPDATE suffix
@ -112,22 +229,21 @@ public class HuaweiFwHelper {
fileSize = fw.length;
byte[] icon = app.getEntryContent("icon_small.png");
if(icon != null) {
if (icon != null) {
previewBitmap = BitmapFactory.decodeByteArray(icon, 0, icon.length);
}
return true;
} catch (FileNotFoundException e) {
LOG.error("The app file was not found.", e);
LOG.error("App: File was not found{}", e.getMessage());
} catch (IOException e) {
LOG.error("General IO error occurred.", e);
LOG.error("App: General IO error occurred {}", e.getMessage());
} catch (HuaweiBinAppParser.HuaweiBinAppParseError e) {
LOG.error("Error parsing app File", e);
LOG.error("App: Error parsing app File {}", e.getMessage());
} catch (Exception e) {
LOG.error("Unknown error occurred.", e);
LOG.error("App: Unknown error occurred", e);
}
return false;
}
@ -172,7 +288,7 @@ public class HuaweiFwHelper {
byte[] watchfaceZip = watchfacePackage.getFileFromZip("com.huawei.watchface");
try {
GBZipFile watchfaceBinZip = new GBZipFile(watchfaceZip);
GBZipFile watchfaceBinZip = new GBZipFile(watchfaceZip);
fw = watchfaceBinZip.getFileFromZip("watchface.bin");
} catch (ZipFileException e) {
LOG.error("Unable to get watchfaceZip, it seems older already watchface.bin");
@ -182,13 +298,13 @@ public class HuaweiFwHelper {
isWatchface = true;
} catch (ZipFileException e) {
LOG.error("Unable to read watchface file.", e);
LOG.error("Watchface: Unable to read file {}", e.getMessage());
} catch (FileNotFoundException e) {
LOG.error("The watchface file was not found.", e);
LOG.error("Watchface: File was not found {}", e.getMessage());
} catch (IOException e) {
LOG.error("General IO error occurred.", e);
LOG.error("Watchface: General IO error occurred {}", e.getMessage());
} catch (Exception e) {
LOG.error("Unknown error occurred.", e);
LOG.error("Watchface: Unknown error occurred", e);
}
return isWatchface;
@ -202,8 +318,12 @@ public class HuaweiFwHelper {
return fileType == FileUpload.Filetype.app;
}
public boolean isMusic() {
return fileType == FileUpload.Filetype.music;
}
public boolean isValid() {
return isWatchface() || isAPP();
return isWatchface() || isAPP() || isMusic();
}
public Bitmap getPreviewBitmap() {
@ -218,6 +338,10 @@ public class HuaweiFwHelper {
return appConfig;
}
public HuaweiMusicManager.AudioInfo getMusicInfo() {
return musicInfo;
}
public byte getFileType() {
return fileType;
}

View File

@ -0,0 +1,128 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendUploadMusicFileInfoResponse;
public class HuaweiMusicManager {
static Logger LOG = LoggerFactory.getLogger(HuaweiMusicManager.class);
public static class AudioInfo {
private final String fileName;
private final long fileSize;
private final String title;
private final String artist;
private final String extension;
private String mimeType;
private long duration;
private int sampleRate;
private int bitrate;
private byte channels;
//public byte musicEncode = -1; // TODO: not sure
//public short unknownBitrate = -1; // TODO: not sure
public AudioInfo(String fileName, long fileSize, String title, String artist, String extension) {
this.fileName = fileName;
this.fileSize = fileSize;
this.title = title;
this.artist = artist;
this.extension = extension;
}
public String getFileName() {
return fileName;
}
public long getFileSize() { return fileSize;}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getExtension() {
return extension;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public void setCharacteristics(long duration, int sampleRate, int bitrate, byte channels) {
this.duration = duration;
this.sampleRate = sampleRate;
this.bitrate = bitrate;
this.channels = channels;
}
public long getDuration() {
return duration;
}
public int getSampleRate() {
return sampleRate;
}
public int getBitrate() {
return bitrate;
}
public byte getChannels() {
return channels;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("AudioInfo{");
sb.append("fileName='").append(fileName).append('\'');
sb.append("fileSize='").append(fileSize).append('\'');
sb.append(", title='").append(title).append('\'');
sb.append(", artist='").append(artist).append('\'');
sb.append(", mimeType='").append(mimeType).append('\'');
sb.append('}');
return sb.toString();
}
}
private final HuaweiSupportProvider support;
private AudioInfo currentMusicInfo;
public HuaweiMusicManager(HuaweiSupportProvider support) {
this.support = support;
}
public void addUploadMusic(AudioInfo audioInfo) {
currentMusicInfo = audioInfo;
}
public void uploadMusicInfo(short songIndex, String fileName) {
AudioInfo current = currentMusicInfo;
if(current == null || (!current.getFileName().equals(fileName))) {
LOG.error("Upload file info does not exist.");
return;
}
try {
SendUploadMusicFileInfoResponse sendUploadMusicFileInfoResponse = new SendUploadMusicFileInfoResponse(support,
songIndex, current.getTitle(), current.getArtist());
sendUploadMusicFileInfoResponse.doPerform();
} catch (IOException e) {
LOG.error("Could not send sendUploadMusicFileInfoResponse", e);
}
}
}

View File

@ -105,6 +105,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetA
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetContactsCount;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetEventAlarmList;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetGpsParameterRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetExtendedMusicInfoParams;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetMusicInfoParams;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSmartAlarmList;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWatchfaceParams;
@ -268,6 +270,8 @@ public class HuaweiSupportProvider {
protected HuaweiEphemerisManager huaweiEphemerisManager = new HuaweiEphemerisManager(this);
protected HuaweiMusicManager huaweiMusicManager = new HuaweiMusicManager(this);
//TODO: we need only one instance of manager and all it services.
protected HuaweiP2PManager huaweiP2PManager = new HuaweiP2PManager(this);
@ -299,6 +303,10 @@ public class HuaweiSupportProvider {
return huaweiEphemerisManager;
}
public HuaweiMusicManager getHuaweiMusicManager() {
return huaweiMusicManager;
}
public HuaweiSupportProvider(HuaweiBRSupport support) {
this.brSupport = support;
}
@ -824,6 +832,8 @@ public class HuaweiSupportProvider {
initRequestQueue.add(new GetWatchfaceParams(this));
initRequestQueue.add(new SendCameraRemoteSetupEvent(this, CameraRemote.CameraRemoteSetup.Request.Event.ENABLE_CAMERA));
initRequestQueue.add(new GetAppInfoParams(this));
initRequestQueue.add(new GetMusicInfoParams(this));
initRequestQueue.add(new GetExtendedMusicInfoParams(this));
initRequestQueue.add(new SetActivateOnLiftRequest(this));
initRequestQueue.add(new SetWearLocationRequest(this));
initRequestQueue.add(new SetNavigateOnRotateRequest(this));
@ -1959,6 +1969,10 @@ public class HuaweiSupportProvider {
HuaweiUploadManager.FileUploadInfo fileInfo = new HuaweiUploadManager.FileUploadInfo();
if(huaweiFwHelper.isMusic()) {
getHuaweiMusicManager().addUploadMusic(huaweiFwHelper.getMusicInfo());
}
fileInfo.setFileType(huaweiFwHelper.getFileType());
if (huaweiFwHelper.isWatchface()) {
fileInfo.setFileName(huaweiWatchfaceManager.getRandomName());
@ -1989,10 +2003,14 @@ public class HuaweiSupportProvider {
if (code == 140004) {
LOG.error("Too many watchfaces installed");
HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(HuaweiSupportProvider.this.getContext().getString(R.string.cannot_upload_watchface_too_many_watchfaces_installed), Toast.LENGTH_LONG, GB.ERROR));
} else if (code == 140008) {
LOG.error("File already exists");
HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage("File already exists", Toast.LENGTH_LONG, GB.ERROR)); //TODO: localization
} else if (code == 140009) {
LOG.error("Insufficient space for upload");
HuaweiSupportProvider.this.handleGBDeviceEvent(new GBDeviceEventDisplayMessage(HuaweiSupportProvider.this.getContext().getString(R.string.insufficient_space_for_upload), Toast.LENGTH_LONG, GB.ERROR));
}
}
});

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 GetExtendedMusicInfoParams extends Request {
private final Logger LOG = LoggerFactory.getLogger(GetExtendedMusicInfoParams.class);
public GetExtendedMusicInfoParams(HuaweiSupportProvider support) {
super(support);
this.serviceId = MusicControl.id;
this.commandId = MusicControl.ExtendedMusicInfoParams.id;
}
@Override
protected boolean requestSupported() {
return supportProvider.getHuaweiCoordinator().supportsMusicUploading();
}
@Override
protected List<byte[]> createRequest() throws Request.RequestCreationException {
try {
return new MusicControl.ExtendedMusicInfoParams.Request(paramsProvider).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new Request.RequestCreationException(e);
}
}
@Override
protected void processResponse() throws Request.ResponseParseException {
LOG.info("MusicControl.ExtendedMusicInfoParams processResponse");
if (!(receivedPacket instanceof MusicControl.ExtendedMusicInfoParams.Response))
throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.ExtendedMusicInfoParams.Response.class);
MusicControl.ExtendedMusicInfoParams.Response resp = (MusicControl.ExtendedMusicInfoParams.Response)(receivedPacket);
supportProvider.getHuaweiCoordinator().setExtendedMusicInfoParams(resp.params);
}
}

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 GetMusicInfoParams extends Request {
private final Logger LOG = LoggerFactory.getLogger(GetMusicInfoParams.class);
public GetMusicInfoParams(HuaweiSupportProvider support) {
super(support);
this.serviceId = MusicControl.id;
this.commandId = MusicControl.MusicInfoParams.id;
}
@Override
protected boolean requestSupported() {
return supportProvider.getHuaweiCoordinator().supportsMusicUploading();
}
@Override
protected List<byte[]> createRequest() throws Request.RequestCreationException {
try {
return new MusicControl.MusicInfoParams.Request(paramsProvider).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new Request.RequestCreationException(e);
}
}
@Override
protected void processResponse() throws Request.ResponseParseException {
LOG.info("MusicControl.MusicInfoParams processResponse");
if (!(receivedPacket instanceof MusicControl.MusicInfoParams.Response))
throw new Request.ResponseTypeMismatchException(receivedPacket, MusicControl.MusicInfoParams.Response.class);
MusicControl.MusicInfoParams.Response resp = (MusicControl.MusicInfoParams.Response)(receivedPacket);
supportProvider.getHuaweiCoordinator().setMusicInfoParams(resp.params);
}
}

View File

@ -0,0 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests;
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 SendUploadMusicFileInfoResponse extends Request {
short songIndex;
String songName;
String songArtist;
public SendUploadMusicFileInfoResponse(HuaweiSupportProvider support, short songIndex, String songName, String songArtist) {
super(support);
this.serviceId = MusicControl.id;
this.commandId = MusicControl.UploadMusicFileInfo.id;
this.songIndex = songIndex;
this.songName = songName;
this.songArtist = songArtist;
this.addToResponse = false;
}
@Override
protected List<byte[]> createRequest() throws Request.RequestCreationException {
try {
return new MusicControl.UploadMusicFileInfo.UploadMusicFileInfoResponse(this.paramsProvider, this.songIndex, this.songName, this.songArtist).serialize();
} catch (HuaweiPacket.CryptoException e) {
throw new Request.RequestCreationException(e);
}
}
}