1dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto/*
2dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * Copyright (C) 2015 The Android Open Source Project
3dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto *
4dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * Licensed under the Apache License, Version 2.0 (the "License");
5dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * you may not use this file except in compliance with the License.
6dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * You may obtain a copy of the License at
7dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto *
8dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto *      http://www.apache.org/licenses/LICENSE-2.0
9dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto *
10dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * Unless required by applicable law or agreed to in writing, software
11dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * distributed under the License is distributed on an "AS IS" BASIS,
12dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * See the License for the specific language governing permissions and
14dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto * limitations under the License.
15dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto */
16dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
17dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotopackage com.android.shell;
18dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
19dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.database.Cursor;
20dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.database.MatrixCursor;
21dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.database.MatrixCursor.RowBuilder;
22c6905cfb1133627dfd500491c60b6528a3e593e0Ben Linimport android.net.Uri;
23dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.os.CancellationSignal;
24dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.os.FileUtils;
25dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.os.ParcelFileDescriptor;
26c6905cfb1133627dfd500491c60b6528a3e593e0Ben Linimport android.provider.DocumentsContract;
27dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.provider.DocumentsContract.Document;
28dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.provider.DocumentsContract.Root;
29dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.provider.DocumentsProvider;
30dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport android.webkit.MimeTypeMap;
31dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
32dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport java.io.File;
33dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotoimport java.io.FileNotFoundException;
34dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
35dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimotopublic class BugreportStorageProvider extends DocumentsProvider {
36c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin    private static final String AUTHORITY = "com.android.shell.documents";
37dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private static final String DOC_ID_ROOT = "bugreport";
38dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
39dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
40dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
41dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            Root.COLUMN_DOCUMENT_ID,
42dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    };
43dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
44dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
45dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
46dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
47dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    };
48dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
49dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private File mRoot;
50dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
51dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    @Override
52dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    public boolean onCreate() {
53dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        mRoot = new File(getContext().getFilesDir(), "bugreports");
54dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return true;
55dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
56dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
57dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    @Override
58dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
59dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
60dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final RowBuilder row = result.newRow();
61dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT);
62774cc932fc29705c9580775abf7c1e7a80c2bbe3Aga Wronska        row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED);
63dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon);
64dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title));
65dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT);
66dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return result;
67dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
68dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
69dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    @Override
70dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    public Cursor queryDocument(String documentId, String[] projection)
71dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            throws FileNotFoundException {
72dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
73dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        if (DOC_ID_ROOT.equals(documentId)) {
74dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            final RowBuilder row = result.newRow();
75dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            row.add(Document.COLUMN_DOCUMENT_ID, documentId);
76dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
77dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName());
78dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified());
79dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED);
80dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        } else {
81dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            addFileRow(result, getFileForDocId(documentId));
82dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        }
83dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return result;
84dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
85dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
86dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    @Override
87dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    public Cursor queryChildDocuments(
88dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            String parentDocumentId, String[] projection, String sortOrder)
89dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            throws FileNotFoundException {
90dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
91dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        if (DOC_ID_ROOT.equals(parentDocumentId)) {
92dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            final File[] files = mRoot.listFiles();
93dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            if (files != null) {
94dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto                for (File file : files) {
95dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto                    addFileRow(result, file);
96dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto                }
97c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin                result.setNotificationUri(getContext().getContentResolver(), getNotificationUri());
98dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            }
99dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        }
100dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return result;
101dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
102dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
103dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    @Override
104dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    public ParcelFileDescriptor openDocument(
105dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            String documentId, String mode, CancellationSignal signal)
106dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            throws FileNotFoundException {
107dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) {
108dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            throw new FileNotFoundException("Failed to open: " + documentId + ", mode = " + mode);
109dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        }
110dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return ParcelFileDescriptor.open(getFileForDocId(documentId),
111dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto                ParcelFileDescriptor.MODE_READ_ONLY);
112dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
113dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
114dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    @Override
115dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    public void deleteDocument(String documentId) throws FileNotFoundException {
116dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        if (!getFileForDocId(documentId).delete()) {
117dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            throw new FileNotFoundException("Failed to delete: " + documentId);
118dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        }
11986805aa3afb5b08e916da424d0daf05149fae7eeBen Lin        getContext().getContentResolver().notifyChange(getNotificationUri(), null);
120dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
121dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
122c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin    // This is used by BugreportProgressService so that the notification uri shared by
123c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin    // BugreportProgressService and BugreportStorageProvider are guaranteed the same and unique
124c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin    protected static Uri getNotificationUri() {
125c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin      return DocumentsContract.buildChildDocumentsUri(AUTHORITY, DOC_ID_ROOT);
126c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin    }
127c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin
128dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private static String[] resolveRootProjection(String[] projection) {
129dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
130dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
131dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
132dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private static String[] resolveDocumentProjection(String[] projection) {
133dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
134dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
135dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
136dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private static String getTypeForName(String name) {
137dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final int lastDot = name.lastIndexOf('.');
138dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        if (lastDot >= 0) {
139dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            final String extension = name.substring(lastDot + 1).toLowerCase();
140dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
141dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            if (mime != null) {
142dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto                return mime;
143dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            }
144dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        }
145dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return "application/octet-stream";
146dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
147dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
148dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private String getDocIdForFile(File file) {
149dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return DOC_ID_ROOT + ":" + file.getName();
150dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
151dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
152dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private File getFileForDocId(String documentId) throws FileNotFoundException {
153dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final int splitIndex = documentId.indexOf(':', 1);
154dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final String name = documentId.substring(splitIndex + 1);
155dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) ||
156dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto                !FileUtils.isValidExtFilename(name)) {
157dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            throw new FileNotFoundException("Invalid document ID: " + documentId);
158dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        }
159dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final File file = new File(mRoot, name);
160dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        if (!file.exists()) {
161dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto            throw new FileNotFoundException("File not found: " + documentId);
162dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        }
163dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        return file;
164dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
165dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto
166dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    private void addFileRow(MatrixCursor result, File file) {
16753a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay        String mimeType = getTypeForName(file.getName());
16853a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay        int flags = Document.FLAG_SUPPORTS_DELETE;
169dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        final RowBuilder row = result.newRow();
170dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Document.COLUMN_DOCUMENT_ID, getDocIdForFile(file));
17153a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay        row.add(Document.COLUMN_MIME_TYPE, mimeType);
172dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
173dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
17453a528bb4f4cdb5a8d07b0d91405fdb2edb62201Steve McKay        row.add(Document.COLUMN_FLAGS, flags);
175dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto        row.add(Document.COLUMN_SIZE, file.length());
176dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto    }
177dd98034edce7ebbfefadf3f8a351c1b7ee7c2a73Ryo Hashimoto}
178