blob: dab54547ec0314443eb91b579ac1f6e19675a67f [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
19import android.content.IContentProvider;
20import android.database.Cursor;
21import android.net.Uri;
22import android.os.RemoteException;
23import android.provider.MediaStore;
24import android.provider.MediaStore.Audio;
25import android.provider.MediaStore.Files;
26import android.provider.MediaStore.Images;
27import android.provider.MediaStore.MediaColumns;
28import android.util.Log;
29
30import java.util.ArrayList;
31
32class MtpPropertyGroup {
33
34 private static final String TAG = "MtpPropertyGroup";
35
36 private class Property {
37 // MTP property code
38 int code;
39 // MTP data type
40 int type;
41 // column index for our query
42 int column;
43
44 Property(int code, int type, int column) {
45 this.code = code;
46 this.type = type;
47 this.column = column;
48 }
49 }
50
51 private final MtpDatabase mDatabase;
52 private final IContentProvider mProvider;
53 private final String mVolumeName;
54 private final Uri mUri;
55
56 // list of all properties in this group
57 private final Property[] mProperties;
58
59 // list of columns for database query
60 private String[] mColumns;
61
62 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwood4356d812011-02-07 13:40:21 -050063 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
64 private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + FORMAT_WHERE;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050065 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood4356d812011-02-07 13:40:21 -050066 private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050067 // constructs a property group for a list of properties
68 public MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String volume,
69 int[] properties) {
70 mDatabase = database;
71 mProvider = provider;
72 mVolumeName = volume;
73 mUri = Files.getMtpObjectsUri(volume);
74
75 int count = properties.length;
76 ArrayList<String> columns = new ArrayList<String>(count);
77 columns.add(Files.FileColumns._ID);
78
79 mProperties = new Property[count];
80 for (int i = 0; i < count; i++) {
81 mProperties[i] = createProperty(properties[i], columns);
82 }
83 count = columns.size();
84 mColumns = new String[count];
85 for (int i = 0; i < count; i++) {
86 mColumns[i] = columns.get(i);
87 }
88 }
89
90 private Property createProperty(int code, ArrayList<String> columns) {
91 String column = null;
92 int type;
93
94 switch (code) {
95 case MtpConstants.PROPERTY_STORAGE_ID:
Mike Lockwoodb239b6832011-04-05 10:21:27 -040096 column = Files.FileColumns.STORAGE_ID;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050097 type = MtpConstants.TYPE_UINT32;
98 break;
99 case MtpConstants.PROPERTY_OBJECT_FORMAT:
100 column = Files.FileColumns.FORMAT;
101 type = MtpConstants.TYPE_UINT16;
102 break;
103 case MtpConstants.PROPERTY_PROTECTION_STATUS:
104 // protection status is always 0
105 type = MtpConstants.TYPE_UINT16;
106 break;
107 case MtpConstants.PROPERTY_OBJECT_SIZE:
108 column = Files.FileColumns.SIZE;
109 type = MtpConstants.TYPE_UINT64;
110 break;
111 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
112 column = Files.FileColumns.DATA;
113 type = MtpConstants.TYPE_STR;
114 break;
115 case MtpConstants.PROPERTY_NAME:
116 column = MediaColumns.TITLE;
117 type = MtpConstants.TYPE_STR;
118 break;
119 case MtpConstants.PROPERTY_DATE_MODIFIED:
120 column = Files.FileColumns.DATE_MODIFIED;
121 type = MtpConstants.TYPE_STR;
122 break;
123 case MtpConstants.PROPERTY_DATE_ADDED:
124 column = Files.FileColumns.DATE_ADDED;
125 type = MtpConstants.TYPE_STR;
126 break;
127 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
128 column = Audio.AudioColumns.YEAR;
129 type = MtpConstants.TYPE_STR;
130 break;
131 case MtpConstants.PROPERTY_PARENT_OBJECT:
132 column = Files.FileColumns.PARENT;
133 type = MtpConstants.TYPE_UINT32;
134 break;
135 case MtpConstants.PROPERTY_PERSISTENT_UID:
136 // PUID is concatenation of storageID and object handle
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400137 column = Files.FileColumns.STORAGE_ID;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500138 type = MtpConstants.TYPE_UINT128;
139 break;
140 case MtpConstants.PROPERTY_DURATION:
141 column = Audio.AudioColumns.DURATION;
142 type = MtpConstants.TYPE_UINT32;
143 break;
144 case MtpConstants.PROPERTY_TRACK:
145 column = Audio.AudioColumns.TRACK;
146 type = MtpConstants.TYPE_UINT16;
147 break;
148 case MtpConstants.PROPERTY_DISPLAY_NAME:
149 column = MediaColumns.DISPLAY_NAME;
150 type = MtpConstants.TYPE_STR;
151 break;
152 case MtpConstants.PROPERTY_ARTIST:
153 type = MtpConstants.TYPE_STR;
154 break;
155 case MtpConstants.PROPERTY_ALBUM_NAME:
156 type = MtpConstants.TYPE_STR;
157 break;
158 case MtpConstants.PROPERTY_ALBUM_ARTIST:
159 column = Audio.AudioColumns.ALBUM_ARTIST;
160 type = MtpConstants.TYPE_STR;
161 break;
162 case MtpConstants.PROPERTY_GENRE:
163 // genre requires a special query
164 type = MtpConstants.TYPE_STR;
165 break;
166 case MtpConstants.PROPERTY_COMPOSER:
167 column = Audio.AudioColumns.COMPOSER;
168 type = MtpConstants.TYPE_STR;
169 break;
170 case MtpConstants.PROPERTY_DESCRIPTION:
171 column = Images.ImageColumns.DESCRIPTION;
172 type = MtpConstants.TYPE_STR;
173 break;
174 default:
175 type = MtpConstants.TYPE_UNDEFINED;
176 Log.e(TAG, "unsupported property " + code);
177 break;
178 }
179
180 if (column != null) {
181 columns.add(column);
182 return new Property(code, type, columns.size() - 1);
183 } else {
184 return new Property(code, type, -1);
185 }
186 }
187
188 private String queryString(int id, String column) {
189 Cursor c = null;
190 try {
191 // for now we are only reading properties from the "objects" table
192 c = mProvider.query(mUri,
193 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800194 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500195 if (c != null && c.moveToNext()) {
196 return c.getString(1);
197 } else {
198 return "";
199 }
200 } catch (Exception e) {
201 return null;
202 } finally {
203 if (c != null) {
204 c.close();
205 }
206 }
207 }
208
209 private String queryAudio(int id, String column) {
210 Cursor c = null;
211 try {
212 c = mProvider.query(Audio.Media.getContentUri(mVolumeName),
213 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800214 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500215 if (c != null && c.moveToNext()) {
216 return c.getString(1);
217 } else {
218 return "";
219 }
220 } catch (Exception e) {
221 return null;
222 } finally {
223 if (c != null) {
224 c.close();
225 }
226 }
227 }
228
229 private String queryGenre(int id) {
230 Cursor c = null;
231 try {
232 Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
233 c = mProvider.query(uri,
234 new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800235 null, null, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500236 if (c != null && c.moveToNext()) {
237 return c.getString(1);
238 } else {
239 return "";
240 }
241 } catch (Exception e) {
242 Log.e(TAG, "queryGenre exception", e);
243 return null;
244 } finally {
245 if (c != null) {
246 c.close();
247 }
248 }
249 }
250
251 private Long queryLong(int id, String column) {
252 Cursor c = null;
253 try {
254 // for now we are only reading properties from the "objects" table
255 c = mProvider.query(mUri,
256 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800257 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500258 if (c != null && c.moveToNext()) {
259 return new Long(c.getLong(1));
260 }
261 } catch (Exception e) {
262 } finally {
263 if (c != null) {
264 c.close();
265 }
266 }
267 return null;
268 }
269
270 private static String nameFromPath(String path) {
271 // extract name from full path
272 int start = 0;
273 int lastSlash = path.lastIndexOf('/');
274 if (lastSlash >= 0) {
275 start = lastSlash + 1;
276 }
277 int end = path.length();
278 if (end - start > 255) {
279 end = start + 255;
280 }
281 return path.substring(start, end);
282 }
283
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400284 MtpPropertyList getPropertyList(int handle, int format, int depth) {
Mike Lockwood071b2b62011-01-25 09:29:27 -0800285 //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500286 if (depth > 1) {
287 // we only support depth 0 and 1
288 // depth 0: single object, depth 1: immediate children
289 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
290 }
291
292 String where;
293 String[] whereArgs;
294 if (format == 0) {
Mike Lockwood4356d812011-02-07 13:40:21 -0500295 if (handle == 0xFFFFFFFF) {
296 // select all objects
297 where = null;
298 whereArgs = null;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500299 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500300 whereArgs = new String[] { Integer.toString(handle) };
301 if (depth == 1) {
302 where = PARENT_WHERE;
303 } else {
304 where = ID_WHERE;
305 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500306 }
307 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500308 if (handle == 0xFFFFFFFF) {
309 // select all objects with given format
310 where = FORMAT_WHERE;
311 whereArgs = new String[] { Integer.toString(format) };
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500312 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500313 whereArgs = new String[] { Integer.toString(handle), Integer.toString(format) };
314 if (depth == 1) {
315 where = PARENT_FORMAT_WHERE;
316 } else {
317 where = ID_FORMAT_WHERE;
318 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500319 }
320 }
321
322 Cursor c = null;
323 try {
324 // don't query if not necessary
Mike Lockwood4356d812011-02-07 13:40:21 -0500325 if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800326 c = mProvider.query(mUri, mColumns, where, whereArgs, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500327 if (c == null) {
328 return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
329 }
330 }
331
332 int count = (c == null ? 1 : c.getCount());
333 MtpPropertyList result = new MtpPropertyList(count * mProperties.length,
334 MtpConstants.RESPONSE_OK);
335
336 // iterate over all objects in the query
337 for (int objectIndex = 0; objectIndex < count; objectIndex++) {
338 if (c != null) {
339 c.moveToNext();
Mike Lockwoodfd22edc2011-02-08 21:45:22 -0500340 handle = (int)c.getLong(0);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500341 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500342
343 // iterate over all properties in the query for the given object
344 for (int propertyIndex = 0; propertyIndex < mProperties.length; propertyIndex++) {
345 Property property = mProperties[propertyIndex];
346 int propertyCode = property.code;
347 int column = property.column;
348
349 // handle some special cases
350 switch (propertyCode) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500351 case MtpConstants.PROPERTY_PROTECTION_STATUS:
352 // protection status is always 0
353 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
354 break;
355 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
356 // special case - need to extract file name from full path
357 String value = c.getString(column);
358 if (value != null) {
359 result.append(handle, propertyCode, nameFromPath(value));
360 } else {
361 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
362 }
363 break;
364 case MtpConstants.PROPERTY_NAME:
365 // first try title
366 String name = c.getString(column);
367 // then try name
368 if (name == null) {
369 name = queryString(handle, Audio.PlaylistsColumns.NAME);
370 }
371 // if title and name fail, extract name from full path
372 if (name == null) {
373 name = queryString(handle, Files.FileColumns.DATA);
374 if (name != null) {
375 name = nameFromPath(name);
376 }
377 }
378 if (name != null) {
379 result.append(handle, propertyCode, name);
380 } else {
381 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
382 }
383 break;
384 case MtpConstants.PROPERTY_DATE_MODIFIED:
385 case MtpConstants.PROPERTY_DATE_ADDED:
386 // convert from seconds to DateTime
387 result.append(handle, propertyCode, format_date_time(c.getInt(column)));
388 break;
389 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
390 // release date is stored internally as just the year
391 int year = c.getInt(column);
392 String dateTime = Integer.toString(year) + "0101T000000";
393 result.append(handle, propertyCode, dateTime);
394 break;
395 case MtpConstants.PROPERTY_PERSISTENT_UID:
396 // PUID is concatenation of storageID and object handle
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400397 long puid = c.getLong(column);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500398 puid <<= 32;
399 puid += handle;
400 result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid);
401 break;
402 case MtpConstants.PROPERTY_TRACK:
403 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16,
404 c.getInt(column) % 1000);
405 break;
406 case MtpConstants.PROPERTY_ARTIST:
407 result.append(handle, propertyCode,
408 queryAudio(handle, Audio.AudioColumns.ARTIST));
409 break;
410 case MtpConstants.PROPERTY_ALBUM_NAME:
411 result.append(handle, propertyCode,
412 queryAudio(handle, Audio.AudioColumns.ALBUM));
413 break;
414 case MtpConstants.PROPERTY_GENRE:
415 String genre = queryGenre(handle);
416 if (genre != null) {
417 result.append(handle, propertyCode, genre);
418 } else {
419 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
420 }
421 break;
422 default:
423 if (property.type == MtpConstants.TYPE_STR) {
424 result.append(handle, propertyCode, c.getString(column));
425 } else if (property.type == MtpConstants.TYPE_UNDEFINED) {
426 result.append(handle, propertyCode, property.type, 0);
427 } else {
428 result.append(handle, propertyCode, property.type,
429 c.getLong(column));
430 }
431 break;
432 }
433 }
434 }
435
436 return result;
437 } catch (RemoteException e) {
438 return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR);
439 } finally {
440 if (c != null) {
441 c.close();
442 }
443 }
444 // impossible to get here, so no return statement
445 }
446
447 private native String format_date_time(long seconds);
448}