All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.devicepolicy.RemoteBugreportManager Maven / Gradle / Ivy

/*
 * Copyright (C) 2020 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.server.devicepolicy;

import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_ACCEPTED;
import static android.app.admin.DevicePolicyManager.ACTION_BUGREPORT_SHARING_DECLINED;
import static android.app.admin.DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH;
import static android.app.admin.DevicePolicyManager.EXTRA_BUGREPORT_NOTIFICATION_TYPE;
import static android.app.admin.DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH;
import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED;
import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED;
import static android.app.admin.DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED;

import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;

import android.annotation.IntDef;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.admin.DeviceAdminReceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.util.Pair;

import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.server.utils.Slogf;

import java.io.FileNotFoundException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Class managing bugreport collection upon device owner's request.
 */
public class RemoteBugreportManager {

    static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";

    private static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
    private static final String CTL_STOP = "ctl.stop";
    private static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
    private static final int NOTIFICATION_ID = SystemMessage.NOTE_REMOTE_BUGREPORT;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            NOTIFICATION_BUGREPORT_STARTED,
            NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED,
            NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED
    })
    @interface RemoteBugreportNotificationType {}
    private final DevicePolicyManagerService mService;
    private final DevicePolicyManagerService.Injector mInjector;

    private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
    private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
    private final Context mContext;

    private final Handler mHandler;

    private final Runnable mRemoteBugreportTimeoutRunnable = () -> {
        if (mRemoteBugreportServiceIsActive.get()) {
            onBugreportFailed();
        }
    };

    private final BroadcastReceiver mRemoteBugreportFinishedReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (ACTION_REMOTE_BUGREPORT_DISPATCH.equals(intent.getAction())
                    && mRemoteBugreportServiceIsActive.get()) {
                onBugreportFinished(intent);
            }
        }
    };

    private final BroadcastReceiver mRemoteBugreportConsentReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID);
            if (ACTION_BUGREPORT_SHARING_ACCEPTED.equals(action)) {
                onBugreportSharingAccepted();
            } else if (ACTION_BUGREPORT_SHARING_DECLINED.equals(action)) {
                onBugreportSharingDeclined();
            }
            mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
        }
    };

    public RemoteBugreportManager(
            DevicePolicyManagerService service, DevicePolicyManagerService.Injector injector) {
        mService = service;
        mInjector = injector;
        mContext = service.mContext;
        mHandler = service.mHandler;
    }

    private Notification buildNotification(@RemoteBugreportNotificationType int type) {
        final Intent dialogIntent = new Intent(Settings.ACTION_SHOW_REMOTE_BUGREPORT_DIALOG);
        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        dialogIntent.putExtra(EXTRA_BUGREPORT_NOTIFICATION_TYPE, type);

        // Fill the component explicitly to prevent the PendingIntent from being intercepted
        // and fired with crafted target. b/155183624
        final ActivityInfo targetInfo = dialogIntent.resolveActivityInfo(
                mContext.getPackageManager(), PackageManager.MATCH_SYSTEM_ONLY);
        if (targetInfo != null) {
            dialogIntent.setComponent(targetInfo.getComponentName());
        } else {
            Slogf.wtf(LOG_TAG, "Failed to resolve intent for remote bugreport dialog");
        }

        // Simple notification clicks are immutable
        final PendingIntent pendingDialogIntent = PendingIntent.getActivityAsUser(mContext, type,
                dialogIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);

        final Notification.Builder builder =
                new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                        .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                        .setOngoing(true)
                        .setLocalOnly(true)
                        .setContentIntent(pendingDialogIntent)
                        .setColor(mContext.getColor(
                                com.android.internal.R.color.system_notification_accent_color))
                        .extend(new Notification.TvExtender());

        if (type == NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED) {
            builder.setContentTitle(mContext.getString(
                        R.string.sharing_remote_bugreport_notification_title))
                    .setProgress(0, 0, true);
        } else if (type == NOTIFICATION_BUGREPORT_STARTED) {
            builder.setContentTitle(mContext.getString(
                        R.string.taking_remote_bugreport_notification_title))
                    .setProgress(0, 0, true);
        } else if (type == NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED) {
            // Simple notification action button clicks are immutable
            final PendingIntent pendingIntentAccept = PendingIntent.getBroadcast(mContext,
                    NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_ACCEPTED),
                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
            // Simple notification action button clicks are immutable
            final PendingIntent pendingIntentDecline = PendingIntent.getBroadcast(mContext,
                    NOTIFICATION_ID, new Intent(ACTION_BUGREPORT_SHARING_DECLINED),
                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
            builder.addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
                        R.string.decline_remote_bugreport_action), pendingIntentDecline).build())
                    .addAction(new Notification.Action.Builder(null /* icon */, mContext.getString(
                        R.string.share_remote_bugreport_action), pendingIntentAccept).build())
                    .setContentTitle(mContext.getString(
                        R.string.share_remote_bugreport_notification_title))
                    .setContentText(mContext.getString(
                        R.string.share_remote_bugreport_notification_message_finished))
                    .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
                        R.string.share_remote_bugreport_notification_message_finished)));
        }

        return builder.build();
    }

    /**
     * Initiates bugreport collection.
     * @return whether collection was initiated successfully.
     */
    public boolean requestBugreport() {
        if (mRemoteBugreportServiceIsActive.get()
                || (mService.getDeviceOwnerRemoteBugreportUriAndHash() != null)) {
            Slogf.d(LOG_TAG, "Remote bugreport wasn't started because there's already one running");
            return false;
        }

        final long callingIdentity = mInjector.binderClearCallingIdentity();
        try {
            mInjector.getIActivityManager().requestRemoteBugReport();

            mRemoteBugreportServiceIsActive.set(true);
            mRemoteBugreportSharingAccepted.set(false);
            registerRemoteBugreportReceivers();
            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
                    buildNotification(NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
            mHandler.postDelayed(mRemoteBugreportTimeoutRunnable, REMOTE_BUGREPORT_TIMEOUT_MILLIS);
            return true;
        } catch (RemoteException re) {
            // should never happen
            Slogf.e(LOG_TAG, "Failed to make remote calls to start bugreportremote service", re);
            return false;
        } finally {
            mInjector.binderRestoreCallingIdentity(callingIdentity);
        }
    }

    private void registerRemoteBugreportReceivers() {
        try {
            final IntentFilter filterFinished =
                    new IntentFilter(ACTION_REMOTE_BUGREPORT_DISPATCH, BUGREPORT_MIMETYPE);
            mContext.registerReceiver(mRemoteBugreportFinishedReceiver, filterFinished);
        } catch (IntentFilter.MalformedMimeTypeException e) {
            // should never happen, as setting a constant
            Slogf.w(LOG_TAG, e, "Failed to set type %s", BUGREPORT_MIMETYPE);
        }
        final IntentFilter filterConsent = new IntentFilter();
        filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
        filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
        mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
    }

    private void onBugreportFinished(Intent intent) {
        mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
        mRemoteBugreportServiceIsActive.set(false);
        final Uri bugreportUri = intent.getData();
        String bugreportUriString = null;
        if (bugreportUri != null) {
            bugreportUriString = bugreportUri.toString();
        }
        final String bugreportHash = intent.getStringExtra(EXTRA_REMOTE_BUGREPORT_HASH);
        if (mRemoteBugreportSharingAccepted.get()) {
            shareBugreportWithDeviceOwnerIfExists(bugreportUriString, bugreportHash);
            mInjector.getNotificationManager().cancel(LOG_TAG,
                    NOTIFICATION_ID);
        } else {
            mService.setDeviceOwnerRemoteBugreportUriAndHash(bugreportUriString, bugreportHash);
            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
                    buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED),
                    UserHandle.ALL);
        }
        mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
    }

    private void onBugreportFailed() {
        mRemoteBugreportServiceIsActive.set(false);
        mInjector.systemPropertiesSet(CTL_STOP, REMOTE_BUGREPORT_SERVICE);
        mRemoteBugreportSharingAccepted.set(false);
        mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
        mInjector.getNotificationManager().cancel(LOG_TAG, NOTIFICATION_ID);
        final Bundle extras = new Bundle();
        extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
                DeviceAdminReceiver.BUGREPORT_FAILURE_FAILED_COMPLETING);
        mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
        mContext.unregisterReceiver(mRemoteBugreportConsentReceiver);
        mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
    }

    private void onBugreportSharingAccepted() {
        mRemoteBugreportSharingAccepted.set(true);
        final Pair uriAndHash = mService.getDeviceOwnerRemoteBugreportUriAndHash();
        if (uriAndHash != null) {
            shareBugreportWithDeviceOwnerIfExists(uriAndHash.first, uriAndHash.second);
        } else if (mRemoteBugreportServiceIsActive.get()) {
            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
                    buildNotification(NOTIFICATION_BUGREPORT_ACCEPTED_NOT_FINISHED),
                    UserHandle.ALL);
        }
    }

    private void onBugreportSharingDeclined() {
        if (mRemoteBugreportServiceIsActive.get()) {
            mInjector.systemPropertiesSet(CTL_STOP,
                    REMOTE_BUGREPORT_SERVICE);
            mRemoteBugreportServiceIsActive.set(false);
            mHandler.removeCallbacks(mRemoteBugreportTimeoutRunnable);
            mContext.unregisterReceiver(mRemoteBugreportFinishedReceiver);
        }
        mRemoteBugreportSharingAccepted.set(false);
        mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
        mService.sendDeviceOwnerCommand(
                DeviceAdminReceiver.ACTION_BUGREPORT_SHARING_DECLINED, null);
    }

    private void shareBugreportWithDeviceOwnerIfExists(
            String bugreportUriString, String bugreportHash) {
        try {
            if (bugreportUriString == null) {
                throw new FileNotFoundException();
            }
            final Uri bugreportUri = Uri.parse(bugreportUriString);
            mService.sendBugreportToDeviceOwner(bugreportUri, bugreportHash);
        } catch (FileNotFoundException e) {
            final Bundle extras = new Bundle();
            extras.putInt(DeviceAdminReceiver.EXTRA_BUGREPORT_FAILURE_REASON,
                    DeviceAdminReceiver.BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE);
            mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_BUGREPORT_FAILED, extras);
        } finally {
            mRemoteBugreportSharingAccepted.set(false);
            mService.setDeviceOwnerRemoteBugreportUriAndHash(null, null);
        }
    }

    /**
     * Check if a bugreport was collected but not shared before reboot because the user didn't act
     * upon sharing notification.
     */
    public void checkForPendingBugreportAfterBoot() {
        if (mService.getDeviceOwnerRemoteBugreportUriAndHash() == null) {
            return;
        }
        final IntentFilter filterConsent = new IntentFilter();
        filterConsent.addAction(ACTION_BUGREPORT_SHARING_DECLINED);
        filterConsent.addAction(ACTION_BUGREPORT_SHARING_ACCEPTED);
        mContext.registerReceiver(mRemoteBugreportConsentReceiver, filterConsent);
        mInjector.getNotificationManager().notifyAsUser(LOG_TAG, NOTIFICATION_ID,
                buildNotification(NOTIFICATION_BUGREPORT_FINISHED_NOT_ACCEPTED), UserHandle.ALL);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy