blob: 858c521eaed5e8697143e6ebaa17e82ef759ddf4 [file] [log] [blame]
/*
* 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());
}
}