19125068a991b24d27810b6392a562b32457b3f5dAdrian Roos/*
29125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * Copyright (C) 2017 The Android Open Source Project
39125068a991b24d27810b6392a562b32457b3f5dAdrian Roos *
49125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * Licensed under the Apache License, Version 2.0 (the "License");
59125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * you may not use this file except in compliance with the License.
69125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * You may obtain a copy of the License at
79125068a991b24d27810b6392a562b32457b3f5dAdrian Roos *
89125068a991b24d27810b6392a562b32457b3f5dAdrian Roos *      http://www.apache.org/licenses/LICENSE-2.0
99125068a991b24d27810b6392a562b32457b3f5dAdrian Roos *
109125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * Unless required by applicable law or agreed to in writing, software
119125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * distributed under the License is distributed on an "AS IS" BASIS,
129125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * See the License for the specific language governing permissions and
149125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * limitations under the License
159125068a991b24d27810b6392a562b32457b3f5dAdrian Roos */
169125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
179125068a991b24d27810b6392a562b32457b3f5dAdrian Roospackage com.android.systemui.util.leak;
189125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
199125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.app.Notification;
209125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.app.NotificationChannel;
219125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.app.NotificationManager;
229125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.app.PendingIntent;
239125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.content.ClipData;
249125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.content.Context;
259125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.content.Intent;
269125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.net.Uri;
279125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.os.Debug;
289125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.os.SystemProperties;
299125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.os.UserHandle;
309125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.support.v4.content.FileProvider;
319125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport android.util.Log;
329125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
339125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport com.google.android.collect.Lists;
349125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
359125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport java.io.File;
369125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport java.io.FileOutputStream;
379125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport java.io.IOException;
389125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport java.io.PrintWriter;
399125068a991b24d27810b6392a562b32457b3f5dAdrian Roosimport java.util.ArrayList;
409125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
419125068a991b24d27810b6392a562b32457b3f5dAdrian Roos/**
429125068a991b24d27810b6392a562b32457b3f5dAdrian Roos * Dumps data to debug leaks and posts a notification to share the data.
439125068a991b24d27810b6392a562b32457b3f5dAdrian Roos */
449125068a991b24d27810b6392a562b32457b3f5dAdrian Roospublic class LeakReporter {
459125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
469125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    static final String TAG = "LeakReporter";
479125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
489125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    public static final String FILEPROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
499125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
509125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    static final String LEAK_DIR = "leak";
519125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    static final String LEAK_HPROF = "leak.hprof";
529125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    static final String LEAK_DUMP = "leak.dump";
539125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
549125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    private final Context mContext;
559125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    private final LeakDetector mLeakDetector;
569125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    private final String mLeakReportEmail;
579125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
589125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    public LeakReporter(Context context, LeakDetector leakDetector, String leakReportEmail) {
599125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        mContext = context;
609125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        mLeakDetector = leakDetector;
619125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        mLeakReportEmail = leakReportEmail;
629125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    }
639125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
649125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    public void dumpLeak(int garbageCount) {
659125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        try {
669125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            File leakDir = new File(mContext.getCacheDir(), LEAK_DIR);
679125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            leakDir.mkdir();
689125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
699125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            File hprofFile = new File(leakDir, LEAK_HPROF);
709125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            Debug.dumpHprofData(hprofFile.getAbsolutePath());
719125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
729125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            File dumpFile = new File(leakDir, LEAK_DUMP);
739125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            try (FileOutputStream fos = new FileOutputStream(dumpFile)) {
749125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                PrintWriter w = new PrintWriter(fos);
759125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                w.print("Build: "); w.println(SystemProperties.get("ro.build.description"));
769125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                w.println();
779125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                w.flush();
789125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                mLeakDetector.dump(fos.getFD(), w, new String[0]);
799125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                w.close();
809125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            }
819125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
829125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            NotificationManager notiMan = mContext.getSystemService(NotificationManager.class);
839125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
849125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            NotificationChannel channel = new NotificationChannel("leak", "Leak Alerts",
859125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                    NotificationManager.IMPORTANCE_HIGH);
869125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            channel.enableVibration(true);
879125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
889125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            notiMan.createNotificationChannel(channel);
899125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            Notification.Builder builder = new Notification.Builder(mContext, channel.getId())
909125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                    .setAutoCancel(true)
919125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                    .setShowWhen(true)
929125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                    .setContentTitle("Memory Leak Detected")
939125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                    .setContentText(String.format(
949125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                            "SystemUI has detected %d leaked objects. Tap to send", garbageCount))
959125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
969125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                    .setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
979125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                            getIntent(hprofFile, dumpFile),
989125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                            PendingIntent.FLAG_UPDATE_CURRENT, null, UserHandle.CURRENT));
999125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            notiMan.notify(TAG, 0, builder.build());
1009125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        } catch (IOException e) {
1019125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            Log.e(TAG, "Couldn't dump heap for leak", e);
1029125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        }
1039125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    }
1049125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1059125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    private Intent getIntent(File hprofFile, File dumpFile) {
1069125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        Uri dumpUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, dumpFile);
1079125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        Uri hprofUri = FileProvider.getUriForFile(mContext, FILEPROVIDER_AUTHORITY, hprofFile);
1089125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1099125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
1109125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        String mimeType = "application/vnd.android.leakreport";
1119125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1129125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1139125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        intent.addCategory(Intent.CATEGORY_DEFAULT);
1149125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        intent.setType(mimeType);
1159125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1169125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        final String subject = "SystemUI leak report";
1179125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
1189125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1199125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
1209125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
1219125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        // create the ClipData object with the attachments URIs.
1229125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        final StringBuilder messageBody = new StringBuilder("Build info: ")
1239125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                .append(SystemProperties.get("ro.build.description"));
1249125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
1259125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        final ClipData clipData = new ClipData(null, new String[] { mimeType },
1269125068a991b24d27810b6392a562b32457b3f5dAdrian Roos                new ClipData.Item(null, null, null, dumpUri));
1279125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        final ArrayList<Uri> attachments = Lists.newArrayList(dumpUri);
1289125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1299125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        clipData.addItem(new ClipData.Item(null, null, null, hprofUri));
1309125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        attachments.add(hprofUri);
1319125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1329125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        intent.setClipData(clipData);
1339125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
1349125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1359125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        String leakReportEmail = mLeakReportEmail;
1369125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        if (leakReportEmail != null) {
1379125068a991b24d27810b6392a562b32457b3f5dAdrian Roos            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { leakReportEmail });
1389125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        }
1399125068a991b24d27810b6392a562b32457b3f5dAdrian Roos
1409125068a991b24d27810b6392a562b32457b3f5dAdrian Roos        return intent;
1419125068a991b24d27810b6392a562b32457b3f5dAdrian Roos    }
1429125068a991b24d27810b6392a562b32457b3f5dAdrian Roos}
143