mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-19 02:38:20 +00:00
-> Pulling out the parts of device profile which can (and need to be) initialized and accessed without access to an Activity context, ie. the invariant bits. -> The invariant bits are stored in InvariantDeviceProfile which is initialized statically from LauncherAppState. -> The DeviceProfile contains the Activity context-dependent bits, and we will create one of these for each Activity instance, and this instance is accessed through the Launcher activity. -> It's possible that we can continue to refactor this such that all appropriate dimensions can be computed without an Activity context (by only specifying orientation). This would be an extension of this CL and allow us to know exactly how launcher will look in both orientations from any context. Sets the stage for some improvements around b/19514688 Change-Id: Ia7daccf14d8ca2b9cb340b8780b684769e9f1892
338 lines
12 KiB
Java
338 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2013 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.launcher3;
|
|
|
|
import com.android.launcher3.backup.BackupProtos.CheckedMessage;
|
|
import com.android.launcher3.backup.BackupProtos.Favorite;
|
|
import com.android.launcher3.backup.BackupProtos.Key;
|
|
import com.android.launcher3.backup.BackupProtos.Journal;
|
|
import com.android.launcher3.backup.BackupProtos.Resource;
|
|
import com.android.launcher3.backup.BackupProtos.Screen;
|
|
import com.android.launcher3.backup.BackupProtos.Widget;
|
|
|
|
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
|
|
import com.google.protobuf.nano.MessageNano;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
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;
|
|
|
|
|
|
/**
|
|
* Commandline utility for decoding Launcher3 backup protocol buffers.
|
|
*
|
|
* <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=" | launcher_protoutil -k
|
|
* </pre>
|
|
*
|
|
* <P>This tool understands these file names and will use the embedded Key to detect the type and
|
|
* extract the payload automatically:
|
|
* <pre>
|
|
* launcher_protoutil /tmp/TDpDQUlnL0pxVTVnOD0=
|
|
* </pre>
|
|
*
|
|
* <P>With payload debugging enabled, base64-encoded protocol buffers will be written to the logs.
|
|
* Copy the encoded snippet from the log, and specify the type explicitly, with the Logs flags:
|
|
* <pre>
|
|
* 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,
|
|
Screen.class,
|
|
Resource.class,
|
|
Widget.class
|
|
};
|
|
static final int ICON_TYPE_BITMAP = 1;
|
|
|
|
public static void main(String[ ] args)
|
|
throws Exception {
|
|
Class defaultType = null;
|
|
boolean extractImages = false;
|
|
boolean fromLogs = false;
|
|
int skip = 0;
|
|
List<File> files = new LinkedList<File>();
|
|
boolean verbose = false;
|
|
|
|
for (int i = 0; i < args.length; i++) {
|
|
if ("-k".equals(args[i])) {
|
|
defaultType = Key.class;
|
|
} else if ("-f".equals(args[i])) {
|
|
defaultType = Favorite.class;
|
|
} else if ("-j".equals(args[i])) {
|
|
defaultType = Journal.class;
|
|
} else if ("-i".equals(args[i])) {
|
|
defaultType = Resource.class;
|
|
} else if ("-s".equals(args[i])) {
|
|
defaultType = Screen.class;
|
|
} else if ("-w".equals(args[i])) {
|
|
defaultType = Widget.class;
|
|
} else if ("-S".equals(args[i])) {
|
|
if ((i + 1) < args.length) {
|
|
skip = Integer.valueOf(args[++i]);
|
|
} else {
|
|
usage(args);
|
|
}
|
|
} else if ("-x".equals(args[i])) {
|
|
extractImages = true;
|
|
} else if ("-v".equals(args[i])) {
|
|
verbose = true;
|
|
} else if ("-L".equals(args[i])) {
|
|
fromLogs = true;
|
|
} else if (args[i] != null && !args[i].startsWith("-")) {
|
|
files.add(new File(args[i]));
|
|
} else {
|
|
System.err.println("Unsupported flag: " + args[i]);
|
|
usage(args);
|
|
}
|
|
}
|
|
|
|
if (defaultType == null && files.isEmpty()) {
|
|
// can't infer file type without the key
|
|
usage(args);
|
|
}
|
|
|
|
if (files.size() > 1 && defaultType != null) {
|
|
System.err.println("Explicit type ignored for multiple files.");
|
|
defaultType = null;
|
|
}
|
|
|
|
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);
|
|
if (key != null) {
|
|
type = TYPES[key.type];
|
|
if (verbose) {
|
|
System.err.println(source.getName() + " is a " + type.getSimpleName());
|
|
System.out.println(key.toString());
|
|
}
|
|
}
|
|
} else {
|
|
type = defaultType;
|
|
}
|
|
|
|
// read in the bytes
|
|
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
|
BufferedInputStream input = null;
|
|
if (source.getName() == STANDARD_IN) {
|
|
input = new BufferedInputStream(System.in);
|
|
} else {
|
|
try {
|
|
input = new BufferedInputStream(new FileInputStream(source));
|
|
} catch (FileNotFoundException e) {
|
|
System.err.println("failed to open file: " + source + ", " + e);
|
|
System.exit(1);
|
|
}
|
|
}
|
|
byte[] buffer = new byte[1024];
|
|
try {
|
|
while (input.available() > 0) {
|
|
int n = input.read(buffer);
|
|
int offset = 0;
|
|
if (skip > 0) {
|
|
offset = Math.min(skip, n);
|
|
n -= offset;
|
|
skip -= offset;
|
|
}
|
|
if (n > 0) {
|
|
byteStream.write(buffer, offset, n);
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
System.err.println("failed to read input: " + e);
|
|
System.exit(1);
|
|
}
|
|
|
|
MessageNano proto = null;
|
|
byte[] payload = byteStream.toByteArray();
|
|
if (type == Key.class) {
|
|
proto = decodeKey(payload, fromLogs);
|
|
} else if (type != null) {
|
|
proto = decodeBackupData(payload, type, fromLogs);
|
|
}
|
|
|
|
// Generic string output
|
|
if (proto != null) {
|
|
System.out.println(proto.toString());
|
|
}
|
|
|
|
if (extractImages) {
|
|
String prefix = "stdin";
|
|
if (source != null) {
|
|
prefix = source.getName();
|
|
}
|
|
// save off the icon bits in a file for inspection
|
|
if (proto instanceof Resource) {
|
|
Resource icon = (Resource) proto;
|
|
writeImageData(icon.data, prefix + ".png");
|
|
}
|
|
|
|
// save off the icon bits in a file for inspection
|
|
if (proto instanceof Favorite) {
|
|
Favorite favorite = (Favorite) proto;
|
|
if (favorite.iconType == ICON_TYPE_BITMAP) {
|
|
writeImageData(favorite.icon, prefix + ".png");
|
|
}
|
|
}
|
|
|
|
// save off the widget icon and preview bits in files for inspection
|
|
if (proto instanceof Widget) {
|
|
Widget widget = (Widget) proto;
|
|
if (widget.icon != null) {
|
|
writeImageData(widget.icon.data, prefix + "_icon.png");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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.");
|
|
return null;
|
|
}
|
|
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);
|
|
return null;
|
|
} catch (IllegalArgumentException base64Exception) {
|
|
System.err.println("failed to extract key from filename: " + base64Exception);
|
|
return null;
|
|
}
|
|
|
|
// keys are self-checked
|
|
if (key.checksum != checkKey(key)) {
|
|
System.err.println("key ckecksum failed");
|
|
return null;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
private static void writeImageData(byte[] data, String path) {
|
|
FileOutputStream iconFile = null;
|
|
try {
|
|
iconFile = new FileOutputStream(path);
|
|
iconFile.write(data);
|
|
System.err.println("wrote " + path);
|
|
} catch (IOException e) {
|
|
System.err.println("failed to write image file: " + e);
|
|
} finally {
|
|
if (iconFile != null) {
|
|
try {
|
|
iconFile.close();
|
|
} catch (IOException e) {
|
|
System.err.println("failed to close the image file: " + e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static long checkKey(Key key) {
|
|
CRC32 checksum = new CRC32();
|
|
checksum.update(key.type);
|
|
checksum.update((int) (key.id & 0xffff));
|
|
checksum.update((int) ((key.id >> 32) & 0xffff));
|
|
if (key.name != null && key.name.length() > 0) {
|
|
checksum.update(key.name.getBytes());
|
|
}
|
|
return checksum.getValue();
|
|
}
|
|
|
|
private static void usage(String[] args) {
|
|
System.err.println("launcher_protoutil [-x] [-S b] [-k|-f|-i|-s|-w] [filename]");
|
|
System.err.println("\t-k\tdecode a key");
|
|
System.err.println("\t-f\tdecode a favorite");
|
|
System.err.println("\t-i\tdecode a icon");
|
|
System.err.println("\t-s\tdecode a screen");
|
|
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-v\tprint key type data, as well as payload");
|
|
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);
|
|
}
|
|
} |