1/* 2 * Copyright (C) 2010 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 17package android.mtp; 18 19import android.content.ContentProviderClient; 20import android.database.Cursor; 21import android.net.Uri; 22import android.os.RemoteException; 23import android.provider.MediaStore.Audio; 24import android.provider.MediaStore.Files; 25import android.provider.MediaStore.Images; 26import android.util.Log; 27 28import java.util.ArrayList; 29 30/** 31 * MtpPropertyGroup represents a list of MTP properties. 32 * {@hide} 33 */ 34class MtpPropertyGroup { 35 private static final String TAG = MtpPropertyGroup.class.getSimpleName(); 36 37 private class Property { 38 int code; 39 int type; 40 int column; 41 42 Property(int code, int type, int column) { 43 this.code = code; 44 this.type = type; 45 this.column = column; 46 } 47 } 48 49 private final ContentProviderClient mProvider; 50 private final String mVolumeName; 51 private final Uri mUri; 52 53 // list of all properties in this group 54 private final Property[] mProperties; 55 56 // list of columns for database query 57 private String[] mColumns; 58 59 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; 60 61 // constructs a property group for a list of properties 62 public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) { 63 mProvider = provider; 64 mVolumeName = volumeName; 65 mUri = Files.getMtpObjectsUri(volumeName); 66 67 int count = properties.length; 68 ArrayList<String> columns = new ArrayList<>(count); 69 columns.add(Files.FileColumns._ID); 70 71 mProperties = new Property[count]; 72 for (int i = 0; i < count; i++) { 73 mProperties[i] = createProperty(properties[i], columns); 74 } 75 count = columns.size(); 76 mColumns = new String[count]; 77 for (int i = 0; i < count; i++) { 78 mColumns[i] = columns.get(i); 79 } 80 } 81 82 private Property createProperty(int code, ArrayList<String> columns) { 83 String column = null; 84 int type; 85 86 switch (code) { 87 case MtpConstants.PROPERTY_STORAGE_ID: 88 type = MtpConstants.TYPE_UINT32; 89 break; 90 case MtpConstants.PROPERTY_OBJECT_FORMAT: 91 type = MtpConstants.TYPE_UINT16; 92 break; 93 case MtpConstants.PROPERTY_PROTECTION_STATUS: 94 type = MtpConstants.TYPE_UINT16; 95 break; 96 case MtpConstants.PROPERTY_OBJECT_SIZE: 97 type = MtpConstants.TYPE_UINT64; 98 break; 99 case MtpConstants.PROPERTY_OBJECT_FILE_NAME: 100 type = MtpConstants.TYPE_STR; 101 break; 102 case MtpConstants.PROPERTY_NAME: 103 type = MtpConstants.TYPE_STR; 104 break; 105 case MtpConstants.PROPERTY_DATE_MODIFIED: 106 type = MtpConstants.TYPE_STR; 107 break; 108 case MtpConstants.PROPERTY_DATE_ADDED: 109 type = MtpConstants.TYPE_STR; 110 break; 111 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: 112 column = Audio.AudioColumns.YEAR; 113 type = MtpConstants.TYPE_STR; 114 break; 115 case MtpConstants.PROPERTY_PARENT_OBJECT: 116 type = MtpConstants.TYPE_UINT32; 117 break; 118 case MtpConstants.PROPERTY_PERSISTENT_UID: 119 type = MtpConstants.TYPE_UINT128; 120 break; 121 case MtpConstants.PROPERTY_DURATION: 122 column = Audio.AudioColumns.DURATION; 123 type = MtpConstants.TYPE_UINT32; 124 break; 125 case MtpConstants.PROPERTY_TRACK: 126 column = Audio.AudioColumns.TRACK; 127 type = MtpConstants.TYPE_UINT16; 128 break; 129 case MtpConstants.PROPERTY_DISPLAY_NAME: 130 type = MtpConstants.TYPE_STR; 131 break; 132 case MtpConstants.PROPERTY_ARTIST: 133 type = MtpConstants.TYPE_STR; 134 break; 135 case MtpConstants.PROPERTY_ALBUM_NAME: 136 type = MtpConstants.TYPE_STR; 137 break; 138 case MtpConstants.PROPERTY_ALBUM_ARTIST: 139 column = Audio.AudioColumns.ALBUM_ARTIST; 140 type = MtpConstants.TYPE_STR; 141 break; 142 case MtpConstants.PROPERTY_GENRE: 143 // genre requires a special query 144 type = MtpConstants.TYPE_STR; 145 break; 146 case MtpConstants.PROPERTY_COMPOSER: 147 column = Audio.AudioColumns.COMPOSER; 148 type = MtpConstants.TYPE_STR; 149 break; 150 case MtpConstants.PROPERTY_DESCRIPTION: 151 column = Images.ImageColumns.DESCRIPTION; 152 type = MtpConstants.TYPE_STR; 153 break; 154 case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC: 155 case MtpConstants.PROPERTY_AUDIO_BITRATE: 156 case MtpConstants.PROPERTY_SAMPLE_RATE: 157 // these are special cased 158 type = MtpConstants.TYPE_UINT32; 159 break; 160 case MtpConstants.PROPERTY_BITRATE_TYPE: 161 case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS: 162 // these are special cased 163 type = MtpConstants.TYPE_UINT16; 164 break; 165 default: 166 type = MtpConstants.TYPE_UNDEFINED; 167 Log.e(TAG, "unsupported property " + code); 168 break; 169 } 170 171 if (column != null) { 172 columns.add(column); 173 return new Property(code, type, columns.size() - 1); 174 } else { 175 return new Property(code, type, -1); 176 } 177 } 178 179 private String queryAudio(String path, String column) { 180 Cursor c = null; 181 try { 182 c = mProvider.query(Audio.Media.getContentUri(mVolumeName), 183 new String [] { column }, 184 PATH_WHERE, new String[] {path}, null, null); 185 if (c != null && c.moveToNext()) { 186 return c.getString(0); 187 } else { 188 return ""; 189 } 190 } catch (Exception e) { 191 return ""; 192 } finally { 193 if (c != null) { 194 c.close(); 195 } 196 } 197 } 198 199 private String queryGenre(String path) { 200 Cursor c = null; 201 try { 202 c = mProvider.query(Audio.Genres.getContentUri(mVolumeName), 203 new String [] { Audio.GenresColumns.NAME }, 204 PATH_WHERE, new String[] {path}, null, null); 205 if (c != null && c.moveToNext()) { 206 return c.getString(0); 207 } else { 208 return ""; 209 } 210 } catch (Exception e) { 211 return ""; 212 } finally { 213 if (c != null) { 214 c.close(); 215 } 216 } 217 } 218 219 /** 220 * Gets the values of the properties represented by this property group for the given 221 * object and adds them to the given property list. 222 * @return Response_OK if the operation succeeded. 223 */ 224 public int getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list) { 225 Cursor c = null; 226 int id = object.getId(); 227 String path = object.getPath().toString(); 228 for (Property property : mProperties) { 229 if (property.column != -1 && c == null) { 230 try { 231 // Look up the entry in MediaProvider only if one of those properties is needed. 232 c = mProvider.query(mUri, mColumns, 233 PATH_WHERE, new String[] {path}, null, null); 234 if (c != null && !c.moveToNext()) { 235 c.close(); 236 c = null; 237 } 238 } catch (RemoteException e) { 239 Log.e(TAG, "Mediaprovider lookup failed"); 240 } 241 } 242 switch (property.code) { 243 case MtpConstants.PROPERTY_PROTECTION_STATUS: 244 // protection status is always 0 245 list.append(id, property.code, property.type, 0); 246 break; 247 case MtpConstants.PROPERTY_NAME: 248 case MtpConstants.PROPERTY_OBJECT_FILE_NAME: 249 case MtpConstants.PROPERTY_DISPLAY_NAME: 250 list.append(id, property.code, object.getName()); 251 break; 252 case MtpConstants.PROPERTY_DATE_MODIFIED: 253 case MtpConstants.PROPERTY_DATE_ADDED: 254 // convert from seconds to DateTime 255 list.append(id, property.code, 256 format_date_time(object.getModifiedTime())); 257 break; 258 case MtpConstants.PROPERTY_STORAGE_ID: 259 list.append(id, property.code, property.type, object.getStorageId()); 260 break; 261 case MtpConstants.PROPERTY_OBJECT_FORMAT: 262 list.append(id, property.code, property.type, object.getFormat()); 263 break; 264 case MtpConstants.PROPERTY_OBJECT_SIZE: 265 list.append(id, property.code, property.type, object.getSize()); 266 break; 267 case MtpConstants.PROPERTY_PARENT_OBJECT: 268 list.append(id, property.code, property.type, 269 object.getParent().isRoot() ? 0 : object.getParent().getId()); 270 break; 271 case MtpConstants.PROPERTY_PERSISTENT_UID: 272 // The persistent uid must be unique and never reused among all objects, 273 // and remain the same between sessions. 274 long puid = (object.getPath().toString().hashCode() << 32) 275 + object.getModifiedTime(); 276 list.append(id, property.code, property.type, puid); 277 break; 278 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: 279 // release date is stored internally as just the year 280 int year = 0; 281 if (c != null) 282 year = c.getInt(property.column); 283 String dateTime = Integer.toString(year) + "0101T000000"; 284 list.append(id, property.code, dateTime); 285 break; 286 case MtpConstants.PROPERTY_TRACK: 287 int track = 0; 288 if (c != null) 289 track = c.getInt(property.column); 290 list.append(id, property.code, MtpConstants.TYPE_UINT16, 291 track % 1000); 292 break; 293 case MtpConstants.PROPERTY_ARTIST: 294 list.append(id, property.code, 295 queryAudio(path, Audio.AudioColumns.ARTIST)); 296 break; 297 case MtpConstants.PROPERTY_ALBUM_NAME: 298 list.append(id, property.code, 299 queryAudio(path, Audio.AudioColumns.ALBUM)); 300 break; 301 case MtpConstants.PROPERTY_GENRE: 302 String genre = queryGenre(path); 303 if (genre != null) { 304 list.append(id, property.code, genre); 305 } 306 break; 307 case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC: 308 case MtpConstants.PROPERTY_AUDIO_BITRATE: 309 case MtpConstants.PROPERTY_SAMPLE_RATE: 310 // we don't have these in our database, so return 0 311 list.append(id, property.code, MtpConstants.TYPE_UINT32, 0); 312 break; 313 case MtpConstants.PROPERTY_BITRATE_TYPE: 314 case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS: 315 // we don't have these in our database, so return 0 316 list.append(id, property.code, MtpConstants.TYPE_UINT16, 0); 317 break; 318 default: 319 switch(property.type) { 320 case MtpConstants.TYPE_UNDEFINED: 321 list.append(id, property.code, property.type, 0); 322 break; 323 case MtpConstants.TYPE_STR: 324 String value = ""; 325 if (c != null) 326 value = c.getString(property.column); 327 list.append(id, property.code, value); 328 break; 329 default: 330 long longValue = 0L; 331 if (c != null) 332 longValue = c.getLong(property.column); 333 list.append(id, property.code, property.type, longValue); 334 } 335 } 336 } 337 if (c != null) 338 c.close(); 339 return MtpConstants.RESPONSE_OK; 340 } 341 342 private native String format_date_time(long seconds); 343} 344