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.database.Cursor; 19 import android.database.MatrixCursor; 20 import android.net.Uri; 21 import android.os.Bundle; 22 import android.os.FileUtils; 23 import android.os.CancellationSignal; 24 import android.os.ParcelFileDescriptor; 25 import android.os.UserManager; 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 76 boolean developerOptionsIsEnabled = 77 Settings.Global.getInt(getContext().getContentResolver(), 78 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; 79 UserManager userManager = getContext().getSystemService(UserManager.class); 80 boolean isAdminUser = userManager.isAdminUser(); 81 boolean debuggingDisallowed = userManager.hasUserRestriction( 82 UserManager.DISALLOW_DEBUGGING_FEATURES); 83 84 // If developer options is not enabled or the user is not an admin, return an empty root 85 // cursor. This removes the provider from the list entirely. 86 if (!developerOptionsIsEnabled || !isAdminUser || debuggingDisallowed) { 87 return null; 88 } 89 90 final MatrixCursor.RowBuilder row = result.newRow(); 91 row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT); 92 row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); 93 row.add(Root.COLUMN_MIME_TYPES, MIME_TYPE); 94 row.add(Root.COLUMN_ICON, R.drawable.bugfood_icon_green); 95 row.add(Root.COLUMN_TITLE, 96 getContext().getString(R.string.system_traces_storage_title)); 97 row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT); 98 return result; 99 } 100 101 @Override queryDocument(String documentId, String[] projection)102 public Cursor queryDocument(String documentId, String[] projection) 103 throws FileNotFoundException { 104 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 105 final MatrixCursor.RowBuilder row = result.newRow(); 106 File file; 107 String mimeType; 108 109 if (DOC_ID_ROOT.equals(documentId)) { 110 file = new File(ROOT_DIR); 111 mimeType = Document.MIME_TYPE_DIR; 112 } else { 113 file = getFileForDocId(documentId); 114 mimeType = MIME_TYPE; 115 } 116 117 row.add(Document.COLUMN_DOCUMENT_ID, documentId); 118 row.add(Document.COLUMN_MIME_TYPE, mimeType); 119 row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 120 row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); 121 row.add(Document.COLUMN_SIZE, file.length()); 122 row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED | Document.FLAG_SUPPORTS_DELETE); 123 return result; 124 } 125 126 @Override queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)127 public Cursor queryChildDocuments( 128 String parentDocumentId, String[] projection, String sortOrder) 129 throws FileNotFoundException { 130 Cursor result = super.queryChildDocuments(parentDocumentId, projection, sortOrder); 131 132 Bundle bundle = new Bundle(); 133 bundle.putString(DocumentsContract.EXTRA_INFO, 134 getContext().getResources().getString(R.string.system_trace_sensitive_data)); 135 result.setExtras(bundle); 136 137 return result; 138 } 139 140 141 @Override openDocument( String documentId, String mode, CancellationSignal signal)142 public ParcelFileDescriptor openDocument( 143 String documentId, String mode, CancellationSignal signal) 144 throws FileNotFoundException, UnsupportedOperationException { 145 if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) { 146 throw new UnsupportedOperationException( 147 "Attempt to open read-only file " + documentId + " in mode " + mode); 148 } 149 return ParcelFileDescriptor.open(getFileForDocId(documentId), 150 ParcelFileDescriptor.MODE_READ_ONLY); 151 } 152 resolveRootProjection(String[] projection)153 private static String[] resolveRootProjection(String[] projection) { 154 return projection != null ? projection : DEFAULT_ROOT_PROJECTION; 155 } 156 resolveDocumentProjection(String[] projection)157 private static String[] resolveDocumentProjection(String[] projection) { 158 return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; 159 } 160 161 @Override buildNotificationUri(String docId)162 protected Uri buildNotificationUri(String docId) { 163 return DocumentsContract.buildChildDocumentsUri(AUTHORITY, docId); 164 } 165 166 @Override getDocIdForFile(File file)167 protected String getDocIdForFile(File file) { 168 return DOC_ID_ROOT + ":" + file.getName(); 169 } 170 171 @Override getFileForDocId(String documentId, boolean visible)172 protected File getFileForDocId(String documentId, boolean visible) 173 throws FileNotFoundException { 174 if (DOC_ID_ROOT.equals(documentId)) { 175 return new File(ROOT_DIR); 176 } else { 177 final int splitIndex = documentId.indexOf(':', 1); 178 final String name = documentId.substring(splitIndex + 1); 179 if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) || 180 !FileUtils.isValidExtFilename(name)) { 181 throw new FileNotFoundException("Invalid document ID: " + documentId); 182 } 183 final File file = new File(ROOT_DIR, name); 184 if (!file.exists()) { 185 throw new FileNotFoundException("File not found: " + documentId); 186 } 187 return file; 188 } 189 } 190 191 } 192