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