• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.android.shell;
18 
19 import android.database.Cursor;
20 import android.database.MatrixCursor;
21 import android.database.MatrixCursor.RowBuilder;
22 import android.net.Uri;
23 import android.os.CancellationSignal;
24 import android.os.FileUtils;
25 import android.os.ParcelFileDescriptor;
26 import android.provider.DocumentsContract;
27 import android.provider.DocumentsContract.Document;
28 import android.provider.DocumentsContract.Root;
29 import android.provider.DocumentsProvider;
30 import android.support.provider.DocumentArchiveHelper;
31 import android.webkit.MimeTypeMap;
32 
33 import java.io.File;
34 import java.io.FileNotFoundException;
35 
36 public class BugreportStorageProvider extends DocumentsProvider {
37     private static final String AUTHORITY = "com.android.shell.documents";
38     private static final String DOC_ID_ROOT = "bugreport";
39 
40     private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
41             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
42             Root.COLUMN_DOCUMENT_ID,
43     };
44 
45     private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
46             Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
47             Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
48     };
49 
50     private File mRoot;
51     private DocumentArchiveHelper mArchiveHelper;
52 
53     @Override
onCreate()54     public boolean onCreate() {
55         mRoot = new File(getContext().getFilesDir(), "bugreports");
56         mArchiveHelper = new DocumentArchiveHelper(this, (char) 0);
57         return true;
58     }
59 
60     @Override
queryRoots(String[] projection)61     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
62         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
63         final RowBuilder row = result.newRow();
64         row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
65         row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED);
66         row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon);
67         row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title));
68         row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
69         return result;
70     }
71 
72     @Override
queryDocument(String documentId, String[] projection)73     public Cursor queryDocument(String documentId, String[] projection)
74             throws FileNotFoundException {
75         if (mArchiveHelper.isArchivedDocument(documentId)) {
76             return mArchiveHelper.queryDocument(documentId, projection);
77         }
78 
79         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
80         if (DOC_ID_ROOT.equals(documentId)) {
81             final RowBuilder row = result.newRow();
82             row.add(Document.COLUMN_DOCUMENT_ID, documentId);
83             row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
84             row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName());
85             row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified());
86             row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
87         } else {
88             addFileRow(result, getFileForDocId(documentId));
89         }
90         return result;
91     }
92 
93     @Override
queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)94     public Cursor queryChildDocuments(
95             String parentDocumentId, String[] projection, String sortOrder)
96             throws FileNotFoundException {
97         if (mArchiveHelper.isArchivedDocument(parentDocumentId) ||
98                 mArchiveHelper.isSupportedArchiveType(getDocumentType(parentDocumentId))) {
99             return mArchiveHelper.queryChildDocuments(parentDocumentId, projection, sortOrder);
100         }
101 
102         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
103         if (DOC_ID_ROOT.equals(parentDocumentId)) {
104             final File[] files = mRoot.listFiles();
105             if (files != null) {
106                 for (File file : files) {
107                     addFileRow(result, file);
108                 }
109                 result.setNotificationUri(getContext().getContentResolver(), getNotificationUri());
110             }
111         }
112         return result;
113     }
114 
115     @Override
openDocument( String documentId, String mode, CancellationSignal signal)116     public ParcelFileDescriptor openDocument(
117             String documentId, String mode, CancellationSignal signal)
118             throws FileNotFoundException {
119         if (mArchiveHelper.isArchivedDocument(documentId)) {
120             return mArchiveHelper.openDocument(documentId, mode, signal);
121         }
122 
123         if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) {
124             throw new FileNotFoundException("Failed to open: " + documentId + ", mode = " + mode);
125         }
126         return ParcelFileDescriptor.open(getFileForDocId(documentId),
127                 ParcelFileDescriptor.MODE_READ_ONLY);
128     }
129 
130     @Override
deleteDocument(String documentId)131     public void deleteDocument(String documentId) throws FileNotFoundException {
132         if (!getFileForDocId(documentId).delete()) {
133             throw new FileNotFoundException("Failed to delete: " + documentId);
134         }
135     }
136 
137     // This is used by BugreportProgressService so that the notification uri shared by
138     // BugreportProgressService and BugreportStorageProvider are guaranteed the same and unique
getNotificationUri()139     protected static Uri getNotificationUri() {
140       return DocumentsContract.buildChildDocumentsUri(AUTHORITY, DOC_ID_ROOT);
141     }
142 
resolveRootProjection(String[] projection)143     private static String[] resolveRootProjection(String[] projection) {
144         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
145     }
146 
resolveDocumentProjection(String[] projection)147     private static String[] resolveDocumentProjection(String[] projection) {
148         return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
149     }
150 
getTypeForName(String name)151     private static String getTypeForName(String name) {
152         final int lastDot = name.lastIndexOf('.');
153         if (lastDot >= 0) {
154             final String extension = name.substring(lastDot + 1).toLowerCase();
155             final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
156             if (mime != null) {
157                 return mime;
158             }
159         }
160         return "application/octet-stream";
161     }
162 
getDocIdForFile(File file)163     private String getDocIdForFile(File file) {
164         return DOC_ID_ROOT + ":" + file.getName();
165     }
166 
getFileForDocId(String documentId)167     private File getFileForDocId(String documentId) throws FileNotFoundException {
168         final int splitIndex = documentId.indexOf(':', 1);
169         final String name = documentId.substring(splitIndex + 1);
170         if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) ||
171                 !FileUtils.isValidExtFilename(name)) {
172             throw new FileNotFoundException("Invalid document ID: " + documentId);
173         }
174         final File file = new File(mRoot, name);
175         if (!file.exists()) {
176             throw new FileNotFoundException("File not found: " + documentId);
177         }
178         return file;
179     }
180 
addFileRow(MatrixCursor result, File file)181     private void addFileRow(MatrixCursor result, File file) {
182         String mimeType = getTypeForName(file.getName());
183         int flags = Document.FLAG_SUPPORTS_DELETE;
184         if (mArchiveHelper.isSupportedArchiveType(mimeType)) {
185             flags |= Document.FLAG_ARCHIVE;
186         }
187 
188         final RowBuilder row = result.newRow();
189         row.add(Document.COLUMN_DOCUMENT_ID, getDocIdForFile(file));
190         row.add(Document.COLUMN_MIME_TYPE, mimeType);
191         row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
192         row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
193         row.add(Document.COLUMN_FLAGS, flags);
194         row.add(Document.COLUMN_SIZE, file.length());
195     }
196 }
197