Merge changes I8b85d8ee,I789f41f1 am: ddaf564365
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1566718 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I747059a2b88f3c632ea69323cbe9a5f123107fab
This commit is contained in:
@@ -678,6 +678,7 @@ gensrcs {
|
|||||||
srcs: [
|
srcs: [
|
||||||
":ipconnectivity-proto-src",
|
":ipconnectivity-proto-src",
|
||||||
":libstats_atom_enum_protos",
|
":libstats_atom_enum_protos",
|
||||||
|
":libtombstone_proto-src",
|
||||||
"core/proto/**/*.proto",
|
"core/proto/**/*.proto",
|
||||||
"libs/incident/**/*.proto",
|
"libs/incident/**/*.proto",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.content.pm.IPackageManager;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.DropBoxManager;
|
import android.os.DropBoxManager;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.FileObserver;
|
|
||||||
import android.os.FileUtils;
|
import android.os.FileUtils;
|
||||||
import android.os.RecoverySystem;
|
import android.os.RecoverySystem;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
@@ -75,7 +74,6 @@ public class BootReceiver extends BroadcastReceiver {
|
|||||||
SystemProperties.getInt("ro.debuggable", 0) == 1 ? 196608 : 65536;
|
SystemProperties.getInt("ro.debuggable", 0) == 1 ? 196608 : 65536;
|
||||||
private static final int GMSCORE_LASTK_LOG_SIZE = 196608;
|
private static final int GMSCORE_LASTK_LOG_SIZE = 196608;
|
||||||
|
|
||||||
private static final File TOMBSTONE_DIR = new File("/data/tombstones");
|
|
||||||
private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
|
private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
|
||||||
|
|
||||||
// The pre-froyo package and class of the system updater, which
|
// The pre-froyo package and class of the system updater, which
|
||||||
@@ -86,9 +84,6 @@ public class BootReceiver extends BroadcastReceiver {
|
|||||||
private static final String OLD_UPDATER_CLASS =
|
private static final String OLD_UPDATER_CLASS =
|
||||||
"com.google.android.systemupdater.SystemUpdateReceiver";
|
"com.google.android.systemupdater.SystemUpdateReceiver";
|
||||||
|
|
||||||
// Keep a reference to the observer so the finalizer doesn't disable it.
|
|
||||||
private static FileObserver sTombstoneObserver = null;
|
|
||||||
|
|
||||||
private static final String LOG_FILES_FILE = "log-files.xml";
|
private static final String LOG_FILES_FILE = "log-files.xml";
|
||||||
private static final AtomicFile sFile = new AtomicFile(new File(
|
private static final AtomicFile sFile = new AtomicFile(new File(
|
||||||
Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files");
|
Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files");
|
||||||
@@ -154,7 +149,7 @@ public class BootReceiver extends BroadcastReceiver {
|
|||||||
Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
|
Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPreviousBootHeaders() {
|
private static String getPreviousBootHeaders() {
|
||||||
try {
|
try {
|
||||||
return FileUtils.readTextFile(lastHeaderFile, 0, null);
|
return FileUtils.readTextFile(lastHeaderFile, 0, null);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -162,7 +157,7 @@ public class BootReceiver extends BroadcastReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCurrentBootHeaders() throws IOException {
|
private static String getCurrentBootHeaders() throws IOException {
|
||||||
return new StringBuilder(512)
|
return new StringBuilder(512)
|
||||||
.append("Build: ").append(Build.FINGERPRINT).append("\n")
|
.append("Build: ").append(Build.FINGERPRINT).append("\n")
|
||||||
.append("Hardware: ").append(Build.BOARD).append("\n")
|
.append("Hardware: ").append(Build.BOARD).append("\n")
|
||||||
@@ -176,7 +171,7 @@ public class BootReceiver extends BroadcastReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String getBootHeadersToLogAndUpdate() throws IOException {
|
private static String getBootHeadersToLogAndUpdate() throws IOException {
|
||||||
final String oldHeaders = getPreviousBootHeaders();
|
final String oldHeaders = getPreviousBootHeaders();
|
||||||
final String newHeaders = getCurrentBootHeaders();
|
final String newHeaders = getCurrentBootHeaders();
|
||||||
|
|
||||||
@@ -248,38 +243,27 @@ public class BootReceiver extends BroadcastReceiver {
|
|||||||
logFsMountTime();
|
logFsMountTime();
|
||||||
addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
|
addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
|
||||||
logSystemServerShutdownTimeMetrics();
|
logSystemServerShutdownTimeMetrics();
|
||||||
|
|
||||||
// Scan existing tombstones (in case any new ones appeared)
|
|
||||||
File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
|
|
||||||
for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
|
|
||||||
if (tombstoneFiles[i].isFile()) {
|
|
||||||
addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(),
|
|
||||||
LOG_SIZE, "SYSTEM_TOMBSTONE");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeTimestamps(timestamps);
|
writeTimestamps(timestamps);
|
||||||
|
}
|
||||||
|
|
||||||
// Start watching for new tombstone files; will record them as they occur.
|
/**
|
||||||
// This gets registered with the singleton file observer thread.
|
* Add a tombstone to the DropBox.
|
||||||
sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CREATE) {
|
*
|
||||||
@Override
|
* @param ctx Context
|
||||||
public void onEvent(int event, String path) {
|
* @param tombstone path to the tombstone
|
||||||
HashMap<String, Long> timestamps = readTimestamps();
|
*/
|
||||||
try {
|
public static void addTombstoneToDropBox(Context ctx, File tombstone) {
|
||||||
File file = new File(TOMBSTONE_DIR, path);
|
final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
|
||||||
if (file.isFile() && file.getName().startsWith("tombstone_")) {
|
final String bootReason = SystemProperties.get("ro.boot.bootreason", null);
|
||||||
addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE,
|
HashMap<String, Long> timestamps = readTimestamps();
|
||||||
TAG_TOMBSTONE);
|
try {
|
||||||
}
|
final String headers = getBootHeadersToLogAndUpdate();
|
||||||
} catch (IOException e) {
|
addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
|
||||||
Slog.e(TAG, "Can't log tombstone", e);
|
TAG_TOMBSTONE);
|
||||||
}
|
} catch (IOException e) {
|
||||||
writeTimestamps(timestamps);
|
Slog.e(TAG, "Can't log tombstone", e);
|
||||||
}
|
}
|
||||||
};
|
writeTimestamps(timestamps);
|
||||||
|
|
||||||
sTombstoneObserver.startWatching();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addLastkToDropBox(
|
private static void addLastkToDropBox(
|
||||||
@@ -764,7 +748,7 @@ public class BootReceiver extends BroadcastReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeTimestamps(HashMap<String, Long> timestamps) {
|
private static void writeTimestamps(HashMap<String, Long> timestamps) {
|
||||||
synchronized (sFile) {
|
synchronized (sFile) {
|
||||||
final FileOutputStream stream;
|
final FileOutputStream stream;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.server.os;
|
||||||
|
|
||||||
|
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
|
||||||
|
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
|
||||||
|
|
||||||
|
import android.annotation.AppIdInt;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.UserIdInt;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.FileObserver;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.util.Slog;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.util.proto.ProtoInputStream;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.server.BootReceiver;
|
||||||
|
import com.android.server.ServiceThread;
|
||||||
|
import com.android.server.os.TombstoneProtos.Tombstone;
|
||||||
|
|
||||||
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to manage native tombstones.
|
||||||
|
*/
|
||||||
|
public final class NativeTombstoneManager {
|
||||||
|
private static final String TAG = NativeTombstoneManager.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final File TOMBSTONE_DIR = new File("/data/tombstones");
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final Handler mHandler;
|
||||||
|
private final TombstoneWatcher mWatcher;
|
||||||
|
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final SparseArray<TombstoneFile> mTombstones;
|
||||||
|
|
||||||
|
NativeTombstoneManager(Context context) {
|
||||||
|
mTombstones = new SparseArray<TombstoneFile>();
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
|
final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher",
|
||||||
|
THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
|
||||||
|
thread.start();
|
||||||
|
mHandler = thread.getThreadHandler();
|
||||||
|
|
||||||
|
mWatcher = new TombstoneWatcher();
|
||||||
|
mWatcher.startWatching();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSystemReady() {
|
||||||
|
// Scan existing tombstones.
|
||||||
|
mHandler.post(() -> {
|
||||||
|
final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
|
||||||
|
for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
|
||||||
|
if (tombstoneFiles[i].isFile()) {
|
||||||
|
handleTombstone(tombstoneFiles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTombstone(File path) {
|
||||||
|
final String filename = path.getName();
|
||||||
|
if (!filename.startsWith("tombstone_")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.endsWith(".pb")) {
|
||||||
|
handleProtoTombstone(path);
|
||||||
|
} else {
|
||||||
|
BootReceiver.addTombstoneToDropBox(mContext, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleProtoTombstone(File path) {
|
||||||
|
final String filename = path.getName();
|
||||||
|
if (!filename.endsWith(".pb")) {
|
||||||
|
Slog.w(TAG, "unexpected tombstone name: " + path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String suffix = filename.substring("tombstone_".length());
|
||||||
|
final String numberStr = suffix.substring(0, suffix.length() - 3);
|
||||||
|
|
||||||
|
int number;
|
||||||
|
try {
|
||||||
|
number = Integer.parseInt(numberStr);
|
||||||
|
if (number < 0 || number > 99) {
|
||||||
|
Slog.w(TAG, "unexpected tombstone name: " + path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
Slog.w(TAG, "unexpected tombstone name: " + path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParcelFileDescriptor pfd;
|
||||||
|
try {
|
||||||
|
pfd = ParcelFileDescriptor.open(path, MODE_READ_WRITE);
|
||||||
|
} catch (FileNotFoundException ex) {
|
||||||
|
Slog.w(TAG, "failed to open " + path, ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
|
||||||
|
if (!parsedTombstone.isPresent()) {
|
||||||
|
IoUtils.closeQuietly(pfd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (mLock) {
|
||||||
|
TombstoneFile previous = mTombstones.get(number);
|
||||||
|
if (previous != null) {
|
||||||
|
previous.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mTombstones.put(number, parsedTombstone.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TombstoneFile {
|
||||||
|
final ParcelFileDescriptor mPfd;
|
||||||
|
|
||||||
|
final @UserIdInt int mUserId;
|
||||||
|
final @AppIdInt int mAppId;
|
||||||
|
|
||||||
|
boolean mPurged = false;
|
||||||
|
|
||||||
|
TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) {
|
||||||
|
mPfd = pfd;
|
||||||
|
mUserId = userId;
|
||||||
|
mAppId = appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(Optional<Integer> userId, Optional<Integer> appId) {
|
||||||
|
if (mPurged) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId.isPresent() && userId.get() != mUserId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appId.isPresent() && appId.get() != mAppId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
IoUtils.closeQuietly(mPfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
|
||||||
|
final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
|
||||||
|
final ProtoInputStream stream = new ProtoInputStream(is);
|
||||||
|
|
||||||
|
int uid = 0;
|
||||||
|
String selinuxLabel = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||||
|
switch (stream.getFieldNumber()) {
|
||||||
|
case (int) Tombstone.UID:
|
||||||
|
uid = stream.readInt(Tombstone.UID);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case (int) Tombstone.SELINUX_LABEL:
|
||||||
|
selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Slog.e(TAG, "Failed to parse tombstone", ex);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UserHandle.isApp(uid)) {
|
||||||
|
Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
final int userId = UserHandle.getUserId(uid);
|
||||||
|
final int appId = UserHandle.getAppId(uid);
|
||||||
|
|
||||||
|
if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
|
||||||
|
Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(new TombstoneFile(pfd, userId, appId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TombstoneWatcher extends FileObserver {
|
||||||
|
TombstoneWatcher() {
|
||||||
|
// Tombstones can be created either by linking an O_TMPFILE temporary file (CREATE),
|
||||||
|
// or by moving a named temporary file in the same directory on kernels where O_TMPFILE
|
||||||
|
// isn't supported (MOVED_TO).
|
||||||
|
super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(int event, @Nullable String path) {
|
||||||
|
mHandler.post(() -> {
|
||||||
|
handleTombstone(new File(TOMBSTONE_DIR, path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.server.os;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.server.LocalServices;
|
||||||
|
import com.android.server.SystemService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that tracks and manages native tombstones.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class NativeTombstoneManagerService extends SystemService {
|
||||||
|
private static final String TAG = "NativeTombstoneManagerService";
|
||||||
|
|
||||||
|
private NativeTombstoneManager mManager;
|
||||||
|
|
||||||
|
public NativeTombstoneManagerService(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
mManager = new NativeTombstoneManager(getContext());
|
||||||
|
LocalServices.addService(NativeTombstoneManager.class, mManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBootPhase(int phase) {
|
||||||
|
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
|
||||||
|
mManager.onSystemReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -139,6 +139,7 @@ import com.android.server.oemlock.OemLockService;
|
|||||||
import com.android.server.om.OverlayManagerService;
|
import com.android.server.om.OverlayManagerService;
|
||||||
import com.android.server.os.BugreportManagerService;
|
import com.android.server.os.BugreportManagerService;
|
||||||
import com.android.server.os.DeviceIdentifiersPolicyService;
|
import com.android.server.os.DeviceIdentifiersPolicyService;
|
||||||
|
import com.android.server.os.NativeTombstoneManagerService;
|
||||||
import com.android.server.os.SchedulingPolicyService;
|
import com.android.server.os.SchedulingPolicyService;
|
||||||
import com.android.server.people.PeopleService;
|
import com.android.server.people.PeopleService;
|
||||||
import com.android.server.pm.BackgroundDexOptService;
|
import com.android.server.pm.BackgroundDexOptService;
|
||||||
@@ -1072,6 +1073,11 @@ public final class SystemServer {
|
|||||||
mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
|
mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
|
||||||
t.traceEnd();
|
t.traceEnd();
|
||||||
|
|
||||||
|
// Tracks native tombstones.
|
||||||
|
t.traceBegin("StartNativeTombstoneManagerService");
|
||||||
|
mSystemServiceManager.startService(NativeTombstoneManagerService.class);
|
||||||
|
t.traceEnd();
|
||||||
|
|
||||||
// Service to capture bugreports.
|
// Service to capture bugreports.
|
||||||
t.traceBegin("StartBugreportManagerService");
|
t.traceBegin("StartBugreportManagerService");
|
||||||
mSystemServiceManager.startService(BugreportManagerService.class);
|
mSystemServiceManager.startService(BugreportManagerService.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user