1 /*
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 
17 package com.example.android.justforus;
18 
19 import android.content.ContentProvider;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.res.AssetFileDescriptor;
23 import android.content.res.AssetManager;
24 import android.database.Cursor;
25 import android.database.MatrixCursor;
26 import android.net.Uri;
27 import android.provider.OpenableColumns;
28 import android.util.Log;
29 
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 
33 import static java.net.URLConnection.guessContentTypeFromName;
34 
35 /**
36  * Generic content provider, which makes any files available in this app's "assets" directory
37  * available publicly.
38  *
39  * <p>To use, add the following to your AndroidManifest.xml:
40  *
41  * <code><pre>
42  * <provider
43  *   android:name=".AssetProvider"
44  *   android:authorities="[YOUR CONTENT PROVIDER DOMAIN HERE]"
45  *   android:grantUriPermissions="true"
46  *   android:exported="true"/>
47  * </pre></code>
48  */
49 public class AssetProvider extends ContentProvider {
50 
51     /**
52      * Content provider authority that identifies data that is offered by this
53      * {@link AssetProvider}.
54      */
55     public static String CONTENT_URI = "com.example.android.justforus";
56 
57     private static final String TAG = "AssetProvider";
58 
59     AssetManager mAssets;
60 
61     @Override
onCreate()62     public boolean onCreate() {
63         Context ctx = getContext();
64         if (ctx == null) {
65             // Context not available. Give up.
66             return false;
67         }
68         mAssets = ctx.getAssets();
69         return true;
70     }
71 
72     @Override
getType(Uri uri)73     public String getType(Uri uri){
74         // Returns the MIME type for the selected URI, in conformance with the ContentProvider
75         // interface. Looks up the file indicated by /res/assets/{uri.path}, and returns the MIME
76         // type for that file as guessed by the URLConnection class.
77 
78         // Setup
79         String path = uri.getLastPathSegment();
80 
81         // Check if file exists
82         if (!fileExists(path)) {
83             return null;
84         }
85 
86         // Determine MIME-type based on filename
87         return guessContentTypeFromName(uri.toString());
88     }
89 
90 
91     @Override
openAssetFile(Uri uri, String mode)92     public AssetFileDescriptor openAssetFile (Uri uri, String mode)
93             throws FileNotFoundException, SecurityException {
94         // ContentProvider interface for opening a file descriptor by URI. This content provider
95         // maps all URIs to the contents of the APK's assets folder, so a file handle to
96         // /res/assets/{uri.path} will be returned.
97 
98         // Security check. This content provider only supports read-only access. (Also, the contents
99         // of an APKs assets folder are immutable, so read-write access doesn't make sense here.)
100         if (!"r".equals(mode)) {
101             throw new SecurityException("Only read-only access is supported, mode must be [r]");
102         }
103 
104         // Open asset from within APK and return file descriptor
105         String path = uri.getLastPathSegment();
106         try {
107             return mAssets.openFd(path);
108         } catch (IOException e) {
109             throw new FileNotFoundException();
110         }
111     }
112 
113     /**
114      * Check if file exists inside APK assets.
115      *
116      * @param path Fully qualified path to file.
117      * @return true if exists, false otherwise.
118      */
fileExists(String path)119     private boolean fileExists(String path) {
120         try {
121             // Check to see if file can be opened. If so, file exists.
122             mAssets.openFd(path).close();
123             return true;
124         } catch (IOException e) {
125             // Unable to open file descriptor for specified path; file doesn't exist.
126             return false;
127         }
128     }
129 
130     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)131     public Cursor query(Uri uri, String[] projection, String selection,
132                         String[] selectionArgs, String sortOrder) {
133         String path = uri.getLastPathSegment();
134         if (!fileExists(path)) {
135             Log.e(TAG, "Requested file doesn't exist at " + path);
136             return null;
137         }
138 
139         // Create matrix cursor
140         if (projection == null) {
141             projection = new String[]{
142                     OpenableColumns.DISPLAY_NAME,
143                     OpenableColumns.SIZE,
144             };
145         }
146 
147         MatrixCursor matrixCursor = new MatrixCursor(projection, 1);
148         Object[] row = new Object[projection.length];
149         for (int col = 0; col < projection.length; col++) {
150             if (OpenableColumns.DISPLAY_NAME.equals(projection[col])) {
151                 row[col] = path;
152             } else if (OpenableColumns.SIZE.equals(projection[col])) {
153                 try {
154                     AssetFileDescriptor afd = openAssetFile(uri, "r");
155                     if (afd != null) {
156                         row[col] = Long.valueOf(afd.getLength());
157                     }
158                     afd.close();
159                 } catch (IOException e) {
160                     Log.e(TAG, e.toString());
161                 }
162             }
163         }
164         matrixCursor.addRow(row);
165         return matrixCursor;
166     }
167 
168     // Required/unused ContentProvider methods below.
169     @Override
insert(Uri uri, ContentValues contentValues)170     public Uri insert(Uri uri, ContentValues contentValues) {
171         throw new RuntimeException("Operation not supported");
172     }
173 
174     @Override
delete(Uri uri, String selection, String[] selectionArgs)175     public int delete(Uri uri, String selection, String[] selectionArgs) {
176         throw new RuntimeException("Operation not supported");
177     }
178 
179     @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)180     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
181         throw new RuntimeException("Operation not supported");
182     }
183 }
184