1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.provider;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.database.Cursor;
24import android.net.Uri;
25import android.provider.DocumentsContract;
26import android.text.TextUtils;
27import android.util.Log;
28
29class DocumentsContractApi19 {
30    private static final String TAG = "DocumentFile";
31
32    public static boolean isDocumentUri(Context context, Uri self) {
33        return DocumentsContract.isDocumentUri(context, self);
34    }
35
36    public static String getName(Context context, Uri self) {
37        return queryForString(context, self, DocumentsContract.Document.COLUMN_DISPLAY_NAME, null);
38    }
39
40    private static String getRawType(Context context, Uri self) {
41        return queryForString(context, self, DocumentsContract.Document.COLUMN_MIME_TYPE, null);
42    }
43
44    public static String getType(Context context, Uri self) {
45        final String rawType = getRawType(context, self);
46        if (DocumentsContract.Document.MIME_TYPE_DIR.equals(rawType)) {
47            return null;
48        } else {
49            return rawType;
50        }
51    }
52
53    public static boolean isDirectory(Context context, Uri self) {
54        return DocumentsContract.Document.MIME_TYPE_DIR.equals(getRawType(context, self));
55    }
56
57    public static boolean isFile(Context context, Uri self) {
58        final String type = getRawType(context, self);
59        if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type) || TextUtils.isEmpty(type)) {
60            return false;
61        } else {
62            return true;
63        }
64    }
65
66    public static long lastModified(Context context, Uri self) {
67        return queryForLong(context, self, DocumentsContract.Document.COLUMN_LAST_MODIFIED, 0);
68    }
69
70    public static long length(Context context, Uri self) {
71        return queryForLong(context, self, DocumentsContract.Document.COLUMN_SIZE, 0);
72    }
73
74    public static boolean canRead(Context context, Uri self) {
75        // Ignore if grant doesn't allow read
76        if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_READ_URI_PERMISSION)
77                != PackageManager.PERMISSION_GRANTED) {
78            return false;
79        }
80
81        // Ignore documents without MIME
82        if (TextUtils.isEmpty(getRawType(context, self))) {
83            return false;
84        }
85
86        return true;
87    }
88
89    public static boolean canWrite(Context context, Uri self) {
90        // Ignore if grant doesn't allow write
91        if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
92                != PackageManager.PERMISSION_GRANTED) {
93            return false;
94        }
95
96        final String type = getRawType(context, self);
97        final int flags = queryForInt(context, self, DocumentsContract.Document.COLUMN_FLAGS, 0);
98
99        // Ignore documents without MIME
100        if (TextUtils.isEmpty(type)) {
101            return false;
102        }
103
104        // Deletable documents considered writable
105        if ((flags & DocumentsContract.Document.FLAG_SUPPORTS_DELETE) != 0) {
106            return true;
107        }
108
109        if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type)
110                && (flags & DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
111            // Directories that allow create considered writable
112            return true;
113        } else if (!TextUtils.isEmpty(type)
114                && (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0) {
115            // Writable normal files considered writable
116            return true;
117        }
118
119        return false;
120    }
121
122    public static boolean delete(Context context, Uri self) {
123        return DocumentsContract.deleteDocument(context.getContentResolver(), self);
124    }
125
126    public static boolean exists(Context context, Uri self) {
127        final ContentResolver resolver = context.getContentResolver();
128
129        Cursor c = null;
130        try {
131            c = resolver.query(self, new String[] {
132                    DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
133            return c.getCount() > 0;
134        } catch (Exception e) {
135            Log.w(TAG, "Failed query: " + e);
136            return false;
137        } finally {
138            closeQuietly(c);
139        }
140    }
141
142    private static String queryForString(Context context, Uri self, String column,
143            String defaultValue) {
144        final ContentResolver resolver = context.getContentResolver();
145
146        Cursor c = null;
147        try {
148            c = resolver.query(self, new String[] { column }, null, null, null);
149            if (c.moveToFirst() && !c.isNull(0)) {
150                return c.getString(0);
151            } else {
152                return defaultValue;
153            }
154        } catch (Exception e) {
155            Log.w(TAG, "Failed query: " + e);
156            return defaultValue;
157        } finally {
158            closeQuietly(c);
159        }
160    }
161
162    private static int queryForInt(Context context, Uri self, String column,
163            int defaultValue) {
164        return (int) queryForLong(context, self, column, defaultValue);
165    }
166
167    private static long queryForLong(Context context, Uri self, String column,
168            long defaultValue) {
169        final ContentResolver resolver = context.getContentResolver();
170
171        Cursor c = null;
172        try {
173            c = resolver.query(self, new String[] { column }, null, null, null);
174            if (c.moveToFirst() && !c.isNull(0)) {
175                return c.getLong(0);
176            } else {
177                return defaultValue;
178            }
179        } catch (Exception e) {
180            Log.w(TAG, "Failed query: " + e);
181            return defaultValue;
182        } finally {
183            closeQuietly(c);
184        }
185    }
186
187    private static void closeQuietly(AutoCloseable closeable) {
188        if (closeable != null) {
189            try {
190                closeable.close();
191            } catch (RuntimeException rethrown) {
192                throw rethrown;
193            } catch (Exception ignored) {
194            }
195        }
196    }
197}
198