blob: fceedd28e04069fec2fd6c4893809a5c6cd06c25 [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:
96 // no query needed until we support multiple storage units
97 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
137 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;
173 default:
174 type = MtpConstants.TYPE_UNDEFINED;
175 Log.e(TAG, "unsupported property " + code);
176 break;
177 }
178
179 if (column != null) {
180 columns.add(column);
181 return new Property(code, type, columns.size() - 1);
182 } else {
183 return new Property(code, type, -1);
184 }
185 }
186
187 private String queryString(int id, String column) {
188 Cursor c = null;
189 try {
190 // for now we are only reading properties from the "objects" table
191 c = mProvider.query(mUri,
192 new String [] { Files.FileColumns._ID, column },
193 ID_WHERE, new String[] { Integer.toString(id) }, null);
194 if (c != null && c.moveToNext()) {
195 return c.getString(1);
196 } else {
197 return "";
198 }
199 } catch (Exception e) {
200 return null;
201 } finally {
202 if (c != null) {
203 c.close();
204 }
205 }
206 }
207
208 private String queryAudio(int id, String column) {
209 Cursor c = null;
210 try {
211 c = mProvider.query(Audio.Media.getContentUri(mVolumeName),
212 new String [] { Files.FileColumns._ID, column },
213 ID_WHERE, new String[] { Integer.toString(id) }, null);
214 if (c != null && c.moveToNext()) {
215 return c.getString(1);
216 } else {
217 return "";
218 }
219 } catch (Exception e) {
220 return null;
221 } finally {
222 if (c != null) {
223 c.close();
224 }
225 }
226 }
227
228 private String queryGenre(int id) {
229 Cursor c = null;
230 try {
231 Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
232 c = mProvider.query(uri,
233 new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
234 null, null, null);
235 if (c != null && c.moveToNext()) {
236 return c.getString(1);
237 } else {
238 return "";
239 }
240 } catch (Exception e) {
241 Log.e(TAG, "queryGenre exception", e);
242 return null;
243 } finally {
244 if (c != null) {
245 c.close();
246 }
247 }
248 }
249
250 private Long queryLong(int id, String column) {
251 Cursor c = null;
252 try {
253 // for now we are only reading properties from the "objects" table
254 c = mProvider.query(mUri,
255 new String [] { Files.FileColumns._ID, column },
256 ID_WHERE, new String[] { Integer.toString(id) }, null);
257 if (c != null && c.moveToNext()) {
258 return new Long(c.getLong(1));
259 }
260 } catch (Exception e) {
261 } finally {
262 if (c != null) {
263 c.close();
264 }
265 }
266 return null;
267 }
268
269 private static String nameFromPath(String path) {
270 // extract name from full path
271 int start = 0;
272 int lastSlash = path.lastIndexOf('/');
273 if (lastSlash >= 0) {
274 start = lastSlash + 1;
275 }
276 int end = path.length();
277 if (end - start > 255) {
278 end = start + 255;
279 }
280 return path.substring(start, end);
281 }
282
283 MtpPropertyList getPropertyList(int handle, int format, int depth, int storageID) {
Mike Lockwood071b2b62011-01-25 09:29:27 -0800284 //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500285 if (depth > 1) {
286 // we only support depth 0 and 1
287 // depth 0: single object, depth 1: immediate children
288 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
289 }
290
291 String where;
292 String[] whereArgs;
293 if (format == 0) {
Mike Lockwood4356d812011-02-07 13:40:21 -0500294 if (handle == 0xFFFFFFFF) {
295 // select all objects
296 where = null;
297 whereArgs = null;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500298 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500299 whereArgs = new String[] { Integer.toString(handle) };
300 if (depth == 1) {
301 where = PARENT_WHERE;
302 } else {
303 where = ID_WHERE;
304 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500305 }
306 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500307 if (handle == 0xFFFFFFFF) {
308 // select all objects with given format
309 where = FORMAT_WHERE;
310 whereArgs = new String[] { Integer.toString(format) };
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500311 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500312 whereArgs = new String[] { Integer.toString(handle), Integer.toString(format) };
313 if (depth == 1) {
314 where = PARENT_FORMAT_WHERE;
315 } else {
316 where = ID_FORMAT_WHERE;
317 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500318 }
319 }
320
321 Cursor c = null;
322 try {
323 // don't query if not necessary
Mike Lockwood4356d812011-02-07 13:40:21 -0500324 if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500325 c = mProvider.query(mUri, mColumns, where, whereArgs, null);
326 if (c == null) {
327 return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
328 }
329 }
330
331 int count = (c == null ? 1 : c.getCount());
Mike Lockwood4356d812011-02-07 13:40:21 -0500332 Log.d(TAG, "count: " + count);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500333 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) {
351 case MtpConstants.PROPERTY_STORAGE_ID:
352 result.append(handle, propertyCode, MtpConstants.TYPE_UINT32,
353 storageID);
354 break;
355 case MtpConstants.PROPERTY_PROTECTION_STATUS:
356 // protection status is always 0
357 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
358 break;
359 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
360 // special case - need to extract file name from full path
361 String value = c.getString(column);
362 if (value != null) {
363 result.append(handle, propertyCode, nameFromPath(value));
364 } else {
365 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
366 }
367 break;
368 case MtpConstants.PROPERTY_NAME:
369 // first try title
370 String name = c.getString(column);
371 // then try name
372 if (name == null) {
373 name = queryString(handle, Audio.PlaylistsColumns.NAME);
374 }
375 // if title and name fail, extract name from full path
376 if (name == null) {
377 name = queryString(handle, Files.FileColumns.DATA);
378 if (name != null) {
379 name = nameFromPath(name);
380 }
381 }
382 if (name != null) {
383 result.append(handle, propertyCode, name);
384 } else {
385 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
386 }
387 break;
388 case MtpConstants.PROPERTY_DATE_MODIFIED:
389 case MtpConstants.PROPERTY_DATE_ADDED:
390 // convert from seconds to DateTime
391 result.append(handle, propertyCode, format_date_time(c.getInt(column)));
392 break;
393 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
394 // release date is stored internally as just the year
395 int year = c.getInt(column);
396 String dateTime = Integer.toString(year) + "0101T000000";
397 result.append(handle, propertyCode, dateTime);
398 break;
399 case MtpConstants.PROPERTY_PERSISTENT_UID:
400 // PUID is concatenation of storageID and object handle
401 long puid = storageID;
402 puid <<= 32;
403 puid += handle;
404 result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid);
405 break;
406 case MtpConstants.PROPERTY_TRACK:
407 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16,
408 c.getInt(column) % 1000);
409 break;
410 case MtpConstants.PROPERTY_ARTIST:
411 result.append(handle, propertyCode,
412 queryAudio(handle, Audio.AudioColumns.ARTIST));
413 break;
414 case MtpConstants.PROPERTY_ALBUM_NAME:
415 result.append(handle, propertyCode,
416 queryAudio(handle, Audio.AudioColumns.ALBUM));
417 break;
418 case MtpConstants.PROPERTY_GENRE:
419 String genre = queryGenre(handle);
420 if (genre != null) {
421 result.append(handle, propertyCode, genre);
422 } else {
423 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
424 }
425 break;
426 default:
427 if (property.type == MtpConstants.TYPE_STR) {
428 result.append(handle, propertyCode, c.getString(column));
429 } else if (property.type == MtpConstants.TYPE_UNDEFINED) {
430 result.append(handle, propertyCode, property.type, 0);
431 } else {
432 result.append(handle, propertyCode, property.type,
433 c.getLong(column));
434 }
435 break;
436 }
437 }
438 }
439
440 return result;
441 } catch (RemoteException e) {
442 return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR);
443 } finally {
444 if (c != null) {
445 c.close();
446 }
447 }
448 // impossible to get here, so no return statement
449 }
450
451 private native String format_date_time(long seconds);
452}