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

Commit 2fd31824 authored by Chris Wren's avatar Chris Wren
Browse files

support multiple files and fix logcat smippets for launcher_protoutil

plus some code cleanup

Change-Id: I09b4a6aedbb1251a65d379464d830da694d38a38
parent f6a22ada
Loading
Loading
Loading
Loading
+167 −132
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */
package com.android.launcher3;

import com.android.launcher3.backup.BackupProtos;
import com.android.launcher3.backup.BackupProtos.CheckedMessage;
import com.android.launcher3.backup.BackupProtos.Favorite;
import com.android.launcher3.backup.BackupProtos.Key;
@@ -35,6 +34,8 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.System;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.CRC32;

import javax.xml.bind.DatatypeConverter;
@@ -46,7 +47,7 @@ import javax.xml.bind.DatatypeConverter;
 * <P>When using com.android.internal.backup.LocalTransport, the file names are base64-encoded Key
 * protocol buffers with a prefix, that have been base64-encoded again by the transport:
 * <pre>
 *     echo TDpDQUlnL0pxVTVnOD0= | base64 -D | dd bs=1 skip=2 | base64 -D | launcher_protoutil -k
 *     echo "TDpDQUlnL0pxVTVnOD0=" | launcher_protoutil -k
 * </pre>
 *
 * <P>This tool understands these file names and will use the embedded Key to detect the type and
@@ -56,13 +57,19 @@ import javax.xml.bind.DatatypeConverter;
 * </pre>
 *
 * <P>With payload debugging enabled, base64-encoded protocol buffers will be written to the logs.
 * Copy the encoded log snippet into a file, and specify the type explicitly:
 * Copy the encoded snippet from the log, and specify the type explicitly, with the Logs flags:
 * <pre>
 *    base64 -D icon.log > icon.bin
 *    launcher_protoutil -i icon.bin
 *    echo "CAEYLiCJ9JKsDw==" | launcher_protoutil -L -k
 * </pre>
 * For backup payloads it is more convenient to copy the log snippet to a file:
 * <pre>
 *    launcher_protoutil -L -f favorite.log
 * </pre>
 */
class DecoderRing {

    public static final String STANDARD_IN = "**stdin**";

    private static Class[] TYPES = {
            Key.class,
            Favorite.class,
@@ -74,24 +81,25 @@ class DecoderRing {

    public static void main(String[ ] args)
            throws Exception {
        File source = null;
        Class type = null;
        Class defaultType = null;
        boolean extractImages = false;
        boolean fromLogs = false;
        int skip = 0;
        List<File> files = new LinkedList<File>();

        for (int i = 0; i < args.length; i++) {
            if ("-k".equals(args[i])) {
                type = Key.class;
                defaultType = Key.class;
            } else if ("-f".equals(args[i])) {
                type = Favorite.class;
                defaultType = Favorite.class;
            } else if ("-j".equals(args[i])) {
                type = Journal.class;
                defaultType = Journal.class;
            } else if ("-i".equals(args[i])) {
                type = Resource.class;
                defaultType = Resource.class;
            } else if ("-s".equals(args[i])) {
                type = Screen.class;
                defaultType = Screen.class;
            } else if ("-w".equals(args[i])) {
                type = Widget.class;
                defaultType = Widget.class;
            } else if ("-S".equals(args[i])) {
                if ((i + 1) < args.length) {
                    skip = Integer.valueOf(args[++i]);
@@ -100,50 +108,44 @@ class DecoderRing {
                }
            } else if ("-x".equals(args[i])) {
                extractImages = true;
            } else if ("-L".equals(args[i])) {
                fromLogs = true;
            } else if (args[i] != null && !args[i].startsWith("-")) {
                source = new File(args[i]);
                files.add(new File(args[i]));
            } else {
                System.err.println("Unsupported flag: " + args[i]);
                usage(args);
            }
        }

        if (type == null) {
            if (source == null) {
        if (defaultType == null && files.isEmpty()) {
            // can't infer file type without the key
            usage(args);
            } else {
                Key key = new Key();
                try {
                    byte[] rawKey = DatatypeConverter.parseBase64Binary(source.getName());
                    if (rawKey[0] != 'L' || rawKey[1] != ':') {
                        System.err.println("you must specify the payload type. " +
                                source.getName() + " is not a launcher backup key.");
                        System.exit(1);
        }
                    String encodedPayload = new String(rawKey, 2, rawKey.length - 2);
                    byte[] keyProtoData = DatatypeConverter.parseBase64Binary(encodedPayload);
                    key = Key.parseFrom(keyProtoData);
                } catch (InvalidProtocolBufferNanoException protoException) {
                    System.err.println("failed to extract key from filename: " + protoException);
                    System.exit(1);
                } catch (IllegalArgumentException base64Exception) {
                    System.err.println("failed to extract key from filename: " + base64Exception);
                    System.exit(1);

        if (files.size() > 1) {
            System.err.println("Explicit type ignored for multiple files.");
            defaultType = null;
        }
                // keys are self-checked
                if (key.checksum != checkKey(key)) {
                    System.err.println("key ckecksum failed");
                    System.exit(1);

        if (files.isEmpty()) {
            files.add(new File(STANDARD_IN));
        }

        for (File source : files) {
            Class type = null;
            if (defaultType == null) {
                Key key = decodeKey(source.getName().getBytes(), fromLogs);
                type = TYPES[key.type];
                System.err.println("This is a " + type.getSimpleName() + " backup");
            }
            } else {
                type = defaultType;
            }

            // read in the bytes
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            BufferedInputStream input = null;
        if (source == null) {
            if (source.getName() == STANDARD_IN) {
                input = new BufferedInputStream(System.in);
            } else {
                try {
@@ -173,43 +175,11 @@ class DecoderRing {
            }

            MessageNano proto = null;
            byte[] payload = byteStream.toByteArray();
            if (type == Key.class) {
            Key key = new Key();
            try {
                key = Key.parseFrom(byteStream.toByteArray());
            } catch (InvalidProtocolBufferNanoException e) {
                System.err.println("failed to parse proto: " + e);
                System.exit(1);
            }
            // keys are self-checked
            if (key.checksum != checkKey(key)) {
                System.err.println("key checksum failed");
                System.exit(1);
            }
            proto = key;
                proto = decodeKey(payload, fromLogs);
            } else {
            // other types are wrapped in a checksum message
            CheckedMessage wrapper = new CheckedMessage();
            try {
                MessageNano.mergeFrom(wrapper, byteStream.toByteArray());
            } catch (InvalidProtocolBufferNanoException e) {
                System.err.println("failed to parse wrapper: " + e);
                System.exit(1);
            }
            CRC32 checksum = new CRC32();
            checksum.update(wrapper.payload);
            if (wrapper.checksum != checksum.getValue()) {
                System.err.println("wrapper checksum failed");
                System.exit(1);
            }
            // decode the actual message
            proto = (MessageNano) type.newInstance();
            try {
                MessageNano.mergeFrom(proto, wrapper.payload);
            } catch (InvalidProtocolBufferNanoException e) {
                System.err.println("failed to parse proto: " + e);
                System.exit(1);
            }
                proto = decodeBackupData(payload, type, fromLogs);
            }

            // Generic string output
@@ -245,11 +215,75 @@ class DecoderRing {
                    }
                }
            }

        // success
        }
        System.exit(0);
    }

    // In logcat, backup data is base64 encoded, but in localtransport files it is raw
    private static MessageNano decodeBackupData(byte[] payload, Class type, boolean fromLogs)
            throws InstantiationException, IllegalAccessException {
        MessageNano proto;// other types are wrapped in a checksum message
        CheckedMessage wrapper = new CheckedMessage();
        try {
            if (fromLogs) {
                payload = DatatypeConverter.parseBase64Binary(new String(payload));
            }
            MessageNano.mergeFrom(wrapper, payload);
        } catch (InvalidProtocolBufferNanoException e) {
            System.err.println("failed to parse wrapper: " + e);
            System.exit(1);
        }

        CRC32 checksum = new CRC32();
        checksum.update(wrapper.payload);
        if (wrapper.checksum != checksum.getValue()) {
            System.err.println("wrapper checksum failed");
            System.exit(1);
        }

        // decode the actual message
        proto = (MessageNano) type.newInstance();
        try {
            MessageNano.mergeFrom(proto, wrapper.payload);
        } catch (InvalidProtocolBufferNanoException e) {
            System.err.println("failed to parse proto: " + e);
            System.exit(1);
        }
        return proto;
    }

    // In logcat, keys are base64 encoded with no prefix.
    // The localtransport adds a prefix and the base64 encodes the whole thing again.
    private static Key decodeKey(byte[] payload, boolean fromLogs) {
        Key key = new Key();
        try {
            String encodedKey = new String(payload);
            if (!fromLogs) {
                byte[] rawKey = DatatypeConverter.parseBase64Binary(encodedKey);
                if (rawKey[0] != 'L' || rawKey[1] != ':') {
                    System.err.println(encodedKey + " is not a launcher backup key.");
                    System.exit(1);
                }
                encodedKey = new String(rawKey, 2, rawKey.length - 2);
            }
            byte[] keyProtoData = DatatypeConverter.parseBase64Binary(encodedKey);
            key = Key.parseFrom(keyProtoData);
        } catch (InvalidProtocolBufferNanoException protoException) {
            System.err.println("failed to extract key from filename: " + protoException);
            System.exit(1);
        } catch (IllegalArgumentException base64Exception) {
            System.err.println("failed to extract key from filename: " + base64Exception);
            System.exit(1);
        }

        // keys are self-checked
        if (key.checksum != checkKey(key)) {
            System.err.println("key ckecksum failed");
            System.exit(1);
        }
        return key;
    }

    private static void writeImageData(byte[] data, String path) {
        FileOutputStream iconFile = null;
        try {
@@ -289,6 +323,7 @@ class DecoderRing {
        System.err.println("\t-w\tdecode a widget");
        System.err.println("\t-S b\tskip b bytes");
        System.err.println("\t-x\textract image data to files");
        System.err.println("\t-l\texpect data from logcat, instead of the local transport");
        System.err.println("\tfilename\tread from filename, not stdin");
        System.exit(1);
    }