blob: dea30083838516c7ff341d96e7ea29cc8c14284b [file] [log] [blame]
Mike Lockwood7d7fb632010-12-01 18:46:23 -05001/*
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
Mike Lockwood0cd01362010-12-30 11:54:33 -050017package android.mtp;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050018
Jeff Sharkey60cfad82016-01-05 17:30:57 -070019import android.content.ContentProviderClient;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050020import android.database.Cursor;
21import android.net.Uri;
22import android.os.RemoteException;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050023import android.provider.MediaStore.Audio;
24import android.provider.MediaStore.Files;
25import android.provider.MediaStore.Images;
26import android.provider.MediaStore.MediaColumns;
27import android.util.Log;
28
29import java.util.ArrayList;
30
31class MtpPropertyGroup {
32
33 private static final String TAG = "MtpPropertyGroup";
34
35 private class Property {
36 // MTP property code
37 int code;
38 // MTP data type
39 int type;
40 // column index for our query
41 int column;
42
43 Property(int code, int type, int column) {
44 this.code = code;
45 this.type = type;
46 this.column = column;
47 }
48 }
49
50 private final MtpDatabase mDatabase;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070051 private final ContentProviderClient mProvider;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050052 private final String mVolumeName;
53 private final Uri mUri;
54
55 // list of all properties in this group
56 private final Property[] mProperties;
57
58 // list of columns for database query
59 private String[] mColumns;
60
61 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwood4356d812011-02-07 13:40:21 -050062 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
63 private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + FORMAT_WHERE;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050064 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood4356d812011-02-07 13:40:21 -050065 private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050066 // constructs a property group for a list of properties
Jeff Sharkey60cfad82016-01-05 17:30:57 -070067 public MtpPropertyGroup(MtpDatabase database, ContentProviderClient provider, String volumeName,
68 int[] properties) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -050069 mDatabase = database;
70 mProvider = provider;
Jeff Sharkey60cfad82016-01-05 17:30:57 -070071 mVolumeName = volumeName;
72 mUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwood7d7fb632010-12-01 18:46:23 -050073
74 int count = properties.length;
75 ArrayList<String> columns = new ArrayList<String>(count);
76 columns.add(Files.FileColumns._ID);
77
78 mProperties = new Property[count];
79 for (int i = 0; i < count; i++) {
80 mProperties[i] = createProperty(properties[i], columns);
81 }
82 count = columns.size();
83 mColumns = new String[count];
84 for (int i = 0; i < count; i++) {
85 mColumns[i] = columns.get(i);
86 }
87 }
88
89 private Property createProperty(int code, ArrayList<String> columns) {
90 String column = null;
91 int type;
92
93 switch (code) {
94 case MtpConstants.PROPERTY_STORAGE_ID:
Mike Lockwoodb239b6832011-04-05 10:21:27 -040095 column = Files.FileColumns.STORAGE_ID;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050096 type = MtpConstants.TYPE_UINT32;
97 break;
98 case MtpConstants.PROPERTY_OBJECT_FORMAT:
99 column = Files.FileColumns.FORMAT;
100 type = MtpConstants.TYPE_UINT16;
101 break;
102 case MtpConstants.PROPERTY_PROTECTION_STATUS:
103 // protection status is always 0
104 type = MtpConstants.TYPE_UINT16;
105 break;
106 case MtpConstants.PROPERTY_OBJECT_SIZE:
107 column = Files.FileColumns.SIZE;
108 type = MtpConstants.TYPE_UINT64;
109 break;
110 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
111 column = Files.FileColumns.DATA;
112 type = MtpConstants.TYPE_STR;
113 break;
114 case MtpConstants.PROPERTY_NAME:
115 column = MediaColumns.TITLE;
116 type = MtpConstants.TYPE_STR;
117 break;
118 case MtpConstants.PROPERTY_DATE_MODIFIED:
119 column = Files.FileColumns.DATE_MODIFIED;
120 type = MtpConstants.TYPE_STR;
121 break;
122 case MtpConstants.PROPERTY_DATE_ADDED:
123 column = Files.FileColumns.DATE_ADDED;
124 type = MtpConstants.TYPE_STR;
125 break;
126 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
127 column = Audio.AudioColumns.YEAR;
128 type = MtpConstants.TYPE_STR;
129 break;
130 case MtpConstants.PROPERTY_PARENT_OBJECT:
131 column = Files.FileColumns.PARENT;
132 type = MtpConstants.TYPE_UINT32;
133 break;
134 case MtpConstants.PROPERTY_PERSISTENT_UID:
135 // PUID is concatenation of storageID and object handle
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400136 column = Files.FileColumns.STORAGE_ID;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500137 type = MtpConstants.TYPE_UINT128;
138 break;
139 case MtpConstants.PROPERTY_DURATION:
140 column = Audio.AudioColumns.DURATION;
141 type = MtpConstants.TYPE_UINT32;
142 break;
143 case MtpConstants.PROPERTY_TRACK:
144 column = Audio.AudioColumns.TRACK;
145 type = MtpConstants.TYPE_UINT16;
146 break;
147 case MtpConstants.PROPERTY_DISPLAY_NAME:
148 column = MediaColumns.DISPLAY_NAME;
149 type = MtpConstants.TYPE_STR;
150 break;
151 case MtpConstants.PROPERTY_ARTIST:
152 type = MtpConstants.TYPE_STR;
153 break;
154 case MtpConstants.PROPERTY_ALBUM_NAME:
155 type = MtpConstants.TYPE_STR;
156 break;
157 case MtpConstants.PROPERTY_ALBUM_ARTIST:
158 column = Audio.AudioColumns.ALBUM_ARTIST;
159 type = MtpConstants.TYPE_STR;
160 break;
161 case MtpConstants.PROPERTY_GENRE:
162 // genre requires a special query
163 type = MtpConstants.TYPE_STR;
164 break;
165 case MtpConstants.PROPERTY_COMPOSER:
166 column = Audio.AudioColumns.COMPOSER;
167 type = MtpConstants.TYPE_STR;
168 break;
169 case MtpConstants.PROPERTY_DESCRIPTION:
170 column = Images.ImageColumns.DESCRIPTION;
171 type = MtpConstants.TYPE_STR;
172 break;
Mike Lockwood71827742015-01-23 10:50:08 -0800173 case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
174 case MtpConstants.PROPERTY_AUDIO_BITRATE:
175 case MtpConstants.PROPERTY_SAMPLE_RATE:
176 // these are special cased
177 type = MtpConstants.TYPE_UINT32;
178 break;
179 case MtpConstants.PROPERTY_BITRATE_TYPE:
180 case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
181 // these are special cased
182 type = MtpConstants.TYPE_UINT16;
183 break;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500184 default:
185 type = MtpConstants.TYPE_UNDEFINED;
186 Log.e(TAG, "unsupported property " + code);
187 break;
188 }
189
190 if (column != null) {
191 columns.add(column);
192 return new Property(code, type, columns.size() - 1);
193 } else {
194 return new Property(code, type, -1);
195 }
196 }
197
198 private String queryString(int id, String column) {
199 Cursor c = null;
200 try {
201 // for now we are only reading properties from the "objects" table
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700202 c = mProvider.query(mUri,
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500203 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800204 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500205 if (c != null && c.moveToNext()) {
206 return c.getString(1);
207 } else {
208 return "";
209 }
210 } catch (Exception e) {
211 return null;
212 } finally {
213 if (c != null) {
214 c.close();
215 }
216 }
217 }
218
219 private String queryAudio(int id, String column) {
220 Cursor c = null;
221 try {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700222 c = mProvider.query(Audio.Media.getContentUri(mVolumeName),
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500223 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800224 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500225 if (c != null && c.moveToNext()) {
226 return c.getString(1);
227 } else {
228 return "";
229 }
230 } catch (Exception e) {
231 return null;
232 } finally {
233 if (c != null) {
234 c.close();
235 }
236 }
237 }
238
239 private String queryGenre(int id) {
240 Cursor c = null;
241 try {
242 Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700243 c = mProvider.query(uri,
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500244 new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800245 null, null, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500246 if (c != null && c.moveToNext()) {
247 return c.getString(1);
248 } else {
249 return "";
250 }
251 } catch (Exception e) {
252 Log.e(TAG, "queryGenre exception", e);
253 return null;
254 } finally {
255 if (c != null) {
256 c.close();
257 }
258 }
259 }
260
261 private Long queryLong(int id, String column) {
262 Cursor c = null;
263 try {
264 // for now we are only reading properties from the "objects" table
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700265 c = mProvider.query(mUri,
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500266 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800267 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500268 if (c != null && c.moveToNext()) {
269 return new Long(c.getLong(1));
270 }
271 } catch (Exception e) {
272 } finally {
273 if (c != null) {
274 c.close();
275 }
276 }
277 return null;
278 }
279
280 private static String nameFromPath(String path) {
281 // extract name from full path
282 int start = 0;
283 int lastSlash = path.lastIndexOf('/');
284 if (lastSlash >= 0) {
285 start = lastSlash + 1;
286 }
287 int end = path.length();
288 if (end - start > 255) {
289 end = start + 255;
290 }
291 return path.substring(start, end);
292 }
293
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400294 MtpPropertyList getPropertyList(int handle, int format, int depth) {
Mike Lockwood071b2b62011-01-25 09:29:27 -0800295 //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500296 if (depth > 1) {
297 // we only support depth 0 and 1
298 // depth 0: single object, depth 1: immediate children
299 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
300 }
301
302 String where;
303 String[] whereArgs;
304 if (format == 0) {
Mike Lockwood4356d812011-02-07 13:40:21 -0500305 if (handle == 0xFFFFFFFF) {
306 // select all objects
307 where = null;
308 whereArgs = null;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500309 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500310 whereArgs = new String[] { Integer.toString(handle) };
311 if (depth == 1) {
312 where = PARENT_WHERE;
313 } else {
314 where = ID_WHERE;
315 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500316 }
317 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500318 if (handle == 0xFFFFFFFF) {
319 // select all objects with given format
320 where = FORMAT_WHERE;
321 whereArgs = new String[] { Integer.toString(format) };
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500322 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500323 whereArgs = new String[] { Integer.toString(handle), Integer.toString(format) };
324 if (depth == 1) {
325 where = PARENT_FORMAT_WHERE;
326 } else {
327 where = ID_FORMAT_WHERE;
328 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500329 }
330 }
331
332 Cursor c = null;
333 try {
334 // don't query if not necessary
Mike Lockwood4356d812011-02-07 13:40:21 -0500335 if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
Jeff Sharkey60cfad82016-01-05 17:30:57 -0700336 c = mProvider.query(mUri, mColumns, where, whereArgs, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500337 if (c == null) {
338 return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
339 }
340 }
341
342 int count = (c == null ? 1 : c.getCount());
343 MtpPropertyList result = new MtpPropertyList(count * mProperties.length,
344 MtpConstants.RESPONSE_OK);
345
346 // iterate over all objects in the query
347 for (int objectIndex = 0; objectIndex < count; objectIndex++) {
348 if (c != null) {
349 c.moveToNext();
Mike Lockwoodfd22edc2011-02-08 21:45:22 -0500350 handle = (int)c.getLong(0);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500351 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500352
353 // iterate over all properties in the query for the given object
354 for (int propertyIndex = 0; propertyIndex < mProperties.length; propertyIndex++) {
355 Property property = mProperties[propertyIndex];
356 int propertyCode = property.code;
357 int column = property.column;
358
359 // handle some special cases
360 switch (propertyCode) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500361 case MtpConstants.PROPERTY_PROTECTION_STATUS:
362 // protection status is always 0
363 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
364 break;
365 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
366 // special case - need to extract file name from full path
367 String value = c.getString(column);
368 if (value != null) {
369 result.append(handle, propertyCode, nameFromPath(value));
370 } else {
371 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
372 }
373 break;
374 case MtpConstants.PROPERTY_NAME:
375 // first try title
376 String name = c.getString(column);
377 // then try name
378 if (name == null) {
379 name = queryString(handle, Audio.PlaylistsColumns.NAME);
380 }
381 // if title and name fail, extract name from full path
382 if (name == null) {
383 name = queryString(handle, Files.FileColumns.DATA);
384 if (name != null) {
385 name = nameFromPath(name);
386 }
387 }
388 if (name != null) {
389 result.append(handle, propertyCode, name);
390 } else {
391 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
392 }
393 break;
394 case MtpConstants.PROPERTY_DATE_MODIFIED:
395 case MtpConstants.PROPERTY_DATE_ADDED:
396 // convert from seconds to DateTime
397 result.append(handle, propertyCode, format_date_time(c.getInt(column)));
398 break;
399 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
400 // release date is stored internally as just the year
401 int year = c.getInt(column);
402 String dateTime = Integer.toString(year) + "0101T000000";
403 result.append(handle, propertyCode, dateTime);
404 break;
405 case MtpConstants.PROPERTY_PERSISTENT_UID:
406 // PUID is concatenation of storageID and object handle
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400407 long puid = c.getLong(column);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500408 puid <<= 32;
409 puid += handle;
410 result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid);
411 break;
412 case MtpConstants.PROPERTY_TRACK:
413 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16,
414 c.getInt(column) % 1000);
415 break;
416 case MtpConstants.PROPERTY_ARTIST:
417 result.append(handle, propertyCode,
418 queryAudio(handle, Audio.AudioColumns.ARTIST));
419 break;
420 case MtpConstants.PROPERTY_ALBUM_NAME:
421 result.append(handle, propertyCode,
422 queryAudio(handle, Audio.AudioColumns.ALBUM));
423 break;
424 case MtpConstants.PROPERTY_GENRE:
425 String genre = queryGenre(handle);
426 if (genre != null) {
427 result.append(handle, propertyCode, genre);
428 } else {
429 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
430 }
431 break;
Mike Lockwood71827742015-01-23 10:50:08 -0800432 case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
433 case MtpConstants.PROPERTY_AUDIO_BITRATE:
434 case MtpConstants.PROPERTY_SAMPLE_RATE:
435 // we don't have these in our database, so return 0
436 result.append(handle, propertyCode, MtpConstants.TYPE_UINT32, 0);
437 break;
438 case MtpConstants.PROPERTY_BITRATE_TYPE:
439 case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
440 // we don't have these in our database, so return 0
441 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
442 break;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500443 default:
444 if (property.type == MtpConstants.TYPE_STR) {
445 result.append(handle, propertyCode, c.getString(column));
446 } else if (property.type == MtpConstants.TYPE_UNDEFINED) {
447 result.append(handle, propertyCode, property.type, 0);
448 } else {
449 result.append(handle, propertyCode, property.type,
450 c.getLong(column));
451 }
452 break;
453 }
454 }
455 }
456
457 return result;
458 } catch (RemoteException e) {
459 return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR);
460 } finally {
461 if (c != null) {
462 c.close();
463 }
464 }
465 // impossible to get here, so no return statement
466 }
467
468 private native String format_date_time(long seconds);
469}