• 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;
24 import android.provider.MediaStore.Audio;
25 import android.provider.MediaStore.Files;
26 import android.provider.MediaStore.Images;
27 import android.provider.MediaStore.MediaColumns;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 
32 class 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 
Property(int code, int type, int column)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 mPackageName;
54     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 + "=?";
64     private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
65     private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + FORMAT_WHERE;
66     private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
67     private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE;
68     // constructs a property group for a list of properties
MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String packageName, String volume, int[] properties)69     public MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String packageName,
70             String volume, int[] properties) {
71         mDatabase = database;
72         mProvider = provider;
73         mPackageName = packageName;
74         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 
createProperty(int code, ArrayList<String> columns)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:
98                 column = Files.FileColumns.STORAGE_ID;
99                 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
139                 column = Files.FileColumns.STORAGE_ID;
140                 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 
queryString(int id, String column)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
194             c = mProvider.query(mPackageName, mUri,
195                             new String [] { Files.FileColumns._ID, column },
196                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
197             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 
queryAudio(int id, String column)211     private String queryAudio(int id, String column) {
212         Cursor c = null;
213         try {
214             c = mProvider.query(mPackageName, Audio.Media.getContentUri(mVolumeName),
215                             new String [] { Files.FileColumns._ID, column },
216                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
217             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 
queryGenre(int id)231     private String queryGenre(int id) {
232         Cursor c = null;
233         try {
234             Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
235             c = mProvider.query(mPackageName, uri,
236                             new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
237                             null, null, null, null);
238             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 
queryLong(int id, String column)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
257             c = mProvider.query(mPackageName, mUri,
258                             new String [] { Files.FileColumns._ID, column },
259                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
260             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 
nameFromPath(String path)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 
getPropertyList(int handle, int format, int depth)286     MtpPropertyList getPropertyList(int handle, int format, int depth) {
287         //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
288         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) {
297             if (handle == 0xFFFFFFFF) {
298                 // select all objects
299                 where = null;
300                 whereArgs = null;
301             } else {
302                 whereArgs = new String[] { Integer.toString(handle) };
303                 if (depth == 1) {
304                     where = PARENT_WHERE;
305                 } else {
306                     where = ID_WHERE;
307                 }
308             }
309         } else {
310             if (handle == 0xFFFFFFFF) {
311                 // select all objects with given format
312                 where = FORMAT_WHERE;
313                 whereArgs = new String[] { Integer.toString(format) };
314             } else {
315                 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                 }
321             }
322         }
323 
324         Cursor c = null;
325         try {
326             // don't query if not necessary
327             if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
328                 c = mProvider.query(mPackageName, mUri, mColumns, where, whereArgs, null, null);
329                 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();
342                     handle = (int)c.getLong(0);
343                 }
344 
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) {
353                         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
399                             long puid = c.getLong(column);
400                             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 
format_date_time(long seconds)449     private native String format_date_time(long seconds);
450 }
451