diff options
Diffstat (limited to 'packages/Shell/src')
| -rw-r--r-- | packages/Shell/src/com/android/shell/BugreportReceiver.java | 122 | ||||
| -rw-r--r-- | packages/Shell/src/com/android/shell/BugreportStorageProvider.java | 164 |
2 files changed, 264 insertions, 22 deletions
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java index b3ba895..6278650 100644 --- a/packages/Shell/src/com/android/shell/BugreportReceiver.java +++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java @@ -34,11 +34,23 @@ import android.os.FileUtils; import android.os.SystemProperties; import android.support.v4.content.FileProvider; import android.text.format.DateUtils; +import android.util.Log; import android.util.Patterns; import com.google.android.collect.Lists; +import libcore.io.Streams; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import java.util.ArrayList; /** @@ -100,30 +112,14 @@ public class BugreportReceiver extends BroadcastReceiver { final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile); final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile); - Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri); - Intent notifIntent; - - // Send through warning dialog by default - if (getWarningState(context, STATE_SHOW) == STATE_SHOW) { - notifIntent = buildWarningIntent(context, sendIntent); + boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt"); + if (!isPlainText) { + // Already zipped, send it right away. + sendBugreportNotification(context, bugreportFile, screenshotFile); } else { - notifIntent = sendIntent; + // Asynchronously zip the file first, then send it. + sendZippedBugreportNotification(context, bugreportFile, screenshotFile); } - notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - final Notification.Builder builder = new Notification.Builder(context) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) - .setContentTitle(context.getString(R.string.bugreport_finished_title)) - .setTicker(context.getString(R.string.bugreport_finished_title)) - .setContentText(context.getString(R.string.bugreport_finished_text)) - .setContentIntent(PendingIntent.getActivity( - context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT)) - .setAutoCancel(true) - .setLocalOnly(true) - .setColor(context.getResources().getColor( - com.android.internal.R.color.system_notification_accent_color)); - - NotificationManager.from(context).notify(TAG, 0, builder.build()); } private static Intent buildWarningIntent(Context context, Intent sendIntent) { @@ -156,6 +152,88 @@ public class BugreportReceiver extends BroadcastReceiver { } /** + * Sends a bugreport notitication. + */ + private static void sendBugreportNotification(Context context, File bugreportFile, + File screenshotFile) { + // Files are kept on private storage, so turn into Uris that we can + // grant temporary permissions for. + final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile); + final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile); + + Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri); + Intent notifIntent; + + // Send through warning dialog by default + if (getWarningState(context, STATE_SHOW) == STATE_SHOW) { + notifIntent = buildWarningIntent(context, sendIntent); + } else { + notifIntent = sendIntent; + } + notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + final Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) + .setContentTitle(context.getString(R.string.bugreport_finished_title)) + .setTicker(context.getString(R.string.bugreport_finished_title)) + .setContentText(context.getString(R.string.bugreport_finished_text)) + .setContentIntent(PendingIntent.getActivity( + context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT)) + .setAutoCancel(true) + .setLocalOnly(true) + .setColor(context.getColor( + com.android.internal.R.color.system_notification_accent_color)); + + NotificationManager.from(context).notify(TAG, 0, builder.build()); + } + + /** + * Sends a zipped bugreport notification. + */ + private static void sendZippedBugreportNotification(final Context context, + final File bugreportFile, final File screenshotFile) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + File zippedFile = zipBugreport(bugreportFile); + sendBugreportNotification(context, zippedFile, screenshotFile); + return null; + } + }.execute(); + } + + /** + * Zips a bugreport file, returning the path to the new file (or to the + * original in case of failure). + */ + private static File zipBugreport(File bugreportFile) { + String bugreportPath = bugreportFile.getAbsolutePath(); + String zippedPath = bugreportPath.replace(".txt", ".zip"); + Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath); + File bugreportZippedFile = new File(zippedPath); + try (InputStream is = new FileInputStream(bugreportFile); + ZipOutputStream zos = new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) { + ZipEntry entry = new ZipEntry(bugreportFile.getName()); + zos.putNextEntry(entry); + int totalBytes = Streams.copy(is, zos); + Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes"); + zos.closeEntry(); + // Delete old file; + boolean deleted = bugreportFile.delete(); + if (deleted) { + Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")"); + } else { + Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")"); + } + return bugreportZippedFile; + } catch (IOException e) { + Log.e(TAG, "exception zipping file " + zippedPath, e); + return bugreportFile; // Return original. + } + } + + /** * Find the best matching {@link Account} based on build properties. */ private static Account findSendToAccount(Context context) { diff --git a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java new file mode 100644 index 0000000..814aa8c --- /dev/null +++ b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.shell; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; +import android.os.CancellationSignal; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; +import android.provider.DocumentsProvider; +import android.webkit.MimeTypeMap; + +import java.io.File; +import java.io.FileNotFoundException; + +public class BugreportStorageProvider extends DocumentsProvider { + private static final String DOC_ID_ROOT = "bugreport"; + + private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { + Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, + Root.COLUMN_DOCUMENT_ID, + }; + + private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { + Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, + }; + + private File mRoot; + + @Override + public boolean onCreate() { + mRoot = new File(getContext().getFilesDir(), "bugreports"); + return true; + } + + @Override + public Cursor queryRoots(String[] projection) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); + final RowBuilder row = result.newRow(); + row.add(Root.COLUMN_ROOT_ID, DOC_ID_ROOT); + row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED); + row.add(Root.COLUMN_ICON, android.R.mipmap.sym_def_app_icon); + row.add(Root.COLUMN_TITLE, getContext().getString(R.string.bugreport_storage_title)); + row.add(Root.COLUMN_DOCUMENT_ID, DOC_ID_ROOT); + return result; + } + + @Override + public Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + if (DOC_ID_ROOT.equals(documentId)) { + final RowBuilder row = result.newRow(); + row.add(Document.COLUMN_DOCUMENT_ID, documentId); + row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); + row.add(Document.COLUMN_DISPLAY_NAME, mRoot.getName()); + row.add(Document.COLUMN_LAST_MODIFIED, mRoot.lastModified()); + row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_PREFERS_LAST_MODIFIED); + } else { + addFileRow(result, getFileForDocId(documentId)); + } + return result; + } + + @Override + public Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + if (DOC_ID_ROOT.equals(parentDocumentId)) { + final File[] files = mRoot.listFiles(); + if (files != null) { + for (File file : files) { + addFileRow(result, file); + } + } + } + return result; + } + + @Override + public ParcelFileDescriptor openDocument( + String documentId, String mode, CancellationSignal signal) + throws FileNotFoundException { + if (ParcelFileDescriptor.parseMode(mode) != ParcelFileDescriptor.MODE_READ_ONLY) { + throw new FileNotFoundException("Failed to open: " + documentId + ", mode = " + mode); + } + return ParcelFileDescriptor.open(getFileForDocId(documentId), + ParcelFileDescriptor.MODE_READ_ONLY); + } + + @Override + public void deleteDocument(String documentId) throws FileNotFoundException { + if (!getFileForDocId(documentId).delete()) { + throw new FileNotFoundException("Failed to delete: " + documentId); + } + } + + private static String[] resolveRootProjection(String[] projection) { + return projection != null ? projection : DEFAULT_ROOT_PROJECTION; + } + + private static String[] resolveDocumentProjection(String[] projection) { + return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; + } + + private static String getTypeForName(String name) { + final int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = name.substring(lastDot + 1).toLowerCase(); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; + } + } + return "application/octet-stream"; + } + + private String getDocIdForFile(File file) { + return DOC_ID_ROOT + ":" + file.getName(); + } + + private File getFileForDocId(String documentId) throws FileNotFoundException { + final int splitIndex = documentId.indexOf(':', 1); + final String name = documentId.substring(splitIndex + 1); + if (splitIndex == -1 || !DOC_ID_ROOT.equals(documentId.substring(0, splitIndex)) || + !FileUtils.isValidExtFilename(name)) { + throw new FileNotFoundException("Invalid document ID: " + documentId); + } + final File file = new File(mRoot, name); + if (!file.exists()) { + throw new FileNotFoundException("File not found: " + documentId); + } + return file; + } + + private void addFileRow(MatrixCursor result, File file) { + final RowBuilder row = result.newRow(); + row.add(Document.COLUMN_DOCUMENT_ID, getDocIdForFile(file)); + row.add(Document.COLUMN_MIME_TYPE, getTypeForName(file.getName())); + row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); + row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); + row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE); + row.add(Document.COLUMN_SIZE, file.length()); + } +} |
