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