1 /*
2  * Copyright (C) 2019 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.shell;
18 
19 import static com.android.shell.BugreportProgressService.isTv;
20 
21 import android.annotation.Nullable;
22 import android.app.Notification;
23 import android.app.NotificationChannel;
24 import android.app.NotificationManager;
25 import android.app.PendingIntent;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.net.Uri;
30 import android.os.AsyncTask;
31 import android.os.FileUtils;
32 import android.os.Process;
33 import android.text.format.DateUtils;
34 import android.util.Log;
35 
36 import java.io.File;
37 
38 /**
39  * Receiver that handles finished heap dumps.
40  */
41 public class HeapDumpReceiver extends BroadcastReceiver {
42     private static final String TAG = "HeapDumpReceiver";
43 
44     /**
45      * Broadcast action to determine when to delete a specific dump heap. Must include a {@link
46      * HeapDumpActivity#KEY_URI} String extra.
47      */
48     static final String ACTION_DELETE_HEAP_DUMP = "com.android.shell.action.DELETE_HEAP_DUMP";
49 
50     /** Broadcast sent when heap dump collection has been completed. */
51     private static final String ACTION_HEAP_DUMP_FINISHED =
52             "com.android.internal.intent.action.HEAP_DUMP_FINISHED";
53 
54     /** The process we are reporting */
55     static final String EXTRA_PROCESS_NAME = "com.android.internal.extra.heap_dump.PROCESS_NAME";
56 
57     /** The size limit the process reached. */
58     static final String EXTRA_SIZE_BYTES = "com.android.internal.extra.heap_dump.SIZE_BYTES";
59 
60     /** Whether the user initiated the dump or not. */
61     static final String EXTRA_IS_USER_INITIATED =
62             "com.android.internal.extra.heap_dump.IS_USER_INITIATED";
63 
64     /** Optional name of package to directly launch. */
65     static final String EXTRA_REPORT_PACKAGE =
66             "com.android.internal.extra.heap_dump.REPORT_PACKAGE";
67 
68     private static final String NOTIFICATION_CHANNEL_ID = "heapdumps";
69     private static final int NOTIFICATION_ID = 2019;
70 
71     /**
72      * Always keep heap dumps taken in the last week.
73      */
74     private static final long MIN_KEEP_AGE_MS = DateUtils.WEEK_IN_MILLIS;
75 
76     @Override
onReceive(Context context, Intent intent)77     public void onReceive(Context context, Intent intent) {
78         Log.d(TAG, "onReceive(): " + intent);
79         final String action = intent.getAction();
80         if (action == null) {
81             Log.e(TAG, "null action received");
82             return;
83         }
84         switch (action) {
85             case Intent.ACTION_BOOT_COMPLETED:
86                 cleanupOldFiles(context);
87                 break;
88             case ACTION_DELETE_HEAP_DUMP:
89                 deleteHeapDump(context, intent.getStringExtra(HeapDumpActivity.KEY_URI));
90                 break;
91             case ACTION_HEAP_DUMP_FINISHED:
92                 showDumpNotification(context, intent);
93                 break;
94         }
95     }
96 
cleanupOldFiles(Context context)97     private void cleanupOldFiles(Context context) {
98         final PendingResult result = goAsync();
99         new AsyncTask<Void, Void, Void>() {
100             @Override
101             protected Void doInBackground(Void... params) {
102                 try {
103                     Log.d(TAG, "Deleting from " + new File(context.getFilesDir(), "heapdumps"));
104                     FileUtils.deleteOlderFiles(new File(context.getFilesDir(), "heapdumps"), 0,
105                             MIN_KEEP_AGE_MS);
106                 } catch (RuntimeException e) {
107                     Log.e(TAG, "Couldn't delete old files", e);
108                 }
109                 result.finish();
110                 return null;
111             }
112         }.execute();
113     }
114 
deleteHeapDump(Context context, @Nullable final String uri)115     private void deleteHeapDump(Context context, @Nullable final String uri) {
116         if (uri == null) {
117             Log.e(TAG, "null URI for delete heap dump intent");
118             return;
119         }
120         final PendingResult result = goAsync();
121         new AsyncTask<Void, Void, Void>() {
122             @Override
123             protected Void doInBackground(Void... params) {
124                 context.getContentResolver().delete(Uri.parse(uri), null, null);
125                 result.finish();
126                 return null;
127             }
128         }.execute();
129     }
130 
showDumpNotification(Context context, Intent intent)131     private void showDumpNotification(Context context, Intent intent) {
132         final boolean isUserInitiated = intent.getBooleanExtra(
133                 EXTRA_IS_USER_INITIATED, false);
134         final String procName = intent.getStringExtra(EXTRA_PROCESS_NAME);
135         final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
136 
137         final String reportPackage = intent.getStringExtra(
138                 EXTRA_REPORT_PACKAGE);
139         final long size = intent.getLongExtra(EXTRA_SIZE_BYTES, 0);
140 
141         if (procName == null) {
142             Log.e(TAG, "No process name sent over");
143             return;
144         }
145 
146         NotificationManager nm = NotificationManager.from(context);
147         nm.createNotificationChannel(
148                 new NotificationChannel(NOTIFICATION_CHANNEL_ID,
149                         "Heap dumps",
150                         NotificationManager.IMPORTANCE_DEFAULT));
151 
152         final int titleId = isUserInitiated
153                 ? com.android.internal.R.string.dump_heap_ready_notification
154                 : com.android.internal.R.string.dump_heap_notification;
155         final String procDisplayName = uid == Process.SYSTEM_UID
156                 ? context.getString(com.android.internal.R.string.android_system_label)
157                 : procName;
158         String text = context.getString(titleId, procDisplayName);
159 
160         Intent shareIntent = new Intent();
161         shareIntent.setClassName(context, HeapDumpActivity.class.getName());
162         shareIntent.putExtra(EXTRA_PROCESS_NAME, procName);
163         shareIntent.putExtra(EXTRA_SIZE_BYTES, size);
164         shareIntent.putExtra(EXTRA_IS_USER_INITIATED, isUserInitiated);
165         shareIntent.putExtra(Intent.EXTRA_UID, uid);
166         if (reportPackage != null) {
167             shareIntent.putExtra(EXTRA_REPORT_PACKAGE, reportPackage);
168         }
169         final Notification.Builder builder = new Notification.Builder(context,
170                 NOTIFICATION_CHANNEL_ID)
171                 .setSmallIcon(
172                         isTv(context) ? R.drawable.ic_bug_report_black_24dp
173                                 : com.android.internal.R.drawable.stat_sys_adb)
174                 .setLocalOnly(true)
175                 .setColor(context.getColor(
176                         com.android.internal.R.color.system_notification_accent_color))
177                 .setContentTitle(text)
178                 .setTicker(text)
179                 .setAutoCancel(true)
180                 .setContentText(context.getText(
181                         com.android.internal.R.string.dump_heap_notification_detail))
182                 .setContentIntent(PendingIntent.getActivity(context, 2, shareIntent,
183                         PendingIntent.FLAG_UPDATE_CURRENT));
184 
185         Log.v(TAG, "Creating share heap dump notification");
186         NotificationManager.from(context).notify(NOTIFICATION_ID, builder.build());
187     }
188 }
189