1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.util.leak; 18 19 import android.app.Notification; 20 import android.app.NotificationChannel; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.ClipData; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.net.Uri; 27 import android.os.Debug; 28 import android.os.SystemProperties; 29 import android.os.UserHandle; 30 import android.support.v4.content.FileProvider; 31 import android.util.Log; 32 33 import com.google.android.collect.Lists; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.PrintWriter; 39 import java.util.ArrayList; 40 41 /** 42 * Dumps data to debug leaks and posts a notification to share the data. 43 */ 44 public class LeakReporter { 45 46 static final String TAG = "LeakReporter"; 47 48 public static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider"; 49 50 static final String LEAK_DIR = "leak"; 51 static final String LEAK_HPROF = "leak.hprof"; 52 static final String LEAK_DUMP = "leak.dump"; 53 54 private final Context mContext; 55 private final LeakDetector mLeakDetector; 56 private final String mLeakReportEmail; 57 LeakReporter(Context context, LeakDetector leakDetector, String leakReportEmail)58 public LeakReporter(Context context, LeakDetector leakDetector, String leakReportEmail) { 59 mContext = context; 60 mLeakDetector = leakDetector; 61 mLeakReportEmail = leakReportEmail; 62 } 63 dumpLeak(int garbageCount)64 public void dumpLeak(int garbageCount) { 65 try { 66 File leakDir = new File(mContext.getCacheDir(), LEAK_DIR); 67 leakDir.mkdir(); 68 69 File hprofFile = new File(leakDir, LEAK_HPROF); 70 Debug.dumpHprofData(hprofFile.getAbsolutePath()); 71 72 File dumpFile = new File(leakDir, LEAK_DUMP); 73 try (FileOutputStream fos = new FileOutputStream(dumpFile)) { 74 PrintWriter w = new PrintWriter(fos); 75 w.print("Build: "); w.println(SystemProperties.get("ro.build.description")); 76 w.println(); 77 w.flush(); 78 mLeakDetector.dump(fos.getFD(), w, new String[0]); 79 w.close(); 80 } 81 82 NotificationManager notiMan = mContext.getSystemService(NotificationManager.class); 83 84 NotificationChannel channel = new NotificationChannel("leak", "Leak Alerts", 85 NotificationManager.IMPORTANCE_HIGH); 86 channel.enableVibration(true); 87 88 notiMan.createNotificationChannel(channel); 89 Notification.Builder builder = new Notification.Builder(mContext, channel.getId()) 90 .setAutoCancel(true) 91 .setShowWhen(true) 92 .setContentTitle("Memory Leak Detected") 93 .setContentText(String.format( 94 "SystemUI has detected %d leaked objects. Tap to send", garbageCount)) 95 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) 96 .setContentIntent(PendingIntent.getActivityAsUser(mContext, 0, 97 getIntent(hprofFile, dumpFile), 98 PendingIntent.FLAG_UPDATE_CURRENT, null, UserHandle.CURRENT)); 99 notiMan.notify(TAG, 0, builder.build()); 100 } catch (IOException e) { 101 Log.e(TAG, "Couldn't dump heap for leak", e); 102 } 103 } 104 getIntent(File hprofFile, File dumpFile)105 private Intent getIntent(File hprofFile, File dumpFile) { 106 Uri dumpUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, dumpFile); 107 Uri hprofUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, hprofFile); 108 109 Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); 110 String mimeType = "application/vnd.android.leakreport"; 111 112 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 113 intent.addCategory(Intent.CATEGORY_DEFAULT); 114 intent.setType(mimeType); 115 116 final String subject = "SystemUI leak report"; 117 intent.putExtra(Intent.EXTRA_SUBJECT, subject); 118 119 // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String. 120 // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually 121 // create the ClipData object with the attachments URIs. 122 final StringBuilder messageBody = new StringBuilder("Build info: ") 123 .append(SystemProperties.get("ro.build.description")); 124 intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString()); 125 final ClipData clipData = new ClipData(null, new String[] { mimeType }, 126 new ClipData.Item(null, null, null, dumpUri)); 127 final ArrayList<Uri> attachments = Lists.newArrayList(dumpUri); 128 129 clipData.addItem(new ClipData.Item(null, null, null, hprofUri)); 130 attachments.add(hprofUri); 131 132 intent.setClipData(clipData); 133 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); 134 135 String leakReportEmail = mLeakReportEmail; 136 if (leakReportEmail != null) { 137 intent.putExtra(Intent.EXTRA_EMAIL, new String[] { leakReportEmail }); 138 } 139 140 return intent; 141 } 142 } 143