/* * 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 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.support.v4.content.FileProvider; import android.util.Log; 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; /** * Dumps data to debug leaks and posts a notification to share the data. */ 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; public LeakReporter(Context context, LeakDetector leakDetector, 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 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; } }