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