Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2cdf2d5a authored by Kweku Adams's avatar Kweku Adams Committed by android-build-merger
Browse files

Merge "Move heap dump sharing to SHELL."

am: c29b5cba

Change-Id: I12f5bc422a29eded0ce35c583757a8f3989e7909
parents 411b5233 c29b5cba
Loading
Loading
Loading
Loading
+26 −1
Original line number Diff line number Diff line
@@ -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" />
@@ -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">
@@ -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"/>
+1 −1
Original line number Diff line number Diff line
@@ -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);
    }

+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();
        }
    }
}
+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_.]", "");
    }
}
+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());
    }
}