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.Bundle;
24 import android.os.CancellationSignal;
25 import android.os.FileUtils;
26 import android.os.ParcelFileDescriptor;
27 import android.provider.DocumentsContract;
28 import android.provider.DocumentsContract.Document;
29 import android.provider.DocumentsContract.Root;
30 
31 import com.android.internal.content.FileSystemProvider;
32 
33 import java.io.File;
34 import java.io.FileNotFoundException;
35 
36 public class BugreportStorageProvider extends FileSystemProvider {
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 
52     @Override
onCreate()53     public boolean onCreate() {
54         super.onCreate(DEFAULT_DOCUMENT_PROJECTION);
55         mRoot = new File(getContext().getFilesDir(), "bugreports");
56         return true;
57     }
58 
59     @Override
queryRoots(String[] projection)60     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
61         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
62         final RowBuilder row = result.newRow();
63         row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
64         row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY);
65         row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon);
66         row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title));
67         row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
68         return result;
69     }
70 
71     @Override
queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)72     public Cursor queryChildDocuments(
73             String parentDocumentId, String[] projection, String sortOrder)
74             throws FileNotFoundException {
75         final Cursor c = super.queryChildDocuments(parentDocumentId, projection, sortOrder);
76         final Bundle extras = new Bundle();
77         extras.putCharSequence(DocumentsContract.EXTRA_INFO,
78                 getContext().getText(R.string.bugreport_confirm));
79         c.setExtras(extras);
80         return c;
81     }
82 
83     @Override
queryDocument(String documentId, String[] projection)84     public Cursor queryDocument(String documentId, String[] projection)
85             throws FileNotFoundException {
86         if (DOC_ID_ROOT.equals(documentId)) {
87             final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
88             includeDefaultDocument(result);
89             return result;
90         } else {
91             return super.queryDocument(documentId, projection);
92         }
93     }
94 
95     @Override
openDocument( String documentId, String mode, CancellationSignal signal)96     public ParcelFileDescriptor openDocument(
97             String documentId, String mode, CancellationSignal signal)
98             throws FileNotFoundException {
99         if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) {
100             throw new FileNotFoundException("Failed to open: " + documentId + ", mode = " + mode);
101         }
102         return ParcelFileDescriptor.open(getFileForDocId(documentId),
103                 ParcelFileDescriptor.MODE_READ_ONLY);
104     }
105 
106     @Override
buildNotificationUri(String docId)107     protected Uri buildNotificationUri(String docId) {
108         return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId);
109     }
110 
resolveRootProjection(String[] projection)111     private static String[] resolveRootProjection(String[] projection) {
112         return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
113     }
114 
resolveDocumentProjection(String[] projection)115     private static String[] resolveDocumentProjection(String[] projection) {
116         return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
117     }
118 
119     @Override
getDocIdForFile(File file)120     protected String getDocIdForFile(File file) {
121         return DOC_ID_ROOT + ":" + file.getName();
122     }
123 
124     @Override
getFileForDocId(String documentId, boolean visible)125     protected File getFileForDocId(String documentId, boolean visible)
126             throws FileNotFoundException {
127         if (DOC_ID_ROOT.equals(documentId)) {
128             return mRoot;
129         } else {
130             final int splitIndex = documentId.indexOf(':', 1);
131             final String name = documentId.substring(splitIndex + 1);
132             if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) ||
133                     !FileUtils.isValidExtFilename(name)) {
134                 throw new FileNotFoundException("Invalid document ID: " + documentId);
135             }
136             final File file = new File(mRoot, name);
137             if (!file.exists()) {
138                 throw new FileNotFoundException("File not found: " + documentId);
139             }
140             return file;
141         }
142     }
143 
144     @Override
includeFile(MatrixCursor result, String docId, File file)145     protected RowBuilder includeFile(MatrixCursor result, String docId, File file)
146             throws FileNotFoundException {
147         RowBuilder row = super.includeFile(result, docId, file);
148         row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE);
149         return row;
150     }
151 
includeDefaultDocument(MatrixCursor result)152     private void includeDefaultDocument(MatrixCursor result) {
153         final RowBuilder row = result.newRow();
154         row.add(Document.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
155         row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
156         row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName());
157         row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified());
158         row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
159     }
160 }
161