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.ContentProviderClient;
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.util.Log;
27 
28 import java.util.ArrayList;
29 
30 /**
31  * MtpPropertyGroup represents a list of MTP properties.
32  * {@hide}
33  */
34 class MtpPropertyGroup {
35     private static final String TAG = MtpPropertyGroup.class.getSimpleName();
36 
37     private class Property {
38         int code;
39         int type;
40         int column;
41 
Property(int code, int type, int column)42         Property(int code, int type, int column) {
43             this.code = code;
44             this.type = type;
45             this.column = column;
46         }
47     }
48 
49     private final ContentProviderClient mProvider;
50     private final String mVolumeName;
51     private final Uri mUri;
52 
53     // list of all properties in this group
54     private final Property[] mProperties;
55 
56     // list of columns for database query
57     private String[] mColumns;
58 
59     private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
60 
61     // constructs a property group for a list of properties
MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties)62     public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) {
63         mProvider = provider;
64         mVolumeName = volumeName;
65         mUri = Files.getMtpObjectsUri(volumeName);
66 
67         int count = properties.length;
68         ArrayList<String> columns = new ArrayList<>(count);
69         columns.add(Files.FileColumns._ID);
70 
71         mProperties = new Property[count];
72         for (int i = 0; i < count; i++) {
73             mProperties[i] = createProperty(properties[i], columns);
74         }
75         count = columns.size();
76         mColumns = new String[count];
77         for (int i = 0; i < count; i++) {
78             mColumns[i] = columns.get(i);
79         }
80     }
81 
createProperty(int code, ArrayList<String> columns)82     private Property createProperty(int code, ArrayList<String> columns) {
83         String column = null;
84         int type;
85 
86         switch (code) {
87             case MtpConstants.PROPERTY_STORAGE_ID:
88                 type = MtpConstants.TYPE_UINT32;
89                 break;
90             case MtpConstants.PROPERTY_OBJECT_FORMAT:
91                 type = MtpConstants.TYPE_UINT16;
92                 break;
93             case MtpConstants.PROPERTY_PROTECTION_STATUS:
94                 type = MtpConstants.TYPE_UINT16;
95                 break;
96             case MtpConstants.PROPERTY_OBJECT_SIZE:
97                 type = MtpConstants.TYPE_UINT64;
98                 break;
99             case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
100                 type = MtpConstants.TYPE_STR;
101                 break;
102             case MtpConstants.PROPERTY_NAME:
103                 type = MtpConstants.TYPE_STR;
104                 break;
105             case MtpConstants.PROPERTY_DATE_MODIFIED:
106                 type = MtpConstants.TYPE_STR;
107                 break;
108             case MtpConstants.PROPERTY_DATE_ADDED:
109                 type = MtpConstants.TYPE_STR;
110                 break;
111             case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
112                 column = Audio.AudioColumns.YEAR;
113                 type = MtpConstants.TYPE_STR;
114                 break;
115             case MtpConstants.PROPERTY_PARENT_OBJECT:
116                 type = MtpConstants.TYPE_UINT32;
117                 break;
118             case MtpConstants.PROPERTY_PERSISTENT_UID:
119                 type = MtpConstants.TYPE_UINT128;
120                 break;
121             case MtpConstants.PROPERTY_DURATION:
122                 column = Audio.AudioColumns.DURATION;
123                 type = MtpConstants.TYPE_UINT32;
124                 break;
125             case MtpConstants.PROPERTY_TRACK:
126                 column = Audio.AudioColumns.TRACK;
127                 type = MtpConstants.TYPE_UINT16;
128                 break;
129             case MtpConstants.PROPERTY_DISPLAY_NAME:
130                 type = MtpConstants.TYPE_STR;
131                 break;
132             case MtpConstants.PROPERTY_ARTIST:
133                 type = MtpConstants.TYPE_STR;
134                 break;
135             case MtpConstants.PROPERTY_ALBUM_NAME:
136                 type = MtpConstants.TYPE_STR;
137                 break;
138             case MtpConstants.PROPERTY_ALBUM_ARTIST:
139                 column = Audio.AudioColumns.ALBUM_ARTIST;
140                 type = MtpConstants.TYPE_STR;
141                 break;
142             case MtpConstants.PROPERTY_GENRE:
143                 // genre requires a special query
144                 type = MtpConstants.TYPE_STR;
145                 break;
146             case MtpConstants.PROPERTY_COMPOSER:
147                 column = Audio.AudioColumns.COMPOSER;
148                 type = MtpConstants.TYPE_STR;
149                 break;
150             case MtpConstants.PROPERTY_DESCRIPTION:
151                 column = Images.ImageColumns.DESCRIPTION;
152                 type = MtpConstants.TYPE_STR;
153                 break;
154             case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
155             case MtpConstants.PROPERTY_AUDIO_BITRATE:
156             case MtpConstants.PROPERTY_SAMPLE_RATE:
157                 // these are special cased
158                 type = MtpConstants.TYPE_UINT32;
159                 break;
160             case MtpConstants.PROPERTY_BITRATE_TYPE:
161             case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
162                 // these are special cased
163                 type = MtpConstants.TYPE_UINT16;
164                 break;
165             default:
166                 type = MtpConstants.TYPE_UNDEFINED;
167                 Log.e(TAG, "unsupported property " + code);
168                 break;
169         }
170 
171         if (column != null) {
172             columns.add(column);
173             return new Property(code, type, columns.size() - 1);
174         } else {
175             return new Property(code, type, -1);
176         }
177     }
178 
queryAudio(String path, String column)179     private String queryAudio(String path, String column) {
180         Cursor c = null;
181         try {
182             c = mProvider.query(Audio.Media.getContentUri(mVolumeName),
183                             new String [] { column },
184                             PATH_WHERE, new String[] {path}, null, null);
185             if (c != null && c.moveToNext()) {
186                 return c.getString(0);
187             } else {
188                 return "";
189             }
190         } catch (Exception e) {
191             return "";
192         } finally {
193             if (c != null) {
194                 c.close();
195             }
196         }
197     }
198 
queryGenre(String path)199     private String queryGenre(String path) {
200         Cursor c = null;
201         try {
202             c = mProvider.query(Audio.Genres.getContentUri(mVolumeName),
203                             new String [] { Audio.GenresColumns.NAME },
204                             PATH_WHERE, new String[] {path}, null, null);
205             if (c != null && c.moveToNext()) {
206                 return c.getString(0);
207             } else {
208                 return "";
209             }
210         } catch (Exception e) {
211             return "";
212         } finally {
213             if (c != null) {
214                 c.close();
215             }
216         }
217     }
218 
219     /**
220      * Gets the values of the properties represented by this property group for the given
221      * object and adds them to the given property list.
222      * @return Response_OK if the operation succeeded.
223      */
getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list)224     public int getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list) {
225         Cursor c = null;
226         int id = object.getId();
227         String path = object.getPath().toString();
228         for (Property property : mProperties) {
229             if (property.column != -1 && c == null) {
230                 try {
231                     // Look up the entry in MediaProvider only if one of those properties is needed.
232                     c = mProvider.query(mUri, mColumns,
233                             PATH_WHERE, new String[] {path}, null, null);
234                     if (c != null && !c.moveToNext()) {
235                         c.close();
236                         c = null;
237                     }
238                 } catch (RemoteException e) {
239                     Log.e(TAG, "Mediaprovider lookup failed");
240                 }
241             }
242             switch (property.code) {
243                 case MtpConstants.PROPERTY_PROTECTION_STATUS:
244                     // protection status is always 0
245                     list.append(id, property.code, property.type, 0);
246                     break;
247                 case MtpConstants.PROPERTY_NAME:
248                 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
249                 case MtpConstants.PROPERTY_DISPLAY_NAME:
250                     list.append(id, property.code, object.getName());
251                     break;
252                 case MtpConstants.PROPERTY_DATE_MODIFIED:
253                 case MtpConstants.PROPERTY_DATE_ADDED:
254                     // convert from seconds to DateTime
255                     list.append(id, property.code,
256                             format_date_time(object.getModifiedTime()));
257                     break;
258                 case MtpConstants.PROPERTY_STORAGE_ID:
259                     list.append(id, property.code, property.type, object.getStorageId());
260                     break;
261                 case MtpConstants.PROPERTY_OBJECT_FORMAT:
262                     list.append(id, property.code, property.type, object.getFormat());
263                     break;
264                 case MtpConstants.PROPERTY_OBJECT_SIZE:
265                     list.append(id, property.code, property.type, object.getSize());
266                     break;
267                 case MtpConstants.PROPERTY_PARENT_OBJECT:
268                     list.append(id, property.code, property.type,
269                             object.getParent().isRoot() ? 0 : object.getParent().getId());
270                     break;
271                 case MtpConstants.PROPERTY_PERSISTENT_UID:
272                     // The persistent uid must be unique and never reused among all objects,
273                     // and remain the same between sessions.
274                     long puid = (object.getPath().toString().hashCode() << 32)
275                             + object.getModifiedTime();
276                     list.append(id, property.code, property.type, puid);
277                     break;
278                 case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
279                     // release date is stored internally as just the year
280                     int year = 0;
281                     if (c != null)
282                         year = c.getInt(property.column);
283                     String dateTime = Integer.toString(year) + "0101T000000";
284                     list.append(id, property.code, dateTime);
285                     break;
286                 case MtpConstants.PROPERTY_TRACK:
287                     int track = 0;
288                     if (c != null)
289                         track = c.getInt(property.column);
290                     list.append(id, property.code, MtpConstants.TYPE_UINT16,
291                             track % 1000);
292                     break;
293                 case MtpConstants.PROPERTY_ARTIST:
294                     list.append(id, property.code,
295                             queryAudio(path, Audio.AudioColumns.ARTIST));
296                     break;
297                 case MtpConstants.PROPERTY_ALBUM_NAME:
298                     list.append(id, property.code,
299                             queryAudio(path, Audio.AudioColumns.ALBUM));
300                     break;
301                 case MtpConstants.PROPERTY_GENRE:
302                     String genre = queryGenre(path);
303                     if (genre != null) {
304                         list.append(id, property.code, genre);
305                     }
306                     break;
307                 case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
308                 case MtpConstants.PROPERTY_AUDIO_BITRATE:
309                 case MtpConstants.PROPERTY_SAMPLE_RATE:
310                     // we don't have these in our database, so return 0
311                     list.append(id, property.code, MtpConstants.TYPE_UINT32, 0);
312                     break;
313                 case MtpConstants.PROPERTY_BITRATE_TYPE:
314                 case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
315                     // we don't have these in our database, so return 0
316                     list.append(id, property.code, MtpConstants.TYPE_UINT16, 0);
317                     break;
318                 default:
319                     switch(property.type) {
320                         case MtpConstants.TYPE_UNDEFINED:
321                             list.append(id, property.code, property.type, 0);
322                             break;
323                         case MtpConstants.TYPE_STR:
324                             String value = "";
325                             if (c != null)
326                                 value = c.getString(property.column);
327                             list.append(id, property.code, value);
328                             break;
329                         default:
330                             long longValue = 0L;
331                             if (c != null)
332                                 longValue = c.getLong(property.column);
333                             list.append(id, property.code, property.type, longValue);
334                     }
335             }
336         }
337         if (c != null)
338             c.close();
339         return MtpConstants.RESPONSE_OK;
340     }
341 
format_date_time(long seconds)342     private native String format_date_time(long seconds);
343 }
344