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