blob: 781988d1910ab0d0b035aacefd75ed71409ad7e9 [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;
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;
51 private final IContentProvider mProvider;
Dianne Hackborn35654b62013-01-14 17:38:02 -080052 private final String mPackageName;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050053 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
Dianne Hackborn35654b62013-01-14 17:38:02 -080068 public MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String packageName,
69 String volume, int[] properties) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -050070 mDatabase = database;
71 mProvider = provider;
Dianne Hackborn35654b62013-01-14 17:38:02 -080072 mPackageName = packageName;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050073 mVolumeName = volume;
74 mUri = Files.getMtpObjectsUri(volume);
75
76 int count = properties.length;
77 ArrayList<String> columns = new ArrayList<String>(count);
78 columns.add(Files.FileColumns._ID);
79
80 mProperties = new Property[count];
81 for (int i = 0; i < count; i++) {
82 mProperties[i] = createProperty(properties[i], columns);
83 }
84 count = columns.size();
85 mColumns = new String[count];
86 for (int i = 0; i < count; i++) {
87 mColumns[i] = columns.get(i);
88 }
89 }
90
91 private Property createProperty(int code, ArrayList<String> columns) {
92 String column = null;
93 int type;
94
95 switch (code) {
96 case MtpConstants.PROPERTY_STORAGE_ID:
Mike Lockwoodb239b6832011-04-05 10:21:27 -040097 column = Files.FileColumns.STORAGE_ID;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050098 type = MtpConstants.TYPE_UINT32;
99 break;
100 case MtpConstants.PROPERTY_OBJECT_FORMAT:
101 column = Files.FileColumns.FORMAT;
102 type = MtpConstants.TYPE_UINT16;
103 break;
104 case MtpConstants.PROPERTY_PROTECTION_STATUS:
105 // protection status is always 0
106 type = MtpConstants.TYPE_UINT16;
107 break;
108 case MtpConstants.PROPERTY_OBJECT_SIZE:
109 column = Files.FileColumns.SIZE;
110 type = MtpConstants.TYPE_UINT64;
111 break;
112 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
113 column = Files.FileColumns.DATA;
114 type = MtpConstants.TYPE_STR;
115 break;
116 case MtpConstants.PROPERTY_NAME:
117 column = MediaColumns.TITLE;
118 type = MtpConstants.TYPE_STR;
119 break;
120 case MtpConstants.PROPERTY_DATE_MODIFIED:
121 column = Files.FileColumns.DATE_MODIFIED;
122 type = MtpConstants.TYPE_STR;
123 break;
124 case MtpConstants.PROPERTY_DATE_ADDED:
125 column = Files.FileColumns.DATE_ADDED;
126 type = MtpConstants.TYPE_STR;
127 break;
128 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
129 column = Audio.AudioColumns.YEAR;
130 type = MtpConstants.TYPE_STR;
131 break;
132 case MtpConstants.PROPERTY_PARENT_OBJECT:
133 column = Files.FileColumns.PARENT;
134 type = MtpConstants.TYPE_UINT32;
135 break;
136 case MtpConstants.PROPERTY_PERSISTENT_UID:
137 // PUID is concatenation of storageID and object handle
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400138 column = Files.FileColumns.STORAGE_ID;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500139 type = MtpConstants.TYPE_UINT128;
140 break;
141 case MtpConstants.PROPERTY_DURATION:
142 column = Audio.AudioColumns.DURATION;
143 type = MtpConstants.TYPE_UINT32;
144 break;
145 case MtpConstants.PROPERTY_TRACK:
146 column = Audio.AudioColumns.TRACK;
147 type = MtpConstants.TYPE_UINT16;
148 break;
149 case MtpConstants.PROPERTY_DISPLAY_NAME:
150 column = MediaColumns.DISPLAY_NAME;
151 type = MtpConstants.TYPE_STR;
152 break;
153 case MtpConstants.PROPERTY_ARTIST:
154 type = MtpConstants.TYPE_STR;
155 break;
156 case MtpConstants.PROPERTY_ALBUM_NAME:
157 type = MtpConstants.TYPE_STR;
158 break;
159 case MtpConstants.PROPERTY_ALBUM_ARTIST:
160 column = Audio.AudioColumns.ALBUM_ARTIST;
161 type = MtpConstants.TYPE_STR;
162 break;
163 case MtpConstants.PROPERTY_GENRE:
164 // genre requires a special query
165 type = MtpConstants.TYPE_STR;
166 break;
167 case MtpConstants.PROPERTY_COMPOSER:
168 column = Audio.AudioColumns.COMPOSER;
169 type = MtpConstants.TYPE_STR;
170 break;
171 case MtpConstants.PROPERTY_DESCRIPTION:
172 column = Images.ImageColumns.DESCRIPTION;
173 type = MtpConstants.TYPE_STR;
174 break;
175 default:
176 type = MtpConstants.TYPE_UNDEFINED;
177 Log.e(TAG, "unsupported property " + code);
178 break;
179 }
180
181 if (column != null) {
182 columns.add(column);
183 return new Property(code, type, columns.size() - 1);
184 } else {
185 return new Property(code, type, -1);
186 }
187 }
188
189 private String queryString(int id, String column) {
190 Cursor c = null;
191 try {
192 // for now we are only reading properties from the "objects" table
Dianne Hackborn35654b62013-01-14 17:38:02 -0800193 c = mProvider.query(mPackageName, mUri,
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500194 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800195 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500196 if (c != null && c.moveToNext()) {
197 return c.getString(1);
198 } else {
199 return "";
200 }
201 } catch (Exception e) {
202 return null;
203 } finally {
204 if (c != null) {
205 c.close();
206 }
207 }
208 }
209
210 private String queryAudio(int id, String column) {
211 Cursor c = null;
212 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800213 c = mProvider.query(mPackageName, Audio.Media.getContentUri(mVolumeName),
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500214 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800215 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500216 if (c != null && c.moveToNext()) {
217 return c.getString(1);
218 } else {
219 return "";
220 }
221 } catch (Exception e) {
222 return null;
223 } finally {
224 if (c != null) {
225 c.close();
226 }
227 }
228 }
229
230 private String queryGenre(int id) {
231 Cursor c = null;
232 try {
233 Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800234 c = mProvider.query(mPackageName, uri,
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500235 new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800236 null, null, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500237 if (c != null && c.moveToNext()) {
238 return c.getString(1);
239 } else {
240 return "";
241 }
242 } catch (Exception e) {
243 Log.e(TAG, "queryGenre exception", e);
244 return null;
245 } finally {
246 if (c != null) {
247 c.close();
248 }
249 }
250 }
251
252 private Long queryLong(int id, String column) {
253 Cursor c = null;
254 try {
255 // for now we are only reading properties from the "objects" table
Dianne Hackborn35654b62013-01-14 17:38:02 -0800256 c = mProvider.query(mPackageName, mUri,
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500257 new String [] { Files.FileColumns._ID, column },
Jeff Brown75ea64f2012-01-25 19:37:13 -0800258 ID_WHERE, new String[] { Integer.toString(id) }, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500259 if (c != null && c.moveToNext()) {
260 return new Long(c.getLong(1));
261 }
262 } catch (Exception e) {
263 } finally {
264 if (c != null) {
265 c.close();
266 }
267 }
268 return null;
269 }
270
271 private static String nameFromPath(String path) {
272 // extract name from full path
273 int start = 0;
274 int lastSlash = path.lastIndexOf('/');
275 if (lastSlash >= 0) {
276 start = lastSlash + 1;
277 }
278 int end = path.length();
279 if (end - start > 255) {
280 end = start + 255;
281 }
282 return path.substring(start, end);
283 }
284
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400285 MtpPropertyList getPropertyList(int handle, int format, int depth) {
Mike Lockwood071b2b62011-01-25 09:29:27 -0800286 //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500287 if (depth > 1) {
288 // we only support depth 0 and 1
289 // depth 0: single object, depth 1: immediate children
290 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
291 }
292
293 String where;
294 String[] whereArgs;
295 if (format == 0) {
Mike Lockwood4356d812011-02-07 13:40:21 -0500296 if (handle == 0xFFFFFFFF) {
297 // select all objects
298 where = null;
299 whereArgs = null;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500300 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500301 whereArgs = new String[] { Integer.toString(handle) };
302 if (depth == 1) {
303 where = PARENT_WHERE;
304 } else {
305 where = ID_WHERE;
306 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500307 }
308 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500309 if (handle == 0xFFFFFFFF) {
310 // select all objects with given format
311 where = FORMAT_WHERE;
312 whereArgs = new String[] { Integer.toString(format) };
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500313 } else {
Mike Lockwood4356d812011-02-07 13:40:21 -0500314 whereArgs = new String[] { Integer.toString(handle), Integer.toString(format) };
315 if (depth == 1) {
316 where = PARENT_FORMAT_WHERE;
317 } else {
318 where = ID_FORMAT_WHERE;
319 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500320 }
321 }
322
323 Cursor c = null;
324 try {
325 // don't query if not necessary
Mike Lockwood4356d812011-02-07 13:40:21 -0500326 if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800327 c = mProvider.query(mPackageName, mUri, mColumns, where, whereArgs, null, null);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500328 if (c == null) {
329 return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
330 }
331 }
332
333 int count = (c == null ? 1 : c.getCount());
334 MtpPropertyList result = new MtpPropertyList(count * mProperties.length,
335 MtpConstants.RESPONSE_OK);
336
337 // iterate over all objects in the query
338 for (int objectIndex = 0; objectIndex < count; objectIndex++) {
339 if (c != null) {
340 c.moveToNext();
Mike Lockwoodfd22edc2011-02-08 21:45:22 -0500341 handle = (int)c.getLong(0);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500342 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500343
344 // iterate over all properties in the query for the given object
345 for (int propertyIndex = 0; propertyIndex < mProperties.length; propertyIndex++) {
346 Property property = mProperties[propertyIndex];
347 int propertyCode = property.code;
348 int column = property.column;
349
350 // handle some special cases
351 switch (propertyCode) {
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500352 case MtpConstants.PROPERTY_PROTECTION_STATUS:
353 // protection status is always 0
354 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
355 break;
356 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
357 // special case - need to extract file name from full path
358 String value = c.getString(column);
359 if (value != null) {
360 result.append(handle, propertyCode, nameFromPath(value));
361 } else {
362 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
363 }
364 break;
365 case MtpConstants.PROPERTY_NAME:
366 // first try title
367 String name = c.getString(column);
368 // then try name
369 if (name == null) {
370 name = queryString(handle, Audio.PlaylistsColumns.NAME);
371 }
372 // if title and name fail, extract name from full path
373 if (name == null) {
374 name = queryString(handle, Files.FileColumns.DATA);
375 if (name != null) {
376 name = nameFromPath(name);
377 }
378 }
379 if (name != null) {
380 result.append(handle, propertyCode, name);
381 } else {
382 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
383 }
384 break;
385 case MtpConstants.PROPERTY_DATE_MODIFIED:
386 case MtpConstants.PROPERTY_DATE_ADDED:
387 // convert from seconds to DateTime
388 result.append(handle, propertyCode, format_date_time(c.getInt(column)));
389 break;
390 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
391 // release date is stored internally as just the year
392 int year = c.getInt(column);
393 String dateTime = Integer.toString(year) + "0101T000000";
394 result.append(handle, propertyCode, dateTime);
395 break;
396 case MtpConstants.PROPERTY_PERSISTENT_UID:
397 // PUID is concatenation of storageID and object handle
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400398 long puid = c.getLong(column);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500399 puid <<= 32;
400 puid += handle;
401 result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid);
402 break;
403 case MtpConstants.PROPERTY_TRACK:
404 result.append(handle, propertyCode, MtpConstants.TYPE_UINT16,
405 c.getInt(column) % 1000);
406 break;
407 case MtpConstants.PROPERTY_ARTIST:
408 result.append(handle, propertyCode,
409 queryAudio(handle, Audio.AudioColumns.ARTIST));
410 break;
411 case MtpConstants.PROPERTY_ALBUM_NAME:
412 result.append(handle, propertyCode,
413 queryAudio(handle, Audio.AudioColumns.ALBUM));
414 break;
415 case MtpConstants.PROPERTY_GENRE:
416 String genre = queryGenre(handle);
417 if (genre != null) {
418 result.append(handle, propertyCode, genre);
419 } else {
420 result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
421 }
422 break;
423 default:
424 if (property.type == MtpConstants.TYPE_STR) {
425 result.append(handle, propertyCode, c.getString(column));
426 } else if (property.type == MtpConstants.TYPE_UNDEFINED) {
427 result.append(handle, propertyCode, property.type, 0);
428 } else {
429 result.append(handle, propertyCode, property.type,
430 c.getLong(column));
431 }
432 break;
433 }
434 }
435 }
436
437 return result;
438 } catch (RemoteException e) {
439 return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR);
440 } finally {
441 if (c != null) {
442 c.close();
443 }
444 }
445 // impossible to get here, so no return statement
446 }
447
448 private native String format_date_time(long seconds);
449}