1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server; 18 19 import android.content.Context; 20 import android.os.Binder; 21 import android.service.runtime.DebugEntryProto; 22 import android.service.runtime.RuntimeServiceInfoProto; 23 import android.util.Slog; 24 import android.util.proto.ProtoOutputStream; 25 26 import libcore.timezone.TimeZoneDataFiles; 27 import libcore.util.CoreLibraryDebug; 28 import libcore.util.DebugInfo; 29 30 import com.android.internal.util.DumpUtils; 31 import com.android.timezone.distro.DistroException; 32 import com.android.timezone.distro.DistroVersion; 33 import com.android.timezone.distro.FileUtils; 34 import com.android.timezone.distro.TimeZoneDistro; 35 36 import java.io.File; 37 import java.io.FileDescriptor; 38 import java.io.IOException; 39 import java.io.PrintWriter; 40 41 /** 42 * This service exists only as a "dumpsys" target which reports information about the status of the 43 * runtime and related libraries. 44 */ 45 public class RuntimeService extends Binder { 46 47 private static final String TAG = "RuntimeService"; 48 49 private final Context mContext; 50 RuntimeService(Context context)51 public RuntimeService(Context context) { 52 mContext = context; 53 } 54 55 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)56 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 57 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { 58 return; 59 } 60 61 boolean protoFormat = hasOption(args, "--proto"); 62 ProtoOutputStream proto = null; 63 64 DebugInfo coreLibraryDebugInfo = CoreLibraryDebug.getDebugInfo(); 65 addTimeZoneApkDebugInfo(coreLibraryDebugInfo); 66 67 if (protoFormat) { 68 proto = new ProtoOutputStream(fd); 69 reportTimeZoneInfoProto(coreLibraryDebugInfo, proto); 70 } else { 71 reportTimeZoneInfo(coreLibraryDebugInfo, pw); 72 } 73 74 if (protoFormat) { 75 proto.flush(); 76 } 77 } 78 79 /** Returns {@code true} if {@code args} contains {@code arg}. */ hasOption(String[] args, String arg)80 private static boolean hasOption(String[] args, String arg) { 81 for (String opt : args) { 82 if (arg.equals(opt)) { 83 return true; 84 } 85 } 86 return false; 87 } 88 89 /** 90 * Add information to {@link DebugInfo} about the time zone data supplied by the 91 * "Time zone updates via APK" feature. 92 */ addTimeZoneApkDebugInfo(DebugInfo coreLibraryDebugInfo)93 private static void addTimeZoneApkDebugInfo(DebugInfo coreLibraryDebugInfo) { 94 // Add /data tz data set using the DistroVersion class (which libcore cannot use). 95 // This update mechanism will be removed after the time zone APEX is launched so this 96 // untidiness will disappear with it. 97 String debugKeyPrefix = "core_library.timezone.source.data_"; 98 String versionFileName = TimeZoneDataFiles.getDataTimeZoneFile( 99 TimeZoneDistro.DISTRO_VERSION_FILE_NAME); 100 addDistroVersionDebugInfo(versionFileName, debugKeyPrefix, coreLibraryDebugInfo); 101 } 102 103 /** 104 * Prints {@code coreLibraryDebugInfo} to {@code pw}. 105 * 106 * <p>If you change this method, make sure to modify 107 * {@link #reportTimeZoneInfoProto(DebugInfo, ProtoOutputStream)} as well. 108 */ reportTimeZoneInfo(DebugInfo coreLibraryDebugInfo, PrintWriter pw)109 private static void reportTimeZoneInfo(DebugInfo coreLibraryDebugInfo, 110 PrintWriter pw) { 111 pw.println("Core Library Debug Info: "); 112 for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) { 113 pw.print(debugEntry.getKey()); 114 pw.print(": \""); 115 pw.print(debugEntry.getStringValue()); 116 pw.println("\""); 117 } 118 } 119 120 /** 121 * Adds {@code coreLibraryDebugInfo} to {@code protoStream}. 122 * 123 * <p>If you change this method, make sure to modify 124 * {@link #reportTimeZoneInfo(DebugInfo, PrintWriter)}. 125 */ reportTimeZoneInfoProto( DebugInfo coreLibraryDebugInfo, ProtoOutputStream protoStream)126 private static void reportTimeZoneInfoProto( 127 DebugInfo coreLibraryDebugInfo, ProtoOutputStream protoStream) { 128 for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) { 129 long entryToken = protoStream.start(RuntimeServiceInfoProto.DEBUG_ENTRY); 130 protoStream.write(DebugEntryProto.KEY, debugEntry.getKey()); 131 protoStream.write(DebugEntryProto.STRING_VALUE, debugEntry.getStringValue()); 132 protoStream.end(entryToken); 133 } 134 } 135 136 /** 137 * Adds version information to {@code debugInfo} from the distro_version file that may exist 138 * at {@code distroVersionFileName}. If the file does not exist or cannot be read this is 139 * reported as debug information too. 140 */ addDistroVersionDebugInfo(String distroVersionFileName, String debugKeyPrefix, DebugInfo debugInfo)141 private static void addDistroVersionDebugInfo(String distroVersionFileName, 142 String debugKeyPrefix, DebugInfo debugInfo) { 143 File file = new File(distroVersionFileName); 144 String statusKey = debugKeyPrefix + "status"; 145 if (file.exists()) { 146 try { 147 byte[] versionBytes = 148 FileUtils.readBytes(file, DistroVersion.DISTRO_VERSION_FILE_LENGTH); 149 DistroVersion distroVersion = DistroVersion.fromBytes(versionBytes); 150 String formatVersionString = distroVersion.formatMajorVersion + "." 151 + distroVersion.formatMinorVersion; 152 debugInfo.addStringEntry(statusKey, "OK") 153 .addStringEntry(debugKeyPrefix + "formatVersion", formatVersionString) 154 .addStringEntry(debugKeyPrefix + "rulesVersion", 155 distroVersion.rulesVersion) 156 .addStringEntry(debugKeyPrefix + "revision", 157 distroVersion.revision); 158 } catch (IOException | DistroException e) { 159 debugInfo.addStringEntry(statusKey, "ERROR"); 160 debugInfo.addStringEntry(debugKeyPrefix + "exception_class", 161 e.getClass().getName()); 162 debugInfo.addStringEntry(debugKeyPrefix + "exception_msg", e.getMessage()); 163 logMessage("Error reading " + file, e); 164 } 165 } else { 166 debugInfo.addStringEntry(statusKey, "NOT_FOUND"); 167 } 168 } 169 logMessage(String msg, Throwable t)170 private static void logMessage(String msg, Throwable t) { 171 Slog.v(TAG, msg, t); 172 } 173 } 174