GadgetBridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java

304 lines
11 KiB
Java

package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
public class PBWReader {
private static final Logger LOG = LoggerFactory.getLogger(PBWReader.class);
private static final HashMap<String, Byte> appFileTypesMap;
private static final HashMap<String, Byte> fwFileTypesMap;
static {
appFileTypesMap = new HashMap<>();
appFileTypesMap.put("application", PebbleProtocol.PUTBYTES_TYPE_BINARY);
appFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_RESOURCES);
appFileTypesMap.put("worker", PebbleProtocol.PUTBYTES_TYPE_WORKER);
}
static {
fwFileTypesMap = new HashMap<>();
fwFileTypesMap.put("firmware", PebbleProtocol.PUTBYTES_TYPE_FIRMWARE);
fwFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_SYSRESOURCES);
}
private final Uri uri;
private final ContentResolver cr;
private GBDeviceApp app;
private ArrayList<PebbleInstallable> pebbleInstallables = null;
private boolean isFirmware = false;
private boolean isLanguage = false;
private boolean isValid = false;
private String hwRevision = null;
private short mSdkVersion;
private short mAppVersion;
private int mIconId;
private int mFlags;
public PBWReader(Uri uri, Context context, String platform) throws FileNotFoundException {
this.uri = uri;
cr = context.getContentResolver();
InputStream fin = new BufferedInputStream(cr.openInputStream(uri));
if (uri.toString().endsWith(".pbl")) {
STM32CRC stm32crc = new STM32CRC();
try {
byte[] buf = new byte[2000];
while (fin.available() > 0) {
int count = fin.read(buf);
stm32crc.addData(buf, count);
}
fin.close();
} catch (IOException e) {
e.printStackTrace();
return;
}
int crc = stm32crc.getResult();
// language file
app = new GBDeviceApp(UUID.randomUUID(), "Language File", "unknown", "unknown", GBDeviceApp.Type.UNKNOWN);
File f = new File(uri.getPath());
pebbleInstallables = new ArrayList<>();
pebbleInstallables.add(new PebbleInstallable("lang", (int) f.length(), crc, PebbleProtocol.PUTBYTES_TYPE_FILE));
isValid = true;
isLanguage = true;
return;
}
String platformDir = "";
if (!uri.toString().endsWith(".pbz")) {
platformDir = platform + "/";
if (platform.equals("aplite")) {
boolean hasApliteDir = false;
InputStream afin = new BufferedInputStream(cr.openInputStream(uri));
ZipInputStream zis = new ZipInputStream(afin);
ZipEntry ze;
try {
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().startsWith("aplite/")) {
hasApliteDir = true;
break;
}
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
}
if (!hasApliteDir) {
platformDir = "";
}
}
}
ZipInputStream zis = new ZipInputStream(fin);
ZipEntry ze;
pebbleInstallables = new ArrayList<>();
byte[] buffer = new byte[1024];
int count;
try {
while ((ze = zis.getNextEntry()) != null) {
String fileName = ze.getName();
if (fileName.equals(platformDir + "manifest.json")) {
long bytes = ze.getSize();
if (bytes > 8192) // that should be too much
break;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((count = zis.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
String jsonString = baos.toString();
try {
JSONObject json = new JSONObject(jsonString);
HashMap<String, Byte> fileTypeMap;
try {
JSONObject firmware = json.getJSONObject("firmware");
fileTypeMap = fwFileTypesMap;
isFirmware = true;
hwRevision = firmware.getString("hwrev");
} catch (JSONException e) {
fileTypeMap = appFileTypesMap;
isFirmware = false;
}
for (Map.Entry<String, Byte> entry : fileTypeMap.entrySet()) {
try {
JSONObject jo = json.getJSONObject(entry.getKey());
String name = jo.getString("name");
int size = jo.getInt("size");
long crc = jo.getLong("crc");
byte type = entry.getValue();
pebbleInstallables.add(new PebbleInstallable(platformDir + name, size, (int) crc, type));
LOG.info("found file to install: " + platformDir + name);
isValid = true;
} catch (JSONException e) {
// not fatal
}
}
} catch (JSONException e) {
// no JSON at all that is a problem
isValid = false;
e.printStackTrace();
break;
}
} else if (fileName.equals("appinfo.json")) {
long bytes = ze.getSize();
if (bytes > 8192) // that should be too much
break;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((count = zis.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
String jsonString = baos.toString();
try {
JSONObject json = new JSONObject(jsonString);
String appName = json.getString("shortName");
String appCreator = json.getString("companyName");
String appVersion = json.getString("versionLabel");
UUID uuid = UUID.fromString(json.getString("uuid"));
if (appName != null && appCreator != null && appVersion != null) {
// FIXME: dont assume WATCHFACE
app = new GBDeviceApp(uuid, appName, appCreator, appVersion, GBDeviceApp.Type.WATCHFACE);
}
} catch (JSONException e) {
isValid = false;
e.printStackTrace();
break;
}
} else if (fileName.equals(platformDir + "pebble-app.bin")) {
zis.read(buffer, 0, 108);
byte[] tmp_buf = new byte[32];
ByteBuffer buf = ByteBuffer.wrap(buffer);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.getLong(); // header, TODO: verifiy
buf.getShort(); // struct version, TODO: verify
mSdkVersion = buf.getShort();
mAppVersion = buf.getShort();
buf.getShort(); // size
buf.getInt(); // offset
buf.getInt(); // crc
buf.get(tmp_buf, 0, 32); // app name
buf.get(tmp_buf, 0, 32); // author
mIconId = buf.getInt();
LOG.info("got icon id from pebble-app.bin: " + mIconId);
buf.getInt(); // symbol table addr
mFlags = buf.getInt();
LOG.info("got flags from pebble-app.bin: " + mFlags);
// more follows but, not interesting for us
}
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean isFirmware() {
return isFirmware;
}
public boolean isLanguage() {
return isLanguage;
}
public boolean isValid() {
return isValid;
}
public GBDeviceApp getGBDeviceApp() {
return app;
}
public InputStream getInputStreamFile(String filename) {
InputStream fin;
try {
fin = new BufferedInputStream(cr.openInputStream(uri));
if (isLanguage) {
return fin;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
ZipInputStream zis = new ZipInputStream(fin);
ZipEntry ze;
try {
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().equals(filename)) {
return zis;
}
}
zis.close();
} catch (Throwable e) {
try {
zis.close();
} catch (IOException e1) {
// ignore
}
e.printStackTrace();
}
return null;
}
public PebbleInstallable[] getPebbleInstallables() {
if (pebbleInstallables == null) {
return null;
}
return pebbleInstallables.toArray(new PebbleInstallable[pebbleInstallables.size()]);
}
public String getHWRevision() {
return hwRevision;
}
public short getSdkVersion() {
return mSdkVersion;
}
public short getAppVersion() {
return mAppVersion;
}
public int getFlags() {
return mFlags;
}
public int getIconId() {
return mIconId;
}
}