Make Huawei AudioInfo reusable

This commit is contained in:
José Rebelo
2025-04-02 20:26:00 +01:00
parent 41024f29a8
commit e9bb18b811
5 changed files with 211 additions and 189 deletions
@@ -36,8 +36,8 @@ 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;
import nodomain.freeyourgadget.gadgetbridge.util.audio.AudioInfo;
public class HuaweiInstallHandler implements InstallHandler {
private static final Logger LOG = LoggerFactory.getLogger(HuaweiInstallHandler.class);
@@ -66,7 +66,7 @@ public class HuaweiInstallHandler implements InstallHandler {
}
//TODO: add proper checks
private boolean checkMediaCompatibility(HuaweiMusicUtils.MusicCapabilities capabilities, HuaweiMusicManager.AudioInfo currentMusicInfo) {
private boolean checkMediaCompatibility(HuaweiMusicUtils.MusicCapabilities capabilities, AudioInfo currentMusicInfo) {
if (capabilities == null) {
LOG.error("No media info from device");
return false;
@@ -257,7 +257,7 @@ public class HuaweiInstallHandler implements InstallHandler {
if (capabilities == null) {
capabilities = huaweiCoordinatorSupplier.getHuaweiCoordinator().getMusicInfoParams();
}
HuaweiMusicManager.AudioInfo currentMusicInfo = helper.getMusicInfo();
AudioInfo currentMusicInfo = helper.getMusicInfo();
boolean isMediaCompatible = checkMediaCompatibility(capabilities, currentMusicInfo);
@@ -17,17 +17,10 @@
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;
@@ -47,6 +40,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBZipFile;
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
import nodomain.freeyourgadget.gadgetbridge.util.ZipFileException;
import nodomain.freeyourgadget.gadgetbridge.util.audio.AudioInfo;
import nodomain.freeyourgadget.gadgetbridge.util.audio.MusicUtils;
public class HuaweiFwHelper {
private static final Logger LOG = LoggerFactory.getLogger(HuaweiFwHelper.class);
@@ -62,7 +57,7 @@ public class HuaweiFwHelper {
Bitmap previewBitmap;
HuaweiWatchfaceManager.WatchfaceDescription watchfaceDescription;
HuaweiAppManager.AppConfig appConfig;
HuaweiMusicManager.AudioInfo musicInfo;
AudioInfo musicInfo;
Context mContext;
public boolean isFirmware = false;
@@ -70,16 +65,8 @@ public class HuaweiFwHelper {
public HuaweiFwHelper(final Uri uri, final Context context) {
this.uri = uri;
final UriHelper uriHelper;
this.mContext = context;
try {
uriHelper = UriHelper.get(uri, context);
} catch (final IOException e) {
LOG.error("Failed to get uri helper for {}", uri, e);
return;
}
parseFile();
}
@@ -90,9 +77,11 @@ public class HuaweiFwHelper {
} else if (parseAsMusic()) {
fileType = FileUpload.Filetype.music;
} else if (parseAsApp()) {
assert appConfig != null;
assert appConfig.bundleName != null;
fileType = FileUpload.Filetype.app;
} else if (parseAsWatchFace()) {
assert watchfaceDescription != null;
assert watchfaceDescription.screen != null;
assert watchfaceDescription.title != null;
fileType = FileUpload.Filetype.watchface;
@@ -158,76 +147,6 @@ public class HuaweiFwHelper {
return false;
}
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();
@@ -245,19 +164,10 @@ public class HuaweiFwHelper {
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);
musicInfo = MusicUtils.audioInfoFromUri(mContext, uri);
if (musicInfo == null)
return false;
musicInfo.setMimeType(mimeType);
byte[] musicData = getFileData(uriHelper);
fileName = musicInfo.getFileName();
@@ -337,8 +247,8 @@ public class HuaweiFwHelper {
byte[] bom = new byte[3];
// get the first 3 bytes
bb.get(bom, 0, bom.length);
String content = new String(GB.hexdump(bom));
String xmlDescription = null;
String content = GB.hexdump(bom);
String xmlDescription;
if ("efbbbf".equalsIgnoreCase(content)) {
byte[] contentAfterFirst3Bytes = new byte[bytesDescription.length - 3];
bb.get(contentAfterFirst3Bytes, 0, contentAfterFirst3Bytes.length);
@@ -405,7 +315,7 @@ public class HuaweiFwHelper {
return appConfig;
}
public HuaweiMusicManager.AudioInfo getMusicInfo() {
public AudioInfo getMusicInfo() {
return musicInfo;
}
@@ -39,97 +39,11 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetM
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMusicOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendUploadMusicFileInfoResponse;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.audio.AudioInfo;
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;
@@ -0,0 +1,92 @@
package nodomain.freeyourgadget.gadgetbridge.util.audio;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.util.GBToStringBuilder;
public class AudioInfo {
private final String fileName;
private final long fileSize;
private final String title;
private final String artist;
private final String mimeType;
private final String extension;
private long duration;
private int sampleRate;
private int bitrate;
private byte channels;
public AudioInfo(final String fileName,
final long fileSize,
final String title,
final String artist,
final String mimeType,
final String extension) {
this.fileName = fileName;
this.fileSize = fileSize;
this.title = title;
this.artist = artist;
this.mimeType = mimeType;
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 setCharacteristics(final long duration, final int sampleRate, final int bitrate, final 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;
}
@NonNull
@Override
public String toString() {
final GBToStringBuilder tsb = new GBToStringBuilder(this);
tsb.append("fileName", fileName);
tsb.append("fileSize", fileSize);
tsb.append("title", title);
tsb.append("artist", artist);
tsb.append("mimeType", mimeType);
return tsb.toString();
}
}
@@ -0,0 +1,106 @@
package nodomain.freeyourgadget.gadgetbridge.util.audio;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.R;
public class MusicUtils {
private static final Logger LOG = LoggerFactory.getLogger(MusicUtils.class);
@Nullable
public static AudioInfo audioInfoFromUri(final Context context, final Uri uri) {
final ContentResolver contentResolver = context.getContentResolver();
final String mimeType = context.getContentResolver().getType(uri);
if (mimeType == null || !mimeType.startsWith("audio/")) {
LOG.warn("Mime type is not an audio file: {}", mimeType);
return null;
}
final String[] filePathColumn = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
final Cursor cursor = contentResolver.query(uri, filePathColumn, null, null, null);
if (cursor == null)
return null;
cursor.moveToFirst();
final int fileNameIndex = cursor.getColumnIndex(filePathColumn[0]);
final String fileName = cursor.getString(fileNameIndex);
final int fileSizeIndex = cursor.getColumnIndex(filePathColumn[1]);
final long fileSize = cursor.getLong(fileSizeIndex);
cursor.close();
final MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(context, uri);
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 = context.getString(R.string.unknown);
}
String extension = getExtension(fileName);
if (!TextUtils.isEmpty(extension)) {
extension = extension.toLowerCase();
}
final AudioInfo audioInfo = new AudioInfo(fileName, fileSize, title, artist, mimeType, extension);
try {
final MediaExtractor mex = new MediaExtractor();
mex.setDataSource(context, uri, null);
final 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.trace("bitRate: {}", bitrate);
LOG.trace("sampleRate: {}", sampleRate);
LOG.trace("channelCount: {}", channels);
LOG.trace("duration: {}", duration);
audioInfo.setCharacteristics(duration, sampleRate, bitrate, (byte) channels);
} catch (final IOException e) {
LOG.error("Failed to set data source on media extractor", e);
}
return audioInfo;
}
private static String getNameWithoutExtension(final String fileName) {
return fileName.indexOf(".") > 0 ? fileName.substring(0, fileName.lastIndexOf(".")) : fileName;
}
@Nullable
private static String getExtension(final String fileName) {
if (TextUtils.isEmpty(fileName)) {
return null;
}
final int lastIndexOf = fileName.lastIndexOf(".");
if (lastIndexOf >= 0 && lastIndexOf + 1 < fileName.length()) {
return fileName.substring(lastIndexOf + 1);
}
return null;
}
}