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