public static abstract class ApiCreator<T> implements Parcelable.Creator<T> {
    @Override
    public final T createFromParcel(Parcel source) {
        ApiParcelInputWrp wrp = new ApiParcelInputWrp(source);
        return create(wrp);
    }

    public abstract T create(ApiDataInput d);
} public static abstract class ApiParcelable extends ApiObject implements IApiParcelable {
    public int describeContents() {
        return 0;
    }

    public final void writeToParcel(Parcel dest, int flags) {
        baseWriteToParcel(this, dest, flags);
    }


    public static int baseDescribeContents() {
        return 0;
    }

    public static void baseWriteToParcel(IApiParcelable parcelable, Parcel dest, int flags) {
        ApiParcelOutputWrp p = new ApiParcelOutputWrp(dest);
        parcelable.save(p, flags);
    }
} public interface ApiDataInput extends ApiDataInputOutputBase {
    int getDataAppVersionCode(); // version code verze aplikace, ktera puvodne dana data ulozila...

    boolean readBoolean();
    int readInt();
    long readLong();

    byte[] readBytes();
    String readString();
    Bitmap readBitmap();
    <T extends IApiObject> T readObject(ApiCreator<T> creator);
    <T extends IApiParcelable> T readParcelableWithName();
    <T extends IApiObject> ImmutableList<T> readImmutableList(ApiCreator<T> creator);
    <T extends IApiParcelable> ImmutableList<T> readImmutableListWithNames();

    <T extends IApiParcelable> T readOptParcelableWithName();
} public interface ApiDataOutput extends ApiDataInputOutputBase {
    boolean write(boolean value);
    void write(int value);
    void write(long value);

    void write(byte[] value);
    void write(String value);
    void write(Bitmap value, int flags);
    void write(IApiObject value, int flags);
    void writeWithName(IApiParcelable value, int flags);
    void write(Collection<? extends IApiObject> value, int flags);
    void writeOpt(IApiObject value, int flags);
    void writeOptWithName(IApiParcelable value, int flags);
} public static abstract class BaseLocalReceiver extends BaseLocalReceiverProt {
    public BaseLocalReceiver(String action) {
        super(action);
    }

    public BaseLocalReceiver(IntentFilter intentFilter) {
        super(intentFilter);
    }

    @Override
    public boolean register(Context context) {
        return super.register(context);
    }
} public class EqualsUtils {
    public static int HASHCODE_INVALID = 654123456;
    public static int HASHCODE_INVALID_REPLACEMENT = 654123457;

    public static <T> int hashCodeCheckNull(T o) {
        return o == null ? 0 : o.hashCode();
    }

    public static <T> boolean equalsCheckNull(T lhs, T rhs) {
        return lhs == null ? rhs == null : lhs.equals(rhs);
    }

    public static int itemsHashCode(List<?> items) {
        int result = 17;
        if (items != null) {
            for (int i = 0; i < items.size(); i++) {
                result = 31 * result + (items.get(i) == null ? 0 : items.get(i).hashCode());
            }
        }
        return result;
    }

    public static int itemsHashCode(int[] items) {
        int result = 17;
        if (items != null) {
            for (int i = 0; i < items.length; i++) {
                result = 31 * result + items[i];
            }
        }
        return result;
    }


    public static boolean itemsEqual(List<?> lhs, List<?> rhs) {
        if (lhs == rhs)
            return true;
        if (lhs == null || rhs == null)
            return false;
        if (lhs.size() != rhs.size())
            return false;

        for (int i = 0; i < lhs.size(); i++) {
            if (lhs.get(i) == null) {
                if (rhs.get(i) != null)
                    return false;
            }
            else if (!lhs.get(i).equals(rhs.get(i))) {
                return false;
            }
        }
        return true;
    }

    public static boolean itemsEqual(int[] lhs, int[] rhs) {
        if (lhs == rhs)
            return true;
        if (lhs == null || rhs == null || lhs.length != rhs.length)
            return false;

        for (int i = 0; i < lhs.length; i++) {
            if (lhs[i] != rhs[i])
                return false;
        }
        return true;
    }

    public static int itemsOfItemsHashCode(List<? extends List<?>> items) {
        int result = 17;
        if (items != null) {
            for (int i = 0; i < items.size(); i++) {
                result = 31 * result + itemsHashCode(items.get(i));
            }
        }
        return result;
    }

    public static boolean itemsOfItemsEqual(List<? extends List<?>> lhs, List<? extends List<?>> rhs) {
        if (lhs == rhs)
            return true;
        if (lhs == null || rhs == null)
            return false;
        if (lhs.size() != rhs.size())
            return false;

        for (int i = 0; i < lhs.size(); i++) {
            if (!itemsEqual(lhs.get(i), rhs.get(i))) {
                return false;
            }
        }
        return true;
    }
} public class FileUtils {
    private static final String TAG = FileUtils.class.getSimpleName();

    private static final String TMP_FILE_POSTFIX = "~tmp";

    /* Checks if external storage is available for read and write */
    public static boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state);
    }

    /* Checks if external storage is available to at least read */
    public static boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state) ||
                Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
    }


    public static boolean readObjsFromFile(Context context, FileObjsStaticInfo info, ReadObjsCallback callback) {
        synchronized (info.getLock()) {
            String fileName = info.getFileName();
            ApiDataInputStreamWrp stream = null;
            try {
                if (!context.getFileStreamPath(fileName).exists()) {
                    fileName += TMP_FILE_POSTFIX; // Pokud dany soubor neexistuje, tak se podivame, jestli neexistuje soubor s priponou ~tmp a pripadne jej zkusime nacist

                    if (!context.getFileStreamPath(fileName).exists()) {
                        callback.setDefaults();
                        return false;
                    }
                }

                stream = new ApiDataInputStreamWrp(new DataInputStream(new BufferedInputStream(context.openFileInput(fileName))), info.getCustomFlags(), info);
                if (!info.canReadFile1(stream.getDataAppVersionCode())) {
                    callback.setDefaults();
                    stream.close();
                    stream = null;
                    context.deleteFile(fileName);
                    return false;
                }
                else {
                    callback.readObjects(stream);
                    return true;
                }
            }
            catch (Exception e) {
                LogUtils.e(TAG, "Exception while reading " + fileName, e);
                callback.setDefaults();
                return false;
            }
            finally {
                if (stream != null)
                    stream.close();
            }
        }
    }

    public static boolean writeObjsToFile(Context context, FileObjsStaticInfo info, WriteObjsCallback callback) {
        final boolean ret;
        synchronized (info.getLock()) {
            ApiDataOutputStreamWrp stream = null;
            String fileNameTmp = info.getFileName() + TMP_FILE_POSTFIX;
            try {
                stream = new ApiDataOutputStreamWrp(new DataOutputStream(new BufferedOutputStream(
                        context.openFileOutput(fileNameTmp, Context.MODE_PRIVATE))));
                callback.writeObjects(stream);
            }
            catch (Exception e) {
                LogUtils.e(TAG, "Exception while writing " + info.getFileName(), e);
                return false;
            }
            finally {
                if (stream != null)
                    stream.close();
            }

            File tmpFile = context.getFileStreamPath(fileNameTmp);
            File origFile = context.getFileStreamPath(info.getFileName());

            if (origFile.exists())
                origFile.delete();
            ret = tmpFile.renameTo(origFile);
        }
        GlobalContextLib.get().requestGoogleBackupIfNeeded(info.getFileName());
        return ret;
    }

    public static void writeObjsToFileAsync(final Context context, final FileObjsStaticInfo info, final WriteObjsCallback callback) {
        Thread t = new Thread(new Runnable() {
            public void run() {
                writeObjsToFile(context, info, callback);
            }
        });
        t.start();
    }

    public static class FileObjsStaticInfo implements ApiDataAppVersionCodeLegacyResolver {
        private final Object lock;
        private final String fileName;
        private final int minReadAppVersionCode;
        private final int customFlags;

        public FileObjsStaticInfo(Object lock, String fileName) {
            this(lock, fileName, Integer.MIN_VALUE);
        }

        public FileObjsStaticInfo(Object lock, String fileName, int minReadAppVersionCode) {
            this(lock, fileName, minReadAppVersionCode, ApiDataInputOutputBase.FLAG_NONE);
        }

        public FileObjsStaticInfo(Object lock, String fileName, int minReadAppVersionCode, int customFlags) {
            this.lock = lock;
            this.fileName = fileName;
            this.minReadAppVersionCode = minReadAppVersionCode;
            this.customFlags = customFlags;
        }

        public Object getLock() {
            return this.lock;
        }

        public String getFileName() {
            return this.fileName;
        }

        public int getCustomFlags() {
            return customFlags;
        }

        public boolean canReadFile1(int fileAppVersionCode) {
            return fileAppVersionCode >= minReadAppVersionCode;
        }

        @Override
        public int resolveAppVersionCodeLegacy(int legacyDataVersion) {
            return 0;
        }

        public FileObjsStaticInfo createPortableInfoForWriting() {
            return new FileObjsStaticInfo(getLock(), getFileName() + ".port", minReadAppVersionCode, customFlags | ApiDataInputOutputBase.FLAG_PORTABLE);
        }
    }


    public interface ReadObjsCallback {
        void readObjects(ApiDataInput d);
        void setDefaults();
    }

    public interface WriteObjsCallback {
        void writeObjects(ApiDataOutput d);
    }

    public interface FileObjsCallback extends WriteObjsCallback, ReadObjsCallback {

    }
} public interface FileObjsCallback extends WriteObjsCallback, ReadObjsCallback {

} public static class FileObjsStaticInfo implements ApiDataAppVersionCodeLegacyResolver {
    private final Object lock;
    private final String fileName;
    private final int minReadAppVersionCode;
    private final int customFlags;

    public FileObjsStaticInfo(Object lock, String fileName) {
        this(lock, fileName, Integer.MIN_VALUE);
    }

    public FileObjsStaticInfo(Object lock, String fileName, int minReadAppVersionCode) {
        this(lock, fileName, minReadAppVersionCode, ApiDataInputOutputBase.FLAG_NONE);
    }

    public FileObjsStaticInfo(Object lock, String fileName, int minReadAppVersionCode, int customFlags) {
        this.lock = lock;
        this.fileName = fileName;
        this.minReadAppVersionCode = minReadAppVersionCode;
        this.customFlags = customFlags;
    }

    public Object getLock() {
        return this.lock;
    }

    public String getFileName() {
        return this.fileName;
    }

    public int getCustomFlags() {
        return customFlags;
    }

    public boolean canReadFile1(int fileAppVersionCode) {
        return fileAppVersionCode >= minReadAppVersionCode;
    }

    @Override
    public int resolveAppVersionCodeLegacy(int legacyDataVersion) {
        return 0;
    }

    public FileObjsStaticInfo createPortableInfoForWriting() {
        return new FileObjsStaticInfo(getLock(), getFileName() + ".port", minReadAppVersionCode, customFlags | ApiDataInputOutputBase.FLAG_PORTABLE);
    }
} public class GlobalContext extends GlobalContextLib {
    private static final String TAG = GlobalContext.class.getSimpleName();

    public static final int RQC_ENTER_PIN = 701;
    public static final int RQC_PICK_HOMESCREEN = 702;
    public static final int RQC_PICK_CUSTOM_IMAGE = 703;
    public static final int RQC_ENTER_PASSPHRASE = 704;
    //public static final int RQC_INIT_TREZOR = 705;

    private static final String FIRMWARE_DIR_V1 = "firmware";
    private static final String FIRMWARE_DIR_V2 = "firmware-v2";
    private static final String FIRMWARE_FILE_PREFIX = "trezor-";
    private static final String FIRMWARE_FILE_POSTFIX = ".bin";

    private final TrezorManager trezorManager;

    private CommonDb commonDb;
    private FirmwareReleases firmwareReleases;

    static void init(Context context) {
        GlobalContextLib.init(new GlobalContext(context));
    }

    public static GlobalContext get() {
        return (GlobalContext)GlobalContextLib.get();
    }

    private GlobalContext(Context context) {
        super(context);
        LogUtils.setLoggingEnabled(!getAppIsInProductionMode());
        this.trezorManager = new TrezorManager(context);
    }

    public TrezorManager getTrezorManager() {
        return trezorManager;
    }


    @Override
    public void requestGoogleBackupIfNeeded(String changedDbFileName) {
    }

    @Override
    public String getCurrentLangAbbrev() {
        return Locale.getDefault().getLanguage();
    }

    public synchronized CommonDb getCommonDb() {
        if (this.commonDb == null) {
            LogUtils.d(TAG, "Before creating CommonDb");
            this.commonDb = new CommonDb(this);
            LogUtils.d(TAG, "After creating CommonDb");
        }
        return this.commonDb;
    }


    public synchronized FirmwareReleases getFirmwareReleases(boolean isV2) {
        if (this.firmwareReleases == null || this.firmwareReleases.isV2 != isV2) {
            this.firmwareReleases = null;
            LogUtils.d(TAG, "Before loading releases.json, isV2: " + isV2);
            BufferedReader releasesReader = null;
            final String firmwareDir = isV2 ? FIRMWARE_DIR_V2 : FIRMWARE_DIR_V1;

            try {
                releasesReader = new BufferedReader(new InputStreamReader(getAndroidContext().getAssets().open(firmwareDir + File.separatorChar + "releases.json", AssetManager.ACCESS_BUFFER), "UTF-8"));
                StringBuilder bRelease = new StringBuilder();

                String line;
                while ((line = releasesReader.readLine()) != null) {
                    bRelease.append(line).append('\n');
                }

                JSONArray jReleases = new JSONArray(bRelease.toString());
                ArrayList<FirmwareRelease> versions = new ArrayList<>();

                for (int i = 0; i < jReleases.length(); i++) {
                    JSONObject jRelease = jReleases.getJSONObject(i);
                    JSONArray jVersion = jRelease.getJSONArray("version");

                    versions.add(new FirmwareRelease(
                            jRelease.getBoolean("required"),
                            new FirmwareVersion(jVersion.getInt(0), jVersion.getInt(1), jVersion.getInt(2)),
                            jRelease.optString("fingerprint", ""),
                            jRelease.getString("changelog")));
                }

                Collections.sort(versions, Collections.reverseOrder());
                this.firmwareReleases = new FirmwareReleases(isV2, ImmutableList.copyOf(versions));
            }
            catch (Exception e) {
                LogUtils.e(TAG, "getBundledFirmwareVersion", e);
                this.firmwareReleases = FirmwareReleases.createInvalid(isV2);
            }
            finally {
                if (releasesReader != null) {
                    try {
                        releasesReader.close();
                    } catch (IOException e) {
                        LogUtils.e(TAG, "getBundledFirmwareVersion (closing releaseReader)", e);
                    }
                }
            }
            LogUtils.d(TAG, "After loading bundled firmware version");
        }
        return this.firmwareReleases;
    }

    public synchronized InputStream openStreamBundledFirmware(boolean isV2) throws IOException {
        FirmwareRelease f = getFirmwareReleases(isV2).getNewest();
        return getAndroidContext().getAssets().open((isV2 ? FIRMWARE_DIR_V2 : FIRMWARE_DIR_V1) + File.separatorChar + FIRMWARE_FILE_PREFIX + f.version.major + "." + f.version.minor + "." + f.version.patch + FIRMWARE_FILE_POSTFIX);
    }

    public synchronized void executeDisconnectTrezorTask() {
        // schvalne resime takto globalne pres taskExecutor, protoze kazdopadne nechceme, aby se task (cekajici ve fronte) zrusil napr. pri zavreni aktivity...
        getTaskExecutor().executeTask("TASK_DISCONNECT_TREZOR_GCT",
                new AsyncTaskParam(TrezorTaskParam.SERIAL_EXECUTION_KEY_TREZOR) {
                    @Override
                    public boolean isExecutionInParallelForbidden(ITaskContext context) {
                        return true;
                    }

                    @Override
                    public void execute(ITaskContext context, ITask task) throws TaskException {
                        getTrezorManager().closeDeviceConnection();
                    }
                },
                null,
                false,
                new EmptyTaskListener(),
                null);
    }


    public static class FirmwareReleases {
        public static final FirmwareReleases createInvalid(boolean isV2) {
            return new FirmwareReleases(isV2, ImmutableList.<FirmwareRelease>of());
        }

        public final boolean isV2;
        public final ImmutableList<FirmwareRelease> releasesDesc; // sorted from the newest to oldest

        // upraveno: versionsDesc
        public FirmwareReleases(boolean isV2, ImmutableList<FirmwareRelease> releasesDesc) {
            this.isV2 = isV2;
            this.releasesDesc = releasesDesc;
        }

        public FirmwareRelease getNewest() {
            return releasesDesc.size() > 0 ? releasesDesc.get(0) : null;
        }

        public boolean isUpdateRequired(FirmwareVersion deviceVersion) {
            if (!deviceVersion.isValid())
                return false;

            for (FirmwareRelease r : releasesDesc) {
                if (!r.version.isNewerThan(deviceVersion))
                    return false;

                if (r.required)
                    return true;
            }
            return false;
        }
    }

    public static class FirmwareRelease implements Comparable<FirmwareRelease> {
        public final boolean required;
        public final FirmwareVersion version;
        public final String fingerprint;
        public final String changelog;

        public FirmwareRelease(boolean required, FirmwareVersion version, String fingerprint, String changelog) {
            this.required = required;
            this.version = version;
            this.fingerprint = fingerprint;
            this.changelog = changelog;
        }

        @Override
        public int compareTo(@NonNull FirmwareRelease v) {
            return version.compareTo(v.version);
        }

        @Override
        public String toString() {
            return version.toString();
        }
    }

    public static class FirmwareVersion implements Comparable<FirmwareVersion> {
        public static final FirmwareVersion INVALID = new FirmwareVersion(0, 0, 0);

        public final int major;
        public final int minor;
        public final int patch;

        public static FirmwareVersion create(Features features) {
            if (features.getBootloaderMode()) {
                return (features.hasFwMajor() && features.hasFwMinor() && features.hasFwPatch() && features.getFwMajor() > 0) ?
                        new FirmwareVersion(features.getFwMajor(), features.getFwMinor(), features.getFwPatch()) : FirmwareVersion.INVALID;
            }
            else
                return new FirmwareVersion(features.getMajorVersion(), features.getMinorVersion(), features.getPatchVersion());
        }

        public FirmwareVersion(int major, int minor, int patch) {
            this.major = major;
            this.minor = minor;
            this.patch = patch;
        }

        public boolean isValid() {
            return major != 0 || minor != 0 || patch != 0;
        }

        public boolean isNewerThan(FirmwareVersion v) {
            return compareTo(v) > 0;
        }

        @Override
        public int compareTo(@NonNull FirmwareVersion v) {
            if (this.major != v.major)
                return this.major < v.major ? -1 : 1;
            else if (this.minor != v.minor)
                return this.minor < v.minor ? -1 : 1;
            else if (this.patch != v.patch)
                return this.patch < v.patch ? -1 : 1;
            else
                return 0;
        }

        @Override
        public String toString() {
            return major + "." + minor + "." + patch;
        }
    }
} public class TrezorManager {
    private static final String TAG = TrezorManager.class.getSimpleName();

    private final Context context;
    private final UsbManager usbManager;

    private UsbDevice deviceWithoutPermission;
    private TrezorDevice device; // lazy loaded

    public TrezorManager(Context context) {
        this.context = context.getApplicationContext();
        this.usbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE);
    }


    public synchronized boolean tryConnectDevice() {
        return tryGetDevice() != null;
    }

    public synchronized void closeDeviceConnection() {
        if (device != null) {
            Timber.d(TAG, "closeDeviceConnection: closing");
            device.close();
            device = null;
        }
        else
            Timber.d(TAG, "closeDeviceConnection: no device connected now");
    }

    public synchronized boolean hasDeviceWithoutPermission(boolean refreshDevices) {
        if (refreshDevices)
            tryGetDevice();
        return this.device == null && this.deviceWithoutPermission != null;
    }

    public synchronized void requestDevicePermissionIfCan(boolean refreshDevices) {
        if (hasDeviceWithoutPermission(refreshDevices)) {
            usbManager.requestPermission(deviceWithoutPermission, PendingIntent.getBroadcast(context, 0, new Intent(UsbPermissionReceiver.ACTION), 0));
        }
    }

    public synchronized Message sendMessage(Message message) throws TrezorException {
        TrezorDevice device = tryGetDevice();
        if (device == null)
            throw new TrezorException(deviceWithoutPermission != null ? TrezorException.TYPE_NEEDS_PERMISSION : TrezorException.TYPE_NOT_CONNECTED);
        else {
            try {
                return device.sendMessage(message);
            }
            catch (Exception e) {
                Timber.e(TAG, "sendMessage: device.sendMessage thrown exception:", e);
                throw new TrezorException(TrezorException.TYPE_COMMUNICATION_ERROR, e);
            }
        }
    }


    public static Message parseMessageFromBytes(MessageType type, byte[] data) throws InvalidProtocolBufferException {
        try {
            String className = com.satoshilabs.trezor.lib.protobuf.TrezorMessage.class.getName() + "$" + type.name().replace("MessageType_", "");
            Class cls = Class.forName(className);
            Method method = cls.getDeclaredMethod("parseFrom", byte[].class);
            //noinspection PrimitiveArrayArgumentToVariableArgMethod
            return (Message)method.invoke(null, data); // TODO cache methods for faster start?
        }
        catch (Exception ex) {
            throw new InvalidProtocolBufferException("Exception while calling: parseMessageFromBytes for MessageType: " + type.name());
        }

    }

    //
    // PRIVATE
    //

    static boolean deviceIsTrezor(UsbDevice usbDevice) {
        // no usable interfaces
        if (usbDevice.getInterfaceCount() <= 0) {
            return false;
        }
        // Trezor v1
        if (usbDevice.getVendorId() == 0x534c) {
            return usbDevice.getProductId() == 0x0001;
        }
        // Trezor v2
        if (usbDevice.getVendorId() == 0x1209) {
            return usbDevice.getProductId() == 0x53c0 || usbDevice.getProductId() == 0x53c1;
        }
        return false;
    }

    private TrezorDevice tryGetDevice() {
        if (device == null) {
            Timber.i(TAG, "tryGetDevice: trying to find and open connection to Trezor");
            HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();

            deviceWithoutPermission = null;

            for (UsbDevice usbDevice : deviceList.values()) {
                // check if the device is Trezor
                if (!deviceIsTrezor(usbDevice))
                    continue;

                Timber.i(TAG, "tryGetDevice: Trezor device found");

                if (!usbManager.hasPermission(usbDevice)) {
                    if (deviceWithoutPermission == null)
                        deviceWithoutPermission = usbDevice;
                    continue;
                }

                // use first interface
                UsbInterface usbInterface = usbDevice.getInterface(0);
                // try to find read/write endpoints
                UsbEndpoint readEndpoint = null, writeEndpoint = null;
                for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
                    UsbEndpoint ep = usbInterface.getEndpoint(i);
                    if (readEndpoint == null && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT && ep.getAddress() == 0x81) { // number = 1 ; dir = USB_DIR_IN
                        readEndpoint = ep;
                        continue;
                    }
                    if (writeEndpoint == null && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT && (ep.getAddress() == 0x01 || ep.getAddress() == 0x02)) { // number = 1 ; dir = USB_DIR_OUT
                        writeEndpoint = ep;
                    }
                }
                if (readEndpoint == null) {
                    Timber.e(TAG, "tryGetDevice: Could not find read endpoint");
                    continue;
                }
                if (writeEndpoint == null) {
                    Timber.e(TAG, "tryGetDevice: Could not find write endpoint");
                    continue;
                }
                if (readEndpoint.getMaxPacketSize() != 64) {
                    Timber.e(TAG, "tryGetDevice: Wrong packet size for read endpoint");
                    continue;
                }
                if (writeEndpoint.getMaxPacketSize() != 64) {
                    Timber.e(TAG, "tryGetDevice: Wrong packet size for write endpoint");
                    continue;
                }

                UsbDeviceConnection conn = usbManager.openDevice(usbDevice);
                if (conn == null) {
                    Timber.e(TAG, "tryGetDevice: could not open connection");
                } else {
                    if (!conn.claimInterface(usbInterface, true)) {
                        Timber.e(TAG, "tryGetDevice: could not claim interface");
                    } else {
                        deviceWithoutPermission = null;
                        device = new TrezorDevice(usbDevice.getDeviceName(), conn.getSerial(), conn, usbInterface, readEndpoint, writeEndpoint);
                        break;
                    }
                }
            }
            Timber.i(TAG, "tryGetDevice: " + (this.device == null ? "Trezor device not found or failed to connect" : "Trezor device successfully connected"));
        } else
            Timber.i(TAG, "tryGetDevice: using already connected device");
        return device;
    }

    //
    // INNER CLASSES
    //

    private static class TrezorDevice {
        private static final String TAG = TrezorDevice.class.getSimpleName();

        private final String deviceName;
        private final String serial;

        // next fields are only valid until calling close()
        private UsbDeviceConnection usbConnection;
        private UsbInterface usbInterface;
        private UsbEndpoint readEndpoint;
        private UsbEndpoint writeEndpoint;

        TrezorDevice(String deviceName,
                     String serial,
                     UsbDeviceConnection usbConnection,
                     UsbInterface usbInterface,
                     UsbEndpoint readEndpoint,
                     UsbEndpoint writeEndpoint) {
            this.deviceName = deviceName;
            this.serial = serial;
            this.usbConnection = usbConnection;
            this.usbInterface = usbInterface;
            this.readEndpoint = readEndpoint;
            this.writeEndpoint = writeEndpoint;
        }

        @Override
        public String toString() {
            return "TREZOR(path:" + this.deviceName + " serial:" + this.serial + ")";
        }

        Message sendMessage(Message msg) throws InvalidProtocolBufferException {
            if (usbConnection == null)
                throw new IllegalStateException(TAG + ": sendMessage: usbConnection already closed, cannot send message");

            messageWrite(msg);
            return messageRead();
        }

        void close() {
            if (this.usbConnection != null) {
                try {
                    usbConnection.releaseInterface(usbInterface);
                }
                catch (Exception ex) {}
                try {
                    usbConnection.close();
                }
                catch (Exception ex) {}

                usbConnection = null;
                usbInterface = null;
                readEndpoint = null;
                writeEndpoint = null;
            }
        }

        //
        // PRIVATE
        //

        private void messageWrite(Message msg) {
            int msg_size = msg.getSerializedSize();
            String msg_name = msg.getClass().getSimpleName();
            int msg_id = MessageType.valueOf("MessageType_" + msg_name).getNumber();
            Timber.i(TAG, String.format("messageWrite: Got message: %s (%d bytes)", msg_name, msg_size));
            ByteBuffer data = ByteBuffer.allocate(msg_size + 1024); // 32768);
            data.put((byte) '#');
            data.put((byte) '#');
            data.put((byte) ((msg_id >> 8) & 0xFF));
            data.put((byte) (msg_id & 0xFF));
            data.put((byte) ((msg_size >> 24) & 0xFF));
            data.put((byte) ((msg_size >> 16) & 0xFF));
            data.put((byte) ((msg_size >> 8) & 0xFF));
            data.put((byte) (msg_size & 0xFF));
            data.put(msg.toByteArray());
            while (data.position() % 63 > 0) {
                data.put((byte) 0);
            }
            UsbRequest request = new UsbRequest();
            request.initialize(usbConnection, writeEndpoint);
            int chunks = data.position() / 63;
            Timber.i(TAG, String.format("messageWrite: Writing %d chunks", chunks));
            data.rewind();
            for (int i = 0; i < chunks; i++) {
                byte[] buffer = new byte[64];
                buffer[0] = (byte) '?';
                data.get(buffer, 1, 63);
                request.queue(ByteBuffer.wrap(buffer), 64);
                usbConnection.requestWait();
            }
        }

        private Message messageRead() throws InvalidProtocolBufferException {
            ByteBuffer data = null;//ByteBuffer.allocate(32768);
            ByteBuffer buffer = ByteBuffer.allocate(64);
            UsbRequest request = new UsbRequest();
            request.initialize(usbConnection, readEndpoint);
            MessageType type;
            int msg_size;
            int invalidChunksCounter = 0;

            for (; ; ) {
                request.queue(buffer, 64);
                usbConnection.requestWait();
                byte[] b = buffer.array();
                Timber.i(TAG, String.format("messageRead: Read chunk: %d bytes", b.length));

                if (b.length < 9 || b[0] != (byte) '?' || b[1] != (byte) '#' || b[2] != (byte) '#') {
                    if (invalidChunksCounter++ > 5)
                        throw new InvalidProtocolBufferException("messageRead: too many invalid chunks");
                    continue;
                }

                type = MessageType.valueOf((((int)b[3] & 0xFF) << 8) + ((int)b[4] & 0xFF));
                msg_size = (((int)b[5] & 0xFF) << 24)
                        + (((int)b[6] & 0xFF) << 16)
                        + (((int)b[7] & 0xFF) << 8)
                        + ((int)b[8] & 0xFF);
                data = ByteBuffer.allocate(msg_size + 1024);
                data.put(b, 9, b.length - 9);
                break;
            }

            invalidChunksCounter = 0;

            while (data.position() < msg_size) {
                request.queue(buffer, 64);
                usbConnection.requestWait();
                byte[] b = buffer.array();
                Timber.i(TAG, String.format("messageRead: Read chunk (cont): %d bytes", b.length));
                if (b[0] != (byte) '?') {
                    if (invalidChunksCounter++ > 5)
                        throw new InvalidProtocolBufferException("messageRead: too many invalid chunks (2)");
                    continue;
                }
                data.put(b, 1, b.length - 1);
            }

            byte[] msgData = Arrays.copyOfRange(data.array(), 0, msg_size);

            Timber.i(TAG, String.format("parseMessageFromBytes: Parsing %s (%d bytes):", type, msgData.length));
            return parseMessageFromBytes(type, msgData);
        }
    }


    public static abstract class UsbPermissionReceiver extends BaseBroadcastReceivers.BaseGlobalReceiver {
        public static final String ACTION = UsbPermissionReceiver.class.getName() + ".ACTION";

        public UsbPermissionReceiver() {
            super(ACTION);
        }

        @Override
        protected final void onReceiveRegistered(Context context, Intent intent) {
            boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
            onUsbPermissionResult(granted);
        }

        public abstract void onUsbPermissionResult(boolean granted);
    }


    public static abstract class TrezorConnectionChangedReceiver extends BaseBroadcastReceivers.BaseGlobalReceiver {
        private static final String TAG = TrezorConnectionChangedReceiver.class.getSimpleName();

        private static IntentFilter createIntentFilter() {
            IntentFilter ret = new IntentFilter();
            ret.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
            ret.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
            return ret;
        }

        public TrezorConnectionChangedReceiver() {
            super(createIntentFilter());
        }

        @Override
        protected final void onReceiveRegistered(Context context, Intent intent) {
            Timber.d(TAG, "onReceiveRegistered: " + intent.getAction());

            UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null && deviceIsTrezor(device)) {
                if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction()))
                    onTrezorConnectionChanged(true);
                else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
                    onTrezorConnectionChanged(false);
                }
            }
        }

        public abstract void onTrezorConnectionChanged(boolean connected);
    }
} public class ImmutableList<E> extends ArrayList<E> {

    public ImmutableList(int size) {
        super(size);
    }

    private void _addAll(Collection<? extends E> c) {
        super.addAll(c);
    }

    @Override
    public boolean add(final E e) {
        throw (new UnsupportedOperationException());
    }

    @Override
    public boolean remove(final Object o) {
        throw (new UnsupportedOperationException());
    }

    @Override
    public boolean addAll(@NonNull final Collection<? extends E> c) {
        throw (new UnsupportedOperationException());
    }

    @Override
    public boolean addAll(final int index, @NonNull final Collection<? extends E> c) {
        throw (new UnsupportedOperationException());
    }

    @Override
    public boolean removeAll(@NonNull final Collection<?> c) {
        throw (new UnsupportedOperationException());
    }

    @Override
    public boolean retainAll(@NonNull final Collection<?> c) {
        throw (new UnsupportedOperationException());
    }

    @Override
    public void clear() {
        throw (new UnsupportedOperationException());
    }

    @Override
    public E set(final int index, final E element) {
        throw (new UnsupportedOperationException());
    }

    @Override
    public E remove(final int index) {
        throw (new UnsupportedOperationException());
    }


    public static <E> ImmutableList<E> copyOf(List<E> from) {
        final Builder<E> builder = builder();
        builder.addAll(from);
        return builder.build();
    }

    public static <E> Builder<E> builder() {
        return new Builder<>();
    }

    public static <E> ImmutableList<E> of() {
        return new Builder<E>().build();
    }

    public static class Builder<E> {

        private final ArrayList<E> arr;

        public Builder() {
            arr = new ArrayList<>();
        }

        public void add(E e) {
            arr.add(e);
        }

        void addAll(Collection<? extends E> c) {
            arr.addAll(c);
        }

        public ImmutableList<E> build() {
            final ImmutableList<E> list = new ImmutableList<>(arr.size());
            list._addAll(arr);
            return list;
        }
    }
}
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.circlegate.liban.base.ApiBase.ApiCreator;
import com.circlegate.liban.base.ApiBase.ApiParcelable;
import com.circlegate.liban.base.ApiDataIO.ApiDataInput;
import com.circlegate.liban.base.ApiDataIO.ApiDataOutput;
import com.satoshilabs.trezor.lib.BaseBroadcastReceivers.BaseLocalReceiver;
import com.circlegate.liban.utils.EqualsUtils;
import com.circlegate.liban.utils.FileUtils;
import com.circlegate.liban.utils.FileUtils.FileObjsCallback;
import com.circlegate.liban.utils.FileUtils.FileObjsStaticInfo;
import com.google.protobuf.InvalidProtocolBufferException;
import com.satoshilabs.trezor.app.common.GlobalContext;
import com.satoshilabs.trezor.lib.TrezorManager;
import com.satoshilabs.trezor.lib.protobuf.TrezorMessage.Features;
import com.satoshilabs.trezor.lib.protobuf.TrezorMessage.MessageType;
import tinyguava.ImmutableList;
package com.satoshilabs.trezor.app.db;


public class CommonDb {

    private final FileObjsStaticInfo info = new FileObjsStaticInfo(getLock(), "CommonDb.obj");
    private final Context context;

    // Persistent data
    private ImmutableList<InitializedTrezor> trezors = ImmutableList.of();
    private String lastSelectedDeviceId = "";

    private final FileObjsCallback fileObjsCallback = new FileObjsCallback() {
        public void setDefaults() {
            trezors = ImmutableList.of();
            lastSelectedDeviceId = "";
        }

        public void readObjects(ApiDataInput d) {
            trezors = d.readImmutableList(InitializedTrezor.CREATOR);
            lastSelectedDeviceId = d.readString();
        }

        public void writeObjects(ApiDataOutput d) {
            d.write(trezors, 0);
            d.write(lastSelectedDeviceId);
        }
    };

    public CommonDb(GlobalContext gct) {
        this.context = gct.getAndroidContext();
        FileUtils.readObjsFromFile(context, info, fileObjsCallback);