blob: bb39ccc52af22a7adaf1e6a984266c6b803503c5 [file] [log] [blame]
/*
* 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;
import android.content.Context;
import android.os.Binder;
import android.service.runtime.DebugEntryProto;
import android.service.runtime.RuntimeServiceInfoProto;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import libcore.timezone.TimeZoneDataFiles;
import libcore.util.CoreLibraryDebug;
import libcore.util.DebugInfo;
import com.android.internal.util.DumpUtils;
import com.android.timezone.distro.DistroException;
import com.android.timezone.distro.DistroVersion;
import com.android.timezone.distro.FileUtils;
import com.android.timezone.distro.TimeZoneDistro;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
/**
* This service exists only as a "dumpsys" target which reports information about the status of the
* runtime and related libraries.
*/
public class RuntimeService extends Binder {
private static final String TAG = "RuntimeService";
private final Context mContext;
public RuntimeService(Context context) {
mContext = context;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
return;
}
boolean protoFormat = hasOption(args, "--proto");
ProtoOutputStream proto = null;
DebugInfo coreLibraryDebugInfo = CoreLibraryDebug.getDebugInfo();
addTimeZoneApkDebugInfo(coreLibraryDebugInfo);
if (protoFormat) {
proto = new ProtoOutputStream(fd);
reportTimeZoneInfoProto(coreLibraryDebugInfo, proto);
} else {
reportTimeZoneInfo(coreLibraryDebugInfo, pw);
}
if (protoFormat) {
proto.flush();
}
}
/** Returns {@code true} if {@code args} contains {@code arg}. */
private static boolean hasOption(String[] args, String arg) {
for (String opt : args) {
if (arg.equals(opt)) {
return true;
}
}
return false;
}
/**
* Add information to {@link DebugInfo} about the time zone data supplied by the
* "Time zone updates via APK" feature.
*/
private static void addTimeZoneApkDebugInfo(DebugInfo coreLibraryDebugInfo) {
// Add /data tz data set using the DistroVersion class (which libcore cannot use).
// This update mechanism will be removed after the time zone APEX is launched so this
// untidiness will disappear with it.
String debugKeyPrefix = "core_library.timezone.source.data_";
String versionFileName = TimeZoneDataFiles.getDataTimeZoneFile(
TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
addDistroVersionDebugInfo(versionFileName, debugKeyPrefix, coreLibraryDebugInfo);
}
/**
* Prints {@code coreLibraryDebugInfo} to {@code pw}.
*
* <p>If you change this method, make sure to modify
* {@link #reportTimeZoneInfoProto(DebugInfo, ProtoOutputStream)} as well.
*/
private static void reportTimeZoneInfo(DebugInfo coreLibraryDebugInfo,
PrintWriter pw) {
pw.println("Core Library Debug Info: ");
for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) {
pw.print(debugEntry.getKey());
pw.print(": \"");
pw.print(debugEntry.getStringValue());
pw.println("\"");
}
}
/**
* Adds {@code coreLibraryDebugInfo} to {@code protoStream}.
*
* <p>If you change this method, make sure to modify
* {@link #reportTimeZoneInfo(DebugInfo, PrintWriter)}.
*/
private static void reportTimeZoneInfoProto(
DebugInfo coreLibraryDebugInfo, ProtoOutputStream protoStream) {
for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) {
long entryToken = protoStream.start(RuntimeServiceInfoProto.DEBUG_ENTRY);
protoStream.write(DebugEntryProto.KEY, debugEntry.getKey());
protoStream.write(DebugEntryProto.STRING_VALUE, debugEntry.getStringValue());
protoStream.end(entryToken);
}
}
/**
* Adds version information to {@code debugInfo} from the distro_version file that may exist
* at {@code distroVersionFileName}. If the file does not exist or cannot be read this is
* reported as debug information too.
*/
private static void addDistroVersionDebugInfo(String distroVersionFileName,
String debugKeyPrefix, DebugInfo debugInfo) {
File file = new File(distroVersionFileName);
String statusKey = debugKeyPrefix + "status";
if (file.exists()) {
try {
byte[] versionBytes =
FileUtils.readBytes(file, DistroVersion.DISTRO_VERSION_FILE_LENGTH);
DistroVersion distroVersion = DistroVersion.fromBytes(versionBytes);
String formatVersionString = distroVersion.formatMajorVersion + "."
+ distroVersion.formatMinorVersion;
debugInfo.addStringEntry(statusKey, "OK")
.addStringEntry(debugKeyPrefix + "formatVersion", formatVersionString)
.addStringEntry(debugKeyPrefix + "rulesVersion",
distroVersion.rulesVersion)
.addStringEntry(debugKeyPrefix + "revision",
distroVersion.revision);
} catch (IOException | DistroException e) {
debugInfo.addStringEntry(statusKey, "ERROR");
debugInfo.addStringEntry(debugKeyPrefix + "exception_class",
e.getClass().getName());
debugInfo.addStringEntry(debugKeyPrefix + "exception_msg", e.getMessage());
logMessage("Error reading " + file, e);
}
} else {
debugInfo.addStringEntry(statusKey, "NOT_FOUND");
}
}
private static void logMessage(String msg, Throwable t) {
Slog.v(TAG, msg, t);
}
}