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 static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; 20 21 import android.app.Notification; 22 import android.app.NotificationChannel; 23 import android.app.NotificationManager; 24 import android.app.PendingIntent; 25 import android.content.ClipData; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.net.Uri; 29 import android.os.Debug; 30 import android.os.SystemProperties; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import androidx.core.content.FileProvider; 35 36 import com.android.systemui.dagger.SysUISingleton; 37 import com.android.systemui.settings.UserTracker; 38 39 import com.google.android.collect.Lists; 40 41 import java.io.File; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 import java.io.PrintWriter; 45 import java.util.ArrayList; 46 47 import javax.inject.Inject; 48 import javax.inject.Named; 49 50 /** 51 * Dumps data to debug leaks and posts a notification to share the data. 52 */ 53 @SysUISingleton 54 public class LeakReporter { 55 56 static final String TAG = "LeakReporter"; 57 58 public static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider"; 59 60 static final String LEAK_DIR = "leak"; 61 static final String LEAK_HPROF = "leak.hprof"; 62 static final String LEAK_DUMP = "leak.dump"; 63 64 private final Context mContext; 65 private final UserTracker mUserTracker; 66 private final LeakDetector mLeakDetector; 67 private final String mLeakReportEmail; 68 69 @Inject LeakReporter(Context context, UserTracker userTracker, LeakDetector leakDetector, @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail)70 public LeakReporter(Context context, UserTracker userTracker, LeakDetector leakDetector, 71 @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) { 72 mContext = context; 73 mUserTracker = userTracker; 74 mLeakDetector = leakDetector; 75 mLeakReportEmail = leakReportEmail; 76 } 77 dumpLeak(int garbageCount)78 public void dumpLeak(int garbageCount) { 79 try { 80 File leakDir = new File(mContext.getCacheDir(), LEAK_DIR); 81 leakDir.mkdir(); 82 83 File hprofFile = new File(leakDir, LEAK_HPROF); 84 Debug.dumpHprofData(hprofFile.getAbsolutePath()); 85 86 File dumpFile = new File(leakDir, LEAK_DUMP); 87 try (FileOutputStream fos = new FileOutputStream(dumpFile)) { 88 PrintWriter w = new PrintWriter(fos); 89 w.print("Build: "); w.println(SystemProperties.get("ro.build.description")); 90 w.println(); 91 w.flush(); 92 mLeakDetector.dump(w, new String[0]); 93 w.close(); 94 } 95 96 NotificationManager notiMan = mContext.getSystemService(NotificationManager.class); 97 98 NotificationChannel channel = new NotificationChannel("leak", "Leak Alerts", 99 NotificationManager.IMPORTANCE_HIGH); 100 channel.enableVibration(true); 101 102 notiMan.createNotificationChannel(channel); 103 Notification.Builder builder = new Notification.Builder(mContext, channel.getId()) 104 .setAutoCancel(true) 105 .setShowWhen(true) 106 .setContentTitle("Memory Leak Detected") 107 .setContentText(String.format( 108 "SystemUI has detected %d leaked objects. Tap to send", garbageCount)) 109 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) 110 .setContentIntent(PendingIntent.getActivityAsUser( 111 mContext, 112 0, 113 getIntent(hprofFile, dumpFile), 114 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, 115 null, 116 mUserTracker.getUserHandle())); 117 notiMan.notify(TAG, 0, builder.build()); 118 } catch (IOException e) { 119 Log.e(TAG, "Couldn't dump heap for leak", e); 120 } 121 } 122 getIntent(File hprofFile, File dumpFile)123 private Intent getIntent(File hprofFile, File dumpFile) { 124 Uri dumpUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, dumpFile); 125 Uri hprofUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, hprofFile); 126 127 Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); 128 String mimeType = "application/vnd.android.leakreport"; 129 130 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 131 intent.addCategory(Intent.CATEGORY_DEFAULT); 132 intent.setType(mimeType); 133 134 final String subject = "SystemUI leak report"; 135 intent.putExtra(Intent.EXTRA_SUBJECT, subject); 136 137 // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String. 138 // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually 139 // create the ClipData object with the attachments URIs. 140 final StringBuilder messageBody = new StringBuilder("Build info: ") 141 .append(SystemProperties.get("ro.build.description")); 142 intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString()); 143 final ClipData clipData = new ClipData(null, new String[] { mimeType }, 144 new ClipData.Item(null, null, null, dumpUri)); 145 final ArrayList<Uri> attachments = Lists.newArrayList(dumpUri); 146 147 clipData.addItem(new ClipData.Item(null, null, null, hprofUri)); 148 attachments.add(hprofUri); 149 150 intent.setClipData(clipData); 151 intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); 152 153 if (!TextUtils.isEmpty(mLeakReportEmail)) { 154 intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mLeakReportEmail }); 155 } 156 157 return intent; 158 } 159 } 160