blob: f75e3bd9b3e84b279c7703d62ec80da30d05c26e [file] [log] [blame]
Jeff Sharkey9e0036e2013-04-26 16:54:55 -07001/*
2 * Copyright (C) 2013 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 com.android.externalstorage;
18
19import android.content.ContentProvider;
20import android.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.UriMatcher;
23import android.database.Cursor;
24import android.database.MatrixCursor;
25import android.net.Uri;
26import android.os.Environment;
27import android.os.ParcelFileDescriptor;
28import android.provider.BaseColumns;
29import android.provider.DocumentsContract;
30import android.provider.DocumentsContract.DocumentColumns;
31import android.webkit.MimeTypeMap;
32
33import com.android.internal.annotations.GuardedBy;
34import com.google.android.collect.Lists;
35
36import java.io.File;
37import java.io.FileNotFoundException;
38import java.util.ArrayList;
39
40public class ExternalStorageProvider extends ContentProvider {
41 private static final String TAG = "ExternalStorage";
42
43 private static final String AUTHORITY = "com.android.externalstorage";
44
45 // TODO: support searching
46 // TODO: support multiple storage devices
47 // TODO: persist GUIDs across launches
48
49 private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
50
51 private static final int URI_DOCS_ID = 1;
52 private static final int URI_DOCS_ID_CONTENTS = 2;
53 private static final int URI_SEARCH = 3;
54
55 static {
56 sMatcher.addURI(AUTHORITY, "docs/#", URI_DOCS_ID);
57 sMatcher.addURI(AUTHORITY, "docs/#/contents", URI_DOCS_ID_CONTENTS);
58 sMatcher.addURI(AUTHORITY, "search", URI_SEARCH);
59 }
60
61 @GuardedBy("mFiles")
62 private ArrayList<File> mFiles = Lists.newArrayList();
63
64 @Override
65 public boolean onCreate() {
66 mFiles.clear();
67 mFiles.add(Environment.getExternalStorageDirectory());
68 return true;
69 }
70
71 @Override
72 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
73 String sortOrder) {
74
75 // TODO: support custom projections
76 projection = new String[] {
77 BaseColumns._ID,
78 DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, DocumentColumns.GUID,
79 DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS };
80
81 final MatrixCursor cursor = new MatrixCursor(projection);
82 switch (sMatcher.match(uri)) {
83 case URI_DOCS_ID: {
84 final int id = Integer.parseInt(uri.getPathSegments().get(1));
85 synchronized (mFiles) {
86 includeFileLocked(cursor, id);
87 }
88 break;
89 }
90 case URI_DOCS_ID_CONTENTS: {
91 final int parentId = Integer.parseInt(uri.getPathSegments().get(1));
92 synchronized (mFiles) {
93 final File parent = mFiles.get(parentId);
94 for (File file : parent.listFiles()) {
95 final int id = findOrCreateFileLocked(file);
96 includeFileLocked(cursor, id);
97 }
98 }
99 break;
100 }
101 default: {
102 cursor.close();
103 throw new UnsupportedOperationException("Unsupported Uri " + uri);
104 }
105 }
106
107 return cursor;
108 }
109
110 private int findOrCreateFileLocked(File file) {
111 int id = mFiles.indexOf(file);
112 if (id == -1) {
113 id = mFiles.size();
114 mFiles.add(file);
115 }
116 return id;
117 }
118
119 private void includeFileLocked(MatrixCursor cursor, int id) {
120 final File file = mFiles.get(id);
121 int flags = 0;
122
123 if (file.isDirectory() && file.canWrite()) {
124 flags |= DocumentsContract.FLAG_SUPPORTS_CREATE;
125 }
126 if (file.canWrite()) {
127 flags |= DocumentsContract.FLAG_SUPPORTS_RENAME;
128 }
129
130 final String mimeType = getTypeLocked(id);
131 if (mimeType.startsWith("image/")) {
132 flags |= DocumentsContract.FLAG_SUPPORTS_THUMBNAIL;
133 }
134
135 cursor.addRow(new Object[] {
136 id, file.getName(), file.length(), id, mimeType, file.lastModified(), flags });
137 }
138
139 @Override
140 public String getType(Uri uri) {
141 switch (sMatcher.match(uri)) {
142 case URI_DOCS_ID: {
143 final int id = Integer.parseInt(uri.getPathSegments().get(1));
144 synchronized (mFiles) {
145 return getTypeLocked(id);
146 }
147 }
148 default: {
149 throw new UnsupportedOperationException("Unsupported Uri " + uri);
150 }
151 }
152 }
153
154 private String getTypeLocked(int id) {
155 final File file = mFiles.get(id);
156
157 if (file.isDirectory()) {
158 return DocumentsContract.MIME_TYPE_DIRECTORY;
159 }
160
161 final int lastDot = file.getName().lastIndexOf('.');
162 if (lastDot >= 0) {
163 final String extension = file.getName().substring(lastDot + 1);
164 final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
165 if (mime != null) {
166 return mime;
167 }
168 }
169
170 return "application/octet-stream";
171 }
172
173 @Override
174 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
175 switch (sMatcher.match(uri)) {
176 case URI_DOCS_ID: {
177 final int id = Integer.parseInt(uri.getPathSegments().get(1));
178 synchronized (mFiles) {
179 final File file = mFiles.get(id);
180 // TODO: turn into thumbnail
181 return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode));
182 }
183 }
184 default: {
185 throw new UnsupportedOperationException("Unsupported Uri " + uri);
186 }
187 }
188 }
189
190 @Override
191 public Uri insert(Uri uri, ContentValues values) {
192 throw new UnsupportedOperationException();
193 }
194
195 @Override
196 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
197 throw new UnsupportedOperationException();
198 }
199
200 @Override
201 public int delete(Uri uri, String selection, String[] selectionArgs) {
202 throw new UnsupportedOperationException();
203 }
204}