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