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