• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.avrcp;
18 
19 import android.graphics.Bitmap;
20 import android.util.Log;
21 
22 import com.android.bluetooth.audio_util.Image;
23 import com.android.bluetooth.avrcpcontroller.BipEncoding;
24 import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
25 import com.android.bluetooth.avrcpcontroller.BipImageFormat;
26 import com.android.bluetooth.avrcpcontroller.BipImageProperties;
27 import com.android.bluetooth.avrcpcontroller.BipPixel;
28 
29 import java.io.ByteArrayOutputStream;
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
32 
33 /**
34  * An object to represent a piece of cover artwork/
35  *
36  * <p>This object abstracts away the actual storage method and provides a means for others to
37  * understand available formats and get the underlying image in a particular format.
38  *
39  * <p>All return values are ready to use by a BIP server.
40  */
41 public class CoverArt {
42     private static final String TAG = CoverArt.class.getSimpleName();
43     private static final BipPixel PIXEL_THUMBNAIL = BipPixel.createFixed(200, 200);
44 
45     private String mImageHandle = null;
46     private Bitmap mImage = null;
47 
48     /** Create a CoverArt object from an audio_util Image abstraction */
CoverArt(Image image)49     CoverArt(Image image) {
50         // Create a scaled version of the image for now, as consumers don't need
51         // anything larger than this at the moment. Also makes each image gathered
52         // the same dimensions for hashing purposes.
53         mImage = Bitmap.createScaledBitmap(image.getImage(), 200, 200, false);
54     }
55 
56     /**
57      * Get the image handle that has been associated with this image.
58      *
59      * <p>If this returns null then you will fail to generate image properties
60      */
getImageHandle()61     public String getImageHandle() {
62         return mImageHandle;
63     }
64 
65     /**
66      * Set the image handle that has been associated with this image.
67      *
68      * <p>This is required to generate image properties
69      */
setImageHandle(String handle)70     public void setImageHandle(String handle) {
71         mImageHandle = handle;
72     }
73 
74     /** Covert a Bitmap to a byte array with an image format without lossy compression */
toByteArray(Bitmap bitmap)75     private byte[] toByteArray(Bitmap bitmap) {
76         if (bitmap == null) return null;
77         ByteArrayOutputStream buffer =
78                 new ByteArrayOutputStream(bitmap.getWidth() * bitmap.getHeight());
79         bitmap.compress(Bitmap.CompressFormat.PNG, 100, buffer);
80         return buffer.toByteArray();
81     }
82 
83     /** Get a hash code of this CoverArt image */
getImageHash()84     public String getImageHash() {
85         byte[] image = toByteArray(mImage);
86         if (image == null) return null;
87         String hash = null;
88         try {
89             final MessageDigest digest = MessageDigest.getInstance("MD5");
90             digest.update(/* Bitmap to input stream */ image);
91             byte[] messageDigest = digest.digest();
92 
93             StringBuffer hexString = new StringBuffer();
94             for (int i = 0; i < messageDigest.length; i++) {
95                 hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
96             }
97             hash = hexString.toString();
98         } catch (NoSuchAlgorithmException e) {
99             Log.e(TAG, "Failed to hash bitmap", e);
100         }
101         return hash;
102     }
103 
104     /** Get the cover artwork image bytes in the native format */
getImage()105     public byte[] getImage() {
106         debug("GetImage(native)");
107         if (mImage == null) return null;
108         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
109         mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
110         return outputStream.toByteArray();
111     }
112 
113     /** Get the cover artwork image bytes in the given encoding and pixel size */
getImage(BipImageDescriptor descriptor)114     public byte[] getImage(BipImageDescriptor descriptor) {
115         debug("GetImage(descriptor=" + descriptor);
116         if (mImage == null) return null;
117         if (descriptor == null) return getImage();
118         if (!isDescriptorValid(descriptor)) {
119             error("Given format isn't available for this image");
120             return null;
121         }
122 
123         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
124         mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
125         return outputStream.toByteArray();
126     }
127 
128     /** Determine if a given image descriptor is valid */
isDescriptorValid(BipImageDescriptor descriptor)129     private boolean isDescriptorValid(BipImageDescriptor descriptor) {
130         debug("isDescriptorValid(descriptor=" + descriptor + ")");
131         if (descriptor == null) return false;
132 
133         BipEncoding encoding = descriptor.getEncoding();
134         BipPixel pixel = descriptor.getPixel();
135 
136         if (encoding.getType() == BipEncoding.JPEG && PIXEL_THUMBNAIL.equals(pixel)) {
137             return true;
138         }
139         return false;
140     }
141 
142     /** Get the cover artwork image bytes as a 200 x 200 JPEG thumbnail */
getThumbnail()143     public byte[] getThumbnail() {
144         debug("GetImageThumbnail()");
145         if (mImage == null) return null;
146         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
147         mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
148         return outputStream.toByteArray();
149     }
150 
151     /** Get the set of image properties that the cover artwork can be turned into */
getImageProperties()152     public BipImageProperties getImageProperties() {
153         debug("GetImageProperties()");
154         if (mImage == null) {
155             error("Can't associate properties with a null image");
156             return null;
157         }
158         if (mImageHandle == null) {
159             error("No handle has been associated with this image. Cannot build properties.");
160             return null;
161         }
162         BipImageProperties.Builder builder = new BipImageProperties.Builder();
163         BipEncoding encoding = new BipEncoding(BipEncoding.JPEG);
164         BipPixel pixel = BipPixel.createFixed(200, 200);
165         BipImageFormat format = BipImageFormat.createNative(encoding, pixel, -1);
166 
167         builder.setImageHandle(mImageHandle);
168         builder.addNativeFormat(format);
169 
170         BipImageProperties properties = builder.build();
171         return properties;
172     }
173 
174     /** Get the storage size of this image in bytes */
size()175     public int size() {
176         return mImage != null ? mImage.getAllocationByteCount() : 0;
177     }
178 
179     @Override
toString()180     public String toString() {
181         return "{handle=" + mImageHandle + ", size=" + size() + " }";
182     }
183 
184     /** Print a message to DEBUG if debug output is enabled */
debug(String msg)185     private void debug(String msg) {
186         Log.d(TAG, msg);
187     }
188 
189     /** Print a message to ERROR */
error(String msg)190     private void error(String msg) {
191         Log.e(TAG, msg);
192     }
193 }
194