diff options
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 12 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 10 | ||||
| -rw-r--r-- | core/java/android/os/BugreportManager.java | 148 | ||||
| -rw-r--r-- | core/java/android/os/BugreportParams.java | 90 | ||||
| -rw-r--r-- | services/core/java/com/android/server/os/BugreportManagerService.java | 43 | ||||
| -rw-r--r-- | services/core/java/com/android/server/os/BugreportManagerServiceImpl.java | 130 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 6 |
7 files changed, 439 insertions, 0 deletions
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 83c93fb3019c..d2f246846ce3 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -109,6 +109,7 @@ import android.net.wifi.rtt.WifiRttManager; import android.nfc.NfcManager; import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.BugreportManager; import android.os.Build; import android.os.DeviceIdleManager; import android.os.DropBoxManager; @@ -116,6 +117,7 @@ import android.os.HardwarePropertiesManager; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; import android.os.IDeviceIdleController; +import android.os.IDumpstate; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; @@ -972,6 +974,16 @@ final class SystemServiceRegistry { return new IncidentManager(ctx); }}); + registerService(Context.BUGREPORT_SERVICE, BugreportManager.class, + new CachedServiceFetcher<BugreportManager>() { + @Override + public BugreportManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow(Context.BUGREPORT_SERVICE); + return new BugreportManager(ctx.getOuterContext(), + IDumpstate.Stub.asInterface(b)); + }}); + registerService(Context.AUTOFILL_MANAGER_SERVICE, AutofillManager.class, new CachedServiceFetcher<AutofillManager>() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 9532f2bf9cd7..89cd0643dd20 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4213,6 +4213,16 @@ public abstract class Context { public static final String STATS_MANAGER = "stats"; /** + * Service to capture a bugreport. + * @see #getSystemService(String) + * @see android.os.BugreportManager + * @hide + */ + // TODO: Expose API when the implementation is more complete. + // @SystemApi + public static final String BUGREPORT_SERVICE = "bugreport"; + + /** * Use with {@link #getSystemService(String)} to retrieve a {@link * android.content.om.OverlayManager} for managing overlay packages. * diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java new file mode 100644 index 000000000000..1343d24d0d94 --- /dev/null +++ b/core/java/android/os/BugreportManager.java @@ -0,0 +1,148 @@ +/* + * 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 android.os; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.content.Context; +import android.os.IBinder.DeathRecipient; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class that provides a privileged API to capture and consume bugreports. + * + * @hide + */ +// TODO: Expose API when the implementation is more complete. +// @SystemApi +@SystemService(Context.BUGREPORT_SERVICE) +public class BugreportManager { + private final Context mContext; + private final IDumpstate mBinder; + + /** @hide */ + public BugreportManager(@NonNull Context context, IDumpstate binder) { + mContext = context; + mBinder = binder; + } + + /** + * An interface describing the listener for bugreport progress and status. + */ + public interface BugreportListener { + /** + * Called when there is a progress update. + * @param progress the progress in [0.0, 100.0] + */ + void onProgress(float progress); + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { + BUGREPORT_ERROR_INVALID_INPUT, + BUGREPORT_ERROR_RUNTIME + }) + + /** Possible error codes taking a bugreport can encounter */ + @interface BugreportErrorCode {} + + /** The input options were invalid */ + int BUGREPORT_ERROR_INVALID_INPUT = 1; + + /** A runtime error occured */ + int BUGREPORT_ERROR_RUNTIME = 2; + + /** + * Called when taking bugreport resulted in an error. + * + * @param errorCode the error that occurred. Possible values are + * {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}. + */ + void onError(@BugreportErrorCode int errorCode); + + /** + * Called when taking bugreport finishes successfully + * + * @param durationMs time capturing bugreport took in milliseconds + * @param title title for the bugreport; helpful in reminding the user why they took it + * @param description detailed description for the bugreport + */ + void onFinished(long durationMs, @NonNull String title, + @NonNull String description); + } + + /** + * Starts a bugreport asynchronously. + * + * @param bugreportFd file to write the bugreport. This should be opened in write-only, + * append mode. + * @param screenshotFd file to write the screenshot, if necessary. This should be opened + * in write-only, append mode. + * @param params options that specify what kind of a bugreport should be taken + * @param listener callback for progress and status updates + */ + @RequiresPermission(android.Manifest.permission.DUMP) + public void startBugreport(@NonNull FileDescriptor bugreportFd, + @Nullable FileDescriptor screenshotFd, + @NonNull BugreportParams params, @Nullable BugreportListener listener) { + // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. + DumpstateListener dsListener = new DumpstateListener(listener); + + try { + mBinder.startBugreport(bugreportFd, screenshotFd, params.getMode(), dsListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + // TODO(b/111441001) Connect up with BugreportListener methods. + private final class DumpstateListener extends IDumpstateListener.Stub + implements DeathRecipient { + private final BugreportListener mListener; + + DumpstateListener(@Nullable BugreportListener listener) { + mListener = listener; + } + + @Override + public void binderDied() { + // TODO(b/111441001): implement + } + + @Override + public void onProgressUpdated(int progress) throws RemoteException { + // TODO(b/111441001): implement + } + + @Override + public void onMaxProgressUpdated(int maxProgress) throws RemoteException { + // TODO(b/111441001): implement + } + + @Override + public void onSectionComplete(String title, int status, int size, int durationMs) + throws RemoteException { + // TODO(b/111441001): implement + } + } +} diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java new file mode 100644 index 000000000000..4e696aed1fa8 --- /dev/null +++ b/core/java/android/os/BugreportParams.java @@ -0,0 +1,90 @@ +/* + * 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 android.os; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Parameters that specify what kind of bugreport should be taken. + * + * @hide + */ +// TODO: Expose API when the implementation is more complete. +// @SystemApi +public final class BugreportParams { + private final int mMode; + + public BugreportParams(@BugreportMode int mode) { + mMode = mode; + } + + public int getMode() { + return mMode; + } + + /** + * Defines acceptable types of bugreports. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "BUGREPORT_MODE_" }, value = { + BUGREPORT_MODE_FULL, + BUGREPORT_MODE_INTERACTIVE, + BUGREPORT_MODE_REMOTE, + BUGREPORT_MODE_WEAR, + BUGREPORT_MODE_TELEPHONY, + BUGREPORT_MODE_WIFI + }) + public @interface BugreportMode {} + + /** + * Options for a bugreport without user interference (and hence causing less + * interference to the system), but includes all sections. + */ + public static final int BUGREPORT_MODE_FULL = IDumpstate.BUGREPORT_MODE_FULL; + + /** + * Options that allow user to monitor progress and enter additional data; might not + * include all sections. + */ + public static final int BUGREPORT_MODE_INTERACTIVE = IDumpstate.BUGREPORT_MODE_INTERACTIVE; + + /** + * Options for a bugreport requested remotely by administrator of the Device Owner app, + * not the device's user. + */ + public static final int BUGREPORT_MODE_REMOTE = IDumpstate.BUGREPORT_MODE_REMOTE; + + /** + * Options for a bugreport on a wearable device. + */ + public static final int BUGREPORT_MODE_WEAR = IDumpstate.BUGREPORT_MODE_WEAR; + + /** + * Options for a lightweight version of bugreport that only includes a few, urgent + * sections used to report telephony bugs. + */ + public static final int BUGREPORT_MODE_TELEPHONY = IDumpstate.BUGREPORT_MODE_TELEPHONY; + + /** + * Options for a lightweight bugreport that only includes a few sections related to + * Wifi. + */ + public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI; +} diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java new file mode 100644 index 000000000000..e2415911e929 --- /dev/null +++ b/services/core/java/com/android/server/os/BugreportManagerService.java @@ -0,0 +1,43 @@ +/* + * 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.server.os; + +import android.content.Context; + +import com.android.server.SystemService; + +/** + * Service that provides a privileged API to capture and consume bugreports. + * + * @hide + */ +public class BugreportManagerService extends SystemService { + private static final String TAG = "BugreportManagerService"; + + private BugreportManagerServiceImpl mService; + + public BugreportManagerService(Context context) { + super(context); + } + + @Override + public void onStart() { + mService = new BugreportManagerServiceImpl(getContext()); + // TODO(b/111441001): Needs sepolicy to be submitted first. + // publishBinderService(Context.BUGREPORT_SERVICE, mService); + } +} diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java new file mode 100644 index 000000000000..faa4714a8697 --- /dev/null +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -0,0 +1,130 @@ +/* + * 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.server.os; + +import android.annotation.RequiresPermission; +import android.content.Context; +import android.os.BugreportParams; +import android.os.IDumpstate; +import android.os.IDumpstateListener; +import android.os.IDumpstateToken; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Slog; + +import java.io.FileDescriptor; + +// TODO(b/111441001): +// 1. Handle the case where another bugreport is in progress +// 2. Make everything threadsafe +// 3. Pass validation & other errors on listener + +/** + * Implementation of the service that provides a privileged API to capture and consume bugreports. + * + * <p>Delegates the actualy generation to a native implementation of {@code Dumpstate}. + */ +class BugreportManagerServiceImpl extends IDumpstate.Stub { + private static final String TAG = "BugreportManagerService"; + private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; + + private IDumpstate mDs = null; + private final Context mContext; + + BugreportManagerServiceImpl(Context context) { + mContext = context; + } + + @Override + @RequiresPermission(android.Manifest.permission.DUMP) + public IDumpstateToken setListener(String name, IDumpstateListener listener, + boolean getSectionDetails) throws RemoteException { + // TODO(b/111441001): Figure out if lazy setting of listener should be allowed + // and if so how to handle it. + throw new UnsupportedOperationException("setListener is not allowed on this service"); + } + + + @Override + @RequiresPermission(android.Manifest.permission.DUMP) + public void startBugreport(FileDescriptor bugreportFd, FileDescriptor screenshotFd, + int bugreportMode, IDumpstateListener listener) throws RemoteException { + + validate(bugreportMode); + + mDs = getDumpstateService(); + if (mDs == null) { + Slog.w(TAG, "Unable to get bugreport service"); + // TODO(b/111441001): pass error on listener + return; + } + mDs.startBugreport(bugreportFd, screenshotFd, bugreportMode, listener); + } + + private boolean validate(@BugreportParams.BugreportMode int mode) { + if (mode != BugreportParams.BUGREPORT_MODE_FULL + && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE + && mode != BugreportParams.BUGREPORT_MODE_REMOTE + && mode != BugreportParams.BUGREPORT_MODE_WEAR + && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY + && mode != BugreportParams.BUGREPORT_MODE_WIFI) { + Slog.w(TAG, "Unknown bugreport mode: " + mode); + return false; + } + return true; + } + + /* + * Start and get a handle to the native implementation of {@code IDumpstate} which does the + * actual bugreport generation. + * + * <p>Generating bugreports requires root privileges. To limit the footprint + * of the root access, the actual generation in Dumpstate binary is accessed as a + * oneshot service 'bugreport'. + */ + private IDumpstate getDumpstateService() { + // Start bugreport service. + SystemProperties.set("ctl.start", "bugreport"); + + IDumpstate ds = null; + boolean timedOut = false; + int totalTimeWaitedMillis = 0; + int seedWaitTimeMillis = 500; + while (!timedOut) { + // Note that the binder service on the native side is "dumpstate". + ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate")); + if (ds != null) { + Slog.i(TAG, "Got bugreport service handle."); + break; + } + SystemClock.sleep(seedWaitTimeMillis); + Slog.i(TAG, + "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)"); + totalTimeWaitedMillis += seedWaitTimeMillis; + seedWaitTimeMillis *= 2; + timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS; + } + if (timedOut) { + Slog.w(TAG, + "Timed out waiting to get dumpstate service handle (" + + totalTimeWaitedMillis + "ms)"); + } + return ds; + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 479fb9a89200..10d979863a96 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -101,6 +101,7 @@ import com.android.server.net.watchlist.NetworkWatchlistService; import com.android.server.notification.NotificationManagerService; import com.android.server.oemlock.OemLockService; import com.android.server.om.OverlayManagerService; +import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.SchedulingPolicyService; import com.android.server.pm.BackgroundDexOptService; @@ -742,6 +743,11 @@ public final class SystemServer { traceBeginAndSlog("StartBinderCallsStatsService"); BinderCallsStatsService.start(); traceEnd(); + + // Service to capture bugreports. + traceBeginAndSlog("StartBugreportManagerService"); + mSystemServiceManager.startService(BugreportManagerService.class); + traceEnd(); } /** |