1e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey/*
2e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * Copyright (C) 2013 The Android Open Source Project
3e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey *
4e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * you may not use this file except in compliance with the License.
6e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * You may obtain a copy of the License at
7e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey *
8e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey *
10e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * Unless required by applicable law or agreed to in writing, software
11e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * See the License for the specific language governing permissions and
14e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey * limitations under the License.
15e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey */
16e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
17e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeypackage com.android.providers.media;
18e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
19e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.content.ContentResolver;
20e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.content.ContentUris;
21ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkeyimport android.content.Context;
22240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkeyimport android.content.res.AssetFileDescriptor;
23e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.database.Cursor;
24e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.database.MatrixCursor;
25e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.database.MatrixCursor.RowBuilder;
260f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkeyimport android.graphics.BitmapFactory;
276e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkeyimport android.graphics.Point;
28e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.net.Uri;
29e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.os.Binder;
305082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkeyimport android.os.Bundle;
316e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkeyimport android.os.CancellationSignal;
32e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.os.ParcelFileDescriptor;
33e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.BaseColumns;
34e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.DocumentsContract;
35498075b5efbc333e462104a83b95c12bd842223eJeff Sharkeyimport android.provider.DocumentsContract.Document;
36498075b5efbc333e462104a83b95c12bd842223eJeff Sharkeyimport android.provider.DocumentsContract.Root;
376e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkeyimport android.provider.DocumentsProvider;
38e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Audio;
39e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Audio.AlbumColumns;
40e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Audio.Albums;
41e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Audio.ArtistColumns;
42e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Audio.Artists;
43e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Audio.AudioColumns;
44b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkeyimport android.provider.MediaStore.Files.FileColumns;
45e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Images;
46e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.provider.MediaStore.Images.ImageColumns;
470f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkeyimport android.provider.MediaStore.Video;
480f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkeyimport android.provider.MediaStore.Video.VideoColumns;
496718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkeyimport android.text.TextUtils;
50e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport android.text.format.DateUtils;
515082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkeyimport android.util.Log;
52e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
53e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport libcore.io.IoUtils;
54e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
550f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkeyimport java.io.File;
56e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkeyimport java.io.FileNotFoundException;
57e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
58e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey/**
59b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey * Presents a {@link DocumentsContract} view of {@link MediaProvider} external
60b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey * contents.
61e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey */
626e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkeypublic class MediaDocumentsProvider extends DocumentsProvider {
635082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey    private static final String TAG = "MediaDocumentsProvider";
646e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey
65b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey    private static final String AUTHORITY = "com.android.providers.media.documents";
66b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey
67498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
684cb8114276183402558ede05ab92ee8f7a996a34Jeff Sharkey            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
694cb8114276183402558ede05ab92ee8f7a996a34Jeff Sharkey            Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_MIME_TYPES
70498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    };
71498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey
72498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
73498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
74498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
756e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    };
766e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey
776718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey    private static final String IMAGE_MIME_TYPES = joinNewline("image/*");
786718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey
790f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private static final String VIDEO_MIME_TYPES = joinNewline("video/*");
800f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
816718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey    private static final String AUDIO_MIME_TYPES = joinNewline(
826718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey            "audio/*", "application/ogg", "application/x-flac");
836718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey
840f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private static final String TYPE_IMAGES_ROOT = "images_root";
850f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private static final String TYPE_IMAGES_BUCKET = "images_bucket";
866e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private static final String TYPE_IMAGE = "image";
87e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
880f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private static final String TYPE_VIDEOS_ROOT = "videos_root";
890f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private static final String TYPE_VIDEOS_BUCKET = "videos_bucket";
900f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private static final String TYPE_VIDEO = "video";
910f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
920f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private static final String TYPE_AUDIO_ROOT = "audio_root";
936e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private static final String TYPE_AUDIO = "audio";
946e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private static final String TYPE_ARTIST = "artist";
956e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private static final String TYPE_ALBUM = "album";
96e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
97ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey    private static boolean sReturnedImagesEmpty = false;
98ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey    private static boolean sReturnedVideosEmpty = false;
99ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey    private static boolean sReturnedAudioEmpty = false;
100ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey
1016718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey    private static String joinNewline(String... args) {
1026718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey        return TextUtils.join("\n", args);
1036718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey    }
1046718b71ef44d06a70eeb0ea9ce66140ecdcafdbbJeff Sharkey
105f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey    private void copyNotificationUri(MatrixCursor result, Cursor cursor) {
106f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey        result.setNotificationUri(getContext().getContentResolver(), cursor.getNotificationUri());
107f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey    }
108f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey
1096e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    @Override
1106e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    public boolean onCreate() {
1116e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        return true;
112e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
113e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
114ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey    private static void notifyRootsChanged(Context context) {
115b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        context.getContentResolver()
116b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey                .notifyChange(DocumentsContract.buildRootsUri(AUTHORITY), null, false);
117ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey    }
118ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey
119b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey    /**
120b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey     * When inserting the first item of each type, we need to trigger a roots
121b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey     * refresh to clear a previously reported {@link Root#FLAG_EMPTY}.
122b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey     */
123b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey    static void onMediaStoreInsert(Context context, String volumeName, int type, long id) {
124b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        if (!"external".equals(volumeName)) return;
125ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey
126b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        if (type == FileColumns.MEDIA_TYPE_IMAGE && sReturnedImagesEmpty) {
127b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            sReturnedImagesEmpty = false;
128ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey            notifyRootsChanged(context);
129b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        } else if (type == FileColumns.MEDIA_TYPE_VIDEO && sReturnedVideosEmpty) {
130ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey            sReturnedVideosEmpty = false;
131b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            notifyRootsChanged(context);
132b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        } else if (type == FileColumns.MEDIA_TYPE_AUDIO && sReturnedAudioEmpty) {
133b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            sReturnedAudioEmpty = false;
134b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            notifyRootsChanged(context);
135ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey        }
136ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey    }
137ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey
138b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey    /**
139b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey     * When deleting an item, we need to revoke any outstanding Uri grants.
140b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey     */
141b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey    static void onMediaStoreDelete(Context context, String volumeName, int type, long id) {
142b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        if (!"external".equals(volumeName)) return;
143b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey
144b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        if (type == FileColumns.MEDIA_TYPE_IMAGE) {
145b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            final Uri uri = DocumentsContract.buildDocumentUri(
146b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey                    AUTHORITY, getDocIdForIdent(TYPE_IMAGE, id));
147b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            context.revokeUriPermission(uri, ~0);
148b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        } else if (type == FileColumns.MEDIA_TYPE_VIDEO) {
149b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            final Uri uri = DocumentsContract.buildDocumentUri(
150b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey                    AUTHORITY, getDocIdForIdent(TYPE_VIDEO, id));
151b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            context.revokeUriPermission(uri, ~0);
152b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey        } else if (type == FileColumns.MEDIA_TYPE_AUDIO) {
153b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            final Uri uri = DocumentsContract.buildDocumentUri(
154b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey                    AUTHORITY, getDocIdForIdent(TYPE_AUDIO, id));
155b39b32d96e938d5a5792bb56e4c37f3e752493cdJeff Sharkey            context.revokeUriPermission(uri, ~0);
156ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey        }
157ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey    }
158ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey
1596e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private static class Ident {
1606e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        public String type;
1616e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        public long id;
1626e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    }
163e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
1646e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private static Ident getIdentForDocId(String docId) {
1656e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final Ident ident = new Ident();
1666e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final int split = docId.indexOf(':');
1676e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        if (split == -1) {
1686e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            ident.type = docId;
1696e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            ident.id = -1;
1706e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        } else {
1716e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            ident.type = docId.substring(0, split);
1726e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            ident.id = Long.parseLong(docId.substring(split + 1));
1736e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        }
1746e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        return ident;
1756e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    }
1766e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey
1776e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private static String getDocIdForIdent(String type, long id) {
1786e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        return type + ":" + id;
1796e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    }
180e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
181498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    private static String[] resolveRootProjection(String[] projection) {
182498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
183498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    }
184498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey
185498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    private static String[] resolveDocumentProjection(String[] projection) {
186498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
187498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    }
188498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey
189e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    @Override
190498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
191498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
192498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        includeImagesRoot(result);
1930f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        includeVideosRoot(result);
194498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        includeAudioRoot(result);
195498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        return result;
196e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
197e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
198e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    @Override
199498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    public Cursor queryDocument(String docId, String[] projection) throws FileNotFoundException {
200e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
201498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
2026e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final Ident ident = getIdentForDocId(docId);
2036e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey
2046e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final long token = Binder.clearCallingIdentity();
2056e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        Cursor cursor = null;
2066e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        try {
2070f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            if (TYPE_IMAGES_ROOT.equals(ident.type)) {
2086e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // single root
2090f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                includeImagesRootDocument(result);
2100f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_IMAGES_BUCKET.equals(ident.type)) {
2116e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // single bucket
2126e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Images.Media.EXTERNAL_CONTENT_URI,
2130f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        ImagesBucketQuery.PROJECTION, ImageColumns.BUCKET_ID + "=" + ident.id,
2144c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey                        null, ImagesBucketQuery.SORT_ORDER);
215f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
2166e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                if (cursor.moveToFirst()) {
2170f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    includeImagesBucket(result, cursor);
218e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                }
2196e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else if (TYPE_IMAGE.equals(ident.type)) {
2206e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // single image
2216e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Images.Media.EXTERNAL_CONTENT_URI,
2226e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        ImageQuery.PROJECTION, BaseColumns._ID + "=" + ident.id, null,
2236e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        null);
224f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
2256e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                if (cursor.moveToFirst()) {
2266e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeImage(result, cursor);
227e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                }
2280f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEOS_ROOT.equals(ident.type)) {
2296e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // single root
2300f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                includeVideosRootDocument(result);
2310f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEOS_BUCKET.equals(ident.type)) {
2320f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                // single bucket
2330f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor = resolver.query(Video.Media.EXTERNAL_CONTENT_URI,
2340f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        VideosBucketQuery.PROJECTION, VideoColumns.BUCKET_ID + "=" + ident.id,
2354c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey                        null, VideosBucketQuery.SORT_ORDER);
2360f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                copyNotificationUri(result, cursor);
2370f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                if (cursor.moveToFirst()) {
238c5341234fb73d7f53a817a563466a0ea76850529Jeff Sharkey                    includeVideosBucket(result, cursor);
2390f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                }
2400f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEO.equals(ident.type)) {
2410f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                // single video
2420f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor = resolver.query(Video.Media.EXTERNAL_CONTENT_URI,
2430f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        VideoQuery.PROJECTION, BaseColumns._ID + "=" + ident.id, null,
2440f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        null);
2450f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                copyNotificationUri(result, cursor);
2460f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                if (cursor.moveToFirst()) {
2470f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    includeVideo(result, cursor);
2480f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                }
2490f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_AUDIO_ROOT.equals(ident.type)) {
2500f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                // single root
2510f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                includeAudioRootDocument(result);
2526e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else if (TYPE_ARTIST.equals(ident.type)) {
2536e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // single artist
2546e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Artists.EXTERNAL_CONTENT_URI,
2556e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        ArtistQuery.PROJECTION, BaseColumns._ID + "=" + ident.id, null,
2566e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        null);
257f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
2586e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                if (cursor.moveToFirst()) {
2596e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeArtist(result, cursor);
2606e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                }
2616e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else if (TYPE_ALBUM.equals(ident.type)) {
2626e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // single album
2636e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Albums.EXTERNAL_CONTENT_URI,
2646e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        AlbumQuery.PROJECTION, BaseColumns._ID + "=" + ident.id, null,
2656e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        null);
266f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
2676e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                if (cursor.moveToFirst()) {
2686e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeAlbum(result, cursor);
2696e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                }
2706e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else if (TYPE_AUDIO.equals(ident.type)) {
2716e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // single song
2726e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Audio.Media.EXTERNAL_CONTENT_URI,
2736e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        SongQuery.PROJECTION, BaseColumns._ID + "=" + ident.id, null,
2746e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        null);
275f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
2766e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                if (cursor.moveToFirst()) {
2776e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeAudio(result, cursor);
2786e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                }
2796e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else {
2806e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                throw new UnsupportedOperationException("Unsupported document " + docId);
281e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey            }
2826e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        } finally {
2836e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            IoUtils.closeQuietly(cursor);
2846e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            Binder.restoreCallingIdentity(token);
2856e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        }
2866e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        return result;
2876e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    }
2886e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey
2896e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    @Override
290498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    public Cursor queryChildDocuments(String docId, String[] projection, String sortOrder)
291498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey            throws FileNotFoundException {
2926e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
293498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
2946e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final Ident ident = getIdentForDocId(docId);
2956e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey
2966e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final long token = Binder.clearCallingIdentity();
2976e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        Cursor cursor = null;
2986e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        try {
2990f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            if (TYPE_IMAGES_ROOT.equals(ident.type)) {
3006e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // include all unique buckets
3016e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Images.Media.EXTERNAL_CONTENT_URI,
3024c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey                        ImagesBucketQuery.PROJECTION, null, null, ImagesBucketQuery.SORT_ORDER);
3034c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey                // multiple orders
304f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
3056e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                long lastId = Long.MIN_VALUE;
3066e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                while (cursor.moveToNext()) {
3070f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    final long id = cursor.getLong(ImagesBucketQuery.BUCKET_ID);
3086e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    if (lastId != id) {
3090f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        includeImagesBucket(result, cursor);
3106e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        lastId = id;
311e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                    }
312e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                }
3130f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_IMAGES_BUCKET.equals(ident.type)) {
3146e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // include images under bucket
3156e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Images.Media.EXTERNAL_CONTENT_URI,
3166e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        ImageQuery.PROJECTION, ImageColumns.BUCKET_ID + "=" + ident.id,
3176e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        null, null);
318f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
3196e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                while (cursor.moveToNext()) {
3206e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeImage(result, cursor);
3216e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                }
3220f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEOS_ROOT.equals(ident.type)) {
3230f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                // include all unique buckets
3240f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor = resolver.query(Video.Media.EXTERNAL_CONTENT_URI,
3254c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey                        VideosBucketQuery.PROJECTION, null, null, VideosBucketQuery.SORT_ORDER);
3260f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                copyNotificationUri(result, cursor);
3270f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                long lastId = Long.MIN_VALUE;
3280f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                while (cursor.moveToNext()) {
3290f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    final long id = cursor.getLong(VideosBucketQuery.BUCKET_ID);
3300f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    if (lastId != id) {
3310f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        includeVideosBucket(result, cursor);
3320f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        lastId = id;
3330f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    }
3340f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                }
3350f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEOS_BUCKET.equals(ident.type)) {
3360f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                // include videos under bucket
3370f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor = resolver.query(Video.Media.EXTERNAL_CONTENT_URI,
3380f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        VideoQuery.PROJECTION, VideoColumns.BUCKET_ID + "=" + ident.id,
3390f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        null, null);
3400f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                copyNotificationUri(result, cursor);
3410f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                while (cursor.moveToNext()) {
3420f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    includeVideo(result, cursor);
3430f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                }
3440f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_AUDIO_ROOT.equals(ident.type)) {
3456e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // include all artists
3466e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Audio.Artists.EXTERNAL_CONTENT_URI,
3476e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        ArtistQuery.PROJECTION, null, null, null);
348f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
3496e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                while (cursor.moveToNext()) {
3506e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeArtist(result, cursor);
3516e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                }
3526e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else if (TYPE_ARTIST.equals(ident.type)) {
3536e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // include all albums under artist
3546e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Artists.Albums.getContentUri("external", ident.id),
3556e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        AlbumQuery.PROJECTION, null, null, null);
356f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
3576e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                while (cursor.moveToNext()) {
3586e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeAlbum(result, cursor);
3596e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                }
3606e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else if (TYPE_ALBUM.equals(ident.type)) {
3616e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                // include all songs under album
3626e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                cursor = resolver.query(Audio.Media.EXTERNAL_CONTENT_URI,
3636e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        SongQuery.PROJECTION, AudioColumns.ALBUM_ID + "=" + ident.id,
3646e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                        null, null);
365f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
3666e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                while (cursor.moveToNext()) {
3676e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    includeAudio(result, cursor);
3686e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                }
3696e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else {
3706e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                throw new UnsupportedOperationException("Unsupported document " + docId);
371e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey            }
3726e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        } finally {
3736e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            IoUtils.closeQuietly(cursor);
3746e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            Binder.restoreCallingIdentity(token);
375e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        }
3766e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        return result;
377e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
378e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
3796e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    @Override
3803c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey    public Cursor queryRecentDocuments(String rootId, String[] projection)
3813c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey            throws FileNotFoundException {
3823c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
3833c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
3843c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey
3853c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        final long token = Binder.clearCallingIdentity();
3863c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        Cursor cursor = null;
3873c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        try {
3880f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            if (TYPE_IMAGES_ROOT.equals(rootId)) {
3893c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey                // include all unique buckets
3903c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey                cursor = resolver.query(Images.Media.EXTERNAL_CONTENT_URI,
3913c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey                        ImageQuery.PROJECTION, null, null, ImageColumns.DATE_MODIFIED + " DESC");
392f9de3a3dd5bbc0251672b33ca12c0f0306556e62Jeff Sharkey                copyNotificationUri(result, cursor);
3930f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                while (cursor.moveToNext() && result.getCount() < 64) {
3943c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey                    includeImage(result, cursor);
3953c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey                }
3960f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEOS_ROOT.equals(rootId)) {
3970f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                // include all unique buckets
3980f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor = resolver.query(Video.Media.EXTERNAL_CONTENT_URI,
3990f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        VideoQuery.PROJECTION, null, null, VideoColumns.DATE_MODIFIED + " DESC");
4000f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                copyNotificationUri(result, cursor);
4010f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                while (cursor.moveToNext() && result.getCount() < 64) {
4020f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    includeVideo(result, cursor);
4030f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                }
4043c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey            } else {
4053c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey                throw new UnsupportedOperationException("Unsupported root " + rootId);
4063c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey            }
4073c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        } finally {
4083c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey            IoUtils.closeQuietly(cursor);
4093c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey            Binder.restoreCallingIdentity(token);
4103c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        }
4113c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey        return result;
4123c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey    }
4133c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey
4143c22789f7fad36e3108d8597d923614c86d60064Jeff Sharkey    @Override
4156e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
4166e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            throws FileNotFoundException {
4176e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final Ident ident = getIdentForDocId(docId);
418e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
4196e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        if (!"r".equals(mode)) {
4206e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            throw new IllegalArgumentException("Media is read-only");
4216e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        }
422e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
4236e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final Uri target;
4246e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        if (TYPE_IMAGE.equals(ident.type) && ident.id != -1) {
4256e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            target = ContentUris.withAppendedId(
4266e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    Images.Media.EXTERNAL_CONTENT_URI, ident.id);
4270f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        } else if (TYPE_VIDEO.equals(ident.type) && ident.id != -1) {
4280f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            target = ContentUris.withAppendedId(
4290f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    Video.Media.EXTERNAL_CONTENT_URI, ident.id);
4306e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        } else if (TYPE_AUDIO.equals(ident.type) && ident.id != -1) {
4316e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            target = ContentUris.withAppendedId(
4326e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                    Audio.Media.EXTERNAL_CONTENT_URI, ident.id);
4336e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        } else {
4346e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            throw new UnsupportedOperationException("Unsupported document " + docId);
4356e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        }
436e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
4376e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        // Delegate to real provider
4386e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final long token = Binder.clearCallingIdentity();
4396e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        try {
4406e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            return getContext().getContentResolver().openFileDescriptor(target, mode);
4416e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        } finally {
4426e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            Binder.restoreCallingIdentity(token);
4436e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        }
444e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
445e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
4466e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    @Override
4476e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    public AssetFileDescriptor openDocumentThumbnail(
4486e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
4490f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
4506e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final Ident ident = getIdentForDocId(docId);
451e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
4526e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final long token = Binder.clearCallingIdentity();
4536e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        try {
4540f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            if (TYPE_IMAGES_BUCKET.equals(ident.type)) {
4550f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                final long id = getImageForBucketCleared(ident.id);
4560f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                return openOrCreateImageThumbnailCleared(id, signal);
4570f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_IMAGE.equals(ident.type)) {
4580f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                return openOrCreateImageThumbnailCleared(ident.id, signal);
4590f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEOS_BUCKET.equals(ident.type)) {
4600f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                final long id = getVideoForBucketCleared(ident.id);
4610f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                return openOrCreateVideoThumbnailCleared(id, signal);
4620f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            } else if (TYPE_VIDEO.equals(ident.type)) {
4630f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                return openOrCreateVideoThumbnailCleared(ident.id, signal);
4646e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            } else {
4656e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey                throw new UnsupportedOperationException("Unsupported document " + docId);
4666e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            }
4676e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        } finally {
4686e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey            Binder.restoreCallingIdentity(token);
469e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        }
470e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
471e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
4720f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private boolean isEmpty(Uri uri) {
4730f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
4740f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final long token = Binder.clearCallingIdentity();
4750f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        Cursor cursor = null;
4760f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        try {
4770f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            cursor = resolver.query(uri, new String[] {
4780f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    BaseColumns._ID }, null, null, null);
47911ca19dc41602c091615141545a307c9d4b7f186Jeff Sharkey            return (cursor == null) || (cursor.getCount() == 0);
4800f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        } finally {
4810f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            IoUtils.closeQuietly(cursor);
4820f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            Binder.restoreCallingIdentity(token);
4830f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
4840f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
4850f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
4866e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private void includeImagesRoot(MatrixCursor result) {
4870f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        int flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_RECENTS;
4880f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        if (isEmpty(Images.Media.EXTERNAL_CONTENT_URI)) {
4890f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            flags |= Root.FLAG_EMPTY;
490ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey            sReturnedImagesEmpty = true;
4910f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
4920f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
4936e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final RowBuilder row = result.newRow();
4940f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_ROOT_ID, TYPE_IMAGES_ROOT);
4950f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_FLAGS, flags);
49682c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Root.COLUMN_TITLE, getContext().getString(R.string.root_images));
4970f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_DOCUMENT_ID, TYPE_IMAGES_ROOT);
49882c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Root.COLUMN_MIME_TYPES, IMAGE_MIME_TYPES);
499e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
500e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
5010f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private void includeVideosRoot(MatrixCursor result) {
5020f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        int flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_RECENTS;
5030f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        if (isEmpty(Video.Media.EXTERNAL_CONTENT_URI)) {
5040f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            flags |= Root.FLAG_EMPTY;
505ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey            sReturnedVideosEmpty = true;
5060f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
5070f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
5080f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final RowBuilder row = result.newRow();
5090f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_ROOT_ID, TYPE_VIDEOS_ROOT);
5100f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_FLAGS, flags);
5110f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_TITLE, getContext().getString(R.string.root_videos));
5120f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_DOCUMENT_ID, TYPE_VIDEOS_ROOT);
5130f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_MIME_TYPES, VIDEO_MIME_TYPES);
5140f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
5150f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
5166e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private void includeAudioRoot(MatrixCursor result) {
5170f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        int flags = Root.FLAG_LOCAL_ONLY;
5180f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        if (isEmpty(Audio.Media.EXTERNAL_CONTENT_URI)) {
5190f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            flags |= Root.FLAG_EMPTY;
520ca709d4f9f6ef66337b96010a150e30a5888855eJeff Sharkey            sReturnedAudioEmpty = true;
5210f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
5220f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
523e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final RowBuilder row = result.newRow();
5240f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_ROOT_ID, TYPE_AUDIO_ROOT);
5250f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_FLAGS, flags);
52682c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Root.COLUMN_TITLE, getContext().getString(R.string.root_audio));
5270f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Root.COLUMN_DOCUMENT_ID, TYPE_AUDIO_ROOT);
52882c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Root.COLUMN_MIME_TYPES, AUDIO_MIME_TYPES);
529498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    }
530498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey
5310f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private void includeImagesRootDocument(MatrixCursor result) {
532498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        final RowBuilder row = result.newRow();
5330f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, TYPE_IMAGES_ROOT);
53482c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, getContext().getString(R.string.root_images));
53586bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey        row.add(Document.COLUMN_FLAGS,
53686bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey                Document.FLAG_DIR_PREFERS_GRID | Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
53782c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
538498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey    }
539498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey
5400f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private void includeVideosRootDocument(MatrixCursor result) {
541498075b5efbc333e462104a83b95c12bd842223eJeff Sharkey        final RowBuilder row = result.newRow();
5420f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, TYPE_VIDEOS_ROOT);
5430f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, getContext().getString(R.string.root_videos));
54486bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey        row.add(Document.COLUMN_FLAGS,
54586bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey                Document.FLAG_DIR_PREFERS_GRID | Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
5460f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
5470f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
5480f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
5490f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private void includeAudioRootDocument(MatrixCursor result) {
5500f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final RowBuilder row = result.newRow();
5510f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, TYPE_AUDIO_ROOT);
55282c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, getContext().getString(R.string.root_audio));
55382c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
554e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
555e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
5560f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private interface ImagesBucketQuery {
557e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final String[] PROJECTION = new String[] {
558e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns.BUCKET_ID,
559e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns.BUCKET_DISPLAY_NAME,
560e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns.DATE_MODIFIED };
5614c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey        final String SORT_ORDER = ImageColumns.BUCKET_ID + ", " + ImageColumns.DATE_MODIFIED
5624c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey                + " DESC";
563e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
564e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int BUCKET_ID = 0;
565e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int BUCKET_DISPLAY_NAME = 1;
566e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int DATE_MODIFIED = 2;
567e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
568e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
5690f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private void includeImagesBucket(MatrixCursor result, Cursor cursor) {
5700f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final long id = cursor.getLong(ImagesBucketQuery.BUCKET_ID);
5710f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String docId = getDocIdForIdent(TYPE_IMAGES_BUCKET, id);
572e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
573e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final RowBuilder row = result.newRow();
57482c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
5750f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME,
5760f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor.getString(ImagesBucketQuery.BUCKET_DISPLAY_NAME));
57782c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
57882c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_LAST_MODIFIED,
5790f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor.getLong(ImagesBucketQuery.DATE_MODIFIED) * DateUtils.SECOND_IN_MILLIS);
58086bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey        row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_GRID
58186bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey                | Document.FLAG_SUPPORTS_THUMBNAIL | Document.FLAG_DIR_PREFERS_LAST_MODIFIED
58286bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey                | Document.FLAG_DIR_HIDE_GRID_TITLES);
583e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
584e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
585e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    private interface ImageQuery {
586e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final String[] PROJECTION = new String[] {
587e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns._ID,
588e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns.DISPLAY_NAME,
589e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns.MIME_TYPE,
590e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns.SIZE,
591e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ImageColumns.DATE_MODIFIED };
592e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
593e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int _ID = 0;
594e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int DISPLAY_NAME = 1;
595e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int MIME_TYPE = 2;
596e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int SIZE = 3;
597e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int DATE_MODIFIED = 4;
598e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
599e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
600e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    private void includeImage(MatrixCursor result, Cursor cursor) {
601e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final long id = cursor.getLong(ImageQuery._ID);
6026e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final String docId = getDocIdForIdent(TYPE_IMAGE, id);
603e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
604e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final RowBuilder row = result.newRow();
60582c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
60682c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, cursor.getString(ImageQuery.DISPLAY_NAME));
60782c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_SIZE, cursor.getLong(ImageQuery.SIZE));
60882c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, cursor.getString(ImageQuery.MIME_TYPE));
60982c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_LAST_MODIFIED,
610e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                cursor.getLong(ImageQuery.DATE_MODIFIED) * DateUtils.SECOND_IN_MILLIS);
61182c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_THUMBNAIL);
612e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
613e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
6140f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private interface VideosBucketQuery {
6150f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String[] PROJECTION = new String[] {
6160f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.BUCKET_ID,
6170f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.BUCKET_DISPLAY_NAME,
6180f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.DATE_MODIFIED };
6194c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey        final String SORT_ORDER = VideoColumns.BUCKET_ID + ", " + VideoColumns.DATE_MODIFIED
6204c9dda96c93853561519de9934167ff337e3b281Jeff Sharkey                + " DESC";
6210f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
6220f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int BUCKET_ID = 0;
6230f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int BUCKET_DISPLAY_NAME = 1;
6240f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int DATE_MODIFIED = 2;
6250f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
6260f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
6270f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private void includeVideosBucket(MatrixCursor result, Cursor cursor) {
6280f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final long id = cursor.getLong(VideosBucketQuery.BUCKET_ID);
6290f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String docId = getDocIdForIdent(TYPE_VIDEOS_BUCKET, id);
6300f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
6310f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final RowBuilder row = result.newRow();
6320f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
6330f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME,
6340f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor.getString(VideosBucketQuery.BUCKET_DISPLAY_NAME));
6350f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
6360f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_LAST_MODIFIED,
6370f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor.getLong(VideosBucketQuery.DATE_MODIFIED) * DateUtils.SECOND_IN_MILLIS);
63886bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey        row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_GRID
63986bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey                | Document.FLAG_SUPPORTS_THUMBNAIL | Document.FLAG_DIR_PREFERS_LAST_MODIFIED
64086bfd7303d107f197c6e2c86cf96efb83156043bJeff Sharkey                | Document.FLAG_DIR_HIDE_GRID_TITLES);
6410f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
6420f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
6430f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private interface VideoQuery {
6440f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String[] PROJECTION = new String[] {
6450f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns._ID,
6460f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.DISPLAY_NAME,
6470f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.MIME_TYPE,
6480f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.SIZE,
6490f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.DATE_MODIFIED };
6500f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
6510f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int _ID = 0;
6520f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int DISPLAY_NAME = 1;
6530f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int MIME_TYPE = 2;
6540f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int SIZE = 3;
6550f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int DATE_MODIFIED = 4;
6560f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
6570f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
6580f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private void includeVideo(MatrixCursor result, Cursor cursor) {
6590f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final long id = cursor.getLong(VideoQuery._ID);
6600f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String docId = getDocIdForIdent(TYPE_VIDEO, id);
6610f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
6620f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final RowBuilder row = result.newRow();
6630f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
6640f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, cursor.getString(VideoQuery.DISPLAY_NAME));
6650f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_SIZE, cursor.getLong(VideoQuery.SIZE));
6660f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, cursor.getString(VideoQuery.MIME_TYPE));
6670f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_LAST_MODIFIED,
6680f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                cursor.getLong(VideoQuery.DATE_MODIFIED) * DateUtils.SECOND_IN_MILLIS);
6690f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_THUMBNAIL);
6700f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
6710f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
672e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    private interface ArtistQuery {
673e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final String[] PROJECTION = new String[] {
674e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                BaseColumns._ID,
675e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                ArtistColumns.ARTIST };
676e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
677e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int _ID = 0;
678e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int ARTIST = 1;
679e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
680e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
681e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    private void includeArtist(MatrixCursor result, Cursor cursor) {
682e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final long id = cursor.getLong(ArtistQuery._ID);
6836e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final String docId = getDocIdForIdent(TYPE_ARTIST, id);
684e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
685e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final RowBuilder row = result.newRow();
68682c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
68782c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, cursor.getString(ArtistQuery.ARTIST));
68882c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
689e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
690e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
691e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    private interface AlbumQuery {
692e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final String[] PROJECTION = new String[] {
693e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                BaseColumns._ID,
694e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                AlbumColumns.ALBUM };
695e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
696e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int _ID = 0;
697e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int ALBUM = 1;
698e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
699e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
700e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    private void includeAlbum(MatrixCursor result, Cursor cursor) {
701e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final long id = cursor.getLong(AlbumQuery._ID);
7026e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final String docId = getDocIdForIdent(TYPE_ALBUM, id);
703e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
704e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final RowBuilder row = result.newRow();
70582c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
70682c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, cursor.getString(AlbumQuery.ALBUM));
70782c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
708e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
709e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
710e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    private interface SongQuery {
711e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final String[] PROJECTION = new String[] {
712e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                AudioColumns._ID,
713e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                AudioColumns.TITLE,
714e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                AudioColumns.MIME_TYPE,
715e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                AudioColumns.SIZE,
716e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                AudioColumns.DATE_MODIFIED };
717e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
718e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int _ID = 0;
719e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int TITLE = 1;
720e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int MIME_TYPE = 2;
721e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int SIZE = 3;
722e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final int DATE_MODIFIED = 4;
723e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
724e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
7256e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey    private void includeAudio(MatrixCursor result, Cursor cursor) {
726e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final long id = cursor.getLong(SongQuery._ID);
7276e21463c8302c6bae572f07b3e0ea832d8ae9f6aJeff Sharkey        final String docId = getDocIdForIdent(TYPE_AUDIO, id);
728e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
729e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey        final RowBuilder row = result.newRow();
73082c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
73182c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, cursor.getString(SongQuery.TITLE));
73282c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_SIZE, cursor.getLong(SongQuery.SIZE));
73382c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_MIME_TYPE, cursor.getString(SongQuery.MIME_TYPE));
73482c4ab2390570a461e297a4e319b5358d55f757bJeff Sharkey        row.add(Document.COLUMN_LAST_MODIFIED,
735e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey                cursor.getLong(SongQuery.DATE_MODIFIED) * DateUtils.SECOND_IN_MILLIS);
736e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey    }
737e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey
7380f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private interface ImagesBucketThumbnailQuery {
739240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        final String[] PROJECTION = new String[] {
740240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey                ImageColumns._ID,
741240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey                ImageColumns.BUCKET_ID,
742240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey                ImageColumns.DATE_MODIFIED };
743240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey
744240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        final int _ID = 0;
745240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        final int BUCKET_ID = 1;
746240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        final int DATE_MODIFIED = 2;
747240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey    }
748240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey
7490f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private long getImageForBucketCleared(long bucketId) throws FileNotFoundException {
750240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
751240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        Cursor cursor = null;
752240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        try {
753240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey            cursor = resolver.query(Images.Media.EXTERNAL_CONTENT_URI,
7540f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    ImagesBucketThumbnailQuery.PROJECTION, ImageColumns.BUCKET_ID + "=" + bucketId,
7550f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    null, ImageColumns.DATE_MODIFIED + " DESC");
7560f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            if (cursor.moveToFirst()) {
7570f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                return cursor.getLong(ImagesBucketThumbnailQuery._ID);
7580f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            }
7590f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        } finally {
7600f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            IoUtils.closeQuietly(cursor);
7610f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
7620f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        throw new FileNotFoundException("No video found for bucket");
7630f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
7640f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
7650f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private interface ImageThumbnailQuery {
7660f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String[] PROJECTION = new String[] {
7670f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                Images.Thumbnails.DATA };
7680f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
7690f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int _DATA = 0;
7700f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
7710f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
7725082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey    private ParcelFileDescriptor openImageThumbnailCleared(long id, CancellationSignal signal)
7730f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            throws FileNotFoundException {
7740f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
7755082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey
7760f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        Cursor cursor = null;
7770f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        try {
7780f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            cursor = resolver.query(Images.Thumbnails.EXTERNAL_CONTENT_URI,
7790f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    ImageThumbnailQuery.PROJECTION, Images.Thumbnails.IMAGE_ID + "=" + id, null,
7800f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    null, signal);
7810f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            if (cursor.moveToFirst()) {
7820f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                final String data = cursor.getString(ImageThumbnailQuery._DATA);
7835082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                return ParcelFileDescriptor.open(
7845082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                        new File(data), ParcelFileDescriptor.MODE_READ_ONLY);
7850f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            }
7860f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        } finally {
7870f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            IoUtils.closeQuietly(cursor);
7880f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
7890f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        return null;
7900f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
7910f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
7920f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private AssetFileDescriptor openOrCreateImageThumbnailCleared(
7930f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            long id, CancellationSignal signal) throws FileNotFoundException {
7940f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
7950f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
7965082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        ParcelFileDescriptor pfd = openImageThumbnailCleared(id, signal);
7975082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        if (pfd == null) {
7980f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            // No thumbnail yet, so generate. This is messy, since we drop the
7990f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            // Bitmap on the floor, but its the least-complicated way.
8000f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            final BitmapFactory.Options opts = new BitmapFactory.Options();
8010f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            opts.inJustDecodeBounds = true;
8020f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            Images.Thumbnails.getThumbnail(resolver, id, Images.Thumbnails.MINI_KIND, opts);
8030f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8045082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            pfd = openImageThumbnailCleared(id, signal);
8050f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
8060f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8075082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        if (pfd == null) {
8080f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            // Phoey, fallback to full image
8090f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            final Uri fullUri = ContentUris.withAppendedId(Images.Media.EXTERNAL_CONTENT_URI, id);
8105082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            pfd = resolver.openFileDescriptor(fullUri, "r", signal);
8110f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
8120f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8135082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        final int orientation = queryOrientationForImage(id, signal);
8145082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        final Bundle extras;
8155082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        if (orientation != 0) {
8165082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            extras = new Bundle(1);
8175082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            extras.putInt(DocumentsContract.EXTRA_ORIENTATION, orientation);
8185082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        } else {
8195082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            extras = null;
8205082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        }
8215082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey
8225082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
8230f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
8240f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8250f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private interface VideosBucketThumbnailQuery {
8260f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String[] PROJECTION = new String[] {
8270f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns._ID,
8280f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.BUCKET_ID,
8290f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                VideoColumns.DATE_MODIFIED };
8300f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8310f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int _ID = 0;
8320f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int BUCKET_ID = 1;
8330f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int DATE_MODIFIED = 2;
8340f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
8350f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8360f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private long getVideoForBucketCleared(long bucketId)
8370f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            throws FileNotFoundException {
8380f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
8390f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        Cursor cursor = null;
8400f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        try {
8410f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            cursor = resolver.query(Video.Media.EXTERNAL_CONTENT_URI,
8420f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    VideosBucketThumbnailQuery.PROJECTION, VideoColumns.BUCKET_ID + "=" + bucketId,
8430f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    null, VideoColumns.DATE_MODIFIED + " DESC");
8440f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            if (cursor.moveToFirst()) {
8450f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                return cursor.getLong(VideosBucketThumbnailQuery._ID);
8460f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            }
8470f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        } finally {
8480f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            IoUtils.closeQuietly(cursor);
8490f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
8500f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        throw new FileNotFoundException("No video found for bucket");
8510f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
8520f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8530f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private interface VideoThumbnailQuery {
8540f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final String[] PROJECTION = new String[] {
8550f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                Video.Thumbnails.DATA };
8560f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8570f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final int _DATA = 0;
8580f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
8590f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8600f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private AssetFileDescriptor openVideoThumbnailCleared(long id, CancellationSignal signal)
8610f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            throws FileNotFoundException {
8620f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
8630f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        Cursor cursor = null;
8640f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        try {
8650f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            cursor = resolver.query(Video.Thumbnails.EXTERNAL_CONTENT_URI,
8660f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    VideoThumbnailQuery.PROJECTION, Video.Thumbnails.VIDEO_ID + "=" + id, null,
8670f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                    null, signal);
868240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey            if (cursor.moveToFirst()) {
8690f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                final String data = cursor.getString(VideoThumbnailQuery._DATA);
8700f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                return new AssetFileDescriptor(ParcelFileDescriptor.open(
8710f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        new File(data), ParcelFileDescriptor.MODE_READ_ONLY), 0,
8720f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey                        AssetFileDescriptor.UNKNOWN_LENGTH);
873240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey            }
874240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        } finally {
875240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey            IoUtils.closeQuietly(cursor);
876240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        }
877240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey        return null;
878240f6b3ea7c4daed5a8690ae27a703e62da96744Jeff Sharkey    }
8790f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8800f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    private AssetFileDescriptor openOrCreateVideoThumbnailCleared(
8810f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            long id, CancellationSignal signal) throws FileNotFoundException {
8820f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
8830f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8840f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        AssetFileDescriptor afd = openVideoThumbnailCleared(id, signal);
8850f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        if (afd == null) {
8860f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            // No thumbnail yet, so generate. This is messy, since we drop the
8870f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            // Bitmap on the floor, but its the least-complicated way.
8880f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            final BitmapFactory.Options opts = new BitmapFactory.Options();
8890f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            opts.inJustDecodeBounds = true;
8900f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            Video.Thumbnails.getThumbnail(resolver, id, Video.Thumbnails.MINI_KIND, opts);
8910f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8920f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey            afd = openVideoThumbnailCleared(id, signal);
8930f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        }
8940f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey
8950f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey        return afd;
8960f90e312f1eeb52b3c9cd668f5593fd72217f9dbJeff Sharkey    }
8975082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey
8985082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey    private interface ImageOrientationQuery {
8995082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        final String[] PROJECTION = new String[] {
9005082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                ImageColumns.ORIENTATION };
9015082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey
9025082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        final int ORIENTATION = 0;
9035082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey    }
9045082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey
9055082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey    private int queryOrientationForImage(long id, CancellationSignal signal) {
9065082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
9075082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey
9085082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        Cursor cursor = null;
9095082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        try {
9105082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            cursor = resolver.query(Images.Media.EXTERNAL_CONTENT_URI,
9115082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                    ImageOrientationQuery.PROJECTION, ImageColumns._ID + "=" + id, null, null,
9125082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                    signal);
9135082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            if (cursor.moveToFirst()) {
9145082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                return cursor.getInt(ImageOrientationQuery.ORIENTATION);
9155082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            } else {
9165082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                Log.w(TAG, "Missing orientation data for " + id);
9175082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey                return 0;
9185082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            }
9195082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        } finally {
9205082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey            IoUtils.closeQuietly(cursor);
9215082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey        }
9225082b808a9821a43c37b58aaf9f98b8daa415fefJeff Sharkey    }
923e215ab5b4f1eeeafc75f5f17c92dd21e8d2161ebJeff Sharkey}
924