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 IApiParcelable extends IApiObject, Parcelable {
    // tridy implementujici toto rozhrani musi mit clen public static ApiCreator CREATOR;
} 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 class NotImplementedException extends RuntimeException {
    public NotImplementedException() {}

} public class ActivityUtils {
    private static final String ACTIVITY_RESULT_PARCELABLE = ActivityUtils.class.getName() + "." + "ACTIVITY_RESULT_PARCELABLE";

    public static void setResultParcelable(Activity activity, int resultCode, Parcelable data) {
        Intent intent = new Intent();
        intent.putExtra(ACTIVITY_RESULT_PARCELABLE, data);
        activity.setResult(resultCode, intent);
    }

    public static <T extends Parcelable> T getResultParcelable(Intent resultData) {
        return resultData != null ? resultData.<T>getParcelableExtra(ACTIVITY_RESULT_PARCELABLE) : null;
    }

} public abstract class BaseActivity extends AppCompatActivity implements IBaseFragmentActivity, ITaskFragmentActivity, IDialogsFragmentActivity, OnRequestPermissionsResultCallback, ITaskResultListener {
    private static final String DIALOG_CONFIRM_ACTION = "BaseActivity.DIALOG_CONFIRM_ACTION";

    private GlobalContext gct;
    private TaskFragment taskFragment;
	private DialogsFragment dialogsFragment;
    private final ArrayList<OnBackPressedListener> onBackPressedListeners = new ArrayList<>();
    private final List<Runnable> pendingTasks = new ArrayList<Runnable>();
    private boolean readyToCommitFragments = false;
    private INavDrawerCallbacks navDrawerCallbacks;

    // pokud je false a pokud nedochazi pouze ke zmene konfigurace (nejcasteji kvuli otoceni displeje), pri kazdem volani onStop dojde k odpojeni trezoru
    // aktivita muze nastavit na true, aby pri prechodu z jedne aktivity do druhe nedochazelo ke zbytecnemu odpojovani
    // V kazdem pripade pri kazdem volani onStop (a onCreate) je nastaveno zpet na false
    private boolean dontDisconnectOnStop = false;

    // Saved state
    private PromptDialog dialogConfirmAction;


    //
    // LIFECYCLE CALLBACKS
    //

	@Override
	protected void onCreate(Bundle savedInstanceState) {
	    this.gct = GlobalContext.get();
        super.onCreate(savedInstanceState);
        onBackPressedListeners.clear(); // pro jistotu...
        readyToCommitFragments = false;
        trezorConnectionChangedReceiver.register(this);
        dontDisconnectOnStop = false;

        this.dialogConfirmAction = (PromptDialog)getSupportFragmentManager().findFragmentByTag(DIALOG_CONFIRM_ACTION);
	}

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == GlobalContext.RQC_ENTER_PIN) {
            EnterPinActivityResult res = ActivityUtils.getResultParcelable(data);
            executeTrezorTaskIfCan(
                    res.param.taskId,
                    new TrezorTaskParam(resultCode == RESULT_OK ? PinMatrixAck.newBuilder().setPin(res.pinEncoded).build() : Cancel.newBuilder().build(), res.param.tag));
        }
        else if (requestCode == GlobalContext.RQC_ENTER_PASSPHRASE) {
            EnterPassphraseActivityResult res = ActivityUtils.getResultParcelable(data);
            executeTrezorTaskIfCan(
                    res.param.taskId,
                    new TrezorTaskParam(resultCode == RESULT_OK ? PassphraseAck.newBuilder().setPassphrase(res.passphrase).build() : Cancel.newBuilder().build(), res.param.tag));
        }
        else
            super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onStart() {
        super.onStart();

        if (this.navDrawerCallbacks != null)
            this.navDrawerCallbacks.onActivityStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        this.readyToCommitFragments = true;

        ArrayList<Runnable> pendingTasksCopy = new ArrayList<Runnable>(this.pendingTasks);
        this.pendingTasks.clear(); // pro jistotu vymazu uz tady a misto toho prochazim pendingTasksCopy

        for (Runnable r : pendingTasksCopy)
            r.run();
    }

    @Override
    protected void onPause() {
        this.readyToCommitFragments = false;
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (this.navDrawerCallbacks != null)
            this.navDrawerCallbacks.onActivityStop();

        if (!dontDisconnectOnStop && !isChangingConfigurations()) {
            gct.executeDisconnectTrezorTask();
        }
        dontDisconnectOnStop = false;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        this.readyToCommitFragments = false;
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        onBackPressedListeners.clear(); // pro jistotu...
        trezorConnectionChangedReceiver.unregister(this);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
            return true;
        }
        else
            return super.onOptionsItemSelected(item);
    }

    @Override
    public void onRequestPermissionsResult(final int requestCode, final @NonNull String[] permissions, final @NonNull int[] grantResults) {
        if (isReadyToCommitFragments()) {
            OnRequestPermissionsResultReceiver.sendBroadcast(this, requestCode, permissions, grantResults);
        }
        else {
            addPendingTask(new Runnable() {
                @Override
                public void run() {
                    OnRequestPermissionsResultReceiver.sendBroadcast(BaseActivity.this, requestCode, permissions, grantResults);
                }
            });
        }
    }


    //
    // GETTERS
    //

	@Override
	public TaskFragment getTaskFragment() {
		if (taskFragment == null) {
			this.taskFragment = TaskFragment.getInstance(this);
		}
		return taskFragment;
	}

    @Override
	public DialogsFragment getDialogsFragment() {
		if (dialogsFragment == null) {
			this.dialogsFragment = DialogsFragment.getInstance(this);
		}
		return dialogsFragment;
	}

    @Override
    public void onBackPressed() {
        // projdeme seznam od konce...
        for (int i = onBackPressedListeners.size() - 1; i >= 0; --i) {
            if (onBackPressedListeners.get(i).onBackPressed()) {
                return; // koncime...
            }
        }
        if (!onBackPressedAfterListeners())
            super.onBackPressed();
    }

    public boolean onBackPressedAfterListeners() {
        return false;
    }

    @Override
    public void addOnBackPressedListener(OnBackPressedListener l) {
        onBackPressedListeners.remove(l); // pro jistotu... navic chceme, aby listener byl v seznamu posledni
        onBackPressedListeners.add(l);
    }

    @Override
    public void removeOnBackPressedListener(OnBackPressedListener l) {
        onBackPressedListeners.remove(l);
    }


    public void addPendingTask(Runnable task) {
        this.pendingTasks.add(task);
    }

    public boolean isReadyToCommitFragments() {
        return readyToCommitFragments;
    }

    public void setNavDrawerCallbacks(INavDrawerCallbacks navDrawerCallbacks) {
        this.navDrawerCallbacks = navDrawerCallbacks;
    }


    public void setDontDisconnectOnStop() {
        this.dontDisconnectOnStop = true;
    }

    public void executeTrezorTaskIfCan(String taskId, Message msgParam) {
        executeTrezorTaskIfCan(taskId, new TrezorTaskParam(msgParam));
    }

    public void executeTrezorTaskIfCan(String taskId, TrezorTaskParam param) {
        if (!getTaskFragment().containsAnyTaskByFragmentTag(null)) {
            getTaskFragment().executeTask(taskId, param, null, false, null);
        }
    }

    protected boolean canHandleButtonReqByDefAction() {
        return false;
    }

    @Override
    public final void onTaskCompleted(String id, ITaskResult result, Bundle bundle) {
        if (result instanceof TrezorTaskResult) {
            TrezorTaskResult res = (TrezorTaskResult)result;
            beforeTrezorTaskCompleted(id, res);

            if (dialogConfirmAction != null) {
                dialogConfirmAction.dismiss();
                dialogConfirmAction = null;
            }

            if (res.isValidResult()) {
                switch (res.getMsgResult().msgType) {
                    case MessageType_PinMatrixRequest:
                        startActivityForResult(EnterPinActivity.createIntent(this, new EnterPinActivityParam(id, res.getParam().getTag(), ((PinMatrixRequest) res.getMsgResult().msg).getType())),
                                GlobalContext.RQC_ENTER_PIN);
                        setDontDisconnectOnStop();
                        break;

                    case MessageType_EntropyRequest: {
                        SecureRandom random = new SecureRandom();
                        byte[] entropy = new byte[32];
                        random.nextBytes(entropy);
                        EntropyAck param = EntropyAck.newBuilder().setEntropy(ByteString.copyFrom(entropy)).build();
                        executeTrezorTaskIfCan(id, new TrezorTaskParam(param, res.getParam().getTag()));
                        break;
                    }

                    case MessageType_PassphraseRequest: {
                        startActivityForResult(EnterPassphraseActivity.createIntent(this, new EnterPassphraseActivityParam(id, res.getParam().getTag())),
                                GlobalContext.RQC_ENTER_PASSPHRASE);
                        setDontDisconnectOnStop();
                        break;
                    }

                    case MessageType_ButtonRequest: {
                        if (canHandleButtonReqByDefAction()) {
                            this.dialogConfirmAction = PromptDialog.show(getSupportFragmentManager(), dialogConfirmAction, DIALOG_CONFIRM_ACTION, DIALOG_CONFIRM_ACTION, "", getString(R.string.confirm_action_on_trezor),
                                    false, false, false, null, "", "");
                            executeTrezorTaskIfCan(id, ButtonAck.newBuilder().build());
                        }
                        else
                            onTrezorTaskCompletedSucc(id, res);
                        break;
                    }

                    default:
                        onTrezorTaskCompletedSucc(id, res);
                }
            }
            else {
                onTrezorError(res.getError());
            }
        }
        else
            onOtherTaskCompleted(id, result, bundle);
    }

    public void onOtherTaskCompleted(String id, ITaskResult result, Bundle bundle) {
    }

    public void beforeTrezorTaskCompleted(String id, TrezorTaskResult res) {
    }

    public void onTrezorTaskCompletedSucc(String id, TrezorTaskResult res) {
    }

    public void onTrezorConnected() {
    }

    public void onTrezorError(ITaskError error) {
        error.showToast(gct);

        gct.getCommonDb().removeAllTrezors();
        gct.executeDisconnectTrezorTask();
        finish();
        startActivity(MainActivity.createIntent(BaseActivity.this));
        setDontDisconnectOnStop();

        if (BaseActivity.this instanceof MainActivity) {
            overridePendingTransition(0, 0);
        }
    }

    public void showToastTrezorMsgFailure(Failure failure) {
        final String s;

        if (failure != null) {
            if (failure.hasMessage())
                s = failure.getMessage();
            else if (failure.hasCode())
                s = failure.getCode().name();
            else
                s = getString(R.string.trezor_err_unexpected_response);
        }
        else
            s = getString(R.string.trezor_err_unexpected_response);

        Toast.makeText(this, s, Toast.LENGTH_LONG).show();
    }

    private final TrezorConnectionChangedReceiver trezorConnectionChangedReceiver = new TrezorConnectionChangedReceiver() {
        @Override
        public void onTrezorConnectionChanged(boolean connected) {
            pendingTasks.remove(pendingTaskTrezorConnected);
            pendingTasks.remove(pendingTaskTrezorDisconnected);

            Runnable task = connected ? pendingTaskTrezorConnected : pendingTaskTrezorDisconnected;

            if (isReadyToCommitFragments())
                task.run();
            else
                addPendingTask(task);
        }
    };

    private final Runnable pendingTaskTrezorConnected = new Runnable() {
        @Override
        public void run() {
            onTrezorConnected();
        }
    };

    private final Runnable pendingTaskTrezorDisconnected = new Runnable() {
        @Override
        public void run() {
            onTrezorError(TrezorError.ERR_NOT_CONNECTED);
        }
    };


    //
    // INNER CLASSES
    //


    /**
     * POZOR Je potreba zajistit, aby byl receiver zaregistrovan dostatecne brzo - bud v onStart, nebo alespon v onResume pred volanim super.onResume!
     */
    public static abstract class OnRequestPermissionsResultReceiver extends BaseLocalReceiver {
        public static final int RQC_COMMON = 0;
        public static final int RQC_ACCESS_FINE_LOC = 100;

        private static final String ACTION = OnRequestPermissionsResultReceiver.class.getName() + ".ACTION";

        public OnRequestPermissionsResultReceiver() {
            super(ACTION);
        }

        public static void sendBroadcast(Context context, int requestCode, String[] permissions, int[] grantResults) {
            Intent intent = new Intent(ACTION)
                    .putExtra("requestCode", requestCode)
                    .putExtra("permissions", permissions)
                    .putExtra("grantResults", grantResults);
            sendBroadcast(context, intent);
        }

        @Override
        public void onReceiveRegistered(Context context, Intent intent) {
            onRequestPermissionsResultReceived(
                    context,
                    intent.getIntExtra("requestCode", 0),
                    intent.getStringArrayExtra("permissions"),
                    intent.getIntArrayExtra("grantResults")
            );
        }

        public abstract void onRequestPermissionsResultReceived(Context context, int requestCode, String[] permissions, int[] grantResults);

        public static boolean isPermissionGranted(String permissionToFind, String[] permissions, int[] grantResults) {
            int ind = tryFindPermissionInd(permissionToFind, permissions);
            return ind >= 0 && grantResults[ind] == PackageManager.PERMISSION_GRANTED;
        }

        public static int tryFindPermissionInd(String permissionToFind, String[] permissions) {
            for (int i = 0; i < permissions.length; i++) {
                if (EqualsUtils.equalsCheckNull(permissionToFind, permissions[i])) {
                    return i;
                }
            }
            return -1;
        }
    }
} 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 CustomHtml {	
    public static final char HARD_SPACE = (char)0xf00a0;
    public static final String TAG_LINE_BREAK = "<BR />";
    
	protected static final String SPAN_TAG = "span";
    protected static final String SPAN_ATTR_TYPE_NAME = "class";

    protected static final String SPAN_ATTR_TYPE_SIZE = "size";
    protected static final String SPAN_ATTR_SIZE_VALUE = "size";

    protected static final String SPAN_ATTR_TYPE_RELATIVE_SIZE = "rltsz";

	private static Typeface boldTypeface;

	public static Typeface getBoldTypeface(Context context) {
		if (boldTypeface == null) {
			boldTypeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
		}
		return boldTypeface;
	}

	public static String getHardSpaces2(int count) {
        StringBuilder ret = new StringBuilder();
        for (int i = 0; i < count; i++) {
            ret.append(i == count - 1 ? " " : HARD_SPACE);
        }
        return ret.toString();
    }

	public static String getFontColorTag(String text, int color) {
		return "<font color=\"" + ColorUtils.getHtmlColor(color) + "\">" + text + "</font>";
	}

    public static String getTextSizeTag(String s, int textSizePx) {
        return "<" + SPAN_TAG + " " + SPAN_ATTR_TYPE_NAME + "=\"" + SPAN_ATTR_TYPE_SIZE + "\" " + SPAN_ATTR_SIZE_VALUE + "=\"" + textSizePx + "\">" + s + "</" + SPAN_TAG + ">";
    }

    public static String getRelativeTextSizeTag(String s, float proportion) {
        return "<" + SPAN_TAG + " " + SPAN_ATTR_TYPE_NAME + "=\"" + SPAN_ATTR_TYPE_RELATIVE_SIZE + "\" " + SPAN_ATTR_SIZE_VALUE + "=\"" + (int)(proportion * 100) + "\">" + s + "</" + SPAN_TAG + ">";
    }
	
	public static String getBoldTag(String text) {
	    return "<b>" + text + "</b>";
	}
	
	public static String getImgTag(String srcAttr) {
	    return "<img src=\"" + srcAttr + "\" />";
	}

	
	public static Spanned fromHtmlWithCustomSpans(String source) {
		return fromHtmlWithCustomSpans(source, null);
	}
	
	public static Spanned fromHtmlWithCustomSpans(String source, Html.ImageGetter imageGetter) {
		return fromHtmlWithCustomSpans(source, imageGetter, (Html.TagHandler)null);
	}


	public static Spanned fromHtmlWithCustomSpans(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
		if (source.indexOf('\n') >= 0) {
		    source = source.replace("\n", TAG_LINE_BREAK);
		}
		
		return SpannedString.valueOf(Html.fromHtml(source, imageGetter, new CustomTagHandler(tagHandler)));
	}



	
	public static class CustomTagHandler implements TagHandler {
		protected final Context context;
		private final TagHandler providedTagHandler;
		protected final Stack<IHtmlTagSpan> spansStack = new Stack<IHtmlTagSpan>();
		
		public CustomTagHandler(TagHandler providedTagHandler) {
			this.context = GlobalContext.get().getAndroidContext();
			this.providedTagHandler = providedTagHandler;
		}
		
		public boolean handleStartTag(String tag, Attributes attributes, Editable output, XMLReader xmlReader) {
			if (tag.equalsIgnoreCase(SPAN_TAG)) {
                String type = attributes.getValue("", SPAN_ATTR_TYPE_NAME);
                if (handleSpanStartTag(type, attributes, output))
                    return true;
                else {
                    spansStack.push(null);
                    return this.providedTagHandler != null && this.providedTagHandler.handleStartTag(tag, attributes, output, xmlReader);
                }
			}
			else {
				return this.providedTagHandler != null && this.providedTagHandler.handleStartTag(tag, attributes, output, xmlReader);
			}
		}

		public boolean handleEndTag(String tag, Editable output, XMLReader xmlReader) {
			if (tag.equalsIgnoreCase(SPAN_TAG)) {
				IHtmlTagSpan spanTag = spansStack.pop();
				if (handleSpanEndTag(spanTag, output))
                    return true;
                else if (spanTag == null) {
                    return this.providedTagHandler != null && this.providedTagHandler.handleEndTag(tag, output, xmlReader);
                }
                else
					throw new RuntimeException();
			}
			else {
				return this.providedTagHandler != null && this.providedTagHandler.handleEndTag(tag, output, xmlReader);
			}
		}


        protected boolean handleSpanStartTag(String type, Attributes attributes, Editable output) {
            if (SPAN_ATTR_TYPE_SIZE.equalsIgnoreCase(type)) {
                spansStack.push(processSizeStart(output, attributes));
                return true;
            }
            else if (SPAN_ATTR_TYPE_RELATIVE_SIZE.equalsIgnoreCase(type)) {
                spansStack.push(processRelativeSizeStart(output, attributes));
                return true;
            }
            else
                return false;
        }

        protected boolean handleSpanEndTag(IHtmlTagSpan spanTag, Editable output) {
            if (spanTag instanceof AbsoluteSizeSpan) {
                processSizeEnd(output);
                return true;
            }
            else if (spanTag instanceof RelativeSizeSpan) {
                processRelativeSizeEnd(output);
                return true;
            }
            else
                return false;
        }


        private AbsoluteSizeSpan processSizeStart(Editable output, Attributes attributes) {
            int len = output.length();
            AbsoluteSizeSpan ret = new AbsoluteSizeSpan(Integer.parseInt(attributes.getValue("", SPAN_ATTR_SIZE_VALUE)));
            output.setSpan(ret, len, len, Spannable.SPAN_MARK_MARK);
            return ret;
        }

        private void processSizeEnd(Editable output) {
            int len = output.length();
            Object obj = getLast(output, AbsoluteSizeSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(obj, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }


        private RelativeSizeSpan processRelativeSizeStart(Editable output, Attributes attributes) {
            int len = output.length();
            RelativeSizeSpan ret = new RelativeSizeSpan(((float) Integer.parseInt(attributes.getValue("", SPAN_ATTR_SIZE_VALUE)) / 100));
            output.setSpan(ret, len, len, Spannable.SPAN_MARK_MARK);
            return ret;
        }

        private void processRelativeSizeEnd(Editable output) {
            int len = output.length();
            Object obj = getLast(output, RelativeSizeSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(obj, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }


	    protected Object getLast(Editable text, @SuppressWarnings("rawtypes") Class kind) {
	        @SuppressWarnings("unchecked")
			Object[] objs = text.getSpans(0, text.length(), kind);

	        if (objs.length == 0) {
	            return null;
	        } else {
	            for(int i = objs.length;i>0;i--) {
	                if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
	                    return objs[i-1];
	                }
	            }
	            return null;
	        }
	    }
	}
} public enum PinMatrixRequestType
    implements com.google.protobuf.ProtocolMessageEnum {
  /**
   * <code>PinMatrixRequestType_Current = 1;</code>
   */
  PinMatrixRequestType_Current(1),
  /**
   * <code>PinMatrixRequestType_NewFirst = 2;</code>
   */
  PinMatrixRequestType_NewFirst(2),
  /**
   * <code>PinMatrixRequestType_NewSecond = 3;</code>
   */
  PinMatrixRequestType_NewSecond(3),
  ;

  /**
   * <code>PinMatrixRequestType_Current = 1;</code>
   */
  public static final int PinMatrixRequestType_Current_VALUE = 1;
  /**
   * <code>PinMatrixRequestType_NewFirst = 2;</code>
   */
  public static final int PinMatrixRequestType_NewFirst_VALUE = 2;
  /**
   * <code>PinMatrixRequestType_NewSecond = 3;</code>
   */
  public static final int PinMatrixRequestType_NewSecond_VALUE = 3;


  public final int getNumber() {
    return value;
  }

  /**
   * @deprecated Use {@link #forNumber(int)} instead.
   */
  @java.lang.Deprecated
  public static PinMatrixRequestType valueOf(int value) {
    return forNumber(value);
  }

  public static PinMatrixRequestType forNumber(int value) {
    switch (value) {
      case 1: return PinMatrixRequestType_Current;
      case 2: return PinMatrixRequestType_NewFirst;
      case 3: return PinMatrixRequestType_NewSecond;
      default: return null;
    }
  }

  public static com.google.protobuf.Internal.EnumLiteMap<PinMatrixRequestType>
      internalGetValueMap() {
    return internalValueMap;
  }
  private static final com.google.protobuf.Internal.EnumLiteMap<
      PinMatrixRequestType> internalValueMap =
        new com.google.protobuf.Internal.EnumLiteMap<PinMatrixRequestType>() {
          public PinMatrixRequestType findValueByNumber(int number) {
            return PinMatrixRequestType.forNumber(number);
          }
        };

  public final com.google.protobuf.Descriptors.EnumValueDescriptor
      getValueDescriptor() {
    return getDescriptor().getValues().get(ordinal());
  }
  public final com.google.protobuf.Descriptors.EnumDescriptor
      getDescriptorForType() {
    return getDescriptor();
  }
  public static final com.google.protobuf.Descriptors.EnumDescriptor
      getDescriptor() {
    return com.satoshilabs.trezor.lib.protobuf.TrezorType.getDescriptor().getEnumTypes().get(5);
  }

  private static final PinMatrixRequestType[] VALUES = values();

  public static PinMatrixRequestType valueOf(
      com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
    if (desc.getType() != getDescriptor()) {
      throw new java.lang.IllegalArgumentException(
        "EnumValueDescriptor is not for this type.");
    }
    return VALUES[desc.getIndex()];
  }

  private final int value;

  private PinMatrixRequestType(int value) {
    this.value = value;
  }

  // @@protoc_insertion_point(enum_scope:PinMatrixRequestType)
} 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.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import com.circlegate.liban.base.ApiBase.ApiCreator;
import com.circlegate.liban.base.ApiBase.ApiParcelable;
import com.circlegate.liban.base.ApiBase.IApiParcelable;
import com.circlegate.liban.base.ApiDataIO.ApiDataInput;
import com.circlegate.liban.base.ApiDataIO.ApiDataOutput;
import com.circlegate.liban.base.Exceptions.NotImplementedException;
import com.circlegate.liban.utils.ActivityUtils;
import com.satoshilabs.trezor.app.R;
import com.satoshilabs.trezor.app.activity.base.BaseActivity;
import com.satoshilabs.trezor.app.common.GlobalContext;
import com.satoshilabs.trezor.app.style.CustomHtml;
import com.satoshilabs.trezor.lib.protobuf.TrezorType.PinMatrixRequestType;
import tinyguava.ImmutableList;

        txtPinStars.addTextChangedListener(new TextWatcher() {

            @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
            @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }

            @Override
            public void afterTextChanged(Editable editable) {
                if (editable.length() == 0) {
                    btnConfirm.setEnabled(false);
                    txtPinStrength.setText(String.valueOf(CustomHtml.HARD_SPACE));
                }
                else {
                    btnConfirm.setEnabled(true);

                    for (int i = 0; i < tmpNumCounts.length; i++)
                        tmpNumCounts[i] = 0;

                    for (int i = 0; i < editable.length(); i++) {
                        int ind = (int) editable.charAt(i) - 49;
                        tmpNumCounts[ind]++;
                    }

                    int diffDigits = 0;
                    for (int i = 0; i < tmpNumCounts.length; i++) {
                        if (tmpNumCounts[i] > 0)
                            diffDigits++;
                    }

                    if (diffDigits < 4) {
                        final int problemColor = ContextCompat.getColor(EnterPinActivity.this, R.color.text_problem);
                        final String html = CustomHtml.getFontColorTag(getString(R.string.enter_pin_strength_weak), problemColor);
                        txtPinStrength.setText(CustomHtml.fromHtmlWithCustomSpans(html));
                    } else if (diffDigits < 6) txtPinStrength.setText(R.string.enter_pin_strength_fine);
                    else if (diffDigits < 8) txtPinStrength.setText(R.string.enter_pin_strength_strong);
                    else txtPinStrength.setText(R.string.enter_pin_strength_ultimate);
                }

                boolean pinBtnsEnabled = editable.length() < PIN_MAX_LENGTH;
                for (View button : buttons)
                    button.setEnabled(pinBtnsEnabled);
            }
        });

        btnCancel.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });

        btnConfirm.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                String pin = txtPinStars.getText().toString();
                if (pin.length() > 0) {
                    ActivityUtils.setResultParcelable(EnterPinActivity.this, RESULT_OK, new EnterPinActivityResult(activityParam, pin));
                    finish();
                }
            }
        });

        switch (activityParam.pinMatrixRequestType) {
            case PinMatrixRequestType_Current:
                txtTitle.setText(R.string.enter_pin_current_prompt);
                txtText.setText(R.string.enter_pin_text);
                break;

            case PinMatrixRequestType_NewFirst:
                txtTitle.setText(R.string.enter_pin_new_prompt);
                txtText.setText(R.string.enter_pin_text);
                break;

            case PinMatrixRequestType_NewSecond:
                txtTitle.setText(R.string.enter_pin_repeat_prompt);
                txtText.setText(R.string.enter_pin_text_repeat);
                break;

            default: throw new NotImplementedException();
        }

        if (savedInstanceState == null) {
            txtPinStars.setText("");
        }

        ActivityUtils.setResultParcelable(this, RESULT_CANCELED, new EnterPinActivityResult(activityParam, ""));
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("tmp", true);
    }


    //
    // PRIVATE
    //

    void addPinButton(ImmutableList.Builder<View> bButtons, int rid, final int number) {
        View button = findViewById(rid);
        button.setOnClickListener(new OnClickListener() {
            @SuppressLint("SetTextI18n")
            @Override
            public void onClick(View view) {
                if (txtPinStars.getText().length() < PIN_MAX_LENGTH) {
                    txtPinStars.setText(txtPinStars.getText().toString() + number);
                }
            }
        });
        bButtons.add(button);
    }


    //
    // INNER CLASSES
    //

    public static class EnterPinActivityParam extends ApiParcelable {
        public final String taskId;
        public final IApiParcelable tag; // optional abstract