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 | PendingIntent.FLAG_IMMUTABLE)); 184 185 Log.v(TAG, "Creating share heap dump notification"); 186 NotificationManager.from(context).notify(NOTIFICATION_ID, builder.build()); 187 } 188 } 189