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 17package com.android.shell; 18 19import android.database.Cursor; 20import android.database.MatrixCursor; 21import android.database.MatrixCursor.RowBuilder; 22import android.os.CancellationSignal; 23import android.os.FileUtils; 24import android.os.ParcelFileDescriptor; 25import android.provider.DocumentsContract.Document; 26import android.provider.DocumentsContract.Root; 27import android.provider.DocumentsProvider; 28import android.webkit.MimeTypeMap; 29 30import java.io.File; 31import java.io.FileNotFoundException; 32 33public class BugreportStorageProvider extends DocumentsProvider { 34 private static final String DOC_ID_ROOT = "bugreport"; 35 36 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 37 Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, 38 Root.COLUMN_DOCUMENT_ID, 39 }; 40 41 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 42 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, 43 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 44 }; 45 46 private File mRoot; 47 48 @Override 49 public boolean onCreate() { 50 mRoot = new File(getContext().getFilesDir(), "bugreports"); 51 return true; 52 } 53 54 @Override 55 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 56 final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); 57 final RowBuilder row = result.newRow(); 58 row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT); 59 row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED); 60 row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon); 61 row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title)); 62 row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT); 63 return result; 64 } 65 66 @Override 67 public Cursor queryDocument(String documentId, String[] projection) 68 throws FileNotFoundException { 69 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 70 if (DOC_ID_ROOT.equals(documentId)) { 71 final RowBuilder row = result.newRow(); 72 row.add(Document.COLUMN_DOCUMENT_ID, documentId); 73 row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); 74 row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName()); 75 row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified()); 76 row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED); 77 } else { 78 addFileRow(result, getFileForDocId(documentId)); 79 } 80 return result; 81 } 82 83 @Override 84 public Cursor queryChildDocuments( 85 String parentDocumentId, String[] projection, String sortOrder) 86 throws FileNotFoundException { 87 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 88 if (DOC_ID_ROOT.equals(parentDocumentId)) { 89 final File[] files = mRoot.listFiles(); 90 if (files != null) { 91 for (File file : files) { 92 addFileRow(result, file); 93 } 94 } 95 } 96 return result; 97 } 98 99 @Override 100 public ParcelFileDescriptor openDocument( 101 String documentId, String mode, CancellationSignal signal) 102 throws FileNotFoundException { 103 if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) { 104 throw new FileNotFoundException("Failed to open: " + documentId + ", mode = " + mode); 105 } 106 return ParcelFileDescriptor.open(getFileForDocId(documentId), 107 ParcelFileDescriptor.MODE_READ_ONLY); 108 } 109 110 @Override 111 public void deleteDocument(String documentId) throws FileNotFoundException { 112 if (!getFileForDocId(documentId).delete()) { 113 throw new FileNotFoundException("Failed to delete: " + documentId); 114 } 115 } 116 117 private static String[] resolveRootProjection(String[] projection) { 118 return projection != null ? projection : DEFAULT_ROOT_PROJECTION; 119 } 120 121 private static String[] resolveDocumentProjection(String[] projection) { 122 return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; 123 } 124 125 private static String getTypeForName(String name) { 126 final int lastDot = name.lastIndexOf('.'); 127 if (lastDot >= 0) { 128 final String extension = name.substring(lastDot + 1).toLowerCase(); 129 final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 130 if (mime != null) { 131 return mime; 132 } 133 } 134 return "application/octet-stream"; 135 } 136 137 private String getDocIdForFile(File file) { 138 return DOC_ID_ROOT + ":" + file.getName(); 139 } 140 141 private File getFileForDocId(String documentId) throws FileNotFoundException { 142 final int splitIndex = documentId.indexOf(':', 1); 143 final String name = documentId.substring(splitIndex + 1); 144 if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) || 145 !FileUtils.isValidExtFilename(name)) { 146 throw new FileNotFoundException("Invalid document ID: " + documentId); 147 } 148 final File file = new File(mRoot, name); 149 if (!file.exists()) { 150 throw new FileNotFoundException("File not found: " + documentId); 151 } 152 return file; 153 } 154 155 private void addFileRow(MatrixCursor result, File file) { 156 final RowBuilder row = result.newRow(); 157 row.add(Document.COLUMN_DOCUMENT_ID, getDocIdForFile(file)); 158 row.add(Document.COLUMN_MIME_TYPE, getTypeForName(file.getName())); 159 row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 160 row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); 161 row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE); 162 row.add(Document.COLUMN_SIZE, file.length()); 163 } 164} 165