/*
 * 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.internal.util.dump;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
import android.util.proto.ProtoOutputStream;

import com.android.internal.util.IndentingPrintWriter;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;

Dump either to a proto or a print writer using the same interface.

This mirrors the interface of ProtoOutputStream.

/** * Dump either to a proto or a print writer using the same interface. * * <p>This mirrors the interface of {@link ProtoOutputStream}. */
public class DualDumpOutputStream { private static final String LOG_TAG = DualDumpOutputStream.class.getSimpleName(); // When writing to a proto, the proto private final @Nullable ProtoOutputStream mProtoStream; // When printing in clear text, the writer private final @Nullable IndentingPrintWriter mIpw; // Temporary storage of data when printing to mIpw private final LinkedList<DumpObject> mDumpObjects = new LinkedList<>(); private static abstract class Dumpable { final String name; private Dumpable(String name) { this.name = name; } abstract void print(IndentingPrintWriter ipw, boolean printName); } private static class DumpObject extends Dumpable { private final LinkedHashMap<String, ArrayList<Dumpable>> mSubObjects = new LinkedHashMap<>(); private DumpObject(String name) { super(name); } @Override void print(IndentingPrintWriter ipw, boolean printName) { if (printName) { ipw.println(name + "={"); } else { ipw.println("{"); } ipw.increaseIndent(); for (ArrayList<Dumpable> subObject: mSubObjects.values()) { int numDumpables = subObject.size(); if (numDumpables == 1) { subObject.get(0).print(ipw, true); } else { ipw.println(subObject.get(0).name + "=["); ipw.increaseIndent(); for (int i = 0; i < numDumpables; i++) { subObject.get(i).print(ipw, false); } ipw.decreaseIndent(); ipw.println("]"); } } ipw.decreaseIndent(); ipw.println("}"); }
Add new field / subobject to this object.

If a name is added twice, they will be printed as a array

Params:
  • fieldName – name of the field added
  • d – The dumpable to add
/** * Add new field / subobject to this object. * * <p>If a name is added twice, they will be printed as a array * * @param fieldName name of the field added * @param d The dumpable to add */
public void add(String fieldName, Dumpable d) { ArrayList<Dumpable> l = mSubObjects.get(fieldName); if (l == null) { l = new ArrayList<>(1); mSubObjects.put(fieldName, l); } l.add(d); } } private static class DumpField extends Dumpable { private final String mValue; private DumpField(String name, String value) { super(name); this.mValue = value; } @Override void print(IndentingPrintWriter ipw, boolean printName) { if (printName) { ipw.println(name + "=" + mValue); } else { ipw.println(mValue); } } }
Create a new DualDumpOutputStream.
Params:
/** * Create a new DualDumpOutputStream. * * @param proto the {@link ProtoOutputStream} */
public DualDumpOutputStream(@NonNull ProtoOutputStream proto) { mProtoStream = proto; mIpw = null; }
Create a new DualDumpOutputStream.
Params:
/** * Create a new DualDumpOutputStream. * * @param ipw the {@link IndentingPrintWriter} */
public DualDumpOutputStream(@NonNull IndentingPrintWriter ipw) { mProtoStream = null; mIpw = ipw; // Add root object mDumpObjects.add(new DumpObject(null)); } public void write(@NonNull String fieldName, long fieldId, double val) { if (mProtoStream != null) { mProtoStream.write(fieldId, val); } else { mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); } } public void write(@NonNull String fieldName, long fieldId, boolean val) { if (mProtoStream != null) { mProtoStream.write(fieldId, val); } else { mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); } } public void write(@NonNull String fieldName, long fieldId, int val) { if (mProtoStream != null) { mProtoStream.write(fieldId, val); } else { mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); } } public void write(@NonNull String fieldName, long fieldId, float val) { if (mProtoStream != null) { mProtoStream.write(fieldId, val); } else { mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); } } public void write(@NonNull String fieldName, long fieldId, byte[] val) { if (mProtoStream != null) { mProtoStream.write(fieldId, val); } else { mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, Arrays.toString(val))); } } public void write(@NonNull String fieldName, long fieldId, long val) { if (mProtoStream != null) { mProtoStream.write(fieldId, val); } else { mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); } } public void write(@NonNull String fieldName, long fieldId, @Nullable String val) { if (mProtoStream != null) { mProtoStream.write(fieldId, val); } else { mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, String.valueOf(val))); } } public long start(@NonNull String fieldName, long fieldId) { if (mProtoStream != null) { return mProtoStream.start(fieldId); } else { DumpObject d = new DumpObject(fieldName); mDumpObjects.getLast().add(fieldName, d); mDumpObjects.addLast(d); return System.identityHashCode(d); } } public void end(long token) { if (mProtoStream != null) { mProtoStream.end(token); } else { if (System.identityHashCode(mDumpObjects.getLast()) != token) { Log.w(LOG_TAG, "Unexpected token for ending " + mDumpObjects.getLast().name + " at " + Arrays.toString(Thread.currentThread().getStackTrace())); } mDumpObjects.removeLast(); } } public void flush() { if (mProtoStream != null) { mProtoStream.flush(); } else { if (mDumpObjects.size() == 1) { mDumpObjects.getFirst().print(mIpw, false); // Reset root object mDumpObjects.clear(); mDumpObjects.add(new DumpObject(null)); } mIpw.flush(); } }
Add a dump from a different service into this dump.

Only for clear text dump. For proto dump use write(String, long, byte[]).

Params:
  • fieldName – The name of the field
  • nestedState – The state of the dump
/** * Add a dump from a different service into this dump. * * <p>Only for clear text dump. For proto dump use {@link #write(String, long, byte[])}. * * @param fieldName The name of the field * @param nestedState The state of the dump */
public void writeNested(@NonNull String fieldName, byte[] nestedState) { if (mIpw == null) { Log.w(LOG_TAG, "writeNested does not work for proto logging"); return; } mDumpObjects.getLast().add(fieldName, new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim())); }
Returns:true iff we are dumping to a proto
/** * @return {@code true} iff we are dumping to a proto */
public boolean isProto() { return mProtoStream != null; } }