• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.mtp;
18 
19 import android.content.IContentProvider;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.os.RemoteException;
23 import android.provider.MediaStore.Audio;
24 import android.provider.MediaStore.Files;
25 import android.provider.MediaStore.Images;
26 import android.provider.MediaStore.MediaColumns;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 
31 class 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 
Property(int code, int type, int column)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;
52     private final String mPackageName;
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 + "=?";
63     private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
64     private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + FORMAT_WHERE;
65     private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
66     private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE;
67     // constructs a property group for a list of properties
MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String packageName, String volume, int[] properties)68     public MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String packageName,
69             String volume, int[] properties) {
70         mDatabase = database;
71         mProvider = provider;
72         mPackageName = packageName;
73         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 
createProperty(int code, ArrayList<String> columns)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:
97                 column = Files.FileColumns.STORAGE_ID;
98                 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
138                 column = Files.FileColumns.STORAGE_ID;
139                 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 
queryString(int id, String column)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
193             c = mProvider.query(mPackageName, mUri,
194                             new String [] { Files.FileColumns._ID, column },
195                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
196             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 
queryAudio(int id, String column)210     private String queryAudio(int id, String column) {
211         Cursor c = null;
212         try {
213             c = mProvider.query(mPackageName, Audio.Media.getContentUri(mVolumeName),
214                             new String [] { Files.FileColumns._ID, column },
215                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
216             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 
queryGenre(int id)230     private String queryGenre(int id) {
231         Cursor c = null;
232         try {
233             Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
234             c = mProvider.query(mPackageName, uri,
235                             new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
236                             null, null, null, null);
237             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 
queryLong(int id, String column)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
256             c = mProvider.query(mPackageName, mUri,
257                             new String [] { Files.FileColumns._ID, column },
258                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
259             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 
nameFromPath(String path)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 
getPropertyList(int handle, int format, int depth)285     MtpPropertyList getPropertyList(int handle, int format, int depth) {
286         //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
287         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) {
296             if (handle == 0xFFFFFFFF) {
297                 // select all objects
298                 where = null;
299                 whereArgs = null;
300             } else {
301                 whereArgs = new String[] { Integer.toString(handle) };
302                 if (depth == 1) {
303                     where = PARENT_WHERE;
304                 } else {
305                     where = ID_WHERE;
306                 }
307             }
308         } else {
309             if (handle == 0xFFFFFFFF) {
310                 // select all objects with given format
311                 where = FORMAT_WHERE;
312                 whereArgs = new String[] { Integer.toString(format) };
313             } else {
314                 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                 }
320             }
321         }
322 
323         Cursor c = null;
324         try {
325             // don't query if not necessary
326             if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
327                 c = mProvider.query(mPackageName, mUri, mColumns, where, whereArgs, null, null);
328                 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();
341                     handle = (int)c.getLong(0);
342                 }
343 
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) {
352                         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
398                             long puid = c.getLong(column);
399                             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 
format_date_time(long seconds)448     private native String format_date_time(long seconds);
449 }
450