WindowManager ProtoLog feature

This CL implements the on-device part of ProtoLog
- the new logging system for WindowManager.

Design doc: go/windowmanager-log2proto

Change-Id: I2c88c97dabb3465ffc0615b8017b335a494bca59
Bug:
Test: atest FrameworksServicesTests:com.android.server.protolog protologtool-tests
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1643221..80bc1af 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -1,3 +1,60 @@
+java_library {
+    name: "protolog-common",
+    srcs: [
+        "java/com/android/server/protolog/common/**/*.java",
+    ],
+    host_supported: true,
+}
+
+java_library {
+    name: "services.core.wm.protologgroups",
+    srcs: [
+        "java/com/android/server/wm/ProtoLogGroup.java",
+    ],
+    static_libs: ["protolog-common"],
+}
+
+genrule {
+    name: "services.core.protologsrc",
+    srcs: [":services.core.wm.protologgroups", "java/**/*.java"],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) transform-protolog-calls " +
+      "--protolog-class com.android.server.protolog.common.ProtoLog " +
+      "--protolog-impl-class com.android.server.protolog.ProtoLogImpl " +
+      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
+      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--output-srcjar $(out) " +
+      "$(locations java/**/*.java)",
+    out: ["services.core.protolog.srcjar"],
+}
+
+genrule {
+    name: "generate-protolog.json",
+    srcs: [":services.core.wm.protologgroups", "java/**/*.java"],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) generate-viewer-config " +
+      "--protolog-class com.android.server.protolog.common.ProtoLog " +
+      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
+      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--viewer-conf $(out) " +
+      "$(locations java/**/*.java)",
+    out: ["services.core.protolog.json"],
+}
+
+genrule {
+    name: "checked-protolog.json",
+    srcs: [
+        ":generate-protolog.json",
+        ":services.core.protolog.json",
+    ],
+    cmd: "cp $(location :generate-protolog.json) $(out) && " +
+      "{ diff $(out) $(location :services.core.protolog.json) >/dev/null 2>&1 || " +
+      "{ echo -e '##### ProtoLog viewer config is stale. ### \nRun: \n " +
+      "cp $(location :generate-protolog.json) " +
+      "$(location :services.core.protolog.json)\n' >&2 && false; } }",
+    out: ["services.core.protolog.json"],
+}
+
 java_library_static {
     name: "services.core.unboosted",
 
@@ -12,7 +69,7 @@
         ],
     },
     srcs: [
-        "java/**/*.java",
+        ":services.core.protologsrc",
         ":dumpstate_aidl",
         ":idmap2_aidl",
         ":installd_aidl",
@@ -34,6 +91,7 @@
 
     required: [
         "gps_debug.conf",
+        "protolog.conf.json.gz",
     ],
 
     static_libs: [
@@ -81,3 +139,15 @@
     name: "gps_debug.conf",
     src: "java/com/android/server/location/gps_debug.conf",
 }
+
+genrule {
+    name: "services.core.json.gz",
+    srcs: [":checked-protolog.json"],
+    out: ["services.core.protolog.json.gz"],
+    cmd: "gzip < $(in) > $(out)",
+}
+
+prebuilt_etc {
+    name: "protolog.conf.json.gz",
+    src: ":services.core.json.gz",
+}
diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
new file mode 100644
index 0000000..239a425
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2019 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.protolog;
+
+import static com.android.server.protolog.ProtoLogFileProto.LOG;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
+import static com.android.server.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
+import static com.android.server.protolog.ProtoLogFileProto.VERSION;
+import static com.android.server.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.DOUBLE_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
+import static com.android.server.protolog.ProtoLogMessage.MESSAGE_HASH;
+import static com.android.server.protolog.ProtoLogMessage.SINT64_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.STR_PARAMS;
+
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.server.protolog.common.LogDataType;
+import com.android.server.utils.TraceBuffer;
+import com.android.server.wm.ProtoLogGroup;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IllegalFormatConversionException;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+
+/**
+ * A service for the ProtoLog logging system.
+ */
+public class ProtoLogImpl {
+    private static final TreeMap<String, IProtoLogGroup> LOG_GROUPS = new TreeMap<>();
+
+    private static void addLogGroupEnum(IProtoLogGroup[] config) {
+        Arrays.stream(config).forEach(group -> LOG_GROUPS.put(group.name(), group));
+    }
+
+    static {
+        addLogGroupEnum(ProtoLogGroup.values());
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void d(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance()
+                .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void v(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
+                args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void i(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void w(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void e(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance()
+                .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void wtf(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
+    }
+
+    private static final int BUFFER_CAPACITY = 1024 * 1024;
+    private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.pb";
+    private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz";
+    private static final String TAG = "ProtoLog";
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+    static final String PROTOLOG_VERSION = "1.0.0";
+
+    private final File mLogFile;
+    private final TraceBuffer mBuffer;
+    private final ProtoLogViewerConfigReader mViewerConfig;
+
+    private boolean mProtoLogEnabled;
+    private boolean mProtoLogEnabledLockFree;
+    private final Object mProtoLogEnabledLock = new Object();
+
+    private static ProtoLogImpl sServiceInstance = null;
+
+    /**
+     * Returns the single instance of the ProtoLogImpl singleton class.
+     */
+    public static synchronized ProtoLogImpl getSingleInstance() {
+        if (sServiceInstance == null) {
+            sServiceInstance = new ProtoLogImpl(new File(LOG_FILENAME), BUFFER_CAPACITY,
+                    new ProtoLogViewerConfigReader());
+        }
+        return sServiceInstance;
+    }
+
+    @VisibleForTesting
+    public static synchronized void setSingleInstance(@Nullable ProtoLogImpl instance) {
+        sServiceInstance = instance;
+    }
+
+    @VisibleForTesting
+    public enum LogLevel {
+        DEBUG, VERBOSE, INFO, WARN, ERROR, WTF
+    }
+
+    /**
+     * Main log method, do not call directly.
+     */
+    @VisibleForTesting
+    public void log(LogLevel level, IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString, Object[] args) {
+        if (group.isLogToProto()) {
+            logToProto(messageHash, paramsMask, args);
+        }
+        if (group.isLogToLogcat()) {
+            logToLogcat(group.getTag(), level, messageHash, messageString, args);
+        }
+    }
+
+    private void logToLogcat(String tag, LogLevel level, int messageHash,
+            @Nullable String messageString, Object[] args) {
+        String message = null;
+        if (messageString == null) {
+            messageString = mViewerConfig.getViewerString(messageHash);
+        }
+        if (messageString != null) {
+            try {
+                message = String.format(messageString, args);
+            } catch (IllegalFormatConversionException ex) {
+                Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+            }
+        }
+        if (message == null) {
+            StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
+            for (Object o : args) {
+                builder.append(" ").append(o);
+            }
+            message = builder.toString();
+        }
+        passToLogcat(tag, level, message);
+    }
+
+    /**
+     * SLog wrapper.
+     */
+    @VisibleForTesting
+    public void passToLogcat(String tag, LogLevel level, String message) {
+        switch (level) {
+            case DEBUG:
+                Slog.d(tag, message);
+                break;
+            case VERBOSE:
+                Slog.v(tag, message);
+                break;
+            case INFO:
+                Slog.i(tag, message);
+                break;
+            case WARN:
+                Slog.w(tag, message);
+                break;
+            case ERROR:
+                Slog.e(tag, message);
+                break;
+            case WTF:
+                Slog.wtf(tag, message);
+                break;
+        }
+    }
+
+    private void logToProto(int messageHash, int paramsMask, Object[] args) {
+        if (!isProtoEnabled()) {
+            return;
+        }
+        try {
+            ProtoOutputStream os = new ProtoOutputStream();
+            long token = os.start(LOG);
+            os.write(MESSAGE_HASH, messageHash);
+            os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+
+            int argIndex = 0;
+            ArrayList<Long> longParams = new ArrayList<>();
+            ArrayList<Double> doubleParams = new ArrayList<>();
+            ArrayList<Boolean> booleanParams = new ArrayList<>();
+            for (Object o : args) {
+                int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
+                try {
+                    switch (type) {
+                        case LogDataType.STRING:
+                            os.write(STR_PARAMS, o.toString());
+                            break;
+                        case LogDataType.LONG:
+                            longParams.add(((Number) o).longValue());
+                            break;
+                        case LogDataType.DOUBLE:
+                            doubleParams.add(((Number) o).doubleValue());
+                            break;
+                        case LogDataType.BOOLEAN:
+                            booleanParams.add((boolean) o);
+                            break;
+                    }
+                } catch (ClassCastException ex) {
+                    // Should not happen unless there is an error in the ProtoLogTool.
+                    os.write(STR_PARAMS, "(INVALID PARAMS_MASK) " + o.toString());
+                    Slog.e(TAG, "Invalid ProtoLog paramsMask", ex);
+                }
+                argIndex++;
+            }
+            if (longParams.size() > 0) {
+                os.writePackedSInt64(SINT64_PARAMS,
+                        longParams.stream().mapToLong(i -> i).toArray());
+            }
+            if (doubleParams.size() > 0) {
+                os.writePackedDouble(DOUBLE_PARAMS,
+                        doubleParams.stream().mapToDouble(i -> i).toArray());
+            }
+            if (booleanParams.size() > 0) {
+                boolean[] arr = new boolean[booleanParams.size()];
+                for (int i = 0; i < booleanParams.size(); i++) {
+                    arr[i] = booleanParams.get(i);
+                }
+                os.writePackedBool(BOOLEAN_PARAMS, arr);
+            }
+            os.end(token);
+            mBuffer.add(os);
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception while logging to proto", e);
+        }
+    }
+
+
+    @VisibleForTesting
+    ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
+        mLogFile = file;
+        mBuffer = new TraceBuffer(bufferCapacity);
+        mViewerConfig = viewerConfig;
+    }
+
+    /**
+     * Starts the logging a circular proto buffer.
+     *
+     * @param pw Print writer
+     */
+    public void startProtoLog(@Nullable PrintWriter pw) {
+        if (isProtoEnabled()) {
+            return;
+        }
+        synchronized (mProtoLogEnabledLock) {
+            logAndPrintln(pw, "Start logging to " + mLogFile + ".");
+            mBuffer.resetBuffer();
+            mProtoLogEnabled = true;
+            mProtoLogEnabledLockFree = true;
+        }
+    }
+
+    /**
+     * Stops logging to proto.
+     *
+     * @param pw          Print writer
+     * @param writeToFile If the current buffer should be written to disk or not
+     */
+    public void stopProtoLog(@Nullable PrintWriter pw, boolean writeToFile) {
+        if (!isProtoEnabled()) {
+            return;
+        }
+        synchronized (mProtoLogEnabledLock) {
+            logAndPrintln(pw, "Stop logging to " + mLogFile + ". Waiting for log to flush.");
+            mProtoLogEnabled = mProtoLogEnabledLockFree = false;
+            if (writeToFile) {
+                writeProtoLogToFileLocked();
+                logAndPrintln(pw, "Log written to " + mLogFile + ".");
+            }
+            if (mProtoLogEnabled) {
+                logAndPrintln(pw, "ERROR: logging was re-enabled while waiting for flush.");
+                throw new IllegalStateException("logging enabled while waiting for flush.");
+            }
+        }
+    }
+
+    /**
+     * Returns {@code true} iff logging to proto is enabled.
+     */
+    public boolean isProtoEnabled() {
+        return mProtoLogEnabledLockFree;
+    }
+
+    private int setLogging(ShellCommand shell, boolean setTextLogging, boolean value) {
+        String group;
+        while ((group = shell.getNextArg()) != null) {
+            IProtoLogGroup g = LOG_GROUPS.get(group);
+            if (g != null) {
+                if (setTextLogging) {
+                    g.setLogToLogcat(value);
+                } else {
+                    g.setLogToProto(value);
+                }
+            } else {
+                logAndPrintln(shell.getOutPrintWriter(), "No IProtoLogGroup named " + group);
+                return -1;
+            }
+        }
+        return 0;
+    }
+
+    private int unknownCommand(PrintWriter pw) {
+        pw.println("Unknown command");
+        pw.println("Window manager logging options:");
+        pw.println("  start: Start proto logging");
+        pw.println("  stop: Stop proto logging");
+        pw.println("  enable [group...]: Enable proto logging for given groups");
+        pw.println("  disable [group...]: Disable proto logging for given groups");
+        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
+        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
+        return -1;
+    }
+
+    /**
+     * Responds to a shell command.
+     */
+    public int onShellCommand(ShellCommand shell) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        String cmd = shell.getNextArg();
+        if (cmd == null) {
+            return unknownCommand(pw);
+        }
+        switch (cmd) {
+            case "start":
+                startProtoLog(pw);
+                return 0;
+            case "stop":
+                stopProtoLog(pw, true);
+                return 0;
+            case "status":
+                logAndPrintln(pw, getStatus());
+                return 0;
+            case "enable":
+                return setLogging(shell, false, true);
+            case "enable-text":
+                mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+                return setLogging(shell, true, true);
+            case "disable":
+                return setLogging(shell, false, false);
+            case "disable-text":
+                return setLogging(shell, true, false);
+            default:
+                return unknownCommand(pw);
+        }
+    }
+
+    /**
+     * Returns a human-readable ProtoLog status text.
+     */
+    public String getStatus() {
+        return "ProtoLog status: "
+                + ((isProtoEnabled()) ? "Enabled" : "Disabled")
+                + "\nEnabled log groups: \n  Proto: "
+                + LOG_GROUPS.values().stream().filter(
+                    it -> it.isEnabled() && it.isLogToProto())
+                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+                + "\n  Logcat: "
+                + LOG_GROUPS.values().stream().filter(
+                    it -> it.isEnabled() && it.isLogToLogcat())
+                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+                + "\nLogging definitions loaded: " + mViewerConfig.knownViewerStringsNumber();
+    }
+
+    /**
+     * Writes the log buffer to a new file for the bugreport.
+     *
+     * This method is synchronized with {@code #startProtoLog(PrintWriter)} and
+     * {@link #stopProtoLog(PrintWriter, boolean)}.
+     */
+    public void writeProtoLogToFile() {
+        synchronized (mProtoLogEnabledLock) {
+            writeProtoLogToFileLocked();
+        }
+    }
+
+    private void writeProtoLogToFileLocked() {
+        try {
+            long offset =
+                    (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000));
+            ProtoOutputStream proto = new ProtoOutputStream();
+            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            proto.write(VERSION, PROTOLOG_VERSION);
+            proto.write(REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS, offset);
+            mBuffer.writeTraceToFile(mLogFile, proto);
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to write buffer to file", e);
+        }
+    }
+
+
+    static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+        Slog.i(TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+            pw.flush();
+        }
+    }
+}
+
diff --git a/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java b/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
new file mode 100644
index 0000000..4944217
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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.protolog;
+
+import static com.android.server.protolog.ProtoLogImpl.logAndPrintln;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Handles loading and parsing of ProtoLog viewer configuration.
+ */
+public class ProtoLogViewerConfigReader {
+    private Map<Integer, String> mLogMessageMap = null;
+
+    /** Returns message format string for its hash or null if unavailable. */
+    public synchronized String getViewerString(int messageHash) {
+        if (mLogMessageMap != null) {
+            return mLogMessageMap.get(messageHash);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Reads the specified viewer configuration file. Does nothing if the config is already loaded.
+     */
+    public synchronized void loadViewerConfig(PrintWriter pw, String viewerConfigFilename) {
+        if (mLogMessageMap != null) {
+            return;
+        }
+        try {
+            InputStreamReader config = new InputStreamReader(
+                    new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
+            BufferedReader reader = new BufferedReader(config);
+            StringBuilder builder = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                builder.append(line).append('\n');
+            }
+            reader.close();
+            JSONObject json = new JSONObject(builder.toString());
+            JSONObject messages = json.getJSONObject("messages");
+
+            mLogMessageMap = new TreeMap<>();
+            Iterator it = messages.keys();
+            while (it.hasNext()) {
+                String key = (String) it.next();
+                try {
+                    int hash = Integer.parseInt(key);
+                    JSONObject val = messages.getJSONObject(key);
+                    String msg = val.getString("message");
+                    mLogMessageMap.put(hash, msg);
+                } catch (NumberFormatException expected) {
+                    // Not a messageHash - skip it
+                }
+            }
+            logAndPrintln(pw, "Loaded " + mLogMessageMap.size() + " log definitions from "
+                    + viewerConfigFilename);
+        } catch (FileNotFoundException e) {
+            logAndPrintln(pw, "Unable to load log definitions: File "
+                    + viewerConfigFilename + " not found." + e);
+        } catch (IOException e) {
+            logAndPrintln(pw, "Unable to load log definitions: IOException while reading "
+                    + viewerConfigFilename + ". " + e);
+        } catch (JSONException e) {
+            logAndPrintln(pw,
+                    "Unable to load log definitions: JSON parsing exception while reading "
+                            + viewerConfigFilename + ". " + e);
+        }
+    }
+
+    /**
+     * Returns the number of loaded log definitions kept in memory.
+     */
+    public synchronized int knownViewerStringsNumber() {
+        if (mLogMessageMap != null) {
+            return mLogMessageMap.size();
+        }
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
new file mode 100644
index 0000000..7bb27b2
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * Error while converting a bitmask representing a list of LogDataTypes.
+ */
+public class BitmaskConversionException extends RuntimeException {
+    BitmaskConversionException(String msg) {
+        super(msg);
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java b/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
new file mode 100644
index 0000000..2c65341
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * Defines a log group configuration object for ProtoLog. Should be implemented as en enum.
+ */
+public interface IProtoLogGroup {
+    /**
+     * if false all log statements for this group are excluded from compilation,
+     */
+    boolean isEnabled();
+
+    /**
+     * is binary logging enabled for the group.
+     */
+    boolean isLogToProto();
+
+    /**
+     * is text logging enabled for the group.
+     */
+    boolean isLogToLogcat();
+
+    /**
+     * returns true is any logging is enabled for this group.
+     */
+    default boolean isLogToAny() {
+        return isLogToLogcat() || isLogToProto();
+    }
+
+    /**
+     * returns the name of the source of the logged message
+     */
+    String getTag();
+
+    /**
+     * set binary logging for this group.
+     */
+    void setLogToProto(boolean logToProto);
+
+    /**
+     * set text logging for this group.
+     */
+    void setLogToLogcat(boolean logToLogcat);
+
+    /**
+     * returns name of the logging group.
+     */
+    String name();
+}
diff --git a/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java b/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
new file mode 100644
index 0000000..947bf98
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * Unsupported/invalid message format string error.
+ */
+public class InvalidFormatStringException extends RuntimeException {
+    public InvalidFormatStringException(String message) {
+        super(message);
+    }
+
+    public InvalidFormatStringException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/LogDataType.java b/services/core/java/com/android/server/protolog/common/LogDataType.java
new file mode 100644
index 0000000..e73b41a
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/LogDataType.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a type of logged data encoded in the proto.
+ */
+public class LogDataType {
+    // When updating this list make sure to update bitmask conversion methods accordingly.
+    // STR type should be the first in the enum in order to be the default type.
+    public static final int STRING = 0b00;
+    public static final int LONG = 0b01;
+    public static final int DOUBLE = 0b10;
+    public static final int BOOLEAN = 0b11;
+
+    private static final int TYPE_WIDTH = 2;
+    private static final int TYPE_MASK = 0b11;
+
+    /**
+     * Creates a bitmask representing a list of data types.
+     */
+    public static int logDataTypesToBitMask(List<Integer> types) {
+        if (types.size() > 16) {
+            throw new BitmaskConversionException("Too many log call parameters "
+                    + "- max 16 parameters supported");
+        }
+        int mask = 0;
+        for (int i = 0; i < types.size(); i++) {
+            int x = types.get(i);
+            mask = mask | (x << (i * TYPE_WIDTH));
+        }
+        return mask;
+    }
+
+    /**
+     * Decodes a bitmask to a list of LogDataTypes of provided length.
+     */
+    public static int bitmaskToLogDataType(int bitmask, int index) {
+        if (index > 16) {
+            throw new BitmaskConversionException("Max 16 parameters allowed");
+        }
+        return (bitmask >> (index * TYPE_WIDTH)) & TYPE_MASK;
+    }
+
+    /**
+     * Creates a list of LogDataTypes from a message format string.
+     */
+    public static List<Integer> parseFormatString(String messageString) {
+        ArrayList<Integer> types = new ArrayList<>();
+        for (int i = 0; i < messageString.length(); ) {
+            if (messageString.charAt(i) == '%') {
+                if (i + 1 >= messageString.length()) {
+                    throw new InvalidFormatStringException("Invalid format string in config");
+                }
+                switch (messageString.charAt(i + 1)) {
+                    case 'b':
+                        types.add(LogDataType.BOOLEAN);
+                        break;
+                    case 'd':
+                    case 'o':
+                    case 'x':
+                        types.add(LogDataType.LONG);
+                        break;
+                    case 'f':
+                    case 'e':
+                    case 'g':
+                        types.add(LogDataType.DOUBLE);
+                        break;
+                    case 's':
+                        types.add(LogDataType.STRING);
+                        break;
+                    case '%':
+                        break;
+                    default:
+                        throw new InvalidFormatStringException("Invalid format string field"
+                                + " %${messageString[i + 1]}");
+                }
+                i += 2;
+            } else {
+                i += 1;
+            }
+        }
+        return types;
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/ProtoLog.java b/services/core/java/com/android/server/protolog/common/ProtoLog.java
new file mode 100644
index 0000000..b631bcb
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/ProtoLog.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * ProtoLog API - exposes static logging methods. Usage of this API is similar
+ * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
+ * a messageString, which is a format string for the log message (has to be a string literal or
+ * a concatenation of string literals) and a vararg array of parameters for the formatter.
+ *
+ * The syntax for the message string is a subset of {@code java.util.Formatter} syntax.
+ * Supported conversions:
+ * %b - boolean
+ * %d, %o and %x - integral type (Short, Integer or Long)
+ * %f, %e and %g - floating point type (Float or Double)
+ * %s - string
+ * %% - a literal percent character
+ * The width and precision modifiers are supported, argument_index and flags are not.
+ *
+ * Methods in this class are stubs, that are replaced by optimised versions by the ProtoLogTool
+ * during build.
+ */
+public class ProtoLog {
+    /**
+     * DEBUG level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void d(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * VERBOSE level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void v(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * INFO level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void i(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * WARNING level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void w(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * ERROR level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void e(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * WHAT A TERRIBLE FAILURE level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void wtf(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/utils/TraceBuffer.java
similarity index 79%
rename from services/core/java/com/android/server/wm/WindowTraceBuffer.java
rename to services/core/java/com/android/server/utils/TraceBuffer.java
index 8c65884..0567960 100644
--- a/services/core/java/com/android/server/wm/WindowTraceBuffer.java
+++ b/services/core/java/com/android/server/utils/TraceBuffer.java
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm;
-
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+package com.android.server.utils;
 
 import android.util.proto.ProtoOutputStream;
 
@@ -33,31 +29,32 @@
 import java.util.Queue;
 
 /**
- * Buffer used for window tracing.
+ * Buffer used for tracing and logging.
  */
-class WindowTraceBuffer {
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
+public class TraceBuffer {
     private final Object mBufferLock = new Object();
 
     private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
     private int mBufferUsedSize;
     private int mBufferCapacity;
 
-    WindowTraceBuffer(int bufferCapacity) {
+    public TraceBuffer(int bufferCapacity) {
         mBufferCapacity = bufferCapacity;
         resetBuffer();
     }
 
-    int getAvailableSpace() {
+    public int getAvailableSpace() {
         return mBufferCapacity - mBufferUsedSize;
     }
 
-    int size() {
+    /**
+     * Returns buffer size.
+     */
+    public int size() {
         return mBuffer.size();
     }
 
-    void setCapacity(int capacity) {
+    public void setCapacity(int capacity) {
         mBufferCapacity = capacity;
     }
 
@@ -68,7 +65,7 @@
      * @throws IllegalStateException if the element cannot be added because it is larger
      *                               than the buffer size.
      */
-    void add(ProtoOutputStream proto) {
+    public void add(ProtoOutputStream proto) {
         int protoLength = proto.getRawSize();
         if (protoLength > mBufferCapacity) {
             throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
@@ -88,19 +85,18 @@
     }
 
     /**
-     * Writes the trace buffer to disk.
+     * Writes the trace buffer to disk inside the encapsulatingProto..
      */
-    void writeTraceToFile(File traceFile) throws IOException {
+    public void writeTraceToFile(File traceFile, ProtoOutputStream encapsulatingProto)
+            throws IOException {
         synchronized (mBufferLock) {
             traceFile.delete();
             try (OutputStream os = new FileOutputStream(traceFile)) {
                 traceFile.setReadable(true /* readable */, false /* ownerOnly */);
-                ProtoOutputStream proto = new ProtoOutputStream();
-                proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
-                os.write(proto.getBytes());
+                os.write(encapsulatingProto.getBytes());
                 for (ProtoOutputStream protoOutputStream : mBuffer) {
-                    proto = protoOutputStream;
-                    byte[] protoBytes = proto.getBytes();
+                    encapsulatingProto = protoOutputStream;
+                    byte[] protoBytes = encapsulatingProto.getBytes();
                     os.write(protoBytes);
                 }
                 os.flush();
@@ -131,7 +127,7 @@
     /**
      * Removes all elements form the buffer
      */
-    void resetBuffer() {
+    public void resetBuffer() {
         synchronized (mBufferLock) {
             mBuffer.clear();
             mBufferUsedSize = 0;
@@ -143,7 +139,10 @@
         return mBufferUsedSize;
     }
 
-    String getStatus() {
+    /**
+     * Returns the buffer status in human-readable form.
+     */
+    public String getStatus() {
         synchronized (mBufferLock) {
             return "Buffer size: "
                     + mBufferCapacity
diff --git a/services/core/java/com/android/server/wm/ProtoLogGroup.java b/services/core/java/com/android/server/wm/ProtoLogGroup.java
new file mode 100644
index 0000000..313cceb
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ProtoLogGroup.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 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.wm;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.server.protolog.common.ProtoLog;
+
+/**
+ * Defines logging groups for ProtoLog.
+ *
+ * This file is used by the ProtoLogTool to generate optimized logging code. All of its dependencies
+ * must be included in services.core.wm.protologgroups build target.
+ */
+public enum ProtoLogGroup implements IProtoLogGroup {
+    GENERIC_WM(true, true, false, "WindowManager"),
+
+    TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+    private final boolean mEnabled;
+    private volatile boolean mLogToProto;
+    private volatile boolean mLogToLogcat;
+    private final String mTag;
+
+    /**
+     * @param enabled     set to false to exclude all log statements for this group from
+     *                    compilation,
+     *                    they will not be available in runtime.
+     * @param logToProto  enable binary logging for the group
+     * @param logToLogcat enable text logging for the group
+     * @param tag         name of the source of the logged message
+     */
+    ProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+        this.mEnabled = enabled;
+        this.mLogToProto = logToProto;
+        this.mLogToLogcat = logToLogcat;
+        this.mTag = tag;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    @Override
+    public boolean isLogToProto() {
+        return mLogToProto;
+    }
+
+    @Override
+    public boolean isLogToLogcat() {
+        return mLogToLogcat;
+    }
+
+    @Override
+    public boolean isLogToAny() {
+        return mLogToLogcat || mLogToProto;
+    }
+
+    @Override
+    public String getTag() {
+        return mTag;
+    }
+
+    @Override
+    public void setLogToProto(boolean logToProto) {
+        this.mLogToProto = logToProto;
+    }
+
+    @Override
+    public void setLogToLogcat(boolean logToLogcat) {
+        this.mLogToLogcat = logToLogcat;
+    }
+
+    /**
+     * Test function for automated integration tests. Can be also called manually from adb shell.
+     */
+    @VisibleForTesting
+    public static void testProtoLog() {
+        ProtoLog.e(ProtoLogGroup.TEST_GROUP,
+                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
+                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 14214b4..4dd3313 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -265,6 +265,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
+import com.android.server.protolog.ProtoLogImpl;
 import com.android.server.utils.PriorityDump;
 
 import java.io.BufferedWriter;
@@ -5811,6 +5812,11 @@
         pw.print(mWindowTracing.getStatus() + "\n");
     }
 
+    private void dumpLogStatus(PrintWriter pw) {
+        pw.println("WINDOW MANAGER LOGGING (dumpsys window logging)");
+        pw.println(ProtoLogImpl.getSingleInstance().getStatus());
+    }
+
     private void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
         pw.println("WINDOW MANAGER SESSIONS (dumpsys window sessions)");
         for (int i=0; i<mSessions.size(); i++) {
@@ -6206,6 +6212,9 @@
             } else if ("trace".equals(cmd)) {
                 dumpTraceStatus(pw);
                 return;
+            } else if ("logging".equals(cmd)) {
+                dumpLogStatus(pw);
+                return;
             } else if ("refresh".equals(cmd)) {
                 dumpHighRefreshRateBlacklist(pw);
                 return;
@@ -6267,6 +6276,10 @@
             if (dumpAll) {
                 pw.println(separator);
             }
+            dumpLogStatus(pw);
+            if (dumpAll) {
+                pw.println(separator);
+            }
             dumpHighRefreshRateBlacklist(pw);
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 7384bb7..e01cbf2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -28,6 +28,8 @@
 import android.view.IWindowManager;
 import android.view.Surface;
 
+import com.android.server.protolog.ProtoLogImpl;
+
 import java.io.PrintWriter;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -75,6 +77,8 @@
                     // the output trace file, so the shell gets the correct semantics for where
                     // trace files can be written.
                     return mInternal.mWindowTracing.onShellCommand(this);
+                case "logging":
+                    return ProtoLogImpl.getSingleInstance().onShellCommand(this);
                 case "set-user-rotation":
                     return runSetDisplayUserRotation(pw);
                 case "set-fix-to-user-rotation":
@@ -389,6 +393,8 @@
         if (!IS_USER) {
             pw.println("  tracing (start | stop)");
             pw.println("    Start or stop window tracing.");
+            pw.println("  logging (start | stop | enable | disable | enable-text | disable-text)");
+            pw.println("    Logging settings.");
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 765e347..bb66530 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -19,6 +19,9 @@
 import static android.os.Build.IS_USER;
 
 import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
 import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
 import static com.android.server.wm.WindowManagerTraceProto.WHERE;
 import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
@@ -31,6 +34,9 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 
+import com.android.server.protolog.ProtoLogImpl;
+import com.android.server.utils.TraceBuffer;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -50,6 +56,7 @@
     private static final int BUFFER_CAPACITY_ALL = 4096 * 1024;
     private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb";
     private static final String TAG = "WindowTracing";
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
 
     private final WindowManagerService mService;
     private final Choreographer mChoreographer;
@@ -57,7 +64,7 @@
 
     private final Object mEnabledLock = new Object();
     private final File mTraceFile;
-    private final WindowTraceBuffer mBuffer;
+    private final com.android.server.utils.TraceBuffer mBuffer;
     private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
             log("onFrame" /* where */);
 
@@ -84,7 +91,7 @@
         mService = service;
         mGlobalLock = globalLock;
         mTraceFile = file;
-        mBuffer = new WindowTraceBuffer(bufferCapacity);
+        mBuffer = new TraceBuffer(bufferCapacity);
         setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */);
     }
 
@@ -94,6 +101,7 @@
             return;
         }
         synchronized (mEnabledLock) {
+            ProtoLogImpl.getSingleInstance().startProtoLog(pw);
             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
             mBuffer.resetBuffer();
             mEnabled = mEnabledLockFree = true;
@@ -132,6 +140,7 @@
                 logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
             }
         }
+        ProtoLogImpl.getSingleInstance().stopProtoLog(pw, writeToFile);
     }
 
     private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
@@ -317,6 +326,7 @@
         synchronized (mEnabledLock) {
             writeTraceToFileLocked();
         }
+        ProtoLogImpl.getSingleInstance().writeProtoLogToFile();
     }
 
     private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
@@ -334,11 +344,13 @@
     private void writeTraceToFileLocked() {
         try {
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
-            mBuffer.writeTraceToFile(mTraceFile);
+            ProtoOutputStream proto = new ProtoOutputStream();
+            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            mBuffer.writeTraceToFile(mTraceFile, proto);
         } catch (IOException e) {
             Log.e(TAG, "Unable to write buffer to file", e);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         }
     }
-}
\ No newline at end of file
+}