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

src.com.android.server.incident.IncidentCompanionService Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2018 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.incident;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.IIncidentAuthListener;
import android.os.IIncidentCompanion;
import android.os.IIncidentManager;
import android.os.IncidentManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

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

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;

/**
 * Helper service for incidentd and dumpstated to provide user feedback
 * and authorization for bug and inicdent reports to be taken.
 */
public class IncidentCompanionService extends SystemService {
    static final String TAG = "IncidentCompanionService";

    /**
     * Dump argument for proxying restricted image dumps to the services
     * listed in the config.
     */
    private static String[] RESTRICTED_IMAGE_DUMP_ARGS = new String[] {
        "--hal", "--restricted_image" };

    /**
     * The two permissions, for sendBroadcastAsUserMultiplePermissions.
     */
    private static final String[] DUMP_AND_USAGE_STATS_PERMISSIONS = new String[] {
        android.Manifest.permission.DUMP,
        android.Manifest.permission.PACKAGE_USAGE_STATS
    };

    /**
     * Tracker for reports pending approval.
     */
    private PendingReports mPendingReports;

    /**
     * Implementation of the IIncidentCompanion binder interface.
     */
    private final class BinderService extends IIncidentCompanion.Stub {
        /**
         * ONEWAY binder call to initiate authorizing the report. If you don't need
         * IncidentCompanionService to check whether the calling UID matches then
         * pass 0 for callingUid.  Either way, the caller must have DUMP and USAGE_STATS
         * permissions to retrieve the data, so it ends up being about the same.
         */
        @Override
        public void authorizeReport(int callingUid, final String callingPackage,
                final String receiverClass, final String reportId,
                final int flags, final IIncidentAuthListener listener) {
            enforceRequestAuthorizationPermission();

            final long ident = Binder.clearCallingIdentity();
            try {
                mPendingReports.authorizeReport(callingUid, callingPackage,
                        receiverClass, reportId, flags, listener);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

        /**
         * ONEWAY binder call to cancel the inbound authorization request.
         * 

* This is a oneway call, and so is authorizeReport, so the * caller's ordering is preserved. The other calls on this object are synchronous, so * their ordering is not guaranteed with respect to these calls. So the implementation * sends out extra broadcasts to allow for eventual consistency. */ public void cancelAuthorization(final IIncidentAuthListener listener) { enforceRequestAuthorizationPermission(); // Caller can cancel if they don't want it anymore, and mRequestQueue elides // authorize/cancel pairs. final long ident = Binder.clearCallingIdentity(); try { mPendingReports.cancelAuthorization(listener); } finally { Binder.restoreCallingIdentity(ident); } } /** * ONEWAY implementation to send broadcast from incidentd, which is native. */ @Override public void sendReportReadyBroadcast(String pkg, String cls) { enforceRequestAuthorizationPermission(); final long ident = Binder.clearCallingIdentity(); try { final Context context = getContext(); final int primaryUser = getAndValidateUser(context); if (primaryUser == UserHandle.USER_NULL) { return; } final Intent intent = new Intent(Intent.ACTION_INCIDENT_REPORT_READY); intent.setComponent(new ComponentName(pkg, cls)); Log.d(TAG, "sendReportReadyBroadcast sending primaryUser=" + primaryUser + " userHandle=" + UserHandle.getUserHandleForUid(primaryUser) + " intent=" + intent); // Send it to the primary user. Only they can do incident reports. context.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.getUserHandleForUid(primaryUser), DUMP_AND_USAGE_STATS_PERMISSIONS); } finally { Binder.restoreCallingIdentity(ident); } } /** * SYNCHRONOUS binder call to get the list of reports that are pending confirmation * by the user. */ @Override public List getPendingReports() { enforceAuthorizePermission(); return mPendingReports.getPendingReports(); } /** * SYNCHRONOUS binder call to mark a report as approved. */ @Override public void approveReport(String uri) { enforceAuthorizePermission(); final long ident = Binder.clearCallingIdentity(); try { mPendingReports.approveReport(uri); } finally { Binder.restoreCallingIdentity(ident); } } /** * SYNCHRONOUS binder call to mark a report as NOT approved. */ @Override public void denyReport(String uri) { enforceAuthorizePermission(); final long ident = Binder.clearCallingIdentity(); try { mPendingReports.denyReport(uri); } finally { Binder.restoreCallingIdentity(ident); } } /** * SYNCHRONOUS binder call to get the list of incident reports waiting for a receiver. */ @Override public List getIncidentReportList(String pkg, String cls) throws RemoteException { enforceAccessReportsPermissions(null); final long ident = Binder.clearCallingIdentity(); try { return getIIncidentManager().getIncidentReportList(pkg, cls); } finally { Binder.restoreCallingIdentity(ident); } } /** * SYNCHRONOUS binder call to commit an incident report */ @Override public void deleteIncidentReports(String pkg, String cls, String id) throws RemoteException { if (pkg == null || cls == null || id == null || pkg.length() == 0 || cls.length() == 0 || id.length() == 0) { throw new RuntimeException("Invalid pkg, cls or id"); } enforceAccessReportsPermissions(pkg); final long ident = Binder.clearCallingIdentity(); try { getIIncidentManager().deleteIncidentReports(pkg, cls, id); } finally { Binder.restoreCallingIdentity(ident); } } /** * SYNCHRONOUS binder call to delete all incident reports for a package. */ @Override public void deleteAllIncidentReports(String pkg) throws RemoteException { if (pkg == null || pkg.length() == 0) { throw new RuntimeException("Invalid pkg"); } enforceAccessReportsPermissions(pkg); final long ident = Binder.clearCallingIdentity(); try { getIIncidentManager().deleteAllIncidentReports(pkg); } finally { Binder.restoreCallingIdentity(ident); } } /** * SYNCHRONOUS binder call to get the IncidentReport object. */ @Override public IncidentManager.IncidentReport getIncidentReport(String pkg, String cls, String id) throws RemoteException { if (pkg == null || cls == null || id == null || pkg.length() == 0 || cls.length() == 0 || id.length() == 0) { throw new RuntimeException("Invalid pkg, cls or id"); } enforceAccessReportsPermissions(pkg); final long ident = Binder.clearCallingIdentity(); try { return getIIncidentManager().getIncidentReport(pkg, cls, id); } finally { Binder.restoreCallingIdentity(ident); } } /** * SYNCHRONOUS implementation of adb shell dumpsys debugreportcompanion. */ @Override protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) { return; } if (args.length == 1 && "--restricted_image".equals(args[0])) { // Does NOT clearCallingIdentity dumpRestrictedImages(fd); } else { // Regular dump mPendingReports.dump(fd, writer, args); } } /** * Proxy for the restricted images section. */ private void dumpRestrictedImages(FileDescriptor fd) { // Only supported on eng or userdebug. if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { return; } final Resources res = getContext().getResources(); final String[] services = res.getStringArray( com.android.internal.R.array.config_restrictedImagesServices); final int servicesCount = services.length; for (int i = 0; i < servicesCount; i++) { final String name = services[i]; Log.d(TAG, "Looking up service " + name); final IBinder service = ServiceManager.getService(name); if (service != null) { Log.d(TAG, "Calling dump on service: " + name); try { service.dump(fd, RESTRICTED_IMAGE_DUMP_ARGS); } catch (RemoteException ex) { Log.w(TAG, "dump --restricted_image of " + name + " threw", ex); } } } } /** * 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() { getContext().enforceCallingOrSelfPermission( 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() { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.APPROVE_INCIDENT_REPORTS, null); } /** * Enforce that the calling process either has APPROVE_INCIDENT_REPORTS or * (DUMP and PACKAGE_USAGE_STATS). This lets the approver get, because showing * information about the report is a prerequisite for letting the user decide. * * If pkg is null, it is not checked, so make sure that you check it for null first * if you do need the packages to match. * * Inside the binder interface class because we want to do all of the authorization * here, before calling out to the helper objects. */ private void enforceAccessReportsPermissions(String pkg) { if (getContext().checkCallingPermission( android.Manifest.permission.APPROVE_INCIDENT_REPORTS) != PackageManager.PERMISSION_GRANTED) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.DUMP, null); getContext().enforceCallingOrSelfPermission( android.Manifest.permission.PACKAGE_USAGE_STATS, null); if (pkg != null) { enforceCallerIsSameApp(pkg); } } } /** * Throw a SecurityException if the incoming binder call is not from pkg. */ private void enforceCallerIsSameApp(String pkg) throws SecurityException { try { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getCallingUserId(); final ApplicationInfo ai = getContext().getPackageManager() .getApplicationInfoAsUser(pkg, 0, userId); if (ai == null) { throw new SecurityException("Unknown package " + pkg); } if (!UserHandle.isSameApp(ai.uid, uid)) { throw new SecurityException("Calling uid " + uid + " gave package " + pkg + " which is owned by uid " + ai.uid); } } catch (PackageManager.NameNotFoundException re) { throw new SecurityException("Unknown package " + pkg + "\n" + re); } } } /** * Construct new IncidentCompanionService with the context. */ public IncidentCompanionService(Context context) { super(context); mPendingReports = new PendingReports(context); } /** * Initialize the service. It is still not safe to do UI until * onBootPhase(SystemService.PHASE_BOOT_COMPLETED). */ @Override public void onStart() { publishBinderService(Context.INCIDENT_COMPANION_SERVICE, new BinderService()); } /** * Handle the boot process... Starts everything running once the system is * up enough for us to do UI. */ @Override public void onBootPhase(int phase) { super.onBootPhase(phase); switch (phase) { case SystemService.PHASE_BOOT_COMPLETED: mPendingReports.onBootCompleted(); break; } } /** * Looks up incidentd every time, so we don't need a complex handshake between * incidentd and IncidentCompanionService. */ private IIncidentManager getIIncidentManager() throws RemoteException { return IIncidentManager.Stub.asInterface( ServiceManager.getService(Context.INCIDENT_SERVICE)); } /** * Check whether the current user is the primary user, and return the user id if they are. * Returns UserHandle.USER_NULL if not valid. */ public static int getAndValidateUser(Context context) { // 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(context); 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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy