Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a534c01 authored by Philip P. Moltmann's avatar Philip P. Moltmann
Browse files

Only have one way of dumping print manager state

Add a class DualDumpOutputStream that maps proto-dump commands onto
print writer commands.

The effect of this is that there is only one - very proto dump like -
way to dump the print manager which is much easier to maintain.

The DualDumpOutputStream tries to produce a result similar to the
incident-report tool.

Test: adb shell dumpsys print
Change-Id: I1f0c56651eaa59f0ce90cdb08c71e89a96c48dd4
parent 6644e252
Loading
Loading
Loading
Loading
+265 −0
Original line number Diff line number Diff line
/*
 * 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.print;

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

import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;

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.
 *
 * <p>This mirrors the interface of {@link ProtoOutputStream}.
 */
public class DualDumpOutputStream {
    // 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.
         *
         * <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. Only one output should be set.
     *
     * @param proto If dumping to proto the {@link ProtoOutputStream}
     * @param ipw If dumping to a print writer, the {@link IndentingPrintWriter}
     */
    public DualDumpOutputStream(@Nullable ProtoOutputStream proto,
            @Nullable IndentingPrintWriter ipw) {
        Preconditions.checkArgument((proto == null) != (ipw == null));

        mProtoStream = proto;
        mIpw = ipw;

        if (!isProto()) {
            // 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 0;
        }
    }

    public void end(long token) {
        if (mProtoStream != null) {
            mProtoStream.end(token);
        } else {
            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.
     *
     * <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) {
        Preconditions.checkNotNull(mIpw);

        mDumpObjects.getLast().add(fieldName,
                new DumpField(fieldName, (new String(nestedState, StandardCharsets.UTF_8)).trim()));
    }

    /**
     * @return {@code true} iff we are dumping to a proto
     */
    public boolean isProto() {
        return mProtoStream != null;
    }
}
+108 −87

File changed.

Preview size limit exceeded, changes collapsed.

+16 −40
Original line number Diff line number Diff line
@@ -59,7 +59,9 @@ import android.util.proto.ProtoOutputStream;

import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.HandlerCaller;
import com.android.internal.print.DualDumpOutputStream;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.printspooler.R;
import com.android.printspooler.util.ApprovedPrintServices;
@@ -159,43 +161,10 @@ public final class PrintSpoolerService extends Service {
        return new PrintSpooler();
    }

    private void dumpLocked(PrintWriter pw, String[] args) {
        String prefix = (args.length > 0) ? args[0] : "";
        String tab = "  ";

        pw.append(prefix).append("print jobs:").println();
        final int printJobCount = mPrintJobs.size();
        for (int i = 0; i < printJobCount; i++) {
            PrintJobInfo printJob = mPrintJobs.get(i);
            pw.append(prefix).append(tab).append(printJob.toString());
            pw.println();
        }

        pw.append(prefix).append("print job files:").println();
        File[] files = getFilesDir().listFiles();
        if (files != null) {
            final int fileCount = files.length;
            for (int i = 0; i < fileCount; i++) {
                File file = files[i];
                if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
                    pw.append(prefix).append(tab).append(file.getName()).println();
                }
            }
        }

        pw.append(prefix).append("approved print services:").println();
        Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices();
        if (approvedPrintServices != null) {
            for (String approvedService : approvedPrintServices) {
                pw.append(prefix).append(tab).append(approvedService).println();
            }
        }
    }

    private void dumpLocked(@NonNull ProtoOutputStream proto) {
    private void dumpLocked(@NonNull DualDumpOutputStream proto) {
        int numPrintJobs = mPrintJobs.size();
        for (int i = 0; i < numPrintJobs; i++) {
            writePrintJobInfo(this, proto, PrintSpoolerInternalStateProto.PRINT_JOBS,
            writePrintJobInfo(this, proto, "print_jobs", PrintSpoolerInternalStateProto.PRINT_JOBS,
                    mPrintJobs.get(i));
        }

@@ -204,7 +173,8 @@ public final class PrintSpoolerService extends Service {
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
                    proto.write(PrintSpoolerInternalStateProto.PRINT_JOB_FILES, file.getName());
                    proto.write("print_job_files", PrintSpoolerInternalStateProto.PRINT_JOB_FILES,
                            file.getName());
                }
            }
        }
@@ -214,8 +184,8 @@ public final class PrintSpoolerService extends Service {
            for (String approvedService : approvedPrintServices) {
                ComponentName componentName = ComponentName.unflattenFromString(approvedService);
                if (componentName != null) {
                    writeComponentName(proto, PrintSpoolerInternalStateProto.APPROVED_SERVICES,
                            componentName);
                    writeComponentName(proto, "approved_services",
                            PrintSpoolerInternalStateProto.APPROVED_SERVICES, componentName);
                }
            }
        }
@@ -244,9 +214,15 @@ public final class PrintSpoolerService extends Service {
        try {
            synchronized (mLock) {
                if (dumpAsProto) {
                    dumpLocked(new ProtoOutputStream(fd));
                    dumpLocked(new DualDumpOutputStream(new ProtoOutputStream(fd), null));
                } else {
                    dumpLocked(pw, args);
                    try (FileOutputStream out = new FileOutputStream(fd)) {
                        try (PrintWriter w = new PrintWriter(out)) {
                            dumpLocked(new DualDumpOutputStream(null, new IndentingPrintWriter(w,
                                    "  ")));
                        }
                    } catch (IOException ignored) {
                    }
                }
            }
        } finally {
+10 −16
Original line number Diff line number Diff line
@@ -57,7 +57,9 @@ import android.util.proto.ProtoOutputStream;

import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.print.DualDumpOutputStream;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService;

@@ -670,20 +672,24 @@ public final class PrintManagerService extends SystemService {
            final long identity = Binder.clearCallingIdentity();
            try {
                if (dumpAsProto) {
                    dump(new ProtoOutputStream(fd), userStatesToDump);
                    dump(new DualDumpOutputStream(new ProtoOutputStream(fd), null),
                            userStatesToDump);
                } else {
                    dump(fd, pw, userStatesToDump);
                    pw.println("PRINT MANAGER STATE (dumpsys print)");

                    dump(new DualDumpOutputStream(null, new IndentingPrintWriter(pw, "  ")),
                            userStatesToDump);
                }
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        private void dump(@NonNull ProtoOutputStream proto,
        private void dump(@NonNull DualDumpOutputStream proto,
                @NonNull ArrayList<UserState> userStatesToDump) {
            final int userStateCount = userStatesToDump.size();
            for (int i = 0; i < userStateCount; i++) {
                long token = proto.start(PrintServiceDumpProto.USER_STATES);
                long token = proto.start("user_states", PrintServiceDumpProto.USER_STATES);
                userStatesToDump.get(i).dump(proto);
                proto.end(token);
            }
@@ -691,18 +697,6 @@ public final class PrintManagerService extends SystemService {
            proto.flush();
        }

        private void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
                @NonNull ArrayList<UserState> userStatesToDump) {
            pw = Preconditions.checkNotNull(pw);

            pw.println("PRINT MANAGER STATE (dumpsys print)");
            final int userStateCount = userStatesToDump.size();
            for (int i = 0; i < userStateCount; i++) {
                userStatesToDump.get(i).dump(fd, pw, "");
                pw.println();
            }
        }

        private void registerContentObservers() {
            final Uri enabledPrintServicesUri = Settings.Secure.getUriFor(
                    Settings.Secure.DISABLED_PRINT_SERVICES);
+13 −33
Original line number Diff line number Diff line
@@ -47,11 +47,10 @@ import android.printservice.IPrintService;
import android.printservice.IPrintServiceClient;
import android.service.print.ActivePrintServiceProto;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.print.DualDumpOutputStream;

import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -532,49 +531,30 @@ final class RemotePrintService implements DeathRecipient {
        }
    }

    public void dump(@NonNull ProtoOutputStream proto) {
        writeComponentName(proto, ActivePrintServiceProto.COMPONENT_NAME, mComponentName);
    public void dump(@NonNull DualDumpOutputStream proto) {
        writeComponentName(proto, "component_name", ActivePrintServiceProto.COMPONENT_NAME,
                mComponentName);

        proto.write(ActivePrintServiceProto.IS_DESTROYED, mDestroyed);
        proto.write(ActivePrintServiceProto.IS_BOUND, isBound());
        proto.write(ActivePrintServiceProto.HAS_DISCOVERY_SESSION, mHasPrinterDiscoverySession);
        proto.write(ActivePrintServiceProto.HAS_ACTIVE_PRINT_JOBS, mHasActivePrintJobs);
        proto.write(ActivePrintServiceProto.IS_DISCOVERING_PRINTERS,
        proto.write("is_destroyed", ActivePrintServiceProto.IS_DESTROYED, mDestroyed);
        proto.write("is_bound", ActivePrintServiceProto.IS_BOUND, isBound());
        proto.write("has_discovery_session", ActivePrintServiceProto.HAS_DISCOVERY_SESSION,
                mHasPrinterDiscoverySession);
        proto.write("has_active_print_jobs", ActivePrintServiceProto.HAS_ACTIVE_PRINT_JOBS,
                mHasActivePrintJobs);
        proto.write("is_discovering_printers", ActivePrintServiceProto.IS_DISCOVERING_PRINTERS,
                mDiscoveryPriorityList != null);

        synchronized (mLock) {
            if (mTrackedPrinterList != null) {
                int numTrackedPrinters = mTrackedPrinterList.size();
                for (int i = 0; i < numTrackedPrinters; i++) {
                    writePrinterId(proto, ActivePrintServiceProto.TRACKED_PRINTERS,
                            mTrackedPrinterList.get(i));
                    writePrinterId(proto, "tracked_printers",
                            ActivePrintServiceProto.TRACKED_PRINTERS, mTrackedPrinterList.get(i));
                }
            }
        }
    }

    public void dump(PrintWriter pw, String prefix) {
        String tab = "  ";
        pw.append(prefix).append("service:").println();
        pw.append(prefix).append(tab).append("componentName=")
                .append(mComponentName.flattenToString()).println();
        pw.append(prefix).append(tab).append("destroyed=")
                .append(String.valueOf(mDestroyed)).println();
        pw.append(prefix).append(tab).append("bound=")
                .append(String.valueOf(isBound())).println();
        pw.append(prefix).append(tab).append("hasDicoverySession=")
                .append(String.valueOf(mHasPrinterDiscoverySession)).println();
        pw.append(prefix).append(tab).append("hasActivePrintJobs=")
                .append(String.valueOf(mHasActivePrintJobs)).println();
        pw.append(prefix).append(tab).append("isDiscoveringPrinters=")
                .append(String.valueOf(mDiscoveryPriorityList != null)).println();

        synchronized (mLock) {
            pw.append(prefix).append(tab).append("trackedPrinters=").append(
                    (mTrackedPrinterList != null) ? mTrackedPrinterList.toString() : "null");
        }
    }

    private boolean isBound() {
        return mPrintService != null;
    }
Loading