Merge "Move heap dump sharing to SHELL." am: c29b5cba29 am: 2cdf2d5ad7

am: 215d92f880

Change-Id: Ic092f2d5c0c0a1497c19e73417c68b8e8616cc63
This commit is contained in:
Kweku Adams
2019-10-30 21:15:56 -07:00
committed by android-build-merger
5 changed files with 458 additions and 2 deletions

View File

@@ -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"/>

View File

@@ -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);
}

View 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();
}
}
}

View 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_.]", "");
}
}

View 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());
}
}