Merge "Move heap dump sharing to SHELL." am: c29b5cba29 am: 2cdf2d5ad7
am: 215d92f880
Change-Id: Ic092f2d5c0c0a1497c19e73417c68b8e8616cc63
This commit is contained in:
@@ -180,6 +180,8 @@
|
||||
<uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" />
|
||||
<!-- Permission needed to invoke DynamicSystem (AOT) -->
|
||||
<uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" />
|
||||
<!-- Used to clean up heap dumps on boot. -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
|
||||
@@ -209,7 +211,7 @@
|
||||
|
||||
<!-- Permission required to test ExplicitHealthCheckServiceImpl. -->
|
||||
<uses-permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE" />
|
||||
|
||||
|
||||
<!-- Permission required for CTS test - CrossProfileAppsHostSideTest -->
|
||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
|
||||
|
||||
@@ -239,6 +241,11 @@
|
||||
</intent-filter>
|
||||
</provider>
|
||||
|
||||
<provider android:name=".HeapDumpProvider"
|
||||
android:authorities="com.android.shell.heapdump"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="true" />
|
||||
|
||||
<activity
|
||||
android:name=".BugreportWarningActivity"
|
||||
android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight"
|
||||
@@ -246,6 +253,14 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name=".HeapDumpActivity"
|
||||
android:theme="@*android:style/Theme.Translucent.NoTitleBar"
|
||||
android:label="@*android:string/dump_heap_title"
|
||||
android:finishOnCloseSystemDialogs="true"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".BugreportRequestedReceiver"
|
||||
android:permission="android.permission.TRIGGER_SHELL_BUGREPORT">
|
||||
@@ -254,6 +269,16 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".HeapDumpReceiver"
|
||||
android:permission="android.permission.DUMP">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="com.android.internal.intent.action.HEAP_DUMP_FINISHED" />
|
||||
<action android:name="com.android.shell.action.DELETE_HEAP_DUMP" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".BugreportProgressService"
|
||||
android:exported="false"/>
|
||||
|
||||
@@ -1560,7 +1560,7 @@ public class BugreportProgressService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isTv(Context context) {
|
||||
static boolean isTv(Context context) {
|
||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
|
||||
}
|
||||
|
||||
|
||||
142
packages/Shell/src/com/android/shell/HeapDumpActivity.java
Normal file
142
packages/Shell/src/com/android/shell/HeapDumpActivity.java
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.HeapDumpProvider.makeUri;
|
||||
import static com.android.shell.HeapDumpReceiver.ACTION_DELETE_HEAP_DUMP;
|
||||
import static com.android.shell.HeapDumpReceiver.EXTRA_IS_USER_INITIATED;
|
||||
import static com.android.shell.HeapDumpReceiver.EXTRA_PROCESS_NAME;
|
||||
import static com.android.shell.HeapDumpReceiver.EXTRA_REPORT_PACKAGE;
|
||||
import static com.android.shell.HeapDumpReceiver.EXTRA_SIZE_BYTES;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.util.DebugUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
/**
|
||||
* This activity is displayed when the system has collected a heap dump.
|
||||
*/
|
||||
public class HeapDumpActivity extends Activity {
|
||||
private static final String TAG = "HeapDumpActivity";
|
||||
|
||||
static final String KEY_URI = "uri";
|
||||
|
||||
private AlertDialog mDialog;
|
||||
private Uri mDumpUri;
|
||||
private boolean mHandled = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String process = getIntent().getStringExtra(EXTRA_PROCESS_NAME);
|
||||
long size = getIntent().getLongExtra(EXTRA_SIZE_BYTES, 0);
|
||||
final boolean isUserInitiated = getIntent().getBooleanExtra(EXTRA_IS_USER_INITIATED, false);
|
||||
final int uid = getIntent().getIntExtra(Intent.EXTRA_UID, 0);
|
||||
final boolean isSystemProcess = uid == Process.SYSTEM_UID;
|
||||
mDumpUri = makeUri(process);
|
||||
final String procDisplayName = isSystemProcess
|
||||
? getString(com.android.internal.R.string.android_system_label)
|
||||
: process;
|
||||
|
||||
final Intent sendIntent = new Intent();
|
||||
ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", mDumpUri);
|
||||
sendIntent.setClipData(clip);
|
||||
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
sendIntent.setType(clip.getDescription().getMimeType(0));
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, mDumpUri);
|
||||
|
||||
String directLaunchPackage = getIntent().getStringExtra(EXTRA_REPORT_PACKAGE);
|
||||
if (directLaunchPackage != null) {
|
||||
sendIntent.setAction(ActivityManager.ACTION_REPORT_HEAP_LIMIT);
|
||||
sendIntent.setPackage(directLaunchPackage);
|
||||
try {
|
||||
startActivity(sendIntent);
|
||||
mHandled = true;
|
||||
finish();
|
||||
return;
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.e(TAG, "Unable to direct launch to " + directLaunchPackage, e);
|
||||
}
|
||||
}
|
||||
|
||||
final int messageId;
|
||||
if (isUserInitiated) {
|
||||
messageId = com.android.internal.R.string.dump_heap_ready_text;
|
||||
} else if (isSystemProcess) {
|
||||
messageId = com.android.internal.R.string.dump_heap_system_text;
|
||||
} else {
|
||||
messageId = com.android.internal.R.string.dump_heap_text;
|
||||
}
|
||||
mDialog = new AlertDialog.Builder(this, android.R.style.Theme_Material_Light_Dialog_Alert)
|
||||
.setTitle(com.android.internal.R.string.dump_heap_title)
|
||||
.setMessage(getString(messageId, procDisplayName,
|
||||
DebugUtils.sizeValueToString(size, null)))
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
mHandled = true;
|
||||
finish();
|
||||
})
|
||||
.setNeutralButton(R.string.delete, (dialog, which) -> {
|
||||
mHandled = true;
|
||||
Intent deleteIntent = new Intent(ACTION_DELETE_HEAP_DUMP);
|
||||
deleteIntent.setClass(getApplicationContext(), HeapDumpReceiver.class);
|
||||
deleteIntent.putExtra(KEY_URI, mDumpUri.toString());
|
||||
sendBroadcast(deleteIntent);
|
||||
finish();
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
mHandled = true;
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.setPackage(null);
|
||||
startActivity(Intent.createChooser(sendIntent,
|
||||
getText(com.android.internal.R.string.dump_heap_title)));
|
||||
finish();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (!isChangingConfigurations()) {
|
||||
if (!mHandled) {
|
||||
Intent deleteIntent = new Intent(ACTION_DELETE_HEAP_DUMP);
|
||||
deleteIntent.setClass(getApplicationContext(), HeapDumpReceiver.class);
|
||||
deleteIntent.putExtra(KEY_URI, mDumpUri.toString());
|
||||
sendBroadcast(deleteIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mDialog != null) {
|
||||
mDialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
101
packages/Shell/src/com/android/shell/HeapDumpProvider.java
Normal file
101
packages/Shell/src/com/android/shell/HeapDumpProvider.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.annotation.NonNull;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/** ContentProvider to write and access heap dumps. */
|
||||
public class HeapDumpProvider extends ContentProvider {
|
||||
private static final String FILENAME_SUFFIX = "_javaheap.bin";
|
||||
private static final Object sLock = new Object();
|
||||
|
||||
private File mRoot;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
synchronized (sLock) {
|
||||
mRoot = new File(getContext().createCredentialProtectedStorageContext().getFilesDir(),
|
||||
"heapdumps");
|
||||
return mRoot.mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException("Insert not allowed.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
String path = sanitizePath(uri.getEncodedPath());
|
||||
String tag = Uri.decode(path);
|
||||
return (new File(mRoot, tag)).delete() ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("Update not allowed.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
String path = sanitizePath(uri.getEncodedPath());
|
||||
String tag = Uri.decode(path);
|
||||
final int pMode;
|
||||
if (Binder.getCallingUid() == Process.SYSTEM_UID) {
|
||||
pMode = ParcelFileDescriptor.MODE_CREATE
|
||||
| ParcelFileDescriptor.MODE_TRUNCATE
|
||||
| ParcelFileDescriptor.MODE_WRITE_ONLY;
|
||||
} else {
|
||||
pMode = ParcelFileDescriptor.MODE_READ_ONLY;
|
||||
}
|
||||
|
||||
synchronized (sLock) {
|
||||
return ParcelFileDescriptor.open(new File(mRoot, tag), pMode);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static Uri makeUri(@NonNull String procName) {
|
||||
return Uri.parse("content://com.android.shell.heapdump/" + procName + FILENAME_SUFFIX);
|
||||
}
|
||||
|
||||
private String sanitizePath(String path) {
|
||||
return path.replaceAll("[^a-zA-Z0-9_.]", "");
|
||||
}
|
||||
}
|
||||
188
packages/Shell/src/com/android/shell/HeapDumpReceiver.java
Normal file
188
packages/Shell/src/com/android/shell/HeapDumpReceiver.java
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.BugreportProgressService.isTv;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
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.FileUtils;
|
||||
import android.os.Process;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Receiver that handles finished heap dumps.
|
||||
*/
|
||||
public class HeapDumpReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "HeapDumpReceiver";
|
||||
|
||||
/**
|
||||
* Broadcast action to determine when to delete a specific dump heap. Must include a {@link
|
||||
* HeapDumpActivity#KEY_URI} String extra.
|
||||
*/
|
||||
static final String ACTION_DELETE_HEAP_DUMP = "com.android.shell.action.DELETE_HEAP_DUMP";
|
||||
|
||||
/** Broadcast sent when heap dump collection has been completed. */
|
||||
private static final String ACTION_HEAP_DUMP_FINISHED =
|
||||
"com.android.internal.intent.action.HEAP_DUMP_FINISHED";
|
||||
|
||||
/** The process we are reporting */
|
||||
static final String EXTRA_PROCESS_NAME = "com.android.internal.extra.heap_dump.PROCESS_NAME";
|
||||
|
||||
/** The size limit the process reached. */
|
||||
static final String EXTRA_SIZE_BYTES = "com.android.internal.extra.heap_dump.SIZE_BYTES";
|
||||
|
||||
/** Whether the user initiated the dump or not. */
|
||||
static final String EXTRA_IS_USER_INITIATED =
|
||||
"com.android.internal.extra.heap_dump.IS_USER_INITIATED";
|
||||
|
||||
/** Optional name of package to directly launch. */
|
||||
static final String EXTRA_REPORT_PACKAGE =
|
||||
"com.android.internal.extra.heap_dump.REPORT_PACKAGE";
|
||||
|
||||
private static final String NOTIFICATION_CHANNEL_ID = "heapdumps";
|
||||
private static final int NOTIFICATION_ID = 2019;
|
||||
|
||||
/**
|
||||
* Always keep heap dumps taken in the last week.
|
||||
*/
|
||||
private static final long MIN_KEEP_AGE_MS = DateUtils.WEEK_IN_MILLIS;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "onReceive(): " + intent);
|
||||
final String action = intent.getAction();
|
||||
if (action == null) {
|
||||
Log.e(TAG, "null action received");
|
||||
return;
|
||||
}
|
||||
switch (action) {
|
||||
case Intent.ACTION_BOOT_COMPLETED:
|
||||
cleanupOldFiles(context);
|
||||
break;
|
||||
case ACTION_DELETE_HEAP_DUMP:
|
||||
deleteHeapDump(context, intent.getStringExtra(HeapDumpActivity.KEY_URI));
|
||||
break;
|
||||
case ACTION_HEAP_DUMP_FINISHED:
|
||||
showDumpNotification(context, intent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupOldFiles(Context context) {
|
||||
final PendingResult result = goAsync();
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
Log.d(TAG, "Deleting from " + new File(context.getFilesDir(), "heapdumps"));
|
||||
FileUtils.deleteOlderFiles(new File(context.getFilesDir(), "heapdumps"), 0,
|
||||
MIN_KEEP_AGE_MS);
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Couldn't delete old files", e);
|
||||
}
|
||||
result.finish();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void deleteHeapDump(Context context, @Nullable final String uri) {
|
||||
if (uri == null) {
|
||||
Log.e(TAG, "null URI for delete heap dump intent");
|
||||
return;
|
||||
}
|
||||
final PendingResult result = goAsync();
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
context.getContentResolver().delete(Uri.parse(uri), null, null);
|
||||
result.finish();
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void showDumpNotification(Context context, Intent intent) {
|
||||
final boolean isUserInitiated = intent.getBooleanExtra(
|
||||
EXTRA_IS_USER_INITIATED, false);
|
||||
final String procName = intent.getStringExtra(EXTRA_PROCESS_NAME);
|
||||
final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
|
||||
|
||||
final String reportPackage = intent.getStringExtra(
|
||||
EXTRA_REPORT_PACKAGE);
|
||||
final long size = intent.getLongExtra(EXTRA_SIZE_BYTES, 0);
|
||||
|
||||
if (procName == null) {
|
||||
Log.e(TAG, "No process name sent over");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationManager nm = NotificationManager.from(context);
|
||||
nm.createNotificationChannel(
|
||||
new NotificationChannel(NOTIFICATION_CHANNEL_ID,
|
||||
"Heap dumps",
|
||||
NotificationManager.IMPORTANCE_DEFAULT));
|
||||
|
||||
final int titleId = isUserInitiated
|
||||
? com.android.internal.R.string.dump_heap_ready_notification
|
||||
: com.android.internal.R.string.dump_heap_notification;
|
||||
final String procDisplayName = uid == Process.SYSTEM_UID
|
||||
? context.getString(com.android.internal.R.string.android_system_label)
|
||||
: procName;
|
||||
String text = context.getString(titleId, procDisplayName);
|
||||
|
||||
Intent shareIntent = new Intent();
|
||||
shareIntent.setClassName(context, HeapDumpActivity.class.getName());
|
||||
shareIntent.putExtra(EXTRA_PROCESS_NAME, procName);
|
||||
shareIntent.putExtra(EXTRA_SIZE_BYTES, size);
|
||||
shareIntent.putExtra(EXTRA_IS_USER_INITIATED, isUserInitiated);
|
||||
shareIntent.putExtra(Intent.EXTRA_UID, uid);
|
||||
if (reportPackage != null) {
|
||||
shareIntent.putExtra(EXTRA_REPORT_PACKAGE, reportPackage);
|
||||
}
|
||||
final Notification.Builder builder = new Notification.Builder(context,
|
||||
NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(
|
||||
isTv(context) ? R.drawable.ic_bug_report_black_24dp
|
||||
: com.android.internal.R.drawable.stat_sys_adb)
|
||||
.setLocalOnly(true)
|
||||
.setColor(context.getColor(
|
||||
com.android.internal.R.color.system_notification_accent_color))
|
||||
.setContentTitle(text)
|
||||
.setTicker(text)
|
||||
.setAutoCancel(true)
|
||||
.setContentText(context.getText(
|
||||
com.android.internal.R.string.dump_heap_notification_detail))
|
||||
.setContentIntent(PendingIntent.getActivity(context, 2, shareIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
Log.v(TAG, "Creating share heap dump notification");
|
||||
NotificationManager.from(context).notify(NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user