Home | History | Annotate | Download | only in leak
      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 
     58     public LeakReporter(Context context, LeakDetector leakDetector, String leakReportEmail) {
     59         mContext = context;
     60         mLeakDetector = leakDetector;
     61         mLeakReportEmail = leakReportEmail;
     62     }
     63 
     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 
    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