am 34f37e74: Merge "Handle finished bugreports, share from private." into jb-mr2-dev
* commit '34f37e74125b09d13537782b602b6b4e37995ff7': Handle finished bugreports, share from private.
This commit is contained in:
@@ -63,6 +63,7 @@ public class Am {
|
||||
|
||||
private int mRepeat = 0;
|
||||
private int mUserId;
|
||||
private String mReceiverPermission;
|
||||
|
||||
private String mProfileFile;
|
||||
|
||||
@@ -332,6 +333,8 @@ public class Am {
|
||||
mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
|
||||
} else if (opt.equals("--user")) {
|
||||
mUserId = parseUserArg(nextArgRequired());
|
||||
} else if (opt.equals("--receiver-permission")) {
|
||||
mReceiverPermission = nextArgRequired();
|
||||
} else {
|
||||
System.err.println("Error: Unknown option: " + opt);
|
||||
return null;
|
||||
@@ -608,7 +611,7 @@ public class Am {
|
||||
Intent intent = makeIntent(UserHandle.USER_ALL);
|
||||
IntentReceiver receiver = new IntentReceiver();
|
||||
System.out.println("Broadcasting: " + intent);
|
||||
mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null,
|
||||
mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, mReceiverPermission,
|
||||
android.app.AppOpsManager.OP_NONE, true, false, mUserId);
|
||||
receiver.waitForFinish();
|
||||
}
|
||||
@@ -1408,6 +1411,7 @@ public class Am {
|
||||
"am broadcast: send a broadcast Intent. Options are:\n" +
|
||||
" --user <USER_ID> | all | current: Specify which user to send to; if not\n" +
|
||||
" specified then send to all users.\n" +
|
||||
" --receiver-permission <PERMISSION>: Require receiver to hold permission.\n" +
|
||||
"\n" +
|
||||
"am instrument: start an Instrumentation. Typically this target <COMPONENT>\n" +
|
||||
" is the form <TEST_PACKAGE>/<RUNNER_CLASS>. Options are:\n" +
|
||||
|
||||
@@ -261,6 +261,7 @@ public final class PendingIntent implements Parcelable {
|
||||
context.getContentResolver()) : null;
|
||||
try {
|
||||
intent.setAllowFds(false);
|
||||
intent.migrateExtraStreamToClipData();
|
||||
IIntentSender target =
|
||||
ActivityManagerNative.getDefault().getIntentSender(
|
||||
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
|
||||
@@ -285,6 +286,7 @@ public final class PendingIntent implements Parcelable {
|
||||
context.getContentResolver()) : null;
|
||||
try {
|
||||
intent.setAllowFds(false);
|
||||
intent.migrateExtraStreamToClipData();
|
||||
IIntentSender target =
|
||||
ActivityManagerNative.getDefault().getIntentSender(
|
||||
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
|
||||
|
||||
@@ -169,6 +169,7 @@
|
||||
<protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" />
|
||||
<protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
|
||||
<protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
|
||||
<protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" />
|
||||
|
||||
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
|
||||
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
|
||||
|
||||
@@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
|
||||
|
||||
LOCAL_PACKAGE_NAME := Shell
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
|
||||
@@ -68,7 +68,32 @@
|
||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||
<uses-permission android:name="android.permission.MANAGE_USERS" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
|
||||
|
||||
<application android:hasCode="false" android:label="@string/app_label">
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
|
||||
<application android:label="@string/app_label">
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.android.shell"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider_paths" />
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".BugreportWarningActivity"
|
||||
android:theme="@*android:style/Theme.Holo.Dialog.Alert"
|
||||
android:finishOnCloseSystemDialogs="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".BugreportReceiver"
|
||||
android:permission="android.permission.DUMP">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BUGREPORT_FINISHED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
37
packages/Shell/res/layout/confirm_repeat.xml
Normal file
37
packages/Shell/res/layout/confirm_repeat.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dip"
|
||||
android:paddingEnd="16dip"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="16dip"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bugreport_confirm"
|
||||
android:paddingBottom="16dip"
|
||||
style="?android:attr/textAppearanceMedium" />
|
||||
<CheckBox
|
||||
android:id="@android:id/checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bugreport_confirm_repeat" />
|
||||
</LinearLayout>
|
||||
@@ -16,4 +16,14 @@
|
||||
|
||||
<resources>
|
||||
<string name="app_label">Shell</string>
|
||||
|
||||
<!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
|
||||
<string name="bugreport_finished_title">Bug report captured</string>
|
||||
<!-- Text of notification indicating that touching will share the captured bugreport. [CHAR LIMIT=100] -->
|
||||
<string name="bugreport_finished_text">Touch to share your bug report</string>
|
||||
|
||||
<!-- Body of dialog informing user about contents of a bugreport. [CHAR LIMIT=NONE] -->
|
||||
<string name="bugreport_confirm">Bug reports contain data from the system\'s various log files, including personal and private information. Only share bug reports with apps and people you trust.</string>
|
||||
<!-- Checkbox that indicates this dialog should be shown again when the next bugreport is taken. [CHAR LIMIT=50] -->
|
||||
<string name="bugreport_confirm_repeat">Show this message next time</string>
|
||||
</resources>
|
||||
|
||||
3
packages/Shell/res/xml/file_provider_paths.xml
Normal file
3
packages/Shell/res/xml/file_provider_paths.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="bugreports" path="bugreports/" />
|
||||
</paths>
|
||||
45
packages/Shell/src/com/android/shell/BugreportPrefs.java
Normal file
45
packages/Shell/src/com/android/shell/BugreportPrefs.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
198
packages/Shell/src/com/android/shell/BugreportReceiver.java
Normal file
198
packages/Shell/src/com/android/shell/BugreportReceiver.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user