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

Commit 2c3e99b5 authored by Joe Onorato's avatar Joe Onorato
Browse files

Refactor IncidentCompanionService to move the pending report logic into its own class.

IncidentCompanionService is about to get logic for sharing the
already-approved reports, and mixing the two is the first step
to making IcidentCompanionService yet another god object service.
Hopefully this can keep it cleaner.

Test: bit GooglePermissionControllerTests:*
Bug: 123543706
Change-Id: Ic24cbeb92c863739997fa74438ddce1f9c053c3b
parent 8daca758
Loading
Loading
Loading
Loading
+22 −381
Original line number Original line Diff line number Diff line
@@ -16,38 +16,18 @@


package com.android.server.incident;
package com.android.server.incident;


import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Binder;
import android.os.Handler;
import android.os.IIncidentAuthListener;
import android.os.IIncidentAuthListener;
import android.os.IIncidentCompanion;
import android.os.IIncidentCompanion;
import android.os.IncidentManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;


import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.SystemService;


import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.List;


// TODO: User changes should deny everything that's pending.

/**
/**
 * Helper service for incidentd and dumpstated to provide user feedback
 * Helper service for incidentd and dumpstated to provide user feedback
 * and authorization for bug and inicdent reports to be taken.
 * and authorization for bug and inicdent reports to be taken.
@@ -55,83 +35,26 @@ import java.util.List;
public class IncidentCompanionService extends SystemService {
public class IncidentCompanionService extends SystemService {
    static final String TAG = "IncidentCompanionService";
    static final String TAG = "IncidentCompanionService";


    private final Handler mHandler = new Handler();
    private final RequestQueue mRequestQueue = new RequestQueue(mHandler);
    private final PackageManager mPackageManager;
    private final AppOpsManager mAppOpsManager;

    //
    // All fields below must be protected by mLock
    //
    private final Object mLock = new Object();
    private final ArrayList<PendingReportRec> mPending = new ArrayList();

    /**
     * The next ID we'll use when we make a PendingReportRec.
     */
    private int mNextPendingId = 1;

    /**
     * One for each authorization that's pending.
     */
    private final class PendingReportRec {
        public int id;
        public String callingPackage;
        public int flags;
        public IIncidentAuthListener listener;
        public long addedRealtime;
        public long addedWalltime;

        /**
         * Construct a PendingReportRec, with an auto-incremented id.
         */
        PendingReportRec(String callingPackage, int flags, IIncidentAuthListener listener) {
            this.id = mNextPendingId++;
            this.callingPackage = callingPackage;
            this.flags = flags;
            this.listener = listener;
            this.addedRealtime = SystemClock.elapsedRealtime();
            this.addedWalltime = System.currentTimeMillis();
        }

    /**
    /**
         * Get the Uri that contains the flattened data.
     * Tracker for reports pending approval.
     */
     */
        Uri getUri() {
    private PendingReports mPendingReports;
            return (new Uri.Builder())
                    .scheme(IncidentManager.URI_SCHEME)
                    .authority(IncidentManager.URI_AUTHORITY)
                    .path(IncidentManager.URI_PATH)
                    .appendQueryParameter(IncidentManager.URI_PARAM_ID, Integer.toString(id))
                    .appendQueryParameter(IncidentManager.URI_PARAM_CALLING_PACKAGE, callingPackage)
                    .appendQueryParameter(IncidentManager.URI_PARAM_FLAGS, Integer.toString(flags))
                    .appendQueryParameter(IncidentManager.URI_PARAM_TIMESTAMP,
                            Long.toString(addedWalltime))
                    .build();
        }
    }


    /**
    /**
     * Implementation of the IIncidentCompanion binder interface.
     * Implementation of the IIncidentCompanion binder interface.
     */
     */
    private final class BinderService extends IIncidentCompanion.Stub {
    private final class BinderService extends IIncidentCompanion.Stub {
        /**
        /**
         * ONEWAY binder call to initiate authorizing the report.  The actual logic is posted
         * ONEWAY binder call to initiate authorizing the report.
         * to mRequestQueue, and may happen later.  The security checks need to happen here.
         */
         */
        @Override
        @Override
        public void authorizeReport(int callingUid, final String callingPackage, final int flags,
        public void authorizeReport(int callingUid, final String callingPackage, int flags,
                final IIncidentAuthListener listener) {
                final IIncidentAuthListener listener) {
            enforceRequestAuthorizationPermission();
            enforceRequestAuthorizationPermission();


            final long ident = Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
            try {
            try {
                // Starting the system server is complicated, and rather than try to
                mPendingReports.authorizeReport(callingUid, callingPackage, flags, listener);
                // have a complicated lifecycle that we share with dumpstated and incidentd,
                // we will accept the request, and then display it whenever it becomes possible to.
                mRequestQueue.enqueue(listener.asBinder(), true, () -> {
                    authorizeReportImpl(callingUid, callingPackage, flags, listener);
                });
            } finally {
            } finally {
                Binder.restoreCallingIdentity(ident);
                Binder.restoreCallingIdentity(ident);
            }
            }
@@ -152,9 +75,7 @@ public class IncidentCompanionService extends SystemService {
            // authorize/cancel pairs.
            // authorize/cancel pairs.
            final long ident = Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
            try {
            try {
                mRequestQueue.enqueue(listener.asBinder(), false, () -> {
                mPendingReports.cancelAuthorization(listener);
                    cancelReportImpl(listener);
                });
            } finally {
            } finally {
                Binder.restoreCallingIdentity(ident);
                Binder.restoreCallingIdentity(ident);
            }
            }
@@ -167,19 +88,11 @@ public class IncidentCompanionService extends SystemService {
        @Override
        @Override
        public List<String> getPendingReports() {
        public List<String> getPendingReports() {
            enforceAuthorizePermission();
            enforceAuthorizePermission();

            return mPendingReports.getPendingReports();
            synchronized (mLock) {
                final int size = mPending.size();
                final ArrayList<String> result = new ArrayList(size);
                for (int i = 0; i < size; i++) {
                    result.add(mPending.get(i).getUri().toString());
                }
                return result;
            }
        }
        }


        /**
        /**
         * ONEWAY binder call to mark a report as approved.
         * SYNCHRONOUS binder call to mark a report as approved.
         */
         */
        @Override
        @Override
        public void approveReport(String uri) {
        public void approveReport(String uri) {
@@ -187,32 +100,14 @@ public class IncidentCompanionService extends SystemService {


            final long ident = Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
            try {
            try {
                final PendingReportRec rec;
                mPendingReports.approveReport(uri);
                synchronized (mLock) {
                    rec = findAndRemovePendingReportRecLocked(uri);
                    if (rec == null) {
                        Log.e(TAG, "confirmApproved: Couldn't find record for uri: " + uri);
                        return;
                    }
                }

                // Re-do the broadcast, so whoever is listening knows the list changed,
                // in case another one was added in the meantime.
                sendBroadcast();

                Log.i(TAG, "Approved report: " + uri);
                try {
                    rec.listener.onReportApproved();
                } catch (RemoteException ex) {
                    Log.w(TAG, "Failed calling back for approval for: " + uri, ex);
                }
            } finally {
            } finally {
                Binder.restoreCallingIdentity(ident);
                Binder.restoreCallingIdentity(ident);
            }
            }
        }
        }


        /**
        /**
         * ONEWAY binder call to mark a report as NOT approved.
         * SYNCHRONOUS binder call to mark a report as NOT approved.
         */
         */
        @Override
        @Override
        public void denyReport(String uri) {
        public void denyReport(String uri) {
@@ -220,25 +115,7 @@ public class IncidentCompanionService extends SystemService {


            final long ident = Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
            try {
            try {
                final PendingReportRec rec;
                mPendingReports.denyReport(uri);
                synchronized (mLock) {
                    rec = findAndRemovePendingReportRecLocked(uri);
                    if (rec == null) {
                        Log.e(TAG, "confirmDenied: Couldn't find record for uri: " + uri);
                        return;
                    }
                }

                // Re-do the broadcast, so whoever is listening knows the list changed,
                // in case another one was added in the meantime.
                sendBroadcast();

                Log.i(TAG, "Denied report: " + uri);
                try {
                    rec.listener.onReportDenied();
                } catch (RemoteException ex) {
                    Log.w(TAG, "Failed calling back for denial for: " + uri, ex);
                }
            } finally {
            } finally {
                Binder.restoreCallingIdentity(ident);
                Binder.restoreCallingIdentity(ident);
            }
            }
@@ -252,27 +129,22 @@ public class IncidentCompanionService extends SystemService {
            if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) {
            if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) {
                return;
                return;
            }
            }
            if (args.length == 0) {
            mPendingReports.dump(fd, writer, args);
                // Standard text dumpsys
                final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                synchronized (mLock) {
                    final int size = mPending.size();
                    writer.println("mPending: (" + size + ")");
                    for (int i = 0; i < size; i++) {
                        final PendingReportRec entry = mPending.get(i);
                        writer.println(String.format("  %11d %s: %s", entry.addedRealtime,
                                    df.format(new Date(entry.addedWalltime)),
                                    entry.getUri().toString()));
                    }
                }
            }
        }
        }


        /**
         * Inside the binder interface class because we want to do all of the authorization
         * here, before calling out to the helper objects.
         */
        private void enforceRequestAuthorizationPermission() {
        private void enforceRequestAuthorizationPermission() {
            getContext().enforceCallingOrSelfPermission(
            getContext().enforceCallingOrSelfPermission(
                    android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL, null);
                    android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL, null);
        }
        }


        /**
         * Inside the binder interface class because we want to do all of the authorization
         * here, before calling out to the helper objects.
         */
        private void enforceAuthorizePermission() {
        private void enforceAuthorizePermission() {
            getContext().enforceCallingOrSelfPermission(
            getContext().enforceCallingOrSelfPermission(
                    android.Manifest.permission.APPROVE_INCIDENT_REPORTS, null);
                    android.Manifest.permission.APPROVE_INCIDENT_REPORTS, null);
@@ -285,8 +157,7 @@ public class IncidentCompanionService extends SystemService {
     */
     */
    public IncidentCompanionService(Context context) {
    public IncidentCompanionService(Context context) {
        super(context);
        super(context);
        mPackageManager = context.getPackageManager();
        mPendingReports = new PendingReports(context);
        mAppOpsManager = context.getSystemService(AppOpsManager.class);
    }
    }


    /**
    /**
@@ -307,239 +178,9 @@ public class IncidentCompanionService extends SystemService {
        super.onBootPhase(phase);
        super.onBootPhase(phase);
        switch (phase) {
        switch (phase) {
            case SystemService.PHASE_BOOT_COMPLETED:
            case SystemService.PHASE_BOOT_COMPLETED:
                // Release the enqueued work.
                mPendingReports.onBootCompleted();
                mRequestQueue.start();
                break;
                break;
        }
        }
    }
    }

    /**
     * Start the confirmation process.
     */
    private void authorizeReportImpl(int callingUid, final String callingPackage, int flags,
            final IIncidentAuthListener listener) {
        // Enforce that the calling package pertains to the callingUid.
        if (!isPackageInUid(callingUid, callingPackage)) {
            Log.w(TAG, "Calling uid " + callingUid + " doesn't match package "
                    + callingPackage);
            denyReportBeforeAddingRec(listener, callingPackage);
            return;
        }

        // Find the primary user of this device.
        final int primaryUser = getAndValidateUser();
        if (primaryUser == UserHandle.USER_NULL) {
            denyReportBeforeAddingRec(listener, callingPackage);
            return;
        }

        // Find the approver app (hint: it's PermissionController).
        final ComponentName receiver = getApproverComponent(primaryUser);
        if (receiver == null) {
            // We couldn't find an approver... so deny the request here and now, before we
            // do anything else.
            denyReportBeforeAddingRec(listener, callingPackage);
            return;
        }

        // Save the record for when the PermissionController comes back to authorize it.
        PendingReportRec rec = null;
        synchronized (mLock) {
            rec = new PendingReportRec(callingPackage, flags, listener);
            mPending.add(rec);
        }

        try {
            listener.asBinder().linkToDeath(() -> {
                Log.i(TAG, "Got death notification listener=" + listener);
                cancelReportImpl(listener, receiver, primaryUser);
            }, 0);
        } catch (RemoteException ex) {
            Log.e(TAG, "Remote died while trying to register death listener: " + rec.getUri());
            // First, remove from our list.
            cancelReportImpl(listener, receiver, primaryUser);
        }

        // Go tell Permission controller to start asking the user.
        sendBroadcast(receiver, primaryUser);
    }

    /**
     * Cancel a pending report request (because of an explicit call to cancel)
     */
    private void cancelReportImpl(IIncidentAuthListener listener) {
        final int primaryUser = getAndValidateUser();
        final ComponentName receiver = getApproverComponent(primaryUser);
        if (primaryUser != UserHandle.USER_NULL && receiver != null) {
            cancelReportImpl(listener, receiver, primaryUser);
        }
    }

    /**
     * Cancel a pending report request (either because of an explicit call to cancel
     * by the calling app, or because of a binder death).
     */
    private void cancelReportImpl(IIncidentAuthListener listener, ComponentName receiver,
            int primaryUser) {
        // First, remove from our list.
        synchronized (mLock) {
            removePendingReportRecLocked(listener);
        }
        // Second, call back to PermissionController to say it's canceled.
        sendBroadcast(receiver, primaryUser);
    }

    /**
     * Send an extra copy of the broadcast, to tell them that the list has changed
     * because of an addition or removal.  This function is less aggressive than
     * authorizeReportImpl in logging about failures, because this is for use in
     * cleanup cases to keep the apps' list in sync with ours.
     */
    private void sendBroadcast() {
        final int primaryUser = getAndValidateUser();
        if (primaryUser == UserHandle.USER_NULL) {
            return;
        }
        final ComponentName receiver = getApproverComponent(primaryUser);
        if (receiver == null) {
            return;
        }
        sendBroadcast(receiver, primaryUser);
    }

    /**
     * Send the confirmation broadcast.
     */
    private void sendBroadcast(ComponentName receiver, int primaryUser) {
        final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
        intent.setComponent(receiver);

        // Send it to the primary user.
        getContext().sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(primaryUser),
                android.Manifest.permission.APPROVE_INCIDENT_REPORTS);
    }

    /**
     * Remove a PendingReportRec keyed by uri, and return it.
     */
    private PendingReportRec findAndRemovePendingReportRecLocked(String uriString) {
        final Uri uri = Uri.parse(uriString);
        final int id;
        try {
            final String idStr = uri.getQueryParameter(IncidentManager.URI_PARAM_ID);
            id = Integer.parseInt(idStr);
        } catch (NumberFormatException ex) {
            Log.w(TAG, "Can't parse id from: " + uriString);
            return null;
        }
        final int size = mPending.size();
        for (int i = 0; i < size; i++) {
            final PendingReportRec rec = mPending.get(i);
            if (rec.id == id) {
                mPending.remove(i);
                return rec;
            }
        }
        return null;
    }

    /**
     * Remove a PendingReportRec keyed by listener.
     */
    private void removePendingReportRecLocked(IIncidentAuthListener listener) {
        final int size = mPending.size();
        for (int i = 0; i < size; i++) {
            final PendingReportRec rec = mPending.get(i);
            if (rec.listener.asBinder() == listener.asBinder()) {
                Log.i(TAG, "  ...Removed PendingReportRec index=" + i + ": " + rec.getUri());
                mPending.remove(i);
            }
        }
    }

    /**
     * Just call listener.deny() (wrapping the RemoteException), without try to
     * add it to the list.
     */
    private void denyReportBeforeAddingRec(IIncidentAuthListener listener, String pkg) {
        try {
            listener.onReportDenied();
        } catch (RemoteException ex) {
            Log.w(TAG, "Failed calling back for denial for " + pkg, ex);
        }
    }

    /**
     * Check whether the current user is the primary user, and return the user id if they are.
     * Returns UserHandle.USER_NULL if not valid.
     */
    private int getAndValidateUser() {
        // Current user
        UserInfo currentUser;
        try {
            currentUser = ActivityManager.getService().getCurrentUser();
        } catch (RemoteException ex) {
            // We're already inside the system process.
            throw new RuntimeException(ex);
        }

        // Primary user
        final UserManager um = UserManager.get(getContext());
        final UserInfo primaryUser = um.getPrimaryUser();

        // Check that we're using the right user.
        if (currentUser == null) {
            Log.w(TAG, "No current user.  Nobody to approve the report."
                    + " The report will be denied.");
            return UserHandle.USER_NULL;
        }
        if (primaryUser == null) {
            Log.w(TAG, "No primary user.  Nobody to approve the report."
                    + " The report will be denied.");
            return UserHandle.USER_NULL;
        }
        if (primaryUser.id != currentUser.id) {
            Log.w(TAG, "Only the primary user can approve bugreports, but they are not"
                    + " the current user. The report will be denied.");
            return UserHandle.USER_NULL;
        }

        return primaryUser.id;
    }

    /**
     * Return the ComponentName of the BroadcastReceiver that will approve reports.
     * The system must have zero or one of these installed.  We only look on the
     * system partition.  When the broadcast happens, the component will also need
     * have the APPROVE_INCIDENT_REPORTS permission.
     */
    private ComponentName getApproverComponent(int userId) {
        // Find the one true BroadcastReceiver
        final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
        final List<ResolveInfo> matches = mPackageManager.queryBroadcastReceiversAsUser(intent,
                PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE
                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
        if (matches.size() == 1) {
            return matches.get(0).getComponentInfo().getComponentName();
        } else {
            Log.w(TAG, "Didn't find exactly one BroadcastReceiver to handle "
                    + Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED
                    + ". The report will be denied. size="
                    + matches.size() + ": matches=" + matches);
            return null;
        }
    }

    /**
     * Return whether the package is one of the packages installed for the uid.
     */
    private boolean isPackageInUid(int uid, String packageName) {
        try {
            mAppOpsManager.checkPackage(uid, packageName);
            return true;
        } catch (SecurityException ex) {
            return false;
        }
    }
}
}
+477 −0

File added.

Preview size limit exceeded, changes collapsed.