content: /*
   * 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);
      }
  }
  