Loading packages/Shell/AndroidManifest.xml +26 −1 Original line number Diff line number Diff line Loading @@ -173,6 +173,8 @@ <uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" /> <!-- Permission needed to invoke DynamicSystem (AOT) --> <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" /> <!-- Used to clean up heap dumps on boot. --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> Loading Loading @@ -231,12 +233,25 @@ </intent-filter> </provider> <provider android:name=".HeapDumpProvider" android:authorities="com.android.shell.heapdump" android:grantUriPermissions="true" android:exported="true" /> <activity android:name=".BugreportWarningActivity" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" android:exported="false" /> <activity android:name=".HeapDumpActivity" android:theme="@*android:style/Theme.Translucent.NoTitleBar" android:label="@*android:string/dump_heap_title" android:finishOnCloseSystemDialogs="true" android:noHistory="true" android:excludeFromRecents="true" android:exported="false" /> <receiver android:name=".BugreportReceiver" android:permission="android.permission.DUMP"> Loading @@ -254,6 +269,16 @@ </intent-filter> </receiver> <receiver android:name=".HeapDumpReceiver" android:permission="android.permission.DUMP"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="com.android.internal.intent.action.HEAP_DUMP_FINISHED" /> <action android:name="com.android.shell.action.DELETE_HEAP_DUMP" /> </intent-filter> </receiver> <service android:name=".BugreportProgressService" android:exported="false"/> Loading packages/Shell/src/com/android/shell/BugreportProgressService.java +1 −1 Original line number Diff line number Diff line Loading @@ -1410,7 +1410,7 @@ public class BugreportProgressService extends Service { return false; } private static boolean isTv(Context context) { static boolean isTv(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); } Loading packages/Shell/src/com/android/shell/HeapDumpActivity.java 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.shell; import static com.android.shell.HeapDumpProvider.makeUri; import static com.android.shell.HeapDumpReceiver.ACTION_DELETE_HEAP_DUMP; import static com.android.shell.HeapDumpReceiver.EXTRA_IS_USER_INITIATED; import static com.android.shell.HeapDumpReceiver.EXTRA_PROCESS_NAME; import static com.android.shell.HeapDumpReceiver.EXTRA_REPORT_PACKAGE; import static com.android.shell.HeapDumpReceiver.EXTRA_SIZE_BYTES; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.util.DebugUtils; import android.util.Log; import com.android.internal.R; /** * This activity is displayed when the system has collected a heap dump. */ public class HeapDumpActivity extends Activity { private static final String TAG = "HeapDumpActivity"; static final String KEY_URI = "uri"; private AlertDialog mDialog; private Uri mDumpUri; private boolean mHandled = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String process = getIntent().getStringExtra(EXTRA_PROCESS_NAME); long size = getIntent().getLongExtra(EXTRA_SIZE_BYTES, 0); final boolean isUserInitiated = getIntent().getBooleanExtra(EXTRA_IS_USER_INITIATED, false); final int uid = getIntent().getIntExtra(Intent.EXTRA_UID, 0); final boolean isSystemProcess = uid == Process.SYSTEM_UID; mDumpUri = makeUri(process); final String procDisplayName = isSystemProcess ? getString(com.android.internal.R.string.android_system_label) : process; final Intent sendIntent = new Intent(); ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", mDumpUri); sendIntent.setClipData(clip); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); sendIntent.setType(clip.getDescription().getMimeType(0)); sendIntent.putExtra(Intent.EXTRA_STREAM, mDumpUri); String directLaunchPackage = getIntent().getStringExtra(EXTRA_REPORT_PACKAGE); if (directLaunchPackage != null) { sendIntent.setAction(ActivityManager.ACTION_REPORT_HEAP_LIMIT); sendIntent.setPackage(directLaunchPackage); try { startActivity(sendIntent); mHandled = true; finish(); return; } catch (ActivityNotFoundException e) { Log.e(TAG, "Unable to direct launch to " + directLaunchPackage, e); } } final int messageId; if (isUserInitiated) { messageId = com.android.internal.R.string.dump_heap_ready_text; } else if (isSystemProcess) { messageId = com.android.internal.R.string.dump_heap_system_text; } else { messageId = com.android.internal.R.string.dump_heap_text; } mDialog = new AlertDialog.Builder(this, android.R.style.Theme_Material_Light_Dialog_Alert) .setTitle(com.android.internal.R.string.dump_heap_title) .setMessage(getString(messageId, procDisplayName, DebugUtils.sizeValueToString(size, null))) .setNegativeButton(android.R.string.cancel, (dialog, which) -> { mHandled = true; finish(); }) .setNeutralButton(R.string.delete, (dialog, which) -> { mHandled = true; Intent deleteIntent = new Intent(ACTION_DELETE_HEAP_DUMP); deleteIntent.setClass(getApplicationContext(), HeapDumpReceiver.class); deleteIntent.putExtra(KEY_URI, mDumpUri.toString()); sendBroadcast(deleteIntent); finish(); }) .setPositiveButton(android.R.string.ok, (dialog, which) -> { mHandled = true; sendIntent.setAction(Intent.ACTION_SEND); sendIntent.setPackage(null); startActivity(Intent.createChooser(sendIntent, getText(com.android.internal.R.string.dump_heap_title))); finish(); }) .show(); } @Override protected void onStop() { super.onStop(); if (!isChangingConfigurations()) { if (!mHandled) { Intent deleteIntent = new Intent(ACTION_DELETE_HEAP_DUMP); deleteIntent.setClass(getApplicationContext(), HeapDumpReceiver.class); deleteIntent.putExtra(KEY_URI, mDumpUri.toString()); sendBroadcast(deleteIntent); } } } @Override protected void onDestroy() { super.onDestroy(); if (mDialog != null) { mDialog.dismiss(); } } } packages/Shell/src/com/android/shell/HeapDumpProvider.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.shell; import android.annotation.NonNull; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.Process; import java.io.File; import java.io.FileNotFoundException; /** ContentProvider to write and access heap dumps. */ public class HeapDumpProvider extends ContentProvider { private static final String FILENAME_SUFFIX = "_javaheap.bin"; private static final Object sLock = new Object(); private File mRoot; @Override public boolean onCreate() { synchronized (sLock) { mRoot = new File(getContext().createCredentialProtectedStorageContext().getFilesDir(), "heapdumps"); return mRoot.mkdir(); } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return "application/octet-stream"; } @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("Insert not allowed."); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { String path = sanitizePath(uri.getEncodedPath()); String tag = Uri.decode(path); return (new File(mRoot, tag)).delete() ? 1 : 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Update not allowed."); } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { String path = sanitizePath(uri.getEncodedPath()); String tag = Uri.decode(path); final int pMode; if (Binder.getCallingUid() == Process.SYSTEM_UID) { pMode = ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE | ParcelFileDescriptor.MODE_WRITE_ONLY; } else { pMode = ParcelFileDescriptor.MODE_READ_ONLY; } synchronized (sLock) { return ParcelFileDescriptor.open(new File(mRoot, tag), pMode); } } @NonNull static Uri makeUri(@NonNull String procName) { return Uri.parse("content://com.android.shell.heapdump/" + procName + FILENAME_SUFFIX); } private String sanitizePath(String path) { return path.replaceAll("[^a-zA-Z0-9_.]", ""); } } packages/Shell/src/com/android/shell/HeapDumpReceiver.java 0 → 100644 +188 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.shell; import static com.android.shell.BugreportProgressService.isTv; import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.FileUtils; import android.os.Process; import android.text.format.DateUtils; import android.util.Log; import java.io.File; /** * Receiver that handles finished heap dumps. */ public class HeapDumpReceiver extends BroadcastReceiver { private static final String TAG = "HeapDumpReceiver"; /** * Broadcast action to determine when to delete a specific dump heap. Must include a {@link * HeapDumpActivity#KEY_URI} String extra. */ static final String ACTION_DELETE_HEAP_DUMP = "com.android.shell.action.DELETE_HEAP_DUMP"; /** Broadcast sent when heap dump collection has been completed. */ private static final String ACTION_HEAP_DUMP_FINISHED = "com.android.internal.intent.action.HEAP_DUMP_FINISHED"; /** The process we are reporting */ static final String EXTRA_PROCESS_NAME = "com.android.internal.extra.heap_dump.PROCESS_NAME"; /** The size limit the process reached. */ static final String EXTRA_SIZE_BYTES = "com.android.internal.extra.heap_dump.SIZE_BYTES"; /** Whether the user initiated the dump or not. */ static final String EXTRA_IS_USER_INITIATED = "com.android.internal.extra.heap_dump.IS_USER_INITIATED"; /** Optional name of package to directly launch. */ static final String EXTRA_REPORT_PACKAGE = "com.android.internal.extra.heap_dump.REPORT_PACKAGE"; private static final String NOTIFICATION_CHANNEL_ID = "heapdumps"; private static final int NOTIFICATION_ID = 2019; /** * Always keep heap dumps taken in the last week. */ private static final long MIN_KEEP_AGE_MS = DateUtils.WEEK_IN_MILLIS; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive(): " + intent); final String action = intent.getAction(); if (action == null) { Log.e(TAG, "null action received"); return; } switch (action) { case Intent.ACTION_BOOT_COMPLETED: cleanupOldFiles(context); break; case ACTION_DELETE_HEAP_DUMP: deleteHeapDump(context, intent.getStringExtra(HeapDumpActivity.KEY_URI)); break; case ACTION_HEAP_DUMP_FINISHED: showDumpNotification(context, intent); break; } } private void cleanupOldFiles(Context context) { final PendingResult result = goAsync(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Log.d(TAG, "Deleting from " + new File(context.getFilesDir(), "heapdumps")); FileUtils.deleteOlderFiles(new File(context.getFilesDir(), "heapdumps"), 0, MIN_KEEP_AGE_MS); } catch (RuntimeException e) { Log.e(TAG, "Couldn't delete old files", e); } result.finish(); return null; } }.execute(); } private void deleteHeapDump(Context context, @Nullable final String uri) { if (uri == null) { Log.e(TAG, "null URI for delete heap dump intent"); return; } final PendingResult result = goAsync(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { context.getContentResolver().delete(Uri.parse(uri), null, null); result.finish(); return null; } }.execute(); } private void showDumpNotification(Context context, Intent intent) { final boolean isUserInitiated = intent.getBooleanExtra( EXTRA_IS_USER_INITIATED, false); final String procName = intent.getStringExtra(EXTRA_PROCESS_NAME); final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0); final String reportPackage = intent.getStringExtra( EXTRA_REPORT_PACKAGE); final long size = intent.getLongExtra(EXTRA_SIZE_BYTES, 0); if (procName == null) { Log.e(TAG, "No process name sent over"); return; } NotificationManager nm = NotificationManager.from(context); nm.createNotificationChannel( new NotificationChannel(NOTIFICATION_CHANNEL_ID, "Heap dumps", NotificationManager.IMPORTANCE_DEFAULT)); final int titleId = isUserInitiated ? com.android.internal.R.string.dump_heap_ready_notification : com.android.internal.R.string.dump_heap_notification; final String procDisplayName = uid == Process.SYSTEM_UID ? context.getString(com.android.internal.R.string.android_system_label) : procName; String text = context.getString(titleId, procDisplayName); Intent shareIntent = new Intent(); shareIntent.setClassName(context, HeapDumpActivity.class.getName()); shareIntent.putExtra(EXTRA_PROCESS_NAME, procName); shareIntent.putExtra(EXTRA_SIZE_BYTES, size); shareIntent.putExtra(EXTRA_IS_USER_INITIATED, isUserInitiated); shareIntent.putExtra(Intent.EXTRA_UID, uid); if (reportPackage != null) { shareIntent.putExtra(EXTRA_REPORT_PACKAGE, reportPackage); } final Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID) .setSmallIcon( isTv(context) ? R.drawable.ic_bug_report_black_24dp : com.android.internal.R.drawable.stat_sys_adb) .setLocalOnly(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)) .setContentTitle(text) .setTicker(text) .setAutoCancel(true) .setContentText(context.getText( com.android.internal.R.string.dump_heap_notification_detail)) .setContentIntent(PendingIntent.getActivity(context, 2, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT)); Log.v(TAG, "Creating share heap dump notification"); NotificationManager.from(context).notify(NOTIFICATION_ID, builder.build()); } } Loading
packages/Shell/AndroidManifest.xml +26 −1 Original line number Diff line number Diff line Loading @@ -173,6 +173,8 @@ <uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" /> <!-- Permission needed to invoke DynamicSystem (AOT) --> <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" /> <!-- Used to clean up heap dumps on boot. --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> Loading Loading @@ -231,12 +233,25 @@ </intent-filter> </provider> <provider android:name=".HeapDumpProvider" android:authorities="com.android.shell.heapdump" android:grantUriPermissions="true" android:exported="true" /> <activity android:name=".BugreportWarningActivity" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" android:exported="false" /> <activity android:name=".HeapDumpActivity" android:theme="@*android:style/Theme.Translucent.NoTitleBar" android:label="@*android:string/dump_heap_title" android:finishOnCloseSystemDialogs="true" android:noHistory="true" android:excludeFromRecents="true" android:exported="false" /> <receiver android:name=".BugreportReceiver" android:permission="android.permission.DUMP"> Loading @@ -254,6 +269,16 @@ </intent-filter> </receiver> <receiver android:name=".HeapDumpReceiver" android:permission="android.permission.DUMP"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="com.android.internal.intent.action.HEAP_DUMP_FINISHED" /> <action android:name="com.android.shell.action.DELETE_HEAP_DUMP" /> </intent-filter> </receiver> <service android:name=".BugreportProgressService" android:exported="false"/> Loading
packages/Shell/src/com/android/shell/BugreportProgressService.java +1 −1 Original line number Diff line number Diff line Loading @@ -1410,7 +1410,7 @@ public class BugreportProgressService extends Service { return false; } private static boolean isTv(Context context) { static boolean isTv(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); } Loading
packages/Shell/src/com/android/shell/HeapDumpActivity.java 0 → 100644 +142 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.shell; import static com.android.shell.HeapDumpProvider.makeUri; import static com.android.shell.HeapDumpReceiver.ACTION_DELETE_HEAP_DUMP; import static com.android.shell.HeapDumpReceiver.EXTRA_IS_USER_INITIATED; import static com.android.shell.HeapDumpReceiver.EXTRA_PROCESS_NAME; import static com.android.shell.HeapDumpReceiver.EXTRA_REPORT_PACKAGE; import static com.android.shell.HeapDumpReceiver.EXTRA_SIZE_BYTES; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Process; import android.util.DebugUtils; import android.util.Log; import com.android.internal.R; /** * This activity is displayed when the system has collected a heap dump. */ public class HeapDumpActivity extends Activity { private static final String TAG = "HeapDumpActivity"; static final String KEY_URI = "uri"; private AlertDialog mDialog; private Uri mDumpUri; private boolean mHandled = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String process = getIntent().getStringExtra(EXTRA_PROCESS_NAME); long size = getIntent().getLongExtra(EXTRA_SIZE_BYTES, 0); final boolean isUserInitiated = getIntent().getBooleanExtra(EXTRA_IS_USER_INITIATED, false); final int uid = getIntent().getIntExtra(Intent.EXTRA_UID, 0); final boolean isSystemProcess = uid == Process.SYSTEM_UID; mDumpUri = makeUri(process); final String procDisplayName = isSystemProcess ? getString(com.android.internal.R.string.android_system_label) : process; final Intent sendIntent = new Intent(); ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", mDumpUri); sendIntent.setClipData(clip); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); sendIntent.setType(clip.getDescription().getMimeType(0)); sendIntent.putExtra(Intent.EXTRA_STREAM, mDumpUri); String directLaunchPackage = getIntent().getStringExtra(EXTRA_REPORT_PACKAGE); if (directLaunchPackage != null) { sendIntent.setAction(ActivityManager.ACTION_REPORT_HEAP_LIMIT); sendIntent.setPackage(directLaunchPackage); try { startActivity(sendIntent); mHandled = true; finish(); return; } catch (ActivityNotFoundException e) { Log.e(TAG, "Unable to direct launch to " + directLaunchPackage, e); } } final int messageId; if (isUserInitiated) { messageId = com.android.internal.R.string.dump_heap_ready_text; } else if (isSystemProcess) { messageId = com.android.internal.R.string.dump_heap_system_text; } else { messageId = com.android.internal.R.string.dump_heap_text; } mDialog = new AlertDialog.Builder(this, android.R.style.Theme_Material_Light_Dialog_Alert) .setTitle(com.android.internal.R.string.dump_heap_title) .setMessage(getString(messageId, procDisplayName, DebugUtils.sizeValueToString(size, null))) .setNegativeButton(android.R.string.cancel, (dialog, which) -> { mHandled = true; finish(); }) .setNeutralButton(R.string.delete, (dialog, which) -> { mHandled = true; Intent deleteIntent = new Intent(ACTION_DELETE_HEAP_DUMP); deleteIntent.setClass(getApplicationContext(), HeapDumpReceiver.class); deleteIntent.putExtra(KEY_URI, mDumpUri.toString()); sendBroadcast(deleteIntent); finish(); }) .setPositiveButton(android.R.string.ok, (dialog, which) -> { mHandled = true; sendIntent.setAction(Intent.ACTION_SEND); sendIntent.setPackage(null); startActivity(Intent.createChooser(sendIntent, getText(com.android.internal.R.string.dump_heap_title))); finish(); }) .show(); } @Override protected void onStop() { super.onStop(); if (!isChangingConfigurations()) { if (!mHandled) { Intent deleteIntent = new Intent(ACTION_DELETE_HEAP_DUMP); deleteIntent.setClass(getApplicationContext(), HeapDumpReceiver.class); deleteIntent.putExtra(KEY_URI, mDumpUri.toString()); sendBroadcast(deleteIntent); } } } @Override protected void onDestroy() { super.onDestroy(); if (mDialog != null) { mDialog.dismiss(); } } }
packages/Shell/src/com/android/shell/HeapDumpProvider.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.shell; import android.annotation.NonNull; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.Process; import java.io.File; import java.io.FileNotFoundException; /** ContentProvider to write and access heap dumps. */ public class HeapDumpProvider extends ContentProvider { private static final String FILENAME_SUFFIX = "_javaheap.bin"; private static final Object sLock = new Object(); private File mRoot; @Override public boolean onCreate() { synchronized (sLock) { mRoot = new File(getContext().createCredentialProtectedStorageContext().getFilesDir(), "heapdumps"); return mRoot.mkdir(); } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return "application/octet-stream"; } @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("Insert not allowed."); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { String path = sanitizePath(uri.getEncodedPath()); String tag = Uri.decode(path); return (new File(mRoot, tag)).delete() ? 1 : 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Update not allowed."); } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { String path = sanitizePath(uri.getEncodedPath()); String tag = Uri.decode(path); final int pMode; if (Binder.getCallingUid() == Process.SYSTEM_UID) { pMode = ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE | ParcelFileDescriptor.MODE_WRITE_ONLY; } else { pMode = ParcelFileDescriptor.MODE_READ_ONLY; } synchronized (sLock) { return ParcelFileDescriptor.open(new File(mRoot, tag), pMode); } } @NonNull static Uri makeUri(@NonNull String procName) { return Uri.parse("content://com.android.shell.heapdump/" + procName + FILENAME_SUFFIX); } private String sanitizePath(String path) { return path.replaceAll("[^a-zA-Z0-9_.]", ""); } }
packages/Shell/src/com/android/shell/HeapDumpReceiver.java 0 → 100644 +188 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.shell; import static com.android.shell.BugreportProgressService.isTv; import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.FileUtils; import android.os.Process; import android.text.format.DateUtils; import android.util.Log; import java.io.File; /** * Receiver that handles finished heap dumps. */ public class HeapDumpReceiver extends BroadcastReceiver { private static final String TAG = "HeapDumpReceiver"; /** * Broadcast action to determine when to delete a specific dump heap. Must include a {@link * HeapDumpActivity#KEY_URI} String extra. */ static final String ACTION_DELETE_HEAP_DUMP = "com.android.shell.action.DELETE_HEAP_DUMP"; /** Broadcast sent when heap dump collection has been completed. */ private static final String ACTION_HEAP_DUMP_FINISHED = "com.android.internal.intent.action.HEAP_DUMP_FINISHED"; /** The process we are reporting */ static final String EXTRA_PROCESS_NAME = "com.android.internal.extra.heap_dump.PROCESS_NAME"; /** The size limit the process reached. */ static final String EXTRA_SIZE_BYTES = "com.android.internal.extra.heap_dump.SIZE_BYTES"; /** Whether the user initiated the dump or not. */ static final String EXTRA_IS_USER_INITIATED = "com.android.internal.extra.heap_dump.IS_USER_INITIATED"; /** Optional name of package to directly launch. */ static final String EXTRA_REPORT_PACKAGE = "com.android.internal.extra.heap_dump.REPORT_PACKAGE"; private static final String NOTIFICATION_CHANNEL_ID = "heapdumps"; private static final int NOTIFICATION_ID = 2019; /** * Always keep heap dumps taken in the last week. */ private static final long MIN_KEEP_AGE_MS = DateUtils.WEEK_IN_MILLIS; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive(): " + intent); final String action = intent.getAction(); if (action == null) { Log.e(TAG, "null action received"); return; } switch (action) { case Intent.ACTION_BOOT_COMPLETED: cleanupOldFiles(context); break; case ACTION_DELETE_HEAP_DUMP: deleteHeapDump(context, intent.getStringExtra(HeapDumpActivity.KEY_URI)); break; case ACTION_HEAP_DUMP_FINISHED: showDumpNotification(context, intent); break; } } private void cleanupOldFiles(Context context) { final PendingResult result = goAsync(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Log.d(TAG, "Deleting from " + new File(context.getFilesDir(), "heapdumps")); FileUtils.deleteOlderFiles(new File(context.getFilesDir(), "heapdumps"), 0, MIN_KEEP_AGE_MS); } catch (RuntimeException e) { Log.e(TAG, "Couldn't delete old files", e); } result.finish(); return null; } }.execute(); } private void deleteHeapDump(Context context, @Nullable final String uri) { if (uri == null) { Log.e(TAG, "null URI for delete heap dump intent"); return; } final PendingResult result = goAsync(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { context.getContentResolver().delete(Uri.parse(uri), null, null); result.finish(); return null; } }.execute(); } private void showDumpNotification(Context context, Intent intent) { final boolean isUserInitiated = intent.getBooleanExtra( EXTRA_IS_USER_INITIATED, false); final String procName = intent.getStringExtra(EXTRA_PROCESS_NAME); final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0); final String reportPackage = intent.getStringExtra( EXTRA_REPORT_PACKAGE); final long size = intent.getLongExtra(EXTRA_SIZE_BYTES, 0); if (procName == null) { Log.e(TAG, "No process name sent over"); return; } NotificationManager nm = NotificationManager.from(context); nm.createNotificationChannel( new NotificationChannel(NOTIFICATION_CHANNEL_ID, "Heap dumps", NotificationManager.IMPORTANCE_DEFAULT)); final int titleId = isUserInitiated ? com.android.internal.R.string.dump_heap_ready_notification : com.android.internal.R.string.dump_heap_notification; final String procDisplayName = uid == Process.SYSTEM_UID ? context.getString(com.android.internal.R.string.android_system_label) : procName; String text = context.getString(titleId, procDisplayName); Intent shareIntent = new Intent(); shareIntent.setClassName(context, HeapDumpActivity.class.getName()); shareIntent.putExtra(EXTRA_PROCESS_NAME, procName); shareIntent.putExtra(EXTRA_SIZE_BYTES, size); shareIntent.putExtra(EXTRA_IS_USER_INITIATED, isUserInitiated); shareIntent.putExtra(Intent.EXTRA_UID, uid); if (reportPackage != null) { shareIntent.putExtra(EXTRA_REPORT_PACKAGE, reportPackage); } final Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID) .setSmallIcon( isTv(context) ? R.drawable.ic_bug_report_black_24dp : com.android.internal.R.drawable.stat_sys_adb) .setLocalOnly(true) .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)) .setContentTitle(text) .setTicker(text) .setAutoCancel(true) .setContentText(context.getText( com.android.internal.R.string.dump_heap_notification_detail)) .setContentIntent(PendingIntent.getActivity(context, 2, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT)); Log.v(TAG, "Creating share heap dump notification"); NotificationManager.from(context).notify(NOTIFICATION_ID, builder.build()); } }