Merge "Refactored the BUGREPORT_RECEIVED logic into a new service."
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4f087db..bf3982d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -147,5 +147,9 @@
<action android:name="android.intent.action.BUGREPORT_FINISHED" />
</intent-filter>
</receiver>
+
+ <service
+ android:name=".BugreportProgressService"
+ android:exported="false"/>
</application>
</manifest>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
new file mode 100644
index 0000000..a2030ef
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -0,0 +1,284 @@
+/*
+ * 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 static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.getWarningState;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import libcore.io.Streams;
+
+import com.google.android.collect.Lists;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.util.Patterns;
+import android.widget.Toast;
+
+public class BugreportProgressService extends Service {
+ private static final String TAG = "Shell";
+
+ private static final String AUTHORITY = "com.android.shell";
+
+ static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+ static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null) {
+ onBugreportFinished(intent);
+ }
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private void onBugreportFinished(Intent intent) {
+ final Context context = getApplicationContext();
+ final Configuration conf = context.getResources().getConfiguration();
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+ final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+ if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
+ triggerLocalNotification(context, bugreportFile, screenshotFile);
+ }
+ stopSelf();
+ }
+
+ /**
+ * Responsible for triggering a notification that allows the user to start a
+ * "share" intent with the bug report. On watches we have other methods to allow the user to
+ * start this intent (usually by triggering it on another connected device); we don't need to
+ * display the notification in this case.
+ */
+ private static void triggerLocalNotification(final Context context, final File bugreportFile,
+ final File screenshotFile) {
+ if (!bugreportFile.exists() || !bugreportFile.canRead()) {
+ Log.e(TAG, "Could not read bugreport file " + bugreportFile);
+ Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
+ if (!isPlainText) {
+ // Already zipped, send it right away.
+ sendBugreportNotification(context, bugreportFile, screenshotFile);
+ } else {
+ // Asynchronously zip the file first, then send it.
+ sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
+ }
+ }
+
+ private static Intent buildWarningIntent(Context context, Intent sendIntent) {
+ final Intent intent = new Intent(context, BugreportWarningActivity.class);
+ intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+ return intent;
+ }
+
+ /**
+ * Build {@link Intent} that can be used to share the given bugreport.
+ */
+ private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+ final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ final String mimeType = "application/vnd.android.bugreport";
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setType(mimeType);
+
+ intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+
+ // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
+ // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
+ // create the ClipData object with the attachments URIs.
+ String messageBody = String.format("Build info: %s\nSerial number:%s",
+ SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
+ intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+ final ClipData clipData = new ClipData(null, new String[] { mimeType },
+ new ClipData.Item(null, null, null, bugreportUri));
+ final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+ if (screenshotUri != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ attachments.add(screenshotUri);
+ }
+ intent.setClipData(clipData);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+ final Account sendToAccount = findSendToAccount(context);
+ if (sendToAccount != null) {
+ intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
+ }
+
+ return intent;
+ }
+
+ /**
+ * 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 = getUri(context, bugreportFile);
+ final Uri screenshotUri = getUri(context, 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());
+ entry.setTime(bugreportFile.lastModified());
+ 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) {
+ final AccountManager am = (AccountManager) context.getSystemService(
+ Context.ACCOUNT_SERVICE);
+
+ String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
+ if (!preferredDomain.startsWith("@")) {
+ preferredDomain = "@" + preferredDomain;
+ }
+
+ final Account[] accounts = am.getAccounts();
+ Account foundAccount = null;
+ for (Account account : accounts) {
+ if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+ if (!preferredDomain.isEmpty()) {
+ // if we have a preferred domain and it matches, return; otherwise keep
+ // looking
+ if (account.name.endsWith(preferredDomain)) {
+ return account;
+ } else {
+ foundAccount = account;
+ }
+ // if we don't have a preferred domain, just return since it looks like
+ // an email address
+ } else {
+ return account;
+ }
+ }
+ }
+ return foundAccount;
+ }
+
+ private static Uri getUri(Context context, File file) {
+ return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+ }
+
+ static File getFileExtra(Intent intent, String key) {
+ final String path = intent.getStringExtra(key);
+ if (path != null) {
+ return new File(path);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index 898e8c9..f1da14d 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -16,53 +16,23 @@
package com.android.shell;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.getFileExtra;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
+import java.io.File;
+
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
-import android.net.Uri;
import android.os.AsyncTask;
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 android.widget.Toast;
-
-import com.google.android.collect.Lists;
-import libcore.io.Streams;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import java.util.ArrayList;
/**
* Receiver that handles finished bugreports, usually by attaching them to an
- * {@link Intent#ACTION_SEND}.
+ * {@link Intent#ACTION_SEND_MULTIPLE}.
*/
public class BugreportReceiver extends BroadcastReceiver {
- private static final String TAG = "Shell";
-
- private static final String AUTHORITY = "com.android.shell";
-
- static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
- static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
/**
* Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are
@@ -77,15 +47,17 @@
@Override
public void onReceive(Context context, Intent intent) {
- final Configuration conf = context.getResources().getConfiguration();
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
-
- if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
- triggerLocalNotification(context, bugreportFile, screenshotFile);
- }
-
// Clean up older bugreports in background
+ cleanupOldFiles(intent);
+
+ // Delegate to service.
+ Intent serviceIntent = new Intent(context, BugreportProgressService.class);
+ serviceIntent.putExtras(intent.getExtras());
+ context.startService(serviceIntent);
+ }
+
+ private void cleanupOldFiles(Intent intent) {
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
final PendingResult result = goAsync();
new AsyncTask<Void, Void, Void>() {
@Override
@@ -97,201 +69,4 @@
}
}.execute();
}
-
- /**
- * Responsible for triggering a notification that allows the user to start a
- * "share" intent with the bug report. On watches we have other methods to allow the user to
- * start this intent (usually by triggering it on another connected device); we don't need to
- * display the notification in this case.
- */
- private void triggerLocalNotification(final Context context, final File bugreportFile,
- final File screenshotFile) {
- if (!bugreportFile.exists() || !bugreportFile.canRead()) {
- Log.e(TAG, "Could not read bugreport file " + bugreportFile);
- Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
- Toast.LENGTH_LONG).show();
- return;
- }
-
- boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
- if (!isPlainText) {
- // Already zipped, send it right away.
- sendBugreportNotification(context, bugreportFile, screenshotFile);
- } else {
- // Asynchronously zip the file first, then send it.
- sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
- }
- }
-
- private static Intent buildWarningIntent(Context context, Intent sendIntent) {
- final Intent intent = new Intent(context, BugreportWarningActivity.class);
- intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
- return intent;
- }
-
- /**
- * Build {@link Intent} that can be used to share the given bugreport.
- */
- private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
- final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
- final String mimeType = "application/vnd.android.bugreport";
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setType(mimeType);
-
- intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
-
- // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
- // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
- // create the ClipData object with the attachments URIs.
- String messageBody = String.format("Build info: %s\nSerial number:%s",
- SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
- intent.putExtra(Intent.EXTRA_TEXT, messageBody);
- final ClipData clipData = new ClipData(null, new String[] { mimeType },
- new ClipData.Item(null, null, null, bugreportUri));
- final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
- if (screenshotUri != null) {
- clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
- attachments.add(screenshotUri);
- }
- intent.setClipData(clipData);
- intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
-
- final Account sendToAccount = findSendToAccount(context);
- if (sendToAccount != null) {
- intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
- }
-
- return intent;
- }
-
- /**
- * 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 = getUri(context, bugreportFile);
- final Uri screenshotUri = getUri(context, 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());
- entry.setTime(bugreportFile.lastModified());
- 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) {
- final AccountManager am = (AccountManager) context.getSystemService(
- Context.ACCOUNT_SERVICE);
-
- String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
- if (!preferredDomain.startsWith("@")) {
- preferredDomain = "@" + preferredDomain;
- }
-
- final Account[] accounts = am.getAccounts();
- Account foundAccount = null;
- for (Account account : accounts) {
- if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
- if (!preferredDomain.isEmpty()) {
- // if we have a preferred domain and it matches, return; otherwise keep
- // looking
- if (account.name.endsWith(preferredDomain)) {
- return account;
- } else {
- foundAccount = account;
- }
- // if we don't have a preferred domain, just return since it looks like
- // an email address
- } else {
- return account;
- }
- }
- }
- return foundAccount;
- }
-
- private static Uri getUri(Context context, File file) {
- return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
- }
-
- private static File getFileExtra(Intent intent, String key) {
- final String path = intent.getStringExtra(key);
- if (path != null) {
- return new File(path);
- } else {
- return null;
- }
- }
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 06565c0..1bdd9dd 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -18,8 +18,8 @@
import static android.test.MoreAsserts.assertContainsRegex;
import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
-import static com.android.shell.BugreportReceiver.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportReceiver.EXTRA_SCREENSHOT;
+import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;