summaryrefslogtreecommitdiffstats
path: root/packages/Shell/src
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-03-08 16:13:15 -0800
committerJeff Sharkey <jsharkey@android.com>2013-03-13 16:42:38 -0700
commit02ffba940ca96988ed3e7774c606b43c58373b5e (patch)
tree9d829654e361a899b1de8f0621716774e75857ec /packages/Shell/src
parent998b692d888765d10827264c953b227439fbf365 (diff)
downloadframeworks_base-02ffba940ca96988ed3e7774c606b43c58373b5e.zip
frameworks_base-02ffba940ca96988ed3e7774c606b43c58373b5e.tar.gz
frameworks_base-02ffba940ca96988ed3e7774c606b43c58373b5e.tar.bz2
Handle finished bugreports, share from private.
Show notification when a bugreport is finished, letting the user launch a SEND_MULTIPLE intent to share them. Add dialog that warns user about contents before sharing. Since bugreports are now stored in private app data of the Shell app, use FileProvider to build Uris that we can grant others access to. Define BUGREPORT_FINISHED as being a protected broadcast. Delete older bugreports automatically to reclaim disk space. Migrate any Intent extras to ClipData when building PendingIntents. Add --receiver-permission support to am shell command. Bug: 7005318 Change-Id: If6c607dbcf137362d5887eac482ff7391563890f
Diffstat (limited to 'packages/Shell/src')
-rw-r--r--packages/Shell/src/com/android/shell/BugreportPrefs.java45
-rw-r--r--packages/Shell/src/com/android/shell/BugreportReceiver.java198
-rw-r--r--packages/Shell/src/com/android/shell/BugreportWarningActivity.java77
3 files changed, 320 insertions, 0 deletions
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
new file mode 100644
index 0000000..3748e89
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 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.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Preferences related to bug reports.
+ */
+public class BugreportPrefs {
+ private static final String PREFS_BUGREPORT = "bugreports";
+
+ private static final String KEY_WARNING_STATE = "warning-state";
+
+ public static final int STATE_UNKNOWN = 0;
+ public static final int STATE_SHOW = 1;
+ public static final int STATE_HIDE = 2;
+
+ public static int getWarningState(Context context, int def) {
+ final SharedPreferences prefs = context.getSharedPreferences(
+ PREFS_BUGREPORT, Context.MODE_PRIVATE);
+ return prefs.getInt(KEY_WARNING_STATE, def);
+ }
+
+ public static void setWarningState(Context context, int value) {
+ final SharedPreferences prefs = context.getSharedPreferences(
+ PREFS_BUGREPORT, Context.MODE_PRIVATE);
+ prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
new file mode 100644
index 0000000..3b1ebf4
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2013 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 android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.SystemProperties;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.util.Patterns;
+
+import com.google.android.collect.Lists;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Receiver that handles finished bugreports, usually by attaching them to an
+ * {@link Intent#ACTION_SEND}.
+ */
+public class BugreportReceiver extends BroadcastReceiver {
+ private static final String TAG = "Shell";
+
+ private static final String AUTHORITY = "com.android.shell";
+
+ private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+ private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+
+ /**
+ * Number of bugreports to retain before deleting the oldest; 4 reports and
+ * 4 screenshots are roughly 17MB of disk space.
+ */
+ private static final int NUM_OLD_FILES = 8;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+ final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+ // 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);
+ builder.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb);
+ builder.setContentTitle(context.getString(R.string.bugreport_finished_title));
+ builder.setContentText(context.getString(R.string.bugreport_finished_text));
+ builder.setContentIntent(PendingIntent.getActivity(
+ context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+ builder.setAutoCancel(true);
+ NotificationManager.from(context).notify(TAG, 0, builder.build());
+
+ // Clean up older bugreports in background
+ final PendingResult result = goAsync();
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ deleteOlderFiles(bugreportFile.getParentFile(), NUM_OLD_FILES);
+ result.finish();
+ return null;
+ }
+ }.execute();
+ }
+
+ 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);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setType("application/vnd.android.bugreport");
+
+ intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+ intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
+
+ final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Delete the oldest files in given directory until only the requested
+ * number remain.
+ */
+ private static void deleteOlderFiles(File dir, int retainNum) {
+ final File[] files = dir.listFiles();
+ if (files == null) return;
+
+ Arrays.sort(files, new ModifiedComparator());
+ for (int i = retainNum; i < files.length; i++) {
+ Log.d(TAG, "Deleting old file " + files[i]);
+ files[i].delete();
+ }
+ }
+
+ private static class ModifiedComparator implements Comparator<File> {
+ @Override
+ public int compare(File lhs, File rhs) {
+ return (int) (rhs.lastModified() - lhs.lastModified());
+ }
+ }
+
+ 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/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
new file mode 100644
index 0000000..a1d879a
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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_HIDE;
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
+import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.BugreportPrefs.setWarningState;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * Dialog that warns about contents of a bugreport.
+ */
+public class BugreportWarningActivity extends AlertActivity
+ implements DialogInterface.OnClickListener {
+
+ private Intent mSendIntent;
+ private CheckBox mConfirmRepeat;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mSendIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+
+ // We need to touch the extras to unpack them so they get migrated to
+ // ClipData correctly.
+ mSendIntent.hasExtra(Intent.EXTRA_STREAM);
+
+ final AlertController.AlertParams ap = mAlertParams;
+ ap.mView = LayoutInflater.from(this).inflate(R.layout.confirm_repeat, null);
+ ap.mPositiveButtonText = getString(android.R.string.ok);
+ ap.mNegativeButtonText = getString(android.R.string.cancel);
+ ap.mPositiveButtonListener = this;
+ ap.mNegativeButtonListener = this;
+
+ mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
+ mConfirmRepeat.setChecked(getWarningState(this, STATE_UNKNOWN) == STATE_SHOW);
+
+ setupAlert();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == AlertDialog.BUTTON_POSITIVE) {
+ // Remember confirm state, and launch target
+ setWarningState(this, mConfirmRepeat.isChecked() ? STATE_SHOW : STATE_HIDE);
+ startActivity(mSendIntent);
+ }
+
+ finish();
+ }
+}