1dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto/* 2dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * Copyright (C) 2015 The Android Open Source Project 3dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * 4dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * Licensed under the Apache License, Version 2.0 (the "License"); 5dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * you may not use this file except in compliance with the License. 6dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * You may obtain a copy of the License at 7dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * 8dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * http://www.apache.org/licenses/LICENSE-2.0 9dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * 10dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * Unless required by applicable law or agreed to in writing, software 11dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * distributed under the License is distributed on an "AS IS" BASIS, 12dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * See the License for the specific language governing permissions and 14dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * limitations under the License. 15dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto */ 16dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 17dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotopackage com.android.shell; 18dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 19dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.database.Cursor; 20dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.database.MatrixCursor; 21dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.database.MatrixCursor.RowBuilder; 22c6905cfb1133627dfd500491c60b6528a3e593e0Ben Linimport android.net.Uri; 23dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.os.CancellationSignal; 24dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.os.FileUtils; 25dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.os.ParcelFileDescriptor; 26c6905cfb1133627dfd500491c60b6528a3e593e0Ben Linimport android.provider.DocumentsContract; 27dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.provider.DocumentsContract.Document; 28dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.provider.DocumentsContract.Root; 29dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.provider.DocumentsProvider; 30dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.webkit.MimeTypeMap; 31dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 32dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport java.io.File; 33dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport java.io.FileNotFoundException; 34dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 35dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotopublic class BugreportStorageProvider extends DocumentsProvider { 36c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin private static final String AUTHORITY = "com.android.shell.documents"; 37dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private static final String DOC_ID_ROOT = "bugreport"; 38dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 39dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 40dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, 41dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto Root.COLUMN_DOCUMENT_ID, 42dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto }; 43dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 44dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 45dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, 46dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 47dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto }; 48dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 49dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private File mRoot; 50dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 51dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto @Override 52dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto public boolean onCreate() { 53dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto mRoot = new File(getContext().getFilesDir(), "bugreports"); 54dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return true; 55dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 56dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 57dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto @Override 58dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto public Cursor queryRoots(String[] projection) throws FileNotFoundException { 59dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); 60dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final RowBuilder row = result.newRow(); 61dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT); 62774cc932fc29705c9580775abf7c1e7a80c2bbe3Aga Wronska row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED); 63dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon); 64dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title)); 65dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT); 66dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return result; 67dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 68dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 69dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto @Override 70dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto public Cursor queryDocument(String documentId, String[] projection) 71dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto throws FileNotFoundException { 72dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 73dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (DOC_ID_ROOT.equals(documentId)) { 74dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final RowBuilder row = result.newRow(); 75dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_DOCUMENT_ID, documentId); 76dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); 77dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName()); 78dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified()); 79dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED); 80dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } else { 81dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto addFileRow(result, getFileForDocId(documentId)); 82dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 83dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return result; 84dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 85dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 86dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto @Override 87dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto public Cursor queryChildDocuments( 88dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto String parentDocumentId, String[] projection, String sortOrder) 89dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto throws FileNotFoundException { 90dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 91dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (DOC_ID_ROOT.equals(parentDocumentId)) { 92dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final File[] files = mRoot.listFiles(); 93dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (files != null) { 94dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto for (File file : files) { 95dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto addFileRow(result, file); 96dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 97c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin result.setNotificationUri(getContext().getContentResolver(), getNotificationUri()); 98dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 99dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 100dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return result; 101dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 102dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 103dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto @Override 104dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto public ParcelFileDescriptor openDocument( 105dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto String documentId, String mode, CancellationSignal signal) 106dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto throws FileNotFoundException { 107dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) { 108dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto throw new FileNotFoundException("Failed to open: " + documentId + ", mode = " + mode); 109dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 110dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return ParcelFileDescriptor.open(getFileForDocId(documentId), 111dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto ParcelFileDescriptor.MODE_READ_ONLY); 112dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 113dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 114dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto @Override 115dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto public void deleteDocument(String documentId) throws FileNotFoundException { 116dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (!getFileForDocId(documentId).delete()) { 117dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto throw new FileNotFoundException("Failed to delete: " + documentId); 118dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 11986805aa3afb5b08e916da424d0daf05149fae7eeBen Lin getContext().getContentResolver().notifyChange(getNotificationUri(), null); 120dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 121dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 122c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin // This is used by BugreportProgressService so that the notification uri shared by 123c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin // BugreportProgressService and BugreportStorageProvider are guaranteed the same and unique 124c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin protected static Uri getNotificationUri() { 125c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin return DocumentsContract.buildChildDocumentsUri(AUTHORITY, DOC_ID_ROOT); 126c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin } 127c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin 128dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private static String[] resolveRootProjection(String[] projection) { 129dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return projection != null ? projection : DEFAULT_ROOT_PROJECTION; 130dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 131dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 132dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private static String[] resolveDocumentProjection(String[] projection) { 133dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; 134dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 135dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 136dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private static String getTypeForName(String name) { 137dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final int lastDot = name.lastIndexOf('.'); 138dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (lastDot >= 0) { 139dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final String extension = name.substring(lastDot + 1).toLowerCase(); 140dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 141dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (mime != null) { 142dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return mime; 143dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 144dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 145dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return "application/octet-stream"; 146dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 147dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 148dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private String getDocIdForFile(File file) { 149dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return DOC_ID_ROOT + ":" + file.getName(); 150dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 151dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 152dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private File getFileForDocId(String documentId) throws FileNotFoundException { 153dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final int splitIndex = documentId.indexOf(':', 1); 154dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final String name = documentId.substring(splitIndex + 1); 155dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) || 156dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto !FileUtils.isValidExtFilename(name)) { 157dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto throw new FileNotFoundException("Invalid document ID: " + documentId); 158dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 159dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final File file = new File(mRoot, name); 160dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto if (!file.exists()) { 161dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto throw new FileNotFoundException("File not found: " + documentId); 162dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 163dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto return file; 164dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 165dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto 166dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto private void addFileRow(MatrixCursor result, File file) { 16753a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay String mimeType = getTypeForName(file.getName()); 16853a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay int flags = Document.FLAG_SUPPORTS_DELETE; 169dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto final RowBuilder row = result.newRow(); 170dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_DOCUMENT_ID, getDocIdForFile(file)); 17153a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay row.add(Document.COLUMN_MIME_TYPE, mimeType); 172dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 173dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); 17453a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay row.add(Document.COLUMN_FLAGS, flags); 175dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto row.add(Document.COLUMN_SIZE, file.length()); 176dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto } 177dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto} 178