base: add support for routing traces to reporters
Change-Id: I4ff55e9c211bfedada85234ee86f7484acf633ee
This commit is contained in:
parent
f11f031190
commit
4b7d9b148b
@ -63,6 +63,7 @@ package android {
|
||||
field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
|
||||
field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
|
||||
field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
|
||||
field public static final String BIND_TRACE_REPORT_SERVICE = "android.permission.BIND_TRACE_REPORT_SERVICE";
|
||||
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
|
||||
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
|
||||
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
|
||||
@ -10120,6 +10121,22 @@ package android.service.timezone {
|
||||
|
||||
}
|
||||
|
||||
package android.service.tracing {
|
||||
|
||||
public class TraceReportService extends android.app.Service {
|
||||
ctor public TraceReportService();
|
||||
method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
|
||||
method public boolean onMessage(@NonNull android.os.Message);
|
||||
method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
|
||||
}
|
||||
|
||||
public static final class TraceReportService.TraceParams {
|
||||
method @NonNull public android.os.ParcelFileDescriptor getFd();
|
||||
method @NonNull public java.util.UUID getUuid();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.service.translation {
|
||||
|
||||
public abstract class TranslationService extends android.app.Service {
|
||||
|
@ -33,6 +33,11 @@ filegroup {
|
||||
srcs: ["android/tracing/ITracingServiceProxy.aidl"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "TraceReportParams.aidl",
|
||||
srcs: ["android/tracing/TraceReportParams.aidl"],
|
||||
}
|
||||
|
||||
// These are subset of framework-core-sources that are needed by the
|
||||
// android.test.mock library. The implementation of android.test.mock references
|
||||
// private members of various components to allow mocking of classes that cannot
|
||||
|
165
core/java/android/service/tracing/TraceReportService.java
Normal file
165
core/java/android/service/tracing/TraceReportService.java
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 android.service.tracing;
|
||||
|
||||
import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.tracing.TraceReportParams;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service to be sub-classed and exposed by (privileged) apps which want to report
|
||||
* system traces.
|
||||
* <p>
|
||||
* Subclasses should implement the onReportTrace method to handle traces reported
|
||||
* to them.
|
||||
* </p>
|
||||
* <pre>
|
||||
* public class SampleReportService extends TraceReportService {
|
||||
* public void onReportTrace(TraceParams args) {
|
||||
* // --- Implementation goes here ---
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* The service declaration in the application manifest must specify
|
||||
* BIND_TRACE_REPORT_SERVICE in the permission attribute.
|
||||
* </p>
|
||||
* <pre>
|
||||
* <application>
|
||||
* <service android:name=".SampleReportService"
|
||||
* android:permission="android.permission.BIND_TRACE_REPORT_SERVICE">
|
||||
* </service>
|
||||
* </application>
|
||||
* </pre>
|
||||
*
|
||||
* Moreover, the package containing this service must hold the DUMP and PACKAGE_USAGE_STATS
|
||||
* permissions.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = PRIVILEGED_APPS)
|
||||
public class TraceReportService extends Service {
|
||||
private static final String TAG = "TraceReportService";
|
||||
private Messenger mMessenger = null;
|
||||
|
||||
/**
|
||||
* Public to allow this to be used by TracingServiceProxy in system_server.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int MSG_REPORT_TRACE = 1;
|
||||
|
||||
/**
|
||||
* Contains information about the trace which is being reported.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = PRIVILEGED_APPS)
|
||||
public static final class TraceParams {
|
||||
private final ParcelFileDescriptor mFd;
|
||||
private final UUID mUuid;
|
||||
|
||||
private TraceParams(TraceReportParams params) {
|
||||
mFd = params.fd;
|
||||
mUuid = new UUID(params.uuidMsb, params.uuidLsb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ParcelFileDescriptor for the collected trace.
|
||||
*/
|
||||
@NonNull
|
||||
public ParcelFileDescriptor getFd() {
|
||||
return mFd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UUID of the trace; this is exactly the UUID created by the tracing system
|
||||
* (i.e. Perfetto) and is also present inside the trace file.
|
||||
*/
|
||||
@NonNull
|
||||
public UUID getUuid() {
|
||||
return mUuid;
|
||||
}
|
||||
}
|
||||
|
||||
// Methods to override.
|
||||
/**
|
||||
* Called when a trace is reported and sent to this class.
|
||||
*
|
||||
* Note: the trace file descriptor should not be persisted beyond the lifetime of this
|
||||
* function as it is owned by the framework and will be closed immediately after this function
|
||||
* returns: if future use of the fd is needed, it should be duped.
|
||||
*/
|
||||
public void onReportTrace(@NonNull TraceParams args) {
|
||||
}
|
||||
|
||||
// Optional methods to override.
|
||||
// Realistically, these methods are internal implementation details but since this class is
|
||||
// a SystemApi, it's better to err on the side of flexibility just in-case we need to override
|
||||
// these methods down the line.
|
||||
|
||||
/**
|
||||
* Handles binder calls from system_server.
|
||||
*/
|
||||
public boolean onMessage(@NonNull Message msg) {
|
||||
if (msg.what == MSG_REPORT_TRACE) {
|
||||
if (!(msg.obj instanceof TraceReportParams)) {
|
||||
Log.e(TAG, "Received invalid type for report trace message.");
|
||||
return false;
|
||||
}
|
||||
TraceParams params = new TraceParams((TraceReportParams) msg.obj);
|
||||
try {
|
||||
onReportTrace(params);
|
||||
} finally {
|
||||
try {
|
||||
params.getFd().close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an IBinder for handling binder calls from system_server.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(@NonNull Intent intent) {
|
||||
if (mMessenger == null) {
|
||||
mMessenger = new Messenger(new Handler(Looper.getMainLooper(), this::onMessage));
|
||||
}
|
||||
return mMessenger.getBinder();
|
||||
}
|
||||
}
|
@ -16,17 +16,25 @@
|
||||
|
||||
package android.tracing;
|
||||
|
||||
import android.tracing.TraceReportParams;
|
||||
|
||||
/**
|
||||
* Binder interface for the TracingServiceProxy running in system_server.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
interface ITracingServiceProxy
|
||||
{
|
||||
interface ITracingServiceProxy {
|
||||
/**
|
||||
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
|
||||
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
|
||||
* there is no buffer available to dump.
|
||||
*/
|
||||
oneway void notifyTraceSessionEnded(boolean sessionStolen);
|
||||
|
||||
/**
|
||||
* Notifies the specified service that a trace has been captured. The contents of |params|
|
||||
* contains the intended recipient (package and class) of this trace as well as a file
|
||||
* descriptor to an unlinked trace |fd| (i.e. an fd opened using O_TMPFILE).
|
||||
*/
|
||||
oneway void reportTrace(in TraceReportParams params);
|
||||
}
|
||||
|
59
core/java/android/tracing/TraceReportParams.aidl
Normal file
59
core/java/android/tracing/TraceReportParams.aidl
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2021, 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 android.tracing;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
/*
|
||||
* Parameters for a trace report.
|
||||
*
|
||||
* See ITracingServiceProxy::reportTrace for more details.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
parcelable TraceReportParams {
|
||||
// The package name containing the reporter service (see |reporterClassName|).
|
||||
String reporterPackageName;
|
||||
|
||||
// The class name of the reporter service. The framework will bind to this service and pass the
|
||||
// trace fd and metadata to this class.
|
||||
// This class should be "trusted" (in practice this means being a priv_app + having DUMP and
|
||||
// USAGE_STATS permissions).
|
||||
String reporterClassName;
|
||||
|
||||
// The file descriptor for the trace file. This will be an unlinked file fd (i.e. created
|
||||
// with O_TMPFILE); the intention is that reporter classes link this fd into a app-private
|
||||
// folder for reporting when conditions are right (e.g. charging, on unmetered networks etc).
|
||||
ParcelFileDescriptor fd;
|
||||
|
||||
// The least-significant-bytes of the UUID of this trace.
|
||||
long uuidLsb;
|
||||
|
||||
// The most-significant-bytes of the UUID of this trace.
|
||||
long uuidMsb;
|
||||
|
||||
// Flag indicating whether, instead of passing the fd from the trace collector, to pass a
|
||||
// pipe fd from system_server and send the file over it.
|
||||
//
|
||||
// This flag is necessary because there is no good way to write a CTS test where a helper
|
||||
// priv_app (in terms of SELinux) is needed (this is because priv_apps are supposed to be
|
||||
// preinstalled on the system partition). By creating a pipe in system_server we work around
|
||||
// this restriction. Note that there is a maximum allowed file size if this flag is set
|
||||
// (see TracingServiceProxy). Further note that, even though SELinux may be worked around,
|
||||
// manifest (i.e. framework) permissions are still checked even if this flag is set.
|
||||
boolean usePipeForTesting;
|
||||
}
|
@ -3453,6 +3453,13 @@
|
||||
<permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
|
||||
android:protectionLevel="signature|privileged|development" />
|
||||
|
||||
<!-- @hide @SystemApi Must be required by a
|
||||
{@link com.android.service.tracing.TraceReportService}, to ensure that only the system
|
||||
can bind to it.
|
||||
<p>Not for use by third-party applications. -->
|
||||
<permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- @hide @SystemApi @TestApi
|
||||
Allow an application to approve incident and bug reports to be
|
||||
shared off-device. There can be only one application installed on the
|
||||
|
@ -37,6 +37,7 @@ cc_library_shared {
|
||||
|
||||
srcs: [
|
||||
":ITracingServiceProxy.aidl",
|
||||
":TraceReportParams.aidl",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
|
@ -15,34 +15,56 @@
|
||||
*/
|
||||
package com.android.server.tracing;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.IMessenger;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
|
||||
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
|
||||
import android.os.UserHandle;
|
||||
import android.service.tracing.TraceReportService;
|
||||
import android.tracing.ITracingServiceProxy;
|
||||
import android.tracing.TraceReportParams;
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.infra.ServiceConnector;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the
|
||||
* system tracing app Traceur.
|
||||
* other components (e.g. system tracing app Traceur, trace reporting apps).
|
||||
*
|
||||
* Access to this service is restricted via SELinux. Normal apps do not have access.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class TracingServiceProxy extends SystemService {
|
||||
private static final String TAG = "TracingServiceProxy";
|
||||
|
||||
public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy";
|
||||
|
||||
private static final String TAG = "TracingServiceProxy";
|
||||
private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur";
|
||||
private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService";
|
||||
|
||||
private static final int MAX_CACHED_REPORTER_SERVICES = 8;
|
||||
|
||||
// The maximum size of the trace allowed if the option |usePipeForTesting| is set.
|
||||
// Note: this size MUST be smaller than the buffer size of the pipe (i.e. what you can
|
||||
// write to the pipe without blocking) to avoid system_server blocking on this.
|
||||
// (on Linux, the minimum value is 4K i.e. 1 minimally sized page).
|
||||
private static final int MAX_FILE_SIZE_BYTES_TO_PIPE = 1024;
|
||||
|
||||
// Keep this in sync with the definitions in TraceService
|
||||
private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
|
||||
"com.android.traceur.NOTIFY_SESSION_STOPPED";
|
||||
@ -51,16 +73,22 @@ public class TracingServiceProxy extends SystemService {
|
||||
|
||||
private final Context mContext;
|
||||
private final PackageManager mPackageManager;
|
||||
private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices;
|
||||
|
||||
private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() {
|
||||
/**
|
||||
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
|
||||
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
|
||||
* there is no buffer available to dump.
|
||||
*/
|
||||
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
|
||||
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
|
||||
* there is no buffer available to dump.
|
||||
*/
|
||||
@Override
|
||||
public void notifyTraceSessionEnded(boolean sessionStolen) {
|
||||
notifyTraceur(sessionStolen);
|
||||
TracingServiceProxy.this.notifyTraceur(sessionStolen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportTrace(@NonNull TraceReportParams params) {
|
||||
TracingServiceProxy.this.reportTrace(params);
|
||||
}
|
||||
};
|
||||
|
||||
@ -68,6 +96,7 @@ public class TracingServiceProxy extends SystemService {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mPackageManager = context.getPackageManager();
|
||||
mCachedReporterServices = new LruCache<>(MAX_CACHED_REPORTER_SERVICES);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -103,4 +132,119 @@ public class TracingServiceProxy extends SystemService {
|
||||
Log.e(TAG, "Failed to locate Traceur", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void reportTrace(@NonNull TraceReportParams params) {
|
||||
// We don't need to do any permission checks on the caller because access
|
||||
// to this service is guarded by SELinux.
|
||||
ComponentName component = new ComponentName(params.reporterPackageName,
|
||||
params.reporterClassName);
|
||||
if (!hasBindServicePermission(component)) {
|
||||
return;
|
||||
}
|
||||
boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP);
|
||||
boolean hasUsageStatsPermission = hasPermission(component,
|
||||
Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
if (!hasDumpPermission || !hasUsageStatsPermission) {
|
||||
return;
|
||||
}
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
reportTrace(getOrCreateReporterService(component), params);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
private void reportTrace(
|
||||
@NonNull ServiceConnector<IMessenger> reporterService,
|
||||
@NonNull TraceReportParams params) {
|
||||
reporterService.post(messenger -> {
|
||||
if (params.usePipeForTesting) {
|
||||
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
try (AutoCloseInputStream i = new AutoCloseInputStream(params.fd)) {
|
||||
try (AutoCloseOutputStream o = new AutoCloseOutputStream(pipe[1])) {
|
||||
byte[] array = i.readNBytes(MAX_FILE_SIZE_BYTES_TO_PIPE);
|
||||
if (array.length == MAX_FILE_SIZE_BYTES_TO_PIPE) {
|
||||
throw new IllegalArgumentException(
|
||||
"Trace file too large when |usePipeForTesting| is set.");
|
||||
}
|
||||
o.write(array);
|
||||
}
|
||||
}
|
||||
params.fd = pipe[0];
|
||||
}
|
||||
|
||||
Message message = Message.obtain();
|
||||
message.what = TraceReportService.MSG_REPORT_TRACE;
|
||||
message.obj = params;
|
||||
messenger.send(message);
|
||||
}).whenComplete((res, err) -> {
|
||||
if (err != null) {
|
||||
Slog.e(TAG, "Failed to report trace", err);
|
||||
}
|
||||
try {
|
||||
params.fd.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ServiceConnector<IMessenger> getOrCreateReporterService(
|
||||
@NonNull ComponentName component) {
|
||||
ServiceConnector<IMessenger> connector = mCachedReporterServices.get(component);
|
||||
if (connector == null) {
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(component);
|
||||
connector = new ServiceConnector.Impl<IMessenger>(
|
||||
mContext, intent,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY,
|
||||
mContext.getUser().getIdentifier(), IMessenger.Stub::asInterface) {
|
||||
private static final long DISCONNECT_TIMEOUT_MS = 15_000;
|
||||
private static final long REQUEST_TIMEOUT_MS = 10_000;
|
||||
|
||||
@Override
|
||||
protected long getAutoDisconnectTimeoutMs() {
|
||||
return DISCONNECT_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getRequestTimeoutMs() {
|
||||
return REQUEST_TIMEOUT_MS;
|
||||
}
|
||||
};
|
||||
mCachedReporterServices.put(intent.getComponent(), connector);
|
||||
}
|
||||
return connector;
|
||||
}
|
||||
|
||||
private boolean hasPermission(@NonNull ComponentName componentName,
|
||||
@NonNull String permission) throws SecurityException {
|
||||
if (mPackageManager.checkPermission(permission, componentName.getPackageName())
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
Slog.e(TAG,
|
||||
"Trace reporting service " + componentName.toShortString() + " does not have "
|
||||
+ permission + " permission");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasBindServicePermission(@NonNull ComponentName componentName) {
|
||||
ServiceInfo info;
|
||||
try {
|
||||
info = mPackageManager.getServiceInfo(componentName, 0);
|
||||
} catch (NameNotFoundException ex) {
|
||||
Slog.e(TAG,
|
||||
"Trace reporting service " + componentName.toShortString() + " does not exist");
|
||||
return false;
|
||||
}
|
||||
if (!Manifest.permission.BIND_TRACE_REPORT_SERVICE.equals(info.permission)) {
|
||||
Slog.e(TAG,
|
||||
"Trace reporting service " + componentName.toShortString()
|
||||
+ " does not request " + Manifest.permission.BIND_TRACE_REPORT_SERVICE
|
||||
+ " permission; instead requests " + info.permission);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user