blob: b25df5f9c07f7329aca2fc7b56b7864265e48580 [file] [log] [blame]
/*
* Copyright (C) 2017 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.systemui.util.leak;
import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Debug;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Log;
import androidx.core.content.FileProvider;
import com.google.android.collect.Lists;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
/**
* Dumps data to debug leaks and posts a notification to share the data.
*/
@Singleton
public class LeakReporter {
static final String TAG = "LeakReporter";
public static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
static final String LEAK_DIR = "leak";
static final String LEAK_HPROF = "leak.hprof";
static final String LEAK_DUMP = "leak.dump";
private final Context mContext;
private final LeakDetector mLeakDetector;
private final String mLeakReportEmail;
@Inject
public LeakReporter(Context context, LeakDetector leakDetector,
@Nullable @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) {
mContext = context;
mLeakDetector = leakDetector;
mLeakReportEmail = leakReportEmail;
}
public void dumpLeak(int garbageCount) {
try {
File leakDir = new File(mContext.getCacheDir(), LEAK_DIR);
leakDir.mkdir();
File hprofFile = new File(leakDir, LEAK_HPROF);
Debug.dumpHprofData(hprofFile.getAbsolutePath());
File dumpFile = new File(leakDir, LEAK_DUMP);
try (FileOutputStream fos = new FileOutputStream(dumpFile)) {
PrintWriter w = new PrintWriter(fos);
w.print("Build: "); w.println(SystemProperties.get("ro.build.description"));
w.println();
w.flush();
mLeakDetector.dump(fos.getFD(), w, new String[0]);
w.close();
}
NotificationManager notiMan = mContext.getSystemService(NotificationManager.class);
NotificationChannel channel = new NotificationChannel("leak", "Leak Alerts",
NotificationManager.IMPORTANCE_HIGH);
channel.enableVibration(true);
notiMan.createNotificationChannel(channel);
Notification.Builder builder = new Notification.Builder(mContext, channel.getId())
.setAutoCancel(true)
.setShowWhen(true)
.setContentTitle("Memory Leak Detected")
.setContentText(String.format(
"SystemUI has detected %d leaked objects. Tap to send", garbageCount))
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
.setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
getIntent(hprofFile, dumpFile),
PendingIntent.FLAG_UPDATE_CURRENT, null, UserHandle.CURRENT));
notiMan.notify(TAG, 0, builder.build());
} catch (IOException e) {
Log.e(TAG, "Couldn't dump heap for leak", e);
}
}
private Intent getIntent(File hprofFile, File dumpFile) {
Uri dumpUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, dumpFile);
Uri hprofUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, hprofFile);
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
String mimeType = "application/vnd.android.leakreport";
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType(mimeType);
final String subject = "SystemUI leak report";
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
// So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
// create the ClipData object with the attachments URIs.
final StringBuilder messageBody = new StringBuilder("Build info: ")
.append(SystemProperties.get("ro.build.description"));
intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
final ClipData clipData = new ClipData(null, new String[] { mimeType },
new ClipData.Item(null, null, null, dumpUri));
final ArrayList<Uri> attachments = Lists.newArrayList(dumpUri);
clipData.addItem(new ClipData.Item(null, null, null, hprofUri));
attachments.add(hprofUri);
intent.setClipData(clipData);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
String leakReportEmail = mLeakReportEmail;
if (leakReportEmail != null) {
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { leakReportEmail });
}
return intent;
}
}