1 /* 2 * Copyright (C) 2019 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.avrcpcontroller; 18 19 import android.util.Log; 20 import android.util.Xml; 21 22 import org.xmlpull.v1.XmlPullParser; 23 import org.xmlpull.v1.XmlPullParserException; 24 import org.xmlpull.v1.XmlPullParserFactory; 25 import org.xmlpull.v1.XmlSerializer; 26 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.StringWriter; 30 import java.io.UnsupportedEncodingException; 31 32 /** 33 * Contains the metadata that describes either (1) the desired size of a image to be downloaded or 34 * (2) the extact size of an image to be uploaded. 35 * 36 * <p>When using this to assert the size of an image to download/pull, it's best to derive this 37 * specific descriptor from any of the available BipImageFormat options returned from a 38 * RequestGetImageProperties. Note that if a BipImageFormat is not of a fixed size type then you 39 * must arrive on a desired fixed size for this descriptor. 40 * 41 * <p>When using this to denote the size of an image when pushing an image for transfer this 42 * descriptor must match the metadata of the image being sent. 43 * 44 * <p>Note, the encoding and pixel values are mandatory by specification. The version number is 45 * fixed. All other values are optional. The transformation field is to have *one* selected option 46 * in it. 47 * 48 * <p>Example: < image-descriptor version=“1.0” > < image encoding=“JPEG” pixel=“1280*960” 49 * size=“500000”/> < /image-descriptor > 50 */ 51 public class BipImageDescriptor { 52 private static final String TAG = "avrcpcontroller.BipImageDescriptor"; 53 private static final String sVersion = "1.0"; 54 55 /** A Builder for an ImageDescriptor object */ 56 public static class Builder { 57 private BipImageDescriptor mImageDescriptor = new BipImageDescriptor(); 58 59 /** 60 * Set the encoding for the descriptor you're building using a BipEncoding object 61 * 62 * @param encoding The encoding you would like to set 63 * @return This object so you can continue building 64 */ setEncoding(BipEncoding encoding)65 public Builder setEncoding(BipEncoding encoding) { 66 mImageDescriptor.mEncoding = encoding; 67 return this; 68 } 69 70 /** 71 * Set the encoding for the descriptor you're building using a BipEncoding.* type value 72 * 73 * @param encoding The encoding you would like to set as a BipEncoding.* type value 74 * @return This object so you can continue building 75 */ setEncoding(int encoding)76 public Builder setEncoding(int encoding) { 77 mImageDescriptor.mEncoding = new BipEncoding(encoding, null); 78 return this; 79 } 80 81 /** 82 * Set the encoding for the descriptor you're building using a BIP defined string name of 83 * the encoding you want 84 * 85 * @param encoding The encoding you would like to set as a BIP spec defined string 86 * @return This object so you can continue building 87 */ setPropietaryEncoding(String encoding)88 public Builder setPropietaryEncoding(String encoding) { 89 mImageDescriptor.mEncoding = new BipEncoding(BipEncoding.USR_XXX, encoding); 90 return this; 91 } 92 93 /** 94 * Set the fixed X by Y image dimensions for the descriptor you're building 95 * 96 * @param width The number of pixels in width of the image 97 * @param height The number of pixels in height of the image 98 * @return This object so you can continue building 99 */ setFixedDimensions(int width, int height)100 public Builder setFixedDimensions(int width, int height) { 101 mImageDescriptor.mPixel = BipPixel.createFixed(width, height); 102 return this; 103 } 104 105 /** 106 * Set the transformation used for the descriptor you're building 107 * 108 * @param transformation The BipTransformation.* type value of the used transformation 109 * @return This object so you can continue building 110 */ setTransformation(int transformation)111 public Builder setTransformation(int transformation) { 112 mImageDescriptor.mTransformation = new BipTransformation(transformation); 113 return this; 114 } 115 116 /** 117 * Set the image file size for the descriptor you're building 118 * 119 * @param size The image size in bytes 120 * @return This object so you can continue building 121 */ setFileSize(int size)122 public Builder setFileSize(int size) { 123 mImageDescriptor.mSize = size; 124 return this; 125 } 126 127 /** 128 * Set the max file size of the image for the descriptor you're building 129 * 130 * @param size The maxe image size in bytes 131 * @return This object so you can continue building 132 */ setMaxFileSize(int size)133 public Builder setMaxFileSize(int size) { 134 mImageDescriptor.mMaxSize = size; 135 return this; 136 } 137 138 /** 139 * Build the object 140 * 141 * @return A BipImageDescriptor object 142 */ build()143 public BipImageDescriptor build() { 144 return mImageDescriptor; 145 } 146 } 147 148 /** The encoding of the image, required by the specification */ 149 private BipEncoding mEncoding = null; 150 151 /** The width and height of the image, required by the specification */ 152 private BipPixel mPixel = null; 153 154 /** 155 * The transformation to be applied to the image, *one* of BipTransformation.STRETCH, 156 * BipTransformation.CROP, or BipTransformation.FILL placed into a BipTransformation object 157 */ 158 private BipTransformation mTransformation = null; 159 160 /** The size in bytes of the image */ 161 private int mSize = -1; 162 163 /** 164 * The max size in bytes of the image. 165 * 166 * <p>Optional, used only when describing an image to pull 167 */ 168 private int mMaxSize = -1; 169 BipImageDescriptor()170 private BipImageDescriptor() {} 171 BipImageDescriptor(InputStream inputStream)172 public BipImageDescriptor(InputStream inputStream) { 173 parse(inputStream); 174 } 175 parse(InputStream inputStream)176 private void parse(InputStream inputStream) { 177 try { 178 XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser(); 179 xpp.setInput(inputStream, "utf-8"); 180 int event = xpp.getEventType(); 181 while (event != XmlPullParser.END_DOCUMENT) { 182 switch (event) { 183 case XmlPullParser.START_TAG: 184 String tag = xpp.getName(); 185 if (tag.equals("image")) { 186 mEncoding = new BipEncoding(xpp.getAttributeValue(null, "encoding")); 187 mPixel = new BipPixel(xpp.getAttributeValue(null, "pixel")); 188 mSize = parseInt(xpp.getAttributeValue(null, "size")); 189 mMaxSize = parseInt(xpp.getAttributeValue(null, "maxsize")); 190 mTransformation = 191 new BipTransformation( 192 xpp.getAttributeValue(null, "transformation")); 193 } else { 194 Log.w(TAG, "Unrecognized tag in x-bt/img-Description object: " + tag); 195 } 196 break; 197 case XmlPullParser.END_TAG: 198 break; 199 } 200 event = xpp.next(); 201 } 202 return; 203 } catch (XmlPullParserException e) { 204 Log.e(TAG, "XML parser error when parsing XML", e); 205 } catch (IOException e) { 206 Log.e(TAG, "I/O error when parsing XML", e); 207 } 208 throw new ParseException("Failed to parse image-descriptor from stream"); 209 } 210 parseInt(String s)211 private static int parseInt(String s) { 212 if (s == null) return -1; 213 try { 214 return Integer.parseInt(s); 215 } catch (NumberFormatException e) { 216 error("Failed to parse '" + s + "'"); 217 } 218 return -1; 219 } 220 getEncoding()221 public BipEncoding getEncoding() { 222 return mEncoding; 223 } 224 getPixel()225 public BipPixel getPixel() { 226 return mPixel; 227 } 228 getTransformation()229 public BipTransformation getTransformation() { 230 return mTransformation; 231 } 232 getSize()233 public int getSize() { 234 return mSize; 235 } 236 getMaxSize()237 public int getMaxSize() { 238 return mMaxSize; 239 } 240 241 /** 242 * Serialize this object into a byte array ready for transfer overOBEX 243 * 244 * @return A byte array containing this object's info, or null on error. 245 */ serialize()246 public byte[] serialize() { 247 String s = toString(); 248 try { 249 return s != null ? s.getBytes("UTF-8") : null; 250 } catch (UnsupportedEncodingException e) { 251 return null; 252 } 253 } 254 255 @Override equals(Object o)256 public boolean equals(Object o) { 257 if (o == this) return true; 258 if (!(o instanceof BipImageDescriptor)) return false; 259 260 BipImageDescriptor d = (BipImageDescriptor) o; 261 return d.getEncoding() == getEncoding() 262 && d.getPixel() == getPixel() 263 && d.getTransformation() == getTransformation() 264 && d.getSize() == getSize() 265 && d.getMaxSize() == getMaxSize(); 266 } 267 268 @Override toString()269 public String toString() { 270 if (mEncoding == null || mPixel == null) { 271 error( 272 "Missing required fields [ " 273 + (mEncoding == null ? "encoding " : "") 274 + (mPixel == null ? "pixel " : "")); 275 return null; 276 } 277 StringWriter writer = new StringWriter(); 278 XmlSerializer xmlMsgElement = Xml.newSerializer(); 279 try { 280 xmlMsgElement.setOutput(writer); 281 xmlMsgElement.startDocument("UTF-8", true); 282 xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 283 xmlMsgElement.startTag(null, "image-descriptor"); 284 xmlMsgElement.attribute(null, "version", sVersion); 285 xmlMsgElement.startTag(null, "image"); 286 xmlMsgElement.attribute(null, "encoding", mEncoding.toString()); 287 xmlMsgElement.attribute(null, "pixel", mPixel.toString()); 288 if (mSize != -1) { 289 xmlMsgElement.attribute(null, "size", Integer.toString(mSize)); 290 } 291 if (mMaxSize != -1) { 292 xmlMsgElement.attribute(null, "maxsize", Integer.toString(mMaxSize)); 293 } 294 if (mTransformation != null && mTransformation.supportsAny()) { 295 xmlMsgElement.attribute(null, "transformation", mTransformation.toString()); 296 } 297 xmlMsgElement.endTag(null, "image"); 298 xmlMsgElement.endTag(null, "image-descriptor"); 299 xmlMsgElement.endDocument(); 300 return writer.toString(); 301 } catch (IllegalArgumentException e) { 302 error(e.toString()); 303 } catch (IllegalStateException e) { 304 error(e.toString()); 305 } catch (IOException e) { 306 error(e.toString()); 307 } 308 return null; 309 } 310 error(String msg)311 private static void error(String msg) { 312 Log.e(TAG, msg); 313 } 314 } 315