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
17package android.mtp;
18
19import android.content.ContentProviderClient;
20import android.database.Cursor;
21import android.net.Uri;
22import android.os.RemoteException;
23import android.provider.MediaStore.Audio;
24import android.provider.MediaStore.Files;
25import android.provider.MediaStore.Images;
26import android.util.Log;
27
28import java.util.ArrayList;
29
30/**
31 * MtpPropertyGroup represents a list of MTP properties.
32 * {@hide}
33 */
34class 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
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
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
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
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
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     */
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
342    private native String format_date_time(long seconds);
343}
344