blob: 8f09949d7e79ead19555fdd176f42472cf31577b [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.avrcp;
import android.graphics.Bitmap;
import android.util.Log;
import com.android.bluetooth.audio_util.Image;
import com.android.bluetooth.avrcpcontroller.BipEncoding;
import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
import com.android.bluetooth.avrcpcontroller.BipImageFormat;
import com.android.bluetooth.avrcpcontroller.BipImageProperties;
import com.android.bluetooth.avrcpcontroller.BipPixel;
import java.io.ByteArrayOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* An object to represent a piece of cover artwork/
*
* This object abstracts away the actual storage method and provides a means for others to
* understand available formats and get the underlying image in a particular format.
*
* All return values are ready to use by a BIP server.
*/
public class CoverArt {
private static final String TAG = "CoverArt";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final BipPixel PIXEL_THUMBNAIL = BipPixel.createFixed(200, 200);
private String mImageHandle = null;
private Bitmap mImage = null;
/**
* Create a CoverArt object from an audio_util Image abstraction
*/
CoverArt(Image image) {
// Create a scaled version of the image for now, as consumers don't need
// anything larger than this at the moment. Also makes each image gathered
// the same dimensions for hashing purposes.
mImage = Bitmap.createScaledBitmap(image.getImage(), 200, 200, false);
}
/**
* Get the image handle that has been associated with this image.
*
* If this returns null then you will fail to generate image properties
*/
public String getImageHandle() {
return mImageHandle;
}
/**
* Set the image handle that has been associated with this image.
*
* This is required to generate image properties
*/
public void setImageHandle(String handle) {
mImageHandle = handle;
}
/**
* Covert a Bitmap to a byte array with an image format without lossy compression
*/
private byte[] toByteArray(Bitmap bitmap) {
if (bitmap == null) return null;
ByteArrayOutputStream buffer = new ByteArrayOutputStream(
bitmap.getWidth() * bitmap.getHeight());
bitmap.compress(Bitmap.CompressFormat.PNG, 100, buffer);
return buffer.toByteArray();
}
/**
* Get a hash code of this CoverArt image
*/
public String getImageHash() {
byte[] image = toByteArray(mImage);
if (image == null) return null;
String hash = null;
try {
final byte[] digestBytes;
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(/* Bitmap to input stream */ image);
byte[] messageDigest = digest.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}
hash = hexString.toString();
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "Failed to hash bitmap", e);
}
return hash;
}
/**
* Get the cover artwork image bytes in the native format
*/
public byte[] getImage() {
debug("GetImage(native)");
if (mImage == null) return null;
byte[] bytes = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
bytes = outputStream.toByteArray();
return bytes;
}
/**
* Get the cover artwork image bytes in the given encoding and pixel size
*/
public byte[] getImage(BipImageDescriptor descriptor) {
debug("GetImage(descriptor=" + descriptor);
if (mImage == null) return null;
if (descriptor == null) return getImage();
if (!isDescriptorValid(descriptor)) {
error("Given format isn't available for this image");
return null;
}
byte[] bytes = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
bytes = outputStream.toByteArray();
return bytes;
}
/**
* Determine if a given image descriptor is valid
*/
private boolean isDescriptorValid(BipImageDescriptor descriptor) {
debug("isDescriptorValid(descriptor=" + descriptor + ")");
if (descriptor == null) return false;
BipEncoding encoding = descriptor.getEncoding();
BipPixel pixel = descriptor.getPixel();
if (encoding.getType() == BipEncoding.JPEG && PIXEL_THUMBNAIL.equals(pixel)) {
return true;
}
return false;
}
/**
* Get the cover artwork image bytes as a 200 x 200 JPEG thumbnail
*/
public byte[] getThumbnail() {
debug("GetImageThumbnail()");
if (mImage == null) return null;
byte[] bytes = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
bytes = outputStream.toByteArray();
return bytes;
}
/**
* Get the set of image properties that the cover artwork can be turned into
*/
public BipImageProperties getImageProperties() {
debug("GetImageProperties()");
if (mImage == null) {
error("Can't associate properties with a null image");
return null;
}
if (mImageHandle == null) {
error("No handle has been associated with this image. Cannot build properties.");
return null;
}
BipImageProperties.Builder builder = new BipImageProperties.Builder();
BipEncoding encoding = new BipEncoding(BipEncoding.JPEG);
BipPixel pixel = BipPixel.createFixed(200, 200);
BipImageFormat format = BipImageFormat.createNative(encoding, pixel, -1);
builder.setImageHandle(mImageHandle);
builder.addNativeFormat(format);
BipImageProperties properties = builder.build();
return properties;
}
/**
* Get the storage size of this image in bytes
*/
public int size() {
return mImage != null ? mImage.getAllocationByteCount() : 0;
}
@Override
public String toString() {
return "{handle=" + mImageHandle + ", size=" + size() + " }";
}
/**
* Print a message to DEBUG if debug output is enabled
*/
private void debug(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
}
}
/**
* Print a message to ERROR
*/
private void error(String msg) {
Log.e(TAG, msg);
}
}