1 /* 2 * Copyright (C) 2018 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.traceur; 18 19 import android.database.Cursor; 20 import android.database.MatrixCursor; 21 import android.net.Uri; 22 import android.os.Bundle; 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.Settings; 30 import android.util.Log; 31 32 import com.android.internal.content.FileSystemProvider; 33 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 37 /** 38 * Adds an entry for traces in the file picker. 39 */ 40 public class StorageProvider extends FileSystemProvider{ 41 42 public static final String TAG = StorageProvider.class.getName(); 43 public static final String AUTHORITY = "com.android.traceur.documents"; 44 45 private static final String DOC_ID_ROOT = "traces"; 46 private static final String ROOT_DIR = "/data/local/traces"; 47 private static final String MIME_TYPE = "application/vnd.android.systrace"; 48 49 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 50 Root.COLUMN_ROOT_ID, 51 Root.COLUMN_ICON, 52 Root.COLUMN_TITLE, 53 Root.COLUMN_FLAGS, 54 Root.COLUMN_DOCUMENT_ID, 55 }; 56 57 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 58 Document.COLUMN_DOCUMENT_ID, 59 Document.COLUMN_DISPLAY_NAME, 60 Document.COLUMN_MIME_TYPE, 61 Document.COLUMN_FLAGS, 62 Document.COLUMN_SIZE, 63 Document.COLUMN_LAST_MODIFIED, 64 }; 65 66 @Override onCreate()67 public boolean onCreate() { 68 super.onCreate(DEFAULT_DOCUMENT_PROJECTION); 69 return true; 70 } 71 72 @Override queryRoots(String[] projection)73 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 74 final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); 75 // Return an empty root cursor, which will remove the provider from the list entirely. 76 if (!Receiver.isTraceurAllowed(getContext())) { 77 return null; 78 } 79 80 final MatrixCursor.RowBuilder row = result.newRow(); 81 row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT); 82 row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); 83 row.add(Root.COLUMN_MIME_TYPES, MIME_TYPE); 84 row.add(Root.COLUMN_ICON, R.drawable.bugfood_icon_green); 85 row.add(Root.COLUMN_TITLE, 86 getContext().getString(R.string.system_traces_storage_title)); 87 row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT); 88 return result; 89 } 90 91 @Override queryDocument(String documentId, String[] projection)92 public Cursor queryDocument(String documentId, String[] projection) 93 throws FileNotFoundException { 94 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 95 final MatrixCursor.RowBuilder row = result.newRow(); 96 File file; 97 String mimeType; 98 99 if (DOC_ID_ROOT.equals(documentId)) { 100 file = new File(ROOT_DIR); 101 mimeType = Document.MIME_TYPE_DIR; 102 } else { 103 file = getFileForDocId(documentId); 104 mimeType = MIME_TYPE; 105 } 106 107 row.add(Document.COLUMN_DOCUMENT_ID, documentId); 108 row.add(Document.COLUMN_MIME_TYPE, mimeType); 109 row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 110 row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); 111 row.add(Document.COLUMN_SIZE, file.length()); 112 row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED | Document.FLAG_SUPPORTS_DELETE); 113 return result; 114 } 115 116 @Override queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)117 public Cursor queryChildDocuments( 118 String parentDocumentId, String[] projection, String sortOrder) 119 throws FileNotFoundException { 120 TraceUtils.cleanupOlderFiles(); 121 122 Cursor result = super.queryChildDocuments(parentDocumentId, projection, sortOrder); 123 Bundle bundle = new Bundle(); 124 bundle.putString(DocumentsContract.EXTRA_INFO, 125 getContext().getResources().getString(R.string.system_trace_sensitive_data)); 126 result.setExtras(bundle); 127 128 return result; 129 } 130 131 132 @Override openDocument( String documentId, String mode, CancellationSignal signal)133 public ParcelFileDescriptor openDocument( 134 String documentId, String mode, CancellationSignal signal) 135 throws FileNotFoundException, UnsupportedOperationException { 136 if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) { 137 throw new UnsupportedOperationException( 138 "Attempt to open read-only file " + documentId + " in mode " + mode); 139 } 140 return ParcelFileDescriptor.open(getFileForDocId(documentId), 141 ParcelFileDescriptor.MODE_READ_ONLY); 142 } 143 resolveRootProjection(String[] projection)144 private static String[] resolveRootProjection(String[] projection) { 145 return projection != null ? projection : DEFAULT_ROOT_PROJECTION; 146 } 147 resolveDocumentProjection(String[] projection)148 private static String[] resolveDocumentProjection(String[] projection) { 149 return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; 150 } 151 152 @Override buildNotificationUri(String docId)153 protected Uri buildNotificationUri(String docId) { 154 return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId); 155 } 156 157 @Override getDocIdForFile(File file)158 protected String getDocIdForFile(File file) { 159 return DOC_ID_ROOT + ":" + file.getName(); 160 } 161 162 @Override getFileForDocId(String documentId, boolean visible)163 protected File getFileForDocId(String documentId, boolean visible) 164 throws FileNotFoundException { 165 if (DOC_ID_ROOT.equals(documentId)) { 166 return new File(ROOT_DIR); 167 } else { 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(ROOT_DIR, name); 175 if (!file.exists()) { 176 throw new FileNotFoundException("File not found: " + documentId); 177 } 178 return file; 179 } 180 } 181 182 } 183