public class HelpActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_help);
    }

    public static void showIntent(Context parent) {
        Intent intent = new Intent(parent, HelpActivity.class);
        Bundle b = new Bundle();
        intent.putExtras(b);
        parent.startActivity(intent);
    }
} public class ItemDetailActivity extends ActionBarActivity {
    public static final String ITEM = "item";
    public static final String STACK_SIZE = "stack-size";
    private static final String LOG_TAG = "ItemDetailActivity";
    private BaseAdapter mAdapter;

    public static void showItemItent(Context parent, Item item) {
        Intent intent = new Intent(parent, ItemDetailActivity.class);
        Bundle b = new Bundle();
        b.putLong(ItemDetailActivity.ITEM, item.getItemHash());
        b.putLong(ItemDetailActivity.STACK_SIZE, item.getStackSize());
        intent.putExtras(b);
        parent.startActivity(intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ItemMonitor.getInstance().unregisterAdapter(mAdapter);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_item_detail);
        Bundle b = getIntent().getExtras();
        Item item = null;
        long itemHash = b.getLong(ITEM);
        long itemStackSize = b.getLong(STACK_SIZE);

        for (Item tmp_item : Items.getInstance().all()) {
            if (tmp_item.getItemHash() == itemHash && tmp_item.getStackSize() == itemStackSize) {
                item = tmp_item;
                break;
            }
        }
        ((ImageView) findViewById(R.id.detail_icon)).setImageBitmap(ImageStorage.getInstance().getImage(item.getItemHash()));
        setTitle(item.getName());
        ((TextView) findViewById(R.id.detail_name)).setText(item.getDetails());
        ListView lv = (ListView) findViewById(R.id.item_actions_list);

        mAdapter = new ItemActionAdapter(this, item);

        ItemMonitor.getInstance().registerAdapter(mAdapter);
        lv.setAdapter(mAdapter);
        setListViewHeightBasedOnChildren(lv);

        lv = (ListView) findViewById(R.id.item_labels_list);
        lv.setAdapter(new ItemLabelsAdapter(this, item));
        setListViewHeightBasedOnChildren(lv);

        TextView labelsLabel = (TextView) findViewById(R.id.labels_label);
        if (lv.getAdapter().getCount() == 0) {
            labelsLabel.setVisibility(View.INVISIBLE);
        } else {
            labelsLabel.setVisibility(View.VISIBLE);
        }
    }

    public static void setListViewHeightBasedOnChildren(ListView listView) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null)
            return;

        int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.UNSPECIFIED);
        int totalHeight = 0;
        View view = null;
        for (int i = 0; i < listAdapter.getCount(); i++) {
            view = listAdapter.getView(i, view, listView);
            if (i == 0)
                view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, GridLayout.LayoutParams.WRAP_CONTENT));

            view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
            totalHeight += view.getMeasuredHeight();
        }
        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
        listView.requestLayout();
    }

    private class ItemLabelsAdapter extends BaseAdapter {
        private final Item item;
        private final Context context;

        public ItemLabelsAdapter(Context context, Item item) {
            this.context = context;
            this.item = item;
        }

        @Override
        public boolean areAllItemsEnabled() {
            return true;
        }

        @Override
        public boolean isEnabled(int position) {
            return true;
        }


        @Override
        public int getCount() {
            return item.getLabels().size();
        }

        @Override
        public Object getItem(int position) {
            return item.getLabels().get(position);
        }

        @Override
        public long getItemId(int position) {
            return ((Label) getItem(position)).getId();
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final LabelView itemView;
            if (convertView == null) {
                itemView = new LabelView(context);
            } else {
                itemView = (LabelView) convertView;
            }
            itemView.setLabel((Label) getItem(position));
            return itemView;
        }

        @Override
        public int getItemViewType(int position) {
            return 0;
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }
    }
} public class LabelsEditorActivity extends AppCompatActivity implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {

    private static final String LOG_TAG = "LabelEditorActivity";
    private BaseAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_labels_editor);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleAddLabel();
            }
        });

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        ListView listview = (ListView) findViewById(R.id.label_editor_list);
        adapter = new LabelEditorAdapter(this);
        listview.setAdapter(adapter);
        listview.setSelected(true);

        LabelMonitor.getInstance().registerAdapter(adapter);
        listview.setOnItemClickListener(this);
        listview.setOnItemLongClickListener(this);
    }

    @Override
    public void onDestroy() {
        LabelMonitor.getInstance().unregisterAdapter(adapter);
        super.onDestroy();
    }

    private void handleAddLabel() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        final Label label = new Label(getString(R.string.new_label), -1, -196602);
        final LabelEditView labelEdit = new LabelEditView(this);
        labelEdit.setLabel(label);

        builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.v(LOG_TAG, "Ok clicked");
                label.save();
            }
        }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.v(LOG_TAG, "cancel clicked");
            }
        }).setView(labelEdit).setTitle(R.string.add_new_layout_label).create().show();

    }

    public static void showIntent(Context parent) {
        Intent intent = new Intent(parent, LabelsEditorActivity.class);
        Bundle b = new Bundle();
        intent.putExtras(b);
        parent.startActivity(intent);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        final Label label = (Label) adapter.getItem(position);

        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        final LabelEditView labelEdit = new LabelEditView(this);
        labelEdit.setLabel(label);
        final String name = label.getName();
        final int color = (int) label.getColor();
        builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                label.save();
            }
        }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                label.setName(name);
                label.setColor(color);
            }
        }).setView(labelEdit).setTitle(R.string.edit_layout_label).create().show();
    }

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        if(adapter.getCount()>1) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            final Label label = (Label) adapter.getItem(position);

            String text = String.format(getResources().getString(R.string.confirm_label_delete), label.getName());
            builder.setTitle(text).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    label.delete();
                }
            }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {

                }
            }).create().show();
        }
        return true;
    }
} public class LoginActivity extends ActionBarActivity {
    public final static String URL = "org.swistowski.destinyshelve.URL";
    public final static int LOGIN_REQUEST = 60;
    private static final String PSN_URL_DEFAULT = "https://www.bungie.net/en/User/SignIn/Psnid";
    private String psn_url = PSN_URL_DEFAULT;
    private String xone_url = XONE_URL_DEFAULT;
    private static final String XONE_URL_DEFAULT = "https://www.bungie.net/en/User/SignIn/Xuid";
    private static final String LOG_TAG = "LoginActivity";
    private static final String XONE_URL_ID = "Xone";
    private static final String PSN_URL_ID = "psn";

    public static void goLogin(Activity c, String xBoxUrl, String psnUrl){
        Intent intent = new Intent(c, LoginActivity.class);
        intent.putExtra(XONE_URL_ID, xBoxUrl);
        intent.putExtra(PSN_URL_ID, psnUrl);
        c.startActivityForResult(intent, LoginActivity.LOGIN_REQUEST);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        String url = intent.getStringExtra(XONE_URL_ID);
        if(url!=null){
            xone_url = url;
        }
        url = intent.getStringExtra(PSN_URL_ID);
        if(url!=null){
            psn_url = url;
        }

        setContentView(R.layout.activity_login);
        TextView loginInformation = (TextView)findViewById(R.id.loginDetailInformation);
        loginInformation.setText(Html.fromHtml(getString(R.string.login_information)));
    }

    void goToUrl(String url){
        Intent i = new Intent(getApplicationContext(), WebViewActivity.class);
        i.putExtra(URL, url);
        startActivityForResult(i, LOGIN_REQUEST);
    }

    private Tracker getTracker(){
        return  ((Application) getApplication()).getTracker();
    }

    public void onPSNButtonClick(View view){
        getTracker().send(new HitBuilders.EventBuilder()
                .setCategory(getString(R.string.tracker_category_user_action))
                .setAction(getString(R.string.tracker_action_click))
                .setLabel("Psn button")
                .build());
        goToUrl(psn_url);
    }

    public void onOneButtonClick(View view) {
        getTracker().send(new HitBuilders.EventBuilder()
                .setCategory(getString(R.string.tracker_category_user_action))
                .setAction(getString(R.string.tracker_action_click))
                .setLabel("One button")
                .build());
        goToUrl(xone_url);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == LOGIN_REQUEST) {
            Intent resultIntent = new Intent();
            setResult(resultCode, resultIntent);
            finish();
        }
    }
} public class SendLogActivity extends Activity {
    public final static String TAG = "AndroidLogCollector";//$NON-NLS-1$
    public final static String LINE_SEPARATOR = System.getProperty("line.separator");//$NON-NLS-1$

    public static final String ACTION_SEND_LOG = "com.xtralogic.logcollector.intent.action.SEND_LOG";//$NON-NLS-1$
    public static final String EXTRA_SEND_INTENT_ACTION = "com.xtralogic.logcollector.intent.extra.SEND_INTENT_ACTION";//$NON-NLS-1$
    public static final String EXTRA_DATA = "com.xtralogic.logcollector.intent.extra.DATA";//$NON-NLS-1$
    public static final String EXTRA_ADDITIONAL_INFO = "com.xtralogic.logcollector.intent.extra.ADDITIONAL_INFO";//$NON-NLS-1$
    public static final String EXTRA_SHOW_UI = "com.xtralogic.logcollector.intent.extra.SHOW_UI";//$NON-NLS-1$
    public static final String EXTRA_FILTER_SPECS = "com.xtralogic.logcollector.intent.extra.FILTER_SPECS";//$NON-NLS-1$
    public static final String EXTRA_FORMAT = "com.xtralogic.logcollector.intent.extra.FORMAT";//$NON-NLS-1$
    public static final String EXTRA_BUFFER = "com.xtralogic.logcollector.intent.extra.BUFFER";//$NON-NLS-1$

    final int MAX_LOG_MESSAGE_LENGTH = 100000;

    private AlertDialog mMainDialog;
    private Intent mSendIntent;
    private CollectLogTask mCollectLogTask;
    private ProgressDialog mProgressDialog;
    private String mAdditonalInfo;
    private boolean mShowUi;
    private String[] mFilterSpecs;
    private String mFormat;
    private String mBuffer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mSendIntent = null;

        Intent intent = getIntent();
        if (null != intent) {
            String action = intent.getAction();
            if (ACTION_SEND_LOG.equals(action)) {
                String extraSendAction = intent.getStringExtra(EXTRA_SEND_INTENT_ACTION);
                if (extraSendAction == null) {
                    Log.e(TAG, "Quiting, EXTRA_SEND_INTENT_ACTION is not supplied");//$NON-NLS-1$
                    finish();
                    return;
                }

                mSendIntent = new Intent(extraSendAction);
                mSendIntent.setType("text/plain");//$NON-NLS-1$

                String[] emails = intent.getStringArrayExtra(Intent.EXTRA_EMAIL);
                if (emails != null) {
                    mSendIntent.putExtra(Intent.EXTRA_EMAIL, emails);
                }

                String[] ccs = intent.getStringArrayExtra(Intent.EXTRA_CC);
                if (ccs != null) {
                    mSendIntent.putExtra(Intent.EXTRA_CC, ccs);
                }

                String[] bccs = intent.getStringArrayExtra(Intent.EXTRA_BCC);
                if (bccs != null) {
                    mSendIntent.putExtra(Intent.EXTRA_BCC, bccs);
                }

                String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
                if (subject != null) {
                    mSendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
                }

                mAdditonalInfo = intent.getStringExtra(EXTRA_ADDITIONAL_INFO);
                mShowUi = intent.getBooleanExtra(EXTRA_SHOW_UI, false);
                mFilterSpecs = intent.getStringArrayExtra(EXTRA_FILTER_SPECS);
                mFormat = intent.getStringExtra(EXTRA_FORMAT);
                mBuffer = intent.getStringExtra(EXTRA_BUFFER);
            }
        }

        if (null == mSendIntent) {
            //standalone application
            mShowUi = true;
            mSendIntent = new Intent(Intent.ACTION_SEND);
            mSendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.message_subject));
            mSendIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {getString(R.string.developer_email)});
            mSendIntent.setType("text/plain");//$NON-NLS-1$

            mAdditonalInfo = getString(R.string.device_info_fmt, getVersionNumber(this), Build.MODEL, Build.VERSION.RELEASE, getFormattedKernelVersion(), Build.DISPLAY);
            mFormat = "time";
        }

        collectAndSendLog();
    }

    @SuppressWarnings("unchecked")
    void collectAndSendLog() {
        /*Usage: logcat [options] [filterspecs]
        options include:
          -s              Set default filter to silent.
                          Like specifying filterspec '*:s'
          -f <filename>   Log to file. Default to stdout
          -r [<kbytes>]   Rotate log every kbytes. (16 if unspecified). Requires -f
          -n <count>      Sets max number of rotated logs to <count>, default 4
          -v <format>     Sets the log print format, where <format> is one of:

                          brief process tag thread raw time threadtime long

          -c              clear (flush) the entire log and exit
          -d              dump the log and then exit (don't block)
          -g              get the size of the log's ring buffer and exit
          -b <buffer>     request alternate ring buffer
                          ('main' (default), 'radio', 'events')
          -B              output the log in binary
        filterspecs are a series of
          <tag>[:priority]

        where <tag> is a log component tag (or * for allAsMap) and priority is:
          V    Verbose
          D    Debug
          I    Info
          W    Warn
          E    Error
          F    Fatal
          S    Silent (supress allAsMap output)

        '*' means '*:d' and <tag> by itself means <tag>:v

        If not specified on the commandline, filterspec is set from ANDROID_LOG_TAGS.
        If no filterspec is found, filter defaults to '*:I'

        If not specified with -v, format is set from ANDROID_PRINTF_LOG
        or defaults to "brief"*/

        ArrayList<String> list = new ArrayList<>();

        if (mFormat != null) {
            list.add("-v");
            list.add(mFormat);
        }

        if (mBuffer != null) {
            list.add("-b");
            list.add(mBuffer);
        }

        if (mFilterSpecs != null) {
            Collections.addAll(list, mFilterSpecs);
        }

        mCollectLogTask = (CollectLogTask) new CollectLogTask().execute(list);
    }

    private class CollectLogTask extends AsyncTask<ArrayList<String>, Void, StringBuilder> {
        @Override
        protected void onPreExecute() {
            showProgressDialog(getString(R.string.acquiring_log_progress_dialog_message));
        }

        @Override
        protected StringBuilder doInBackground(ArrayList<String>... params) {
            final StringBuilder log = new StringBuilder();
            try {
                ArrayList<String> commandLine = new ArrayList<>();
                commandLine.add("logcat");//$NON-NLS-1$
                commandLine.add("-d");//$NON-NLS-1$
                ArrayList<String> arguments = ((params != null) && (params.length > 0)) ? params[0] : null;
                if (null != arguments) {
                    commandLine.addAll(arguments);
                }

                Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[0]));
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));

                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    log.append(line);
                    log.append(LINE_SEPARATOR);
                }
            } catch (IOException e) {
                Log.e(TAG, "CollectLogTask.doInBackground failed", e);//$NON-NLS-1$
            }

            return log;
        }

        @Override
        protected void onPostExecute(StringBuilder log) {
            if (null != log) {
                //truncate if necessary
                int keepOffset = Math.max(log.length() - MAX_LOG_MESSAGE_LENGTH, 0);
                if (keepOffset > 0) {
                    log.delete(0, keepOffset);
                }

                if (mAdditonalInfo != null) {
                    log.insert(0, LINE_SEPARATOR);
                    log.insert(0, mAdditonalInfo);
                }

                mSendIntent.putExtra(Intent.EXTRA_TEXT, log.toString());
                startActivity(Intent.createChooser(mSendIntent, getString(R.string.chooser_title)));
                dismissProgressDialog();
                dismissMainDialog();
                finish();
            } else {
                dismissProgressDialog();
                showErrorDialog(getString(R.string.failed_to_get_log_message));
            }
        }
    }

    void showErrorDialog(String errorMessage) {
        new AlertDialog.Builder(this)
                .setTitle(getString(R.string.app_name))
                .setMessage(errorMessage)
                .setIcon(android.R.drawable.ic_dialog_alert)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        finish();
                    }
                })
                .show();
    }

    void dismissMainDialog() {
        if (null != mMainDialog && mMainDialog.isShowing()) {
            mMainDialog.dismiss();
            mMainDialog = null;
        }
    }

    void showProgressDialog(String message) {
        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setIndeterminate(true);
        mProgressDialog.setMessage(message);
        mProgressDialog.setCancelable(true);
        mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            public void onCancel(DialogInterface dialog) {
                cancellCollectTask();
                finish();
            }
        });
        mProgressDialog.show();
    }

    private void dismissProgressDialog() {
        if (null != mProgressDialog && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
        }
    }

    void cancellCollectTask() {
        if (mCollectLogTask != null && mCollectLogTask.getStatus() == AsyncTask.Status.RUNNING) {
            mCollectLogTask.cancel(true);
            mCollectLogTask = null;
        }
    }

    @Override
    protected void onPause() {
        cancellCollectTask();
        dismissProgressDialog();
        dismissMainDialog();

        super.onPause();
    }

    private static String getVersionNumber(Context context) {
        String version = "?";
        try {
            PackageInfo packagInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            version = packagInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
        }
        ;

        return version;
    }

    private String getFormattedKernelVersion() {
        String procVersionStr;

        try {
            BufferedReader reader = new BufferedReader(new FileReader("/proc/version"), 256);
            try {
                procVersionStr = reader.readLine();
            } finally {
                reader.close();
            }

            final String PROC_VERSION_REGEX =
                    "\\w+\\s+" + /* ignore: Linux */
                            "\\w+\\s+" + /* ignore: version */
                            "([^\\s]+)\\s+" + /* group 1: 2.6.22-omap1 */
                            "\\(([^\\s@]+(?:@[^\\s.]+)?)[^)]*\\)\\s+" + /* group 2: (xxxxxx@xxxxx.constant) */
                            "\\([^)]+\\)\\s+" + /* ignore: (gcc ..) */
                            "([^\\s]+)\\s+" + /* group 3: #26 */
                            "(?:PREEMPT\\s+)?" + /* ignore: PREEMPT (optional) */
                            "(.+)"; /* group 4: date */

            Pattern p = Pattern.compile(PROC_VERSION_REGEX);
            Matcher m = p.matcher(procVersionStr);

            if (!m.matches()) {
                Log.e(TAG, "Regex did not match on /proc/version: " + procVersionStr);
                return "Unavailable";
            } else if (m.groupCount() < 4) {
                Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
                        + " groups");
                return "Unavailable";
            } else {
                return (new StringBuilder(m.group(1)).append("\n").append(
                        m.group(2)).append(" ").append(m.group(3)).append("\n")
                        .append(m.group(4))).toString();
            }
        } catch (IOException e) {
            Log.e(TAG,
                    "IO Exception when getting kernel version for Device Info screen",
                    e);

            return "Unavailable";
        }
    }
} public class SettingsActivity extends AppCompatActivity {
    public static void openSettingsActivity(Activity activity) {
        Intent intent = new Intent(activity, SettingsActivity.class);
        activity.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setupActionBar();
        setContentView(R.layout.activity_settings);
    }

    private void setupActionBar() {
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
    }

} public class WebViewActivity extends Activity {
    public static final int RESULT_RELOAD = 123;
    private static final String LOG_TAG = "WebViewActivity";
    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view);
        Intent intent = getIntent();
        String url = intent.getStringExtra(LoginActivity.URL);
        getWebView().loadUrl(url);
    }


    WebView getWebView() {
        if (webView == null) {
            webView = (WebView) findViewById(R.id.webView);
           // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || true) {
                /* fallback, i can get headers in normal way only in lollipop */
                class JsObject {
                    @JavascriptInterface
                    public void pageFinished() {
                        pageLoaded();
                    }
                }
                webView.addJavascriptInterface(new JsObject(), "INTERFACE");
                webView.setWebViewClient(new WebViewClient() {
                    @Override
                    public void onPageFinished(WebView view, String url) {
                        // call the pageLoaded method with javascript
                        super.onPageFinished(view, url);
                        if(url.equals("https://www.bungie.net/")){
                            view.loadUrl("javascript:window.INTERFACE.pageFinished()");
                        }
                    }
                });
            /*
            } else {
                webView.setWebViewClient(new WebViewClient() {
                    @Override
                    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
                        WebResourceResponse ret = super.shouldInterceptRequest(view, request);
                        if (request.getUrl().toString().startsWith("https://www.bungie.net/Platform/User/GetBungieNetUser/")) {
                            pageLoaded();
                        }
                        return ret;
                    }
                });
            }
            */

            WebSettings webSettings = webView.getSettings();
            webSettings.setJavaScriptEnabled(true);
        }
        return webView;
    }

    private void pageLoaded() {
        Intent resultIntent = new Intent();
        setResult(RESULT_RELOAD, resultIntent);
        finish();
    }
} public class ItemsFragmentPagerAdapter extends FragmentStatePagerAdapter {
    private static final String LOG_TAG = "ItemsFragmentPagerAdapter";
    private final List<Page> mPages = new ArrayList<>();

    public ItemsFragmentPagerAdapter(FragmentManager fm, Context context, boolean isPremium) {
        super(fm);
        if (!isPremium) {
            mPages.add(new Page(context.getString(R.string.ad), new FragmentProvider() {
                @Override
                public Fragment getFragment() {
                    return AdFragment.newInstance();
                }
            }));
        }

        if (Characters.getInstance().all() != null) {
            for (final Character character : Characters.getInstance().all()) {
                Page page = new Page(Html.fromHtml(character.getLabel()), new FragmentProvider() {
                    @Override
                    public Fragment getFragment() {
                        return ItemListFragment.newInstance(ItemListFragment.DIRECTION_TO, character.getId());
                    }
                });

                mPages.add(page);
                /*
                  Future functionality, move from character too
                "▼ " +
                mPages.add(new Page("▲ " + character.toString(), new FragmentProvider() {
                    @Override
                    public Fragment getFragment() {
                        return ItemListFragment.newInstance(ItemListFragment.DIRECTION_FROM, character.getId());
                    }
                }));
                */
            }
        }

        if (Items.getInstance().allAsMap().size() > 0) {
            mPages.add(new Page(context.getString(R.string.vault), new FragmentProvider() {

                @Override
                public Fragment getFragment() {
                    return ItemListFragment.newInstance(ItemListFragment.DIRECTION_TO, Items.VAULT_ID);
                }
            }));
        }
        mPages.add(new Page(context.getString(R.string.materials), new FragmentProvider() {
            @Override
            public Fragment getFragment() {
                return MaterialFragment.newInstance();
            }
        }));
        if (isPremium) {
            mPages.add(new Page(context.getString(R.string.ad), new FragmentProvider() {
                @Override
                public Fragment getFragment() {
                    return AdFragment.newInstance();
                }
            }));
        }
    }

    @Override
    public Fragment getItem(int i) {
        return mPages.get(i).getFragment();
    }

    @Override
    public int getCount() {
        return mPages.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mPages.get(position).getLabel();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    public void setIsPremium(Context context) {
        if (this.mPages.get(0).label == context.getString(R.string.ad)) {
            Page ad_page = mPages.get(0);
            for (int i = 0; i < mPages.size() - 1; i++) {
                mPages.set(i, mPages.get(i + 1));
            }
            mPages.set(mPages.size() - 1, ad_page);
            try {
                notifyDataSetChanged();
            } catch (IllegalStateException e) {
                // really rare issue, when notifiDataSetChanged is called when screen is changed from horizontal to vertical
            }
        } else {
        }

    }

    private interface FragmentProvider {
        public Fragment getFragment();
    }

    private class Page {
        private final CharSequence label;
        private final FragmentProvider fp;

        public Page(CharSequence label, FragmentProvider fp) {
            this.label = label;
            this.fp = fp;
        }

        public CharSequence getLabel() {
            return label;
        }

        public Fragment getFragment() {
            return fp.getFragment();
        }
    }
} public class AdFragment extends Fragment {

    private static final String URL = "https://play.google.com/store/apps/details?id=org.swistowski.vaulthelper";
    private static final int PLUS_ONE_REQUEST_CODE = 0;
    private OnAdIterationListener mListener;
    private PlusOneButton mPlusOneButton;


    public static AdFragment newInstance() {
        AdFragment fragment = new AdFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    public AdFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_ad, container, false);
        /*
        here i can connect listeners
         */
        AdView mAdView = (AdView) view.findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder().addTestDevice(AdRequest.DEVICE_ID_EMULATOR).addTestDevice("29C967B06B52D5394DC9B626917C8021").build();
        mAdView.loadAd(adRequest);
        view.findViewById(R.id.support_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mListener.onRequestSupportDev();
            }
        });
        view.findViewById(R.id.rate_me_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                handleRateMeClick(view);
            }
        });
        view.findViewById(R.id.isues_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleIssuesClick(v);
            }
        });
        mPlusOneButton = (PlusOneButton) view.findViewById(R.id.plus_one_button);

        view.findViewById(R.id.share_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleShareButtonClick();
            }
        });
        /*
        view.findViewById(R.id.share_button).setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                handleShareClick(v);
            }
        });
        */
        return view;
    }

    private void handleShareButtonClick() {
        Intent sharingIntent = new Intent(Intent.ACTION_SEND);
        sharingIntent.setType("text/plain");
        sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.google_plus_share_title));
        sharingIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_text));
        startActivity(Intent.createChooser(sharingIntent, getString(R.string.share_action_label)));
    }

    private void handleShareClick(View v) {
        /*
        Intent shareIntent = new PlusShare.Builder(getActivity()).setType("text/plain")
                .setText(getActivity().getString(R.string.google_plus_share_title))
                .setContentUrl(Uri.parse("https://play.google.com/store/apps/details?id=org.swistowski.vaulthelper"))
                .getIntent();
        getActivity().startActivityForResult(shareIntent, 0);
        */
    }

    private boolean startActivityHelper(Intent aIntent) {
        try
        {
            startActivity(aIntent);
            return true;
        }
        catch (ActivityNotFoundException e)
        {
            return false;
        }
    }

    private void handleRateMeClick(View view) {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("market://details?id=org.swistowski.vaulthelper"));
            if (!startActivityHelper(intent)) {
                intent.setData(Uri.parse("https://play.google.com/store/apps/details?id=org.swistowski.vaulthelper"));
                if (!startActivityHelper(intent)) {
                    Toast.makeText(getActivity(), "Could not open Android market, please install the market app.", Toast.LENGTH_SHORT).show();
                }
            }
    }

    private void handleIssuesClick(View view){
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("https://github.com/DestinyVaultHelper/dvh/issues"));
        startActivityHelper(intent);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnAdIterationListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnAdIterationListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnAdIterationListener {
        public void onRequestSupportDev();
    }

    @Override
    public void onResume() {
        mPlusOneButton.initialize(URL, PLUS_ONE_REQUEST_CODE);
        super.onResume();
    }
} public class ItemListFragment extends Fragment implements AdapterView.OnItemLongClickListener, AdapterView.OnItemClickListener, SwipeRefreshLayout.OnRefreshListener {

    public static final int DIRECTION_TO = 1;
    private static final String ARG_DIRECTION = "direction";
    private static final String ARG_SUBJECT = "subject";
    private ItemAdapter mAdapter;
    private OnItemIterationListener mListener;

    private String mSubject;
    private boolean mEnabled = true;


    public static Fragment newInstance(int direction, String subject) {
        ItemListFragment fragment = new ItemListFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_DIRECTION, direction);
        args.putString(ARG_SUBJECT, subject);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getArguments() != null) {
            mSubject = getArguments().getString(ARG_SUBJECT);
        }

        mAdapter = new ItemAdapter(getActivity(), mSubject);
        ItemMonitor.getInstance().registerAdapter(mAdapter);
    }

    @Override
    public void onDestroy() {
        ItemMonitor.getInstance().unregisterAdapter(mAdapter);
        super.onDestroy();
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(
                R.layout.fragment_collection_object, container, false);

        AdapterView listView = (AdapterView) rootView.findViewById(R.id.listView);
        listView.setAdapter(mAdapter);
        listView.setOnItemClickListener(this);
        listView.setOnItemLongClickListener(this);

        return rootView;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnItemIterationListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnFragmentInteractionListener");
        }

    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        if (!mEnabled) {
            return false;
        }
        if (null != mListener) {
            return mListener.onItemLongClicked(this, mAdapter.getItem(position), mSubject);
        }
        return false;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if (!mEnabled) {
            return;
        }
        if (null != mListener) {
            Item item = mAdapter.getItem(position);
            if (!((ItemView) view).getIsGrayed() && item.isMoveable()) {
                mListener.onItemClicked(this, item, mSubject);
            } else {
                mListener.onItemLongClicked(this, item, mSubject);
            }
        }
    }

    @Override
    public void onRefresh() {
        mEnabled = false;
        mListener.refreshRequest(new Runnable() {
            @Override
            public void run() {
                mEnabled = true;
            }
        });
    }

    public interface OnItemIterationListener {
        void onItemClicked(ItemListFragment fragment, Item item, String subject);

        boolean onItemLongClicked(ItemListFragment fragment, Item item, String subject);

        void refreshRequest(Runnable finished);
    }
} public class SettingsFragment extends Fragment {

    private OnSettingsIterationListener mListener;

    public static SettingsFragment newInstance() {
        SettingsFragment fragment = new SettingsFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    public SettingsFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_settings, container, false);
        View.OnClickListener ocl = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onButtonPressed(v.getId());
            }
        };
        view.findViewById(R.id.button_login).setOnClickListener(ocl);
        view.findViewById(R.id.button_reload).setOnClickListener(ocl);
        view.findViewById(R.id.button_logout).setOnClickListener(ocl);
        return view;
    }

    void onButtonPressed(int buttonId) {
        if (mListener != null) {
            mListener.onButtonPressed(buttonId);
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnSettingsIterationListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnSettingsIterationListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();

        mListener = null;
    }

    public interface OnSettingsIterationListener {
        public void onButtonPressed(int buttonId);
    }

} public class Character implements Serializable {
    private static final String LOG_TAG = "ModelCharacter";
    private static final long GENDER_FEMALE = 2204441813L;
    private static final long GENDER_MALE = 3111576190L;
    private static final long RACE_EXO = 898834093L;
    private static final long RACE_AWOKEN = 2803282938L;
    private static final long RACE_HUMAN = 3887404748L;
    private static final int APPEARANCE_SHORT = 0;
    private static final int APPEARANCE_LONG = 1;
    private static final int APPEARANCE_MEDIUM = 2;
    private final int mClassType;
    private final int mLevel;
    private final String mEmblemPath;
    private final String mBackgroundPath;
    private final String mId;
    private final long mGenderHash;
    private final long mRaceHash;

    private Character(final String characterId, final int classType, final int level, final String emblemPath, final String backgroundPath, final long raceHash, final long genderHash) {
        mId = characterId;
        mLevel = level;
        mClassType = classType;
        mEmblemPath = emblemPath;
        mBackgroundPath = backgroundPath;
        mRaceHash = raceHash;
        mGenderHash = genderHash;
    }

    static public ArrayList<Character> collectionFromJson(JSONArray data) throws JSONException {
        ArrayList<Character> collection = new ArrayList<>();

        for (int i = 0; i < data.length(); i++) {
            final Character c = Character.fromJson(data.getJSONObject(i));
            collection.add(c);
        }
        return collection;
    }

    private static Character fromJson(JSONObject data) throws JSONException {
        return new Character(
                data.getJSONObject("characterBase").getString("characterId"),
                data.getJSONObject("characterBase").getInt("classType"),
                data.getInt("characterLevel"),
                data.optString("emblemPath", ""),
                data.optString("backgroundPath", ""),
                data.getJSONObject("characterBase").optLong("raceHash"),
                data.getJSONObject("characterBase").optLong("genderHash")
        );
    }

    public String getId() {
        return mId;
    }

    String getClassName() {
        switch (mClassType) {
            case 0:
                return "Titan";
            case 1:
                return "Hunter";
            default:
                return "Warlock";
        }
    }

    String getGenderName() {
        if (mGenderHash == GENDER_FEMALE) {
            return "Female";
        } else if (mGenderHash == GENDER_MALE) {
            return "Male";
        }
        return "Unknown";
    }

    String getGenderSymbol() {
        if (mGenderHash == GENDER_FEMALE) {
            return "♀";
        } else if (mGenderHash == GENDER_MALE) {
            return "♂";
        }
        return "";
    }

    String getRaceName() {
        if (mRaceHash == RACE_EXO) {
            return "Exo";
        } else if (mRaceHash == RACE_AWOKEN) {
            return "Awoken";
        } else if (mRaceHash == RACE_HUMAN) {
            return "Human";
        }
        return "Unknown";
    }

    String getRaceSymbol() {
        return getRaceName().substring(0, 1);
    }

    public String getBackgroundPath() {
        return mBackgroundPath;
    }

    public String getEmblemPath() {
        return mEmblemPath;
    }

    public String getLabel(int i) {
        switch (i) {
            case APPEARANCE_SHORT:
                return "<b>" + getClassName() + "</b> " + mLevel;
            case APPEARANCE_LONG:
                return "<b>" + mLevel + " " + getClassName() + "</b> " + getRaceName() + " " + getGenderName();
            case APPEARANCE_MEDIUM:
                return "<b>" + mLevel + " " + getClassName() + "</b> " + getRaceSymbol() + getGenderSymbol();
        }
        return "<b>" + getClassName() + "</b> " + mLevel;
    }

    public String getLabel() {
        return getLabel(Preferences.getInstance().tabStyle());
    }
} public class Item implements Serializable, Comparable<Item> {
    public static final int LOCATION_UNKNOWN = 0;
    public static final int LOCATION_INVENTORY = 1;
    public static final int LOCATION_VAULT = 2;
    public static final int LOCATION_VENDOR = 3;
    public static final int LOCATION_POSTMASTER = 4;
    private static final String LOG_TAG = "Item";
    public static final int DAMAGE_TYPE_SOLAR = 3;
    public static final int DAMAGE_TYPE_ARC = 2;
    public static final int DAMAGE_TYPE_VOID = 4;

    private final long mItemHash;
    private final String mItemInstanceId;
    private final int mBindStatus;
    private final int mDamageType;
    private final int mPrimaryStatValue;
    private final int mTierType;
    private final int mCannotEquipReason;
    private final int mLocation;
    private boolean mIsEquipped;
    private boolean mIsLocked;
    private final int mItemLevel;
    private int mStackSize;
    private final int mQualityLevel;
    private boolean mCanEquip;
    private boolean mIsEquipment;
    private final boolean mIsGridComplete;

    private final String mName;
    private final String mItemDescription;
    private String mIcon;
    private String mSecondaryIcon;
    private final String mTierTypeName;
    private final String mItemTypeName;
    private final long mBucketTypeHash;
    private final int mItemType;
    private final int mItemSubType;
    private final int mClassType;
    private final String mBucketName;
    private final long mUnlockFlagHashRequiredToEquip;


    private final String mJson;
    private final String mBucketDescription;
    private boolean mIsEquippable;


    private Item(long itemHash, int bindStatus, boolean isEquipped, int itemLevel, int stackSize, int qualityLevel, boolean canEquip, boolean isEquipment, boolean isGridComplete, String itemInstanceId,

                 String itemName, String itemDescription, String icon, String secondaryIcon, String tierTypeName, int tierType, String itemTypeName, long bucketTypeHash, int itemType, int itemSubType, int classType,
                 int primaryStatValue,
                 int damageType,
                 String bucketName,
                 int cannotEquipReason,
                 int location,
                 String bucketDescription,
                 String json, String definition,
                 long unlockFlagHashRequiredToEquip,
                 boolean isLocked,
                 boolean isEquippable

    ) {
        mItemHash = itemHash;
        mBindStatus = bindStatus;
        mIsEquipped = isEquipped;
        mItemLevel = itemLevel;
        mStackSize = stackSize;
        mQualityLevel = qualityLevel;
        mCanEquip = canEquip;
        mIsEquipment = isEquipment;
        mIsGridComplete = isGridComplete;
        mIsEquipment = isEquipment;

        mName = (itemName == null) ? "No name" : itemName;
        mIcon = icon;
        mItemDescription = itemDescription;
        mTierTypeName = tierTypeName;
        mTierType = tierType;
        mItemTypeName = itemTypeName;
        mBucketTypeHash = bucketTypeHash;
        mItemType = itemType;
        mItemSubType = itemSubType;
        mClassType = classType;
        mItemInstanceId = itemInstanceId;

        mPrimaryStatValue = primaryStatValue;
        mDamageType = damageType;
        mBucketName = bucketName;
        mCannotEquipReason = cannotEquipReason;
        mLocation = location;
        mBucketDescription = bucketDescription;

        mJson = json;
        mUnlockFlagHashRequiredToEquip = unlockFlagHashRequiredToEquip;
        mIsLocked = isLocked;
        mIsEquippable = isEquippable;
        //mDefinition = definition;
    }

    private static List<Item> fillFromBucket(JSONArray bucket_content, JSONObject items_definitions, JSONObject bucket_definitions, String key, List<Item> items) {
        for (int j = 0; j < bucket_content.length(); j++) {
            JSONArray bucket_items = bucket_content.optJSONObject(j).optJSONArray("items");
            for (int k = 0; k < bucket_items.length(); k++) {
                JSONObject item = bucket_items.optJSONObject(k);
                JSONObject definition = items_definitions.optJSONObject(item.optString("itemHash"));
                JSONObject bucket;
                if (definition != null) {
                    if (bucket_definitions != null)
                        bucket = bucket_definitions.optJSONObject(definition.optString("bucketTypeHash"));
                    else
                        bucket = null;
                    // i'm interested only in transferrable items
                    if (!definition.optBoolean("nonTransferrable")) {
                        //Log.v(LOG_TAG, definition.toString() + " " + bucket.toString());
                        items.add(createItem(key, item, definition, bucket));
                    }
                }
            }

        }
        return items;
    }

    private static List<Item> fromJson(JSONObject data, boolean isVault) throws JSONException {
        List<Item> items = new ArrayList<>();
        JSONObject items_definitions = data.getJSONObject("definitions").getJSONObject("items");
        JSONObject bucket_definitions = data.getJSONObject("definitions").getJSONObject("buckets");
        if (!isVault) {
            JSONObject buckets = data.getJSONObject("data").getJSONObject("buckets");
            Iterator<String> i = buckets.keys();
            while (i.hasNext()) {
                String key = i.next();
                JSONArray bucket_content = buckets.getJSONArray(key);
                items = fillFromBucket(bucket_content, items_definitions, bucket_definitions, key, items);
            }
        } else {
            JSONArray bucket_content = data.optJSONObject("data").optJSONArray("buckets");
            items = fillFromBucket(bucket_content, items_definitions, bucket_definitions, "VAULT", items);
        }

        return items;
    }

    private static Item createItem(String key, JSONObject item, JSONObject definition, JSONObject bucket) {
        JSONObject primaryStat = item.optJSONObject("primaryStat");
        int primaryStatValue = 0;
        if (primaryStat != null) {
            primaryStatValue = primaryStat.optInt("value", 0);
        }
        return new Item(
                //key,
                item.optLong("itemHash"),
                item.optInt("bindStatus"),
                item.optBoolean("isEquipped"),
                item.optInt("itemLevel"),
                item.optInt("stackSize"),
                item.optInt("qualityLevel"),
                item.optBoolean("canEquip"),
                item.optBoolean("isEquipment"),
                item.optBoolean("isGridComplete"),
                item.optString("itemInstanceId"),

                definition.optString("itemName"),
                definition.optString("itemDescription"),
                definition.optString("icon"),
                definition.optString("secondaryIcon"),
                definition.optString("tierTypeName"),
                definition.optInt("tierType"),
                definition.optString("itemTypeName"),
                definition.optLong("bucketTypeHash"),
                definition.optInt("itemType"),
                definition.optInt("itemSubType"),
                definition.optInt("classType"),

                primaryStatValue,
                item.optInt("damageType", 0),
                bucket != null ? bucket.optString("bucketName") : "",
                item.optInt("cannotEquipReason", 0),
                item.optInt("location", 0),
                bucket != null ? bucket.optString("bucketDescription") : "",
                item.toString(),
                definition.toString(),
                item.optLong("unlockFlagHashRequiredToEquip", 0),
                item.optBoolean("locked", false),
                definition.optBoolean("equippable", false)
        );
    }

    public static List<Item> fromJson(String result, boolean isVault) throws JSONException {
        return fromJson(new JSONObject(result), isVault);
    }

    public static List<Item> fromJson(String result) throws JSONException {
        return fromJson(result, false);
    }

    private static final int typeToImportance(int type) {
        switch (type) {
            case 3: // weapon
                return 100;
            case 2: // armor
                return 90;
            case 8: //
                return 80;
            case 9:
                return 70;

        }
        return 3;
    }


    @Override
    public int compareTo(Item another) {
        return Ordering.getInstance().doOrder(this, another);
        //return orderFunctionMap.get(Preferences.getInstance().getOrdering()).doOrder(this, another);
    }

    public String[] debugAttrs() {
        String[] ret = new String[]{};
        return ret;
    }

    public String toString() {
        return mName;
    }

    public long getItemHash() {
        return mItemHash;
    }

    public int getLocation() {
        return mLocation;
    }

    public String getName() {
        return mName;
    }

    public String getIcon() {
        return mIcon;
    }

    public void setIcon(String icon) {
        mIcon = icon;
    }

    public boolean isVisible() {
        if (getStackSize() == 0) {
            return false;
        }
        return true;
    }

    public String getItemId() {
        return mItemInstanceId;
    }

    public String getDetails() {
        return mItemDescription;
    }

    public void moveTo(String target, int stackSize) {
        Items.getInstance().changeOwner(this, target, stackSize);
    }

    public boolean isEquipped() {
        return mIsEquipped;
    }

    public long getBucketTypeHash() {
        return mBucketTypeHash;
    }

    public void setIsEquipped(boolean isEquipped) {
        mIsEquipped = isEquipped;
    }


    public int getPrimaryStatValue() {
        return mPrimaryStatValue;
    }

    public int getDamageType() {
        return mDamageType;
    }

    public String getDamageTypeName() {
        if (mDamageType == DAMAGE_TYPE_SOLAR) {
            return "Solar";
        } else if (mDamageType == DAMAGE_TYPE_ARC) {
            return "Arc";
        } else if (mDamageType == DAMAGE_TYPE_VOID) {
            return "Void";
        }
        return "";
    }

    public int getType() {
        return mItemType;
    }

    public int getStackSize() {
        return mStackSize;
    }

    public String getBucketName() {
        return mBucketName;
    }

    public boolean getIsCompleted() {
        return mIsGridComplete;
    }

    public long getInstanceId() {
        return Long.valueOf(mItemInstanceId);
    }

    public boolean getCanEquip() {
        return mCanEquip;
    }

    public void setCanEquip(boolean canEquip) {
        mCanEquip = canEquip;
    }

    public boolean isMoveable() {
        return mLocation == LOCATION_INVENTORY || mLocation == LOCATION_VAULT;
    }

    private void doMove(final Activity activity, final Item finalItem, final String owner, int stackSize) {
        ItemMover.move(((Application) activity.getApplication()).getWebView(), finalItem, owner, stackSize).then(
                new ItemMover.Result() {
                    @Override
                    public void onSuccess() {
                        activity.finish();
                    }

                    @Override
                    public void onError(String e) {
                        String message;
                        try {
                            message = new JSONObject(e).optString("errorMessage");
                        } catch (JSONException e1) {
                            message = e;
                        }
                        Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
                        // onMoveError(e);
                    }
                }
        );
    }

    public List<Action> posibleActions() {

        List<Action> actions = new LinkedList<>();
        if (mCanEquip) {
            actions.add(new Action(R.string.equip, new Action.ActionRunnable() {
                @Override
                public void run(Activity activity) {
                    doEquip(activity);
                }
            }));
        }
        if (isMoveable()) {
            for (final String owner : Items.getInstance().allAsMap().keySet()) {
                if (!owner.equals(Items.getInstance().getItemOwner(this))) {
                    String ownerLabel = owner;
                    if (Characters.getInstance().get(owner) != null) {
                        ownerLabel = Characters.getInstance().get(owner).getLabel();
                    }
                    Item item = null;
                    for (Item tmp_item : Items.getInstance().all()) {
                        if (Item.this.getItemHash() == tmp_item.getItemHash()) {
                            item = tmp_item;
                            break;
                        }
                    }
                    final Item finalItem = item;
                    actions.add(new Action(R.string.move_to, new Action.ActionRunnable() {
                        @Override
                        public void run(final Activity activity) {
                            if (finalItem.getStackSize() > 1) {
                                QuantitySelectView.getStackValue(activity, finalItem.getStackSize(), new QuantitySelectView.OnStackSelectInterface() {
                                    @Override
                                    public void onStackSizeSelect(int i) {
                                        doMove(activity, finalItem, owner, i);
                                    }
                                });
                            } else {
                                doMove(activity, finalItem, owner, finalItem.getStackSize());
                            }

                        }
                    }, ownerLabel));
                }
            }
        }
        if (isEquippable() && mCanEquip) {
            if (getIsLocked()) {
                actions.add(new Action(R.string.unlock_action_label, new Action.ActionRunnable() {
                    @Override
                    public void run(final Activity activity) {
                        doUnLock(activity);
                    }
                }));
            } else {
                actions.add(new Action(R.string.lock_action_label, new Action.ActionRunnable() {
                    @Override
                    public void run(Activity activity) {
                        doLock(activity);
                    }
                }));
            }
        }
        return actions;
    }


    private void doUnLock(final Activity activity) {
        Toast.makeText(activity, activity.getString(R.string.do_unlock_in_progress), Toast.LENGTH_SHORT).show();
        final ClientWebView webView = ((Application) activity.getApplication()).getWebView();
        final String owner = Items.getInstance().getItemOwner(this);
        ItemMover.setLockState(webView, this, owner, false, null).then(new ItemMover.Result() {
            @Override
            public void onSuccess() {
                Toast.makeText(activity, activity.getString(R.string.do_unlock_success), Toast.LENGTH_SHORT).show();
                setIsLocked(false);
                ItemMonitor.getInstance().notifyChanged();
                activity.finish();
            }

            @Override
            public void onError(String e) {
                Toast.makeText(activity, activity.getString(R.string.do_unlock_error), Toast.LENGTH_SHORT).show();

            }
        });
    }

    private void doLock(final Activity activity) {
        Toast.makeText(activity, activity.getString(R.string.do_lock_in_progress), Toast.LENGTH_SHORT).show();
        final ClientWebView webView = ((Application) activity.getApplication()).getWebView();
        final String owner = Items.getInstance().getItemOwner(this);

        ItemMover.setLockState(webView, this, owner, true, null).then(new ItemMover.Result() {
            @Override
            public void onSuccess() {
                Toast.makeText(activity, activity.getString(R.string.do_lock_success), Toast.LENGTH_SHORT).show();
                setIsLocked(true);
                webView.queueRunnable(new Runnable() {
                    @Override
                    public void run() {
                        ItemMonitor.getInstance().notifyChanged();
                        //activity.finish();
                    }
                });
            }

            @Override
            public void onError(String e) {
                Toast.makeText(activity, activity.getString(R.string.do_lock_error), Toast.LENGTH_SHORT).show();
            }
        });

    }

    protected void setIsLocked(boolean isLocked) {
        mIsLocked = isLocked;
    }

    private void doEquip(final Activity activity) {
        Toast.makeText(activity, activity.getString(R.string.do_equip_in_progress), Toast.LENGTH_SHORT).show();
        // activity.finish();

        final ClientWebView webView = ((Application) activity.getApplication()).getWebView();
        final String owner = Items.getInstance().getItemOwner(this);
        ItemMover.equip(webView, owner, this, null).then(
                new ItemMover.Result() {
                    @Override
                    public void onSuccess() {
                        Membership membership = Data.getInstance().getMembership();
                        org.swistowski.vaulthelper.models.Character character = Characters.getInstance().get(owner);
                        webView.call("destinyService.GetCharacterInventory", "" + membership.getType(), "" + membership.getId(), character.getId(), "true").then(new ClientWebView.Callback() {
                            @Override
                            public void onAccept(String result) {
                                List<Item> items = Items.getInstance().allAsMap().get(owner);
                                HashMap<Long, Item> hash2item = new HashMap<>();
                                for (Item ii : items) {
                                    hash2item.put(ii.getInstanceId(), ii);
                                }

                                try {
                                    JSONObject loadedItems = new JSONObject(result);
                                    for (Iterator<String> iter = loadedItems.optJSONObject("data").optJSONObject("buckets").keys(); iter.hasNext(); ) {
                                        JSONArray bucketData = loadedItems.optJSONObject("data").optJSONObject("buckets").optJSONArray(iter.next());
                                        for (int i = 0; i < bucketData.length(); i++) {
                                            JSONArray bucketItems = bucketData.optJSONObject(i).optJSONArray("items");
                                            for (int j = 0; j < bucketItems.length(); j++) {
                                                JSONObject bi = bucketData.optJSONObject(j);
                                                if (bi != null) {
                                                    Item currentItem = hash2item.get(bi.optLong("itemHash"));
                                                    if (currentItem != null) {
                                                        /*
                                                        if (currentItem.getCanEquip() != bi.optBoolean("canEquip")) {
                                                        }
                                                        if (currentItem.getIsEquipped() != bi.opt("isEquipped")) {
                                                        }
                                                        */
                                                        currentItem.setIsEquipped(bi.optBoolean("isEquipped"));
                                                        currentItem.setCanEquip(bi.optBoolean("canEquip"));
                                                    }
                                                }

                                            }
                                        }
                                    }
                                } catch (JSONException e) {
                                    e.printStackTrace();
                                }

                                new Handler(Looper.getMainLooper()).post(new Runnable() {
                                    @Override
                                    public void run() {
                                        ItemMonitor.getInstance().notifyChanged();
                                        Toast.makeText(activity, String.format(activity.getString(R.string.do_equip_finished), Item.this.toString()), Toast.LENGTH_SHORT).show();
                                        activity.finish();
                                    }
                                });

                            }

                            @Override
                            public void onError(String result) {
                                Log.e("doGetCharacterInventory", "unsucessfull " + result);
                                //onMoveError(result);
                            }
                        });


                    }

                    @Override
                    public void onError(String e) {
                        //onMoveError(e);
                    }
                }

        );

    }

    public Object getIsEquipped() {
        return mIsEquipped;
    }

    public String getTierTypeName() {
        return mTierTypeName;
    }

    public void setStackSize(int stackSize) {
        this.mStackSize = stackSize;
    }

    public Item make_clone() {
        return new Item(mItemHash, mBindStatus, mIsEquipped, mItemLevel,
                mStackSize, mQualityLevel, mCanEquip, mIsEquipment, mIsGridComplete, mItemInstanceId,
                mName, mItemDescription, mIcon, mSecondaryIcon, mTierTypeName, mTierType, mItemTypeName,
                mBucketTypeHash, mItemType, mItemSubType, mClassType,
                mPrimaryStatValue, mDamageType, mBucketName, mCannotEquipReason,
                mLocation, "", "", "", mUnlockFlagHashRequiredToEquip, mIsLocked, mIsEquippable
        );
    }

    public List<Label> getLabels() {
        return Labels.getInstance().getLabelsForItem(getInstanceId());
    }

    public int getTierType() {
        return mTierType;
    }

    public int getTypeImportance() {
        return typeToImportance(mItemType);
    }

    public boolean getIsLocked() {
        return mIsLocked;
    }

    public boolean isEquippable() {
        return mIsEquippable;
    }

    public static class Action {
        private final int mLabel;
        private final ActionRunnable mAction;
        private String[] mArgs;

        public Action(int label, ActionRunnable action) {
            mLabel = label;
            mAction = action;
        }

        public Action(int label, ActionRunnable action, String... args) {
            this(label, action);
            mArgs = args;
        }

        public void doAction(Activity activity) {
            mAction.run(activity);
        }

        public int getLabel() {
            return mLabel;
        }

        public String[] getArgs() {
            return mArgs;
        }

        public interface ActionRunnable {
            void run(Activity activity);
        }
    }
} public class ItemMover {
    private static final String LOG_TAG = "ItemMover";

    public static Promise equip(final ClientWebView webView, final String owner, final Item item, final Promise p) {
        final Promise p_inner = new Promise();

        JSONObject obj = new JSONObject();
        try {
            obj.put("characterId", owner);
            obj.put("itemId", item.getItemId());
            obj.put("membershipType", Data.getInstance().getMembership().getType());
        } catch (JSONException e) {
            Log.e(LOG_TAG, "exception", e);
            e.printStackTrace();
        }

        webView.call("destinyService.EquipItem", obj).then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                for (Item tested_item : Items.getInstance().all()) {
                    if (tested_item.getBucketTypeHash() == item.getBucketTypeHash() && item.getItemHash() != tested_item.getItemHash() && Items.getInstance().getItemOwner(tested_item).equals(owner) && tested_item.getCanEquip()) {
                        tested_item.setIsEquipped(false);
                        item.setIsEquipped(true);
                        break;
                    }
                }
                p_inner.onSuccess();
            }

            @Override
            public void onError(String result) {
                p_inner.onError(result);
            }
        });
        return p_inner;
    }

    public static Promise setLockState(final ClientWebView webView, final Item item, final String owner, final boolean lockState, final Promise p) {
        final Promise p_inner = new Promise();
        JSONObject obj = new JSONObject();
        try {
            obj.put("characterId", owner);
            obj.put("itemId", item.getItemId());
            obj.put("membershipType", Data.getInstance().getMembership().getType());
            obj.put("state", lockState);
        }catch (JSONException e) {
            Log.e(LOG_TAG, "exception", e);
            e.printStackTrace();
        }

        webView.call("destinyService.SetItemLockState", obj).then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                for (Item tested_item : Items.getInstance().all()) {
                    if (tested_item.getBucketTypeHash() == item.getBucketTypeHash() && item.getItemHash() != tested_item.getItemHash() && Items.getInstance().getItemOwner(tested_item).equals(owner) && tested_item.getCanEquip()) {
                        item.setIsLocked(lockState);
                        break;
                    }
                }
                p_inner.onSuccess();
            }

            @Override
            public void onError(String result) {
                p_inner.onError(result);
            }
        });
        return p_inner;
    }

    public static Promise lock(final ClientWebView webView, final Item item, final Promise p) {
        final Promise p_inner = new Promise();


        return p_inner;
    }




    private static Promise moveToVault(final ClientWebView webView, final String owner, final Item item, final int stackSize, final Promise p) {
        JSONObject obj = generateMoveJson(owner, item, true, stackSize);
        final Promise p_inner = new Promise();
        if (item.isEquipped()) {
            /* find items from the same hash */
            List<Item> proposed = new ArrayList<>();
            String item_owner = Items.getInstance().getItemOwner(item);
            for (Item tested_item : Items.getInstance().all()) {
                if (tested_item.getBucketTypeHash() == item.getBucketTypeHash() && item.getItemHash() != tested_item.getItemHash() && Items.getInstance().getItemOwner(tested_item).equals(item_owner) && tested_item.getCanEquip()) {
                    proposed.add(tested_item);
                }
            }
            Collections.sort(proposed);


            if (proposed.size() > 0) {
                final Item to_equip = proposed.get(proposed.size() - 1);
                equip(webView, owner, to_equip, p).then(new Result() {
                    @Override
                    public void onSuccess() {
                        item.setIsEquipped(false);
                        to_equip.setIsEquipped(true);
                        moveToVault(webView, owner, item, stackSize, p).then(new Result() {
                            @Override
                            public void onSuccess() {
                                p_inner.onSuccess();

                            }

                            @Override
                            public void onError(String e) {
                                p_inner.onError(e);
                            }
                        });

                    }

                    @Override
                    public void onError(String e) {
                        p.onError(e);
                        p_inner.onError(e);
                    }
                });
            } else {
                webView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        String message = webView.getContext().getString(R.string.cannot_move_error, item.toString());
                        p_inner.onError(message);
                        if (p != null) {
                            p.onError(message);
                        } else {
                            Log.e(LOG_TAG, message);
                        }
                    }
                }, 1);
            }
            return p_inner;
        }

        webView.call("destinyService.TransferItem", obj).then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                webView.queueRunnable(new Runnable() {
                    @Override
                    public void run() {
                        item.moveTo(Items.VAULT_ID, stackSize);
                        if (p != null)
                            p.onSuccess();
                        p_inner.onSuccess();
                    }
                });
            }

            @Override
            public void onError(String result) {

                try {
                    if (new JSONObject(result).optInt("errorCode") == 1656) {
                        // fix equipped status
                        String item_owner = Items.getInstance().getItemOwner(item);
                        for (Item tested_item : Items.getInstance().all()) {
                            if (tested_item.getBucketTypeHash() == item.getBucketTypeHash() && item.getItemHash() != tested_item.getItemHash() && Items.getInstance().getItemOwner(tested_item).equals(item_owner) && tested_item.isEquipped()) {
                                tested_item.setIsEquipped(false);
                            }
                        }
                        item.setIsEquipped(true);
                        moveToVault(webView, owner, item, stackSize, p_inner);
                    } else {
                        if (p != null)
                            p.onError(result);
                        p_inner.onError(result);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        return p_inner;
    }

    private static Promise moveFromVault(final ClientWebView webView, final String subject, final Item item, final int stackSize, final Promise p) {
        JSONObject obj = generateMoveJson(subject, item, false, stackSize);
        final Promise p_inner = new Promise();
        webView.call("destinyService.TransferItem", obj).then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                webView.queueRunnable(new Runnable() {
                    @Override
                    public void run() {
                        item.moveTo(subject, stackSize);
                        p.onSuccess();
                        p_inner.onSuccess();
                    }
                });
            }

            @Override
            public void onError(String result) {
                p.onError(result);
                p_inner.onError(result);
            }
        });
        return p_inner;
    }


    public static Promise move(final ClientWebView webView, final Item item, final String subject, final int stackSize) {
        final Promise p = new Promise();

        final String owner = Items.getInstance().getItemOwner(item);
        if ((owner.equals(Items.VAULT_ID) || subject.equals(Items.VAULT_ID))) {
            if (subject.equals(Items.VAULT_ID)) {
                moveToVault(webView, owner, item, stackSize, p);
            } else {
                moveFromVault(webView, subject, item, stackSize, p);
            }
        } else {
            moveToVault(webView, owner, item, stackSize, null).then(new Result() {
                @Override
                public void onSuccess() {
                    Item vault_item = null;
                    for (Item tmp_item : Items.getInstance().allAsMap().get(Items.VAULT_ID)) {
                        if (tmp_item.getItemHash() == item.getItemHash() && tmp_item.getItemId() == item.getItemId()) {
                            vault_item = tmp_item;
                            break;
                        }
                    }
                    final Item tmp_item = vault_item;
                    webView.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            moveFromVault(webView, subject, tmp_item, stackSize, p);
                        }
                    }, 1000);
                }

                @Override
                public void onError(String e) {

                    p.onError(e);
                }
            });

        }


        return p;
    }

    private static JSONObject generateMoveJson(String characterId, Item item, boolean transferToVault, int stackSize) {
        JSONObject json = new JSONObject();
        try {
            json.put("characterId", characterId);
            json.put("itemId", item.getItemId());
            json.put("itemReferenceHash", item.getItemHash());
            json.put("membershipType", Data.getInstance().getMembership().getType());
            json.put("stackSize", stackSize);
            json.put("transferToVault", transferToVault);
        } catch (JSONException e) {
            Log.e(LOG_TAG, "exception", e);
            e.printStackTrace();
        }
        return json;
    }

    private static JSONObject generateMoveJson(String characterId, String itemId, long itemReferenceHash, int membershipType, boolean transferToVault, int stackSize) {
        JSONObject json = new JSONObject();
        try {
            json.put("characterId", characterId);
            json.put("itemId", itemId);
            json.put("itemReferenceHash", itemReferenceHash);
            json.put("membershipType", membershipType);
            json.put("stackSize", stackSize);
            json.put("transferToVault", transferToVault);
        } catch (JSONException e) {
            Log.e(LOG_TAG, "exception", e);
            e.printStackTrace();
        }
        return json;
    }

    public interface Result {
        public void onSuccess();

        public void onError(String e);
    }

    public static class Promise {
        private Result callback;

        public void then(Result callback) {
            this.callback = callback;
        }

        public void onSuccess() {
            if (callback != null)
                callback.onSuccess();
        }

        public void onError(String e) {
            if (callback != null) {
                callback.onError(e);
            }
        }
    }
} public class IabHelper {
    // Is debug logging enabled?
    boolean mDebugLog = false;
    String mDebugTag = "IabHelper";

    // Is setup done?
    boolean mSetupDone = false;

    // Has this object been disposed of? (If so, we should ignore callbacks, etc)
    boolean mDisposed = false;

    // Are subscriptions supported?
    boolean mSubscriptionsSupported = false;

    // Is an asynchronous operation in progress?
    // (only one at a time can be in progress)
    boolean mAsyncInProgress = false;

    // (for logging/debugging)
    // if mAsyncInProgress == true, what asynchronous operation is in progress?
    String mAsyncOperation = "";

    // Context we were passed during initialization
    Context mContext;

    // Connection to the service
    IInAppBillingService mService;
    ServiceConnection mServiceConn;

    // The request code used to launch purchase flow
    int mRequestCode;

    // The item type of the current purchase flow
    String mPurchasingItemType;

    // Public key for verifying signature, in base64 encoding
    String mSignatureBase64 = null;

    // Billing response codes
    public static final int BILLING_RESPONSE_RESULT_OK = 0;
    public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
    public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
    public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
    public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
    public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
    public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
    public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;

    // IAB Helper error codes
    public static final int IABHELPER_ERROR_BASE = -1000;
    public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
    public static final int IABHELPER_BAD_RESPONSE = -1002;
    public static final int IABHELPER_VERIFICATION_FAILED = -1003;
    public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
    public static final int IABHELPER_USER_CANCELLED = -1005;
    public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
    public static final int IABHELPER_MISSING_TOKEN = -1007;
    public static final int IABHELPER_UNKNOWN_ERROR = -1008;
    public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
    public static final int IABHELPER_INVALID_CONSUMPTION = -1010;

    // Keys for the responses from InAppBillingService
    public static final String RESPONSE_CODE = "RESPONSE_CODE";
    public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
    public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
    public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
    public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
    public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
    public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
    public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
    public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";

    // Item types
    public static final String ITEM_TYPE_INAPP = "inapp";
    public static final String ITEM_TYPE_SUBS = "subs";

    // some fields on the getSkuDetails response bundle
    public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
    public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";

    /**
     * Creates an instance. After creation, it will not yet be ready to use. You must perform
     * setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
     * block and is safe to call from a UI thread.
     *
     * @param ctx             Your application or Activity context. Needed to bind to the in-app billing service.
     * @param base64PublicKey Your application's public key, encoded in base64.
     *                        This is used for verification of purchase signatures. You can find your app's base64-encoded
     *                        public key in your application's page on Google Play Developer Console. Note that this
     *                        is NOT your "developer public key".
     */
    public IabHelper(Context ctx, String base64PublicKey) {
        mContext = ctx.getApplicationContext();
        mSignatureBase64 = base64PublicKey;
        logDebug("IAB helper created.");
    }

    /**
     * Enables or disable debug logging through LogCat.
     */
    public void enableDebugLogging(boolean enable, String tag) {
        checkNotDisposed();
        mDebugLog = enable;
        mDebugTag = tag;
    }

    public void enableDebugLogging(boolean enable) {
        checkNotDisposed();
        mDebugLog = enable;
    }

    /**
     * Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
     * when the setup process is complete.
     */
    public interface OnIabSetupFinishedListener {
        /**
         * Called to notify that setup is complete.
         *
         * @param result The result of the setup process.
         */
        public void onIabSetupFinished(IabResult result);
    }

    /**
     * Starts the setup process. This will start up the setup process asynchronously.
     * You will be notified through the listener when the setup process is complete.
     * This method is safe to call from a UI thread.
     *
     * @param listener The listener to notify when the setup process is complete.
     */
    public void startSetup(final OnIabSetupFinishedListener listener) {
        // If already set up, can't do it again.
        checkNotDisposed();
        if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");

        // Connection to IAB service
        logDebug("Starting in-app billing setup.");
        mServiceConn = new ServiceConnection() {
            @Override
            public void onServiceDisconnected(ComponentName name) {
                logDebug("Billing service disconnected.");
                mService = null;
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                if (mDisposed) return;
                logDebug("Billing service connected.");
                mService = IInAppBillingService.Stub.asInterface(service);
                String packageName = mContext.getPackageName();
                try {
                    logDebug("Checking for in-app billing 3 support.");

                    // check for in-app billing v3 support
                    int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
                    if (response != BILLING_RESPONSE_RESULT_OK) {
                        if (listener != null) listener.onIabSetupFinished(new IabResult(response,
                                "Error checking for billing v3 support."));

                        // if in-app purchases aren't supported, neither are subscriptions.
                        mSubscriptionsSupported = false;
                        return;
                    }
                    logDebug("In-app billing version 3 supported for " + packageName);

                    // check for v3 subscriptions support
                    response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
                    if (response == BILLING_RESPONSE_RESULT_OK) {
                        logDebug("Subscriptions AVAILABLE.");
                        mSubscriptionsSupported = true;
                    } else {
                        logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
                    }

                    mSetupDone = true;
                } catch (RemoteException e) {
                    if (listener != null) {
                        listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
                                "RemoteException while setting up in-app billing."));
                    }
                    e.printStackTrace();
                    return;
                }

                if (listener != null) {
                    listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
                }
            }
        };

        Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
        serviceIntent.setPackage("com.android.vending");
        if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
            // service available to handle that Intent
            mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
        } else {
            // no service available to handle that Intent
            if (listener != null) {
                listener.onIabSetupFinished(
                        new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
                                "Billing service unavailable on device."));
            }
        }
    }

    /**
     * Dispose of object, releasing resources. It's very important to call this
     * method when you are done with this object. It will release any resources
     * used by it such as service connections. Naturally, once the object is
     * disposed of, it can't be used again.
     */
    public void dispose() {
        logDebug("Disposing.");
        mSetupDone = false;
        if (mServiceConn != null) {
            logDebug("Unbinding from service.");
            try {
                if (mContext != null) mContext.unbindService(mServiceConn);
            } catch (IllegalArgumentException e) {

            }
        }
        mDisposed = true;
        mContext = null;
        mServiceConn = null;
        mService = null;
        mPurchaseListener = null;
    }

    private void checkNotDisposed() {
        if (mDisposed)
            throw new IllegalStateException("IabHelper was disposed of, so it cannot be used.");
    }

    /**
     * Returns whether subscriptions are supported.
     */
    public boolean subscriptionsSupported() {
        checkNotDisposed();
        return mSubscriptionsSupported;
    }


    /**
     * Callback that notifies when a purchase is finished.
     */
    public interface OnIabPurchaseFinishedListener {
        /**
         * Called to notify that an in-app purchase finished. If the purchase was successful,
         * then the sku parameter specifies which item was purchased. If the purchase failed,
         * the sku and extraData parameters may or may not be null, depending on how far the purchase
         * process went.
         *
         * @param result The result of the purchase.
         * @param info   The purchase information (null if purchase failed)
         */
        public void onIabPurchaseFinished(IabResult result, Purchase info);
    }

    // The listener registered on launchPurchaseFlow, which we have to call back when
    // the purchase finishes
    OnIabPurchaseFinishedListener mPurchaseListener;

    public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
        launchPurchaseFlow(act, sku, requestCode, listener, "");
    }

    public void launchPurchaseFlow(Activity act, String sku, int requestCode,
                                   OnIabPurchaseFinishedListener listener, String extraData) {
        launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
    }

    public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
                                               OnIabPurchaseFinishedListener listener) {
        launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
    }

    public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
                                               OnIabPurchaseFinishedListener listener, String extraData) {
        launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
    }

    /**
     * Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
     * which will involve bringing up the Google Play screen. The calling activity will be paused while
     * the user interacts with Google Play, and the result will be delivered via the activity's
     * {@link android.app.Activity#onActivityResult} method, at which point you must call
     * this object's {@link #handleActivityResult} method to continue the purchase flow. This method
     * MUST be called from the UI thread of the Activity.
     *
     * @param act         The calling activity.
     * @param sku         The sku of the item to purchase.
     * @param itemType    indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
     * @param requestCode A request code (to differentiate from other responses --
     *                    as in {@link android.app.Activity#startActivityForResult}).
     * @param listener    The listener to notify when the purchase process finishes
     * @param extraData   Extra data (developer payload), which will be returned with the purchase data
     *                    when the purchase completes. This extra data will be permanently bound to that purchase
     *                    and will always be returned when the purchase is queried.
     */
    public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
                                   OnIabPurchaseFinishedListener listener, String extraData) {
        checkNotDisposed();
        checkSetupDone("launchPurchaseFlow");
        flagStartAsync("launchPurchaseFlow");
        IabResult result;

        if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
            IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
                    "Subscriptions are not available.");
            flagEndAsync();
            if (listener != null) listener.onIabPurchaseFinished(r, null);
            return;
        }

        try {
            logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
            Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
            int response = getResponseCodeFromBundle(buyIntentBundle);
            if (response != BILLING_RESPONSE_RESULT_OK) {
                logError("Unable to buy item, Error response: " + getResponseDesc(response));
                flagEndAsync();
                result = new IabResult(response, "Unable to buy item");
                if (listener != null) listener.onIabPurchaseFinished(result, null);
                return;
            }

            PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
            logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
            mRequestCode = requestCode;
            mPurchaseListener = listener;
            mPurchasingItemType = itemType;
            act.startIntentSenderForResult(pendingIntent.getIntentSender(),
                    requestCode, new Intent(),
                    Integer.valueOf(0), Integer.valueOf(0),
                    Integer.valueOf(0));
        } catch (SendIntentException e) {
            logError("SendIntentException while launching purchase flow for sku " + sku);
            e.printStackTrace();
            flagEndAsync();

            result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
            if (listener != null) listener.onIabPurchaseFinished(result, null);
        } catch (RemoteException e) {
            logError("RemoteException while launching purchase flow for sku " + sku);
            e.printStackTrace();
            flagEndAsync();

            result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
            if (listener != null) listener.onIabPurchaseFinished(result, null);
        }
    }

    /**
     * Handles an activity result that's part of the purchase flow in in-app billing. If you
     * are calling {@link #launchPurchaseFlow}, then you must call this method from your
     * Activity's {@link android.app.Activity@onActivityResult} method. This method
     * MUST be called from the UI thread of the Activity.
     *
     * @param requestCode The requestCode as you received it.
     * @param resultCode  The resultCode as you received it.
     * @param data        The data (Intent) as you received it.
     * @return Returns true if the result was related to a purchase flow and was handled;
     * false if the result was not related to a purchase, in which case you should
     * handle it normally.
     */
    public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
        IabResult result;
        if (requestCode != mRequestCode) return false;

        checkNotDisposed();
        checkSetupDone("handleActivityResult");

        // end of async purchase operation that started on launchPurchaseFlow
        flagEndAsync();

        if (data == null) {
            logError("Null data in IAB activity result.");
            result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
            if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
            return true;
        }

        int responseCode = getResponseCodeFromIntent(data);
        String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
        String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

        if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
            logDebug("Successful resultcode from purchase activity.");
            logDebug("Purchase data: " + purchaseData);
            logDebug("Data signature: " + dataSignature);
            logDebug("Extras: " + data.getExtras());
            logDebug("Expected item type: " + mPurchasingItemType);

            if (purchaseData == null || dataSignature == null) {
                logError("BUG: either purchaseData or dataSignature is null.");
                logDebug("Extras: " + data.getExtras().toString());
                result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
                if (mPurchaseListener != null)
                    mPurchaseListener.onIabPurchaseFinished(result, null);
                return true;
            }

            Purchase purchase = null;
            try {
                purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
                String sku = purchase.getSku();

                // Verify signature
                if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
                    logError("Purchase signature verification FAILED for sku " + sku);
                    result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
                    if (mPurchaseListener != null)
                        mPurchaseListener.onIabPurchaseFinished(result, purchase);
                    return true;
                }
                logDebug("Purchase signature successfully verified.");
            } catch (JSONException e) {
                logError("Failed to parse purchase data.");
                e.printStackTrace();
                result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
                if (mPurchaseListener != null)
                    mPurchaseListener.onIabPurchaseFinished(result, null);
                return true;
            }

            if (mPurchaseListener != null) {
                mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
            }
        } else if (resultCode == Activity.RESULT_OK) {
            // result code was OK, but in-app billing response was not OK.
            logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
            if (mPurchaseListener != null) {
                result = new IabResult(responseCode, "Problem purchashing item.");
                mPurchaseListener.onIabPurchaseFinished(result, null);
            }
        } else if (resultCode == Activity.RESULT_CANCELED) {
            logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
            result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
            if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
        } else {
            logError("Purchase failed. Result code: " + Integer.toString(resultCode)
                    + ". Response: " + getResponseDesc(responseCode));
            result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
            if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
        }
        return true;
    }

    public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
        return queryInventory(querySkuDetails, moreSkus, null);
    }

    /**
     * Queries the inventory. This will query allAsMap owned items from the server, as well as
     * information on additional skus, if specified. This method may block or take long to execute.
     * Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}.
     *
     * @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
     *                        as purchase information.
     * @param moreItemSkus    additional PRODUCT skus to query information on, regardless of ownership.
     *                        Ignored if null or if querySkuDetails is false.
     * @param moreSubsSkus    additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
     *                        Ignored if null or if querySkuDetails is false.
     * @throws IabException if a problem occurs while refreshing the inventory.
     */
    public Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus,
                                    List<String> moreSubsSkus) throws IabException {
        checkNotDisposed();
        checkSetupDone("queryInventory");
        try {
            Inventory inv = new Inventory();
            int r = queryPurchases(inv, ITEM_TYPE_INAPP);
            if (r != BILLING_RESPONSE_RESULT_OK) {
                throw new IabException(r, "Error refreshing inventory (querying owned items).");
            }

            if (querySkuDetails) {
                r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
                if (r != BILLING_RESPONSE_RESULT_OK) {
                    throw new IabException(r, "Error refreshing inventory (querying prices of items).");
                }
            }

            // if subscriptions are supported, then also query for subscriptions
            if (mSubscriptionsSupported) {
                r = queryPurchases(inv, ITEM_TYPE_SUBS);
                if (r != BILLING_RESPONSE_RESULT_OK) {
                    throw new IabException(r, "Error refreshing inventory (querying owned subscriptions).");
                }

                if (querySkuDetails) {
                    r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
                    if (r != BILLING_RESPONSE_RESULT_OK) {
                        throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
                    }
                }
            }

            return inv;
        } catch (RemoteException e) {
            throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
        } catch (JSONException e) {
            throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
        }
    }

    /**
     * Listener that notifies when an inventory query operation completes.
     */
    public interface QueryInventoryFinishedListener {
        /**
         * Called to notify that an inventory query operation completed.
         *
         * @param result The result of the operation.
         * @param inv    The inventory.
         */
        public void onQueryInventoryFinished(IabResult result, Inventory inv);
    }


    /**
     * Asynchronous wrapper for inventory query. This will perform an inventory
     * query as described in {@link #queryInventory}, but will do so asynchronously
     * and call back the specified listener upon completion. This method is safe to
     * call from a UI thread.
     *
     * @param querySkuDetails as in {@link #queryInventory}
     * @param moreSkus        as in {@link #queryInventory}
     * @param listener        The listener to notify when the refresh operation completes.
     */
    public void queryInventoryAsync(final boolean querySkuDetails,
                                    final List<String> moreSkus,
                                    final QueryInventoryFinishedListener listener) {
        final Handler handler = new Handler();
        checkNotDisposed();
        checkSetupDone("queryInventory");
        flagStartAsync("refresh inventory");
        (new Thread(new Runnable() {
            public void run() {
                IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
                Inventory inv = null;
                try {
                    inv = queryInventory(querySkuDetails, moreSkus);
                } catch (IabException ex) {
                    result = ex.getResult();
                }

                flagEndAsync();

                final IabResult result_f = result;
                final Inventory inv_f = inv;
                if (!mDisposed && listener != null) {
                    handler.post(new Runnable() {
                        public void run() {
                            listener.onQueryInventoryFinished(result_f, inv_f);
                        }
                    });
                }
            }
        })).start();
    }

    public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
        queryInventoryAsync(true, null, listener);
    }

    public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
        queryInventoryAsync(querySkuDetails, null, listener);
    }


    /**
     * Consumes a given in-app product. Consuming can only be done on an item
     * that's owned, and as a result of consumption, the user will no longer own it.
     * This method may block or take long to return. Do not call from the UI thread.
     * For that, see {@link #consumeAsync}.
     *
     * @param itemInfo The PurchaseInfo that represents the item to consume.
     * @throws IabException if there is a problem during consumption.
     */
    void consume(Purchase itemInfo) throws IabException {
        checkNotDisposed();
        checkSetupDone("consume");

        if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
            throw new IabException(IABHELPER_INVALID_CONSUMPTION,
                    "Items of type '" + itemInfo.mItemType + "' can't be consumed.");
        }

        try {
            String token = itemInfo.getToken();
            String sku = itemInfo.getSku();
            if (token == null || token.equals("")) {
                logError("Can't consume " + sku + ". No token.");
                throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
                        + sku + " " + itemInfo);
            }

            logDebug("Consuming sku: " + sku + ", token: " + token);
            int response = mService.consumePurchase(3, mContext.getPackageName(), token);
            if (response == BILLING_RESPONSE_RESULT_OK) {
                logDebug("Successfully consumed sku: " + sku);
            } else {
                logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
                throw new IabException(response, "Error consuming sku " + sku);
            }
        } catch (RemoteException e) {
            throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
        }
    }

    /**
     * Callback that notifies when a consumption operation finishes.
     */
    public interface OnConsumeFinishedListener {
        /**
         * Called to notify that a consumption has finished.
         *
         * @param purchase The purchase that was (or was to be) consumed.
         * @param result   The result of the consumption operation.
         */
        public void onConsumeFinished(Purchase purchase, IabResult result);
    }

    /**
     * Callback that notifies when a multi-item consumption operation finishes.
     */
    public interface OnConsumeMultiFinishedListener {
        /**
         * Called to notify that a consumption of multiple items has finished.
         *
         * @param purchases The purchases that were (or were to be) consumed.
         * @param results   The results of each consumption operation, corresponding to each
         *                  sku.
         */
        public void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results);
    }

    /**
     * Asynchronous wrapper to item consumption. Works like {@link #consume}, but
     * performs the consumption in the background and notifies completion through
     * the provided listener. This method is safe to call from a UI thread.
     *
     * @param purchase The purchase to be consumed.
     * @param listener The listener to notify when the consumption operation finishes.
     */
    public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) {
        checkNotDisposed();
        checkSetupDone("consume");
        List<Purchase> purchases = new ArrayList<>();
        purchases.add(purchase);
        consumeAsyncInternal(purchases, listener, null);
    }

    /**
     * Same as {@link consumeAsync}, but for multiple items at once.
     *
     * @param purchases The list of PurchaseInfo objects representing the purchases to consume.
     * @param listener  The listener to notify when the consumption operation finishes.
     */
    public void consumeAsync(List<Purchase> purchases, OnConsumeMultiFinishedListener listener) {
        checkNotDisposed();
        checkSetupDone("consume");
        consumeAsyncInternal(purchases, null, listener);
    }

    /**
     * Returns a human-readable description for the given response code.
     *
     * @param code The response code
     * @return A human-readable string explaining the result code.
     * It also includes the result code numerically.
     */
    public static String getResponseDesc(int code) {
        String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
                "3:Billing Unavailable/4:Item unavailable/" +
                "5:Developer Error/6:Error/7:Item Already Owned/" +
                "8:Item not owned").split("/");
        String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
                "-1002:Bad response received/" +
                "-1003:Purchase signature verification failed/" +
                "-1004:Send intent failed/" +
                "-1005:User cancelled/" +
                "-1006:Unknown purchase response/" +
                "-1007:Missing token/" +
                "-1008:Unknown error/" +
                "-1009:Subscriptions not available/" +
                "-1010:Invalid consumption attempt").split("/");

        if (code <= IABHELPER_ERROR_BASE) {
            int index = IABHELPER_ERROR_BASE - code;
            if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index];
            else return String.valueOf(code) + ":Unknown IAB Helper Error";
        } else if (code < 0 || code >= iab_msgs.length)
            return String.valueOf(code) + ":Unknown";
        else
            return iab_msgs[code];
    }


    // Checks that setup was done; if not, throws an exception.
    void checkSetupDone(String operation) {
        if (!mSetupDone) {
            logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
            throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
        }
    }

    // Workaround to bug where sometimes response codes come as Long instead of Integer
    int getResponseCodeFromBundle(Bundle b) {
        Object o = b.get(RESPONSE_CODE);
        if (o == null) {
            logDebug("Bundle with null response code, assuming OK (known issue)");
            return BILLING_RESPONSE_RESULT_OK;
        } else if (o instanceof Integer) return ((Integer) o).intValue();
        else if (o instanceof Long) return (int) ((Long) o).longValue();
        else {
            logError("Unexpected type for bundle response code.");
            logError(o.getClass().getName());
            throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
        }
    }

    // Workaround to bug where sometimes response codes come as Long instead of Integer
    int getResponseCodeFromIntent(Intent i) {
        Object o = i.getExtras().get(RESPONSE_CODE);
        if (o == null) {
            logError("Intent with no response code, assuming OK (known issue)");
            return BILLING_RESPONSE_RESULT_OK;
        } else if (o instanceof Integer) return ((Integer) o).intValue();
        else if (o instanceof Long) return (int) ((Long) o).longValue();
        else {
            logError("Unexpected type for intent response code.");
            logError(o.getClass().getName());
            throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
        }
    }

    void flagStartAsync(String operation) {
        if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
                operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
        mAsyncOperation = operation;
        mAsyncInProgress = true;
        logDebug("Starting async operation: " + operation);
    }

    void flagEndAsync() {
        logDebug("Ending async operation: " + mAsyncOperation);
        mAsyncOperation = "";
        mAsyncInProgress = false;
    }


    int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
        if(mContext==null){
            return IABHELPER_VERIFICATION_FAILED;
        }
        // Query purchases
        logDebug("Querying owned items, item type: " + itemType);
        logDebug("Package name: " + mContext.getPackageName());
        boolean verificationFailed = false;
        String continueToken = null;

        do {
            logDebug("Calling getPurchases with continuation token: " + continueToken);
            Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
                    itemType, continueToken);

            int response = getResponseCodeFromBundle(ownedItems);
            logDebug("Owned items response: " + String.valueOf(response));
            if (response != BILLING_RESPONSE_RESULT_OK) {
                logDebug("getPurchases() failed: " + getResponseDesc(response));
                return response;
            }
            if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
                    || !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
                    || !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
                logError("Bundle returned from getPurchases() doesn't contain required fields.");
                return IABHELPER_BAD_RESPONSE;
            }

            ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
                    RESPONSE_INAPP_ITEM_LIST);
            ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
                    RESPONSE_INAPP_PURCHASE_DATA_LIST);
            ArrayList<String> signatureList = ownedItems.getStringArrayList(
                    RESPONSE_INAPP_SIGNATURE_LIST);

            for (int i = 0; i < purchaseDataList.size(); ++i) {
                String purchaseData = purchaseDataList.get(i);
                String signature = signatureList.get(i);
                String sku = ownedSkus.get(i);
                if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
                    logDebug("Sku is owned: " + sku);
                    Purchase purchase = new Purchase(itemType, purchaseData, signature);

                    if (TextUtils.isEmpty(purchase.getToken())) {
                        logWarn("BUG: empty/null token!");
                        logDebug("Purchase data: " + purchaseData);
                    }

                    // Record ownership and token
                    inv.addPurchase(purchase);
                } else {
                    logWarn("Purchase signature verification **FAILED**. Not adding item.");
                    logDebug("   Purchase data: " + purchaseData);
                    logDebug("   Signature: " + signature);
                    verificationFailed = true;
                }
            }

            continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
            logDebug("Continuation token: " + continueToken);
        } while (!TextUtils.isEmpty(continueToken));

        return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
    }

    int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
            throws RemoteException, JSONException {
        logDebug("Querying SKU details.");
        ArrayList<String> skuList = new ArrayList<>();
        skuList.addAll(inv.getAllOwnedSkus(itemType));
        if (moreSkus != null) {
            for (String sku : moreSkus) {
                if (!skuList.contains(sku)) {
                    skuList.add(sku);
                }
            }
        }

        if (skuList.size() == 0) {
            logDebug("queryPrices: nothing to do because there are no SKUs.");
            return BILLING_RESPONSE_RESULT_OK;
        }

        Bundle querySkus = new Bundle();
        querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
        if(mService==null){
            return IABHELPER_BAD_RESPONSE;
        }
        Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(),
                itemType, querySkus);

        if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
            int response = getResponseCodeFromBundle(skuDetails);
            if (response != BILLING_RESPONSE_RESULT_OK) {
                logDebug("getSkuDetails() failed: " + getResponseDesc(response));
                return response;
            } else {
                logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
                return IABHELPER_BAD_RESPONSE;
            }
        }

        ArrayList<String> responseList = skuDetails.getStringArrayList(
                RESPONSE_GET_SKU_DETAILS_LIST);

        for (String thisResponse : responseList) {
            SkuDetails d = new SkuDetails(itemType, thisResponse);
            logDebug("Got sku details: " + d);
            inv.addSkuDetails(d);
        }
        return BILLING_RESPONSE_RESULT_OK;
    }


    void consumeAsyncInternal(final List<Purchase> purchases,
                              final OnConsumeFinishedListener singleListener,
                              final OnConsumeMultiFinishedListener multiListener) {
        final Handler handler = new Handler();
        flagStartAsync("consume");
        (new Thread(new Runnable() {
            public void run() {
                final List<IabResult> results = new ArrayList<>();
                for (Purchase purchase : purchases) {
                    try {
                        consume(purchase);
                        results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
                    } catch (IabException ex) {
                        results.add(ex.getResult());
                    }
                }

                flagEndAsync();
                if (!mDisposed && singleListener != null) {
                    handler.post(new Runnable() {
                        public void run() {
                            singleListener.onConsumeFinished(purchases.get(0), results.get(0));
                        }
                    });
                }
                if (!mDisposed && multiListener != null) {
                    handler.post(new Runnable() {
                        public void run() {
                            multiListener.onConsumeMultiFinished(purchases, results);
                        }
                    });
                }
            }
        })).start();
    }

    void logDebug(String msg) {
        if (mDebugLog) Log.d(mDebugTag, msg);
    }

    void logError(String msg) {
        Log.e(mDebugTag, "In-app billing error: " + msg);
    }

    void logWarn(String msg) {
        Log.w(mDebugTag, "In-app billing warning: " + msg);
    }
} public class IabResult {
    int mResponse;
    String mMessage;

    public IabResult(int response, String message) {
        mResponse = response;
        if (message == null || message.trim().length() == 0) {
            mMessage = IabHelper.getResponseDesc(response);
        }
        else {
            mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
        }
    }
    public int getResponse() { return mResponse; }
    public String getMessage() { return mMessage; }
    public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
    public boolean isFailure() { return !isSuccess(); }
    public String toString() { return "IabResult: " + getMessage(); }
} public class Inventory {
    Map<String,SkuDetails> mSkuMap = new HashMap<>();
    Map<String,Purchase> mPurchaseMap = new HashMap<>();

    Inventory() { }

    /** Returns the listing details for an in-app product. */
    public SkuDetails getSkuDetails(String sku) {
        return mSkuMap.get(sku);
    }

    /** Returns purchase information for a given product, or null if there is no purchase. */
    public Purchase getPurchase(String sku) {
        return mPurchaseMap.get(sku);
    }

    /** Returns whether or not there exists a purchase of the given product. */
    public boolean hasPurchase(String sku) {
        return mPurchaseMap.containsKey(sku);
    }

    /** Return whether or not details about the given product are available. */
    public boolean hasDetails(String sku) {
        return mSkuMap.containsKey(sku);
    }

    /**
     * Erase a purchase (locally) from the inventory, given its product ID. This just
     * modifies the Inventory object locally and has no effect on the server! This is
     * useful when you have an existing Inventory object which you know to be up to date,
     * and you have just consumed an item successfully, which means that erasing its
     * purchase data from the Inventory you already have is quicker than querying for
     * a new Inventory.
     */
    public void erasePurchase(String sku) {
        if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
    }

    /** Returns a list of allAsMap owned product IDs. */
    List<String> getAllOwnedSkus() {
        return new ArrayList<>(mPurchaseMap.keySet());
    }

    /** Returns a list of allAsMap owned product IDs of a given type */
    List<String> getAllOwnedSkus(String itemType) {
        List<String> result = new ArrayList<>();
        for (Purchase p : mPurchaseMap.values()) {
            if (p.getItemType().equals(itemType)) result.add(p.getSku());
        }
        return result;
    }

    /** Returns a list of allAsMap purchases. */
    List<Purchase> getAllPurchases() {
        return new ArrayList<>(mPurchaseMap.values());
    }

    void addSkuDetails(SkuDetails d) {
        mSkuMap.put(d.getSku(), d);
    }

    void addPurchase(Purchase p) {
        mPurchaseMap.put(p.getSku(), p);
    }
} public class Purchase {
    String mItemType;  // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
    String mOrderId;
    String mPackageName;
    String mSku;
    long mPurchaseTime;
    int mPurchaseState;
    String mDeveloperPayload;
    String mToken;
    String mOriginalJson;
    String mSignature;

    public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
        mItemType = itemType;
        mOriginalJson = jsonPurchaseInfo;
        JSONObject o = new JSONObject(mOriginalJson);
        mOrderId = o.optString("orderId");
        mPackageName = o.optString("packageName");
        mSku = o.optString("productId");
        mPurchaseTime = o.optLong("purchaseTime");
        mPurchaseState = o.optInt("purchaseState");
        mDeveloperPayload = o.optString("developerPayload");
        mToken = o.optString("token", o.optString("purchaseToken"));
        mSignature = signature;
    }

    public String getItemType() { return mItemType; }
    public String getOrderId() { return mOrderId; }
    public String getPackageName() { return mPackageName; }
    public String getSku() { return mSku; }
    public long getPurchaseTime() { return mPurchaseTime; }
    public int getPurchaseState() { return mPurchaseState; }
    public String getDeveloperPayload() { return mDeveloperPayload; }
    public String getToken() { return mToken; }
    public String getOriginalJson() { return mOriginalJson; }
    public String getSignature() { return mSignature; }

    @Override
    public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
} public class Characters {
    public static Characters mInstance = new Characters();
    private Characters(){}

    public static Characters getInstance(){
        return mInstance;
    }

    private List<Character> mCharacters;

    public void loadFromJson(JSONArray jsonArray) throws JSONException {
        mCharacters = Character.collectionFromJson(jsonArray);
    }

    public List<Character> all() {
        return mCharacters;
    }

    public void clean() {
        mCharacters=null;
    }

    public Character get(String id) {
        for (Character character : mCharacters) {
            if (character.getId().equals(id)) {
                return character;
            }
        }
        return null;
    }
} public class FilterMonitor {
    public void registerSearchWatcher(SearchWatcher searchWatcher) {
        registeredWatchers.add(searchWatcher);
    }

    public void unregisterSearchWatcher(SearchWatcher searchWatcher){
        registeredWatchers.remove(searchWatcher);
    }

    public void doUpdateSearch(String name) {
        for (SearchWatcher registeredWatcher : registeredWatchers) {
            registeredWatcher.onUpdateSearch(name);
        }
    }

    public interface SearchWatcher {
        void onUpdateSearch(String newValue);
    }
    private final Set<SearchWatcher> registeredWatchers = new HashSet<>();
    private static final FilterMonitor mInstance = new FilterMonitor();

    private FilterMonitor() {

    }

    static public FilterMonitor getInstance() {
        return mInstance;
    }
    /*
    public void registerMonitor(Runnable monitor) {
        registeredAdapters.add(monitor);
    }

    public void unregisterMonitor(Runnable monitor) {
        registeredAdapters.remove(monitor);
    }


    public void notifyChanged() {
        for (Runnable monitor : registeredAdapters) {
            Log.v("FinterMonitor", "notifing: "+adapter);
            adapter.notifyDataSetChanged();
        }
    }
    */
} public class Filters implements Serializable {
    private final static Collection<BaseFilter> FILTERS;
    private static final Filters mInstance = new Filters();
    private String mFilterText = "";

    static {
        FILTERS = new LinkedList<>();
        FILTERS.add(new BucketFilter());
        FILTERS.add(new DamageFilter());
        FILTERS.add(new CompletedFilter());
        FILTERS.add(new LockedFilter());
        FILTERS.add(new TierNameFilter());
        FILTERS.add(new LightLevelFilter());
    }

    private Filters() {
    }

    static public Filters getInstance() {
        return mInstance;
    }


    public boolean isVisible(Item item) {
        for (BaseFilter filter : getFilters()) {
            if (!filter.filter(item)) {
                return false;
            }
        }
        return filterByText(item);
    }

    private boolean filterByText(Item item) {
        if (!mFilterText.equals("")) {
            return item.getName().toLowerCase().contains(mFilterText.toLowerCase());
        }
        return true;
    }

    public void setFilterText(String filterText) {
        this.mFilterText = filterText;
        ItemMonitor.getInstance().notifyChanged();
    }

    public Collection<BaseFilter> getFilters() {
        return FILTERS;
    }

} public class Data implements Serializable {
    private static final String LOG_TAG = "Database";
    private static final Data ourInstance = new Data();

    private User mUser;
    private Membership mMembership;
    private boolean mShowAll = true;

    private Context context;


    private boolean mIsLoading = false;

    private Data() {
    }

    public static Data getInstance() {
        return ourInstance;
    }


    public User loadUserFromJson(JSONObject json) throws JSONException {
        mUser = User.fromJson(json);
        return mUser;
    }

    public User getUser() {
        return mUser;
    }

    public Membership loadMembershipFromJson(JSONObject json) {
        this.mMembership = Membership.fromJson(json);
        return mMembership;
    }


    public Membership getMembership() {
        return mMembership;
    }


    public void clean() {
        mUser = null;
        mMembership = null;
        Characters.getInstance().clean();
        Items.getInstance().clean();
    }

    public void cleanCharacters() {
        Characters.getInstance().clean();
        Items.getInstance().clean();
    }


    public synchronized void setIsLoading(boolean isLoading) {
        mIsLoading = isLoading;
    }

    public boolean getIsLoading() {
        return mIsLoading;
    }



    public Context getContext() {
        return context;
    }

    public void setContext(Context context) {
        this.context = context;
        Labels.getInstance().setContext(context);
    }
} public class Preferences implements SharedPreferences.OnSharedPreferenceChangeListener {
    private static Preferences instance;
    private Context context;
    private String Pr;

    private Preferences(Context context) {
        this.context = context;
        PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this);
    }

    public static Preferences getInstance() {
        return instance;
    }

    public static Preferences getInstance(Context context) {
        if (instance == null) {
            instance = new Preferences(context);
        } else {
            instance.context = context;
        }
        return instance;
    }

    public int tabStyle() {
        return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString("tab_style", "0"));
    }

    public boolean showAll() {
        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("show_all", true);
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (key.equals("tab_style")) {
            ItemMonitor.getInstance().notifyChanged();
        } else if (key.equals("show_all")) {
            ItemMonitor.getInstance().notifyChanged();
        } else if (key.equals("ordering")) {
            ItemMonitor.getInstance().notifyChanged();
            Ordering.getInstance().notifyChanged();
        }

        Log.v("Preferences", "key changed: " + key + " " + sharedPreferences.toString());
    }

    public String getOrdering() {
        return PreferenceManager.getDefaultSharedPreferences(context).getString("ordering", "light_10");
    }

    public void setOrdering(String ordering) {
        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
        editor.putString("ordering", ordering);
        editor.apply();
    }
} public class DataLoader {
    private final String LOG_TAG = "DatabaseLoader";
    private final Activity act;
    private final ClientWebView webView;
    private final Data data;
    private Runnable mOnFinish;
    private Callback mOnError;
    private Callback mOnMessage;


    public interface Callback{
        public void onMessage(String message);
    }

    public DataLoader(Activity act, ClientWebView webView, Data data) {
        this.act = act;
        this.webView = webView;
        this.data = data;
    }

    public void process(Runnable onFinish, Callback onError, Callback onMessage){
        mOnFinish = onFinish;
        mOnError = onError;
        mOnMessage = onMessage;
        doLoadAllData();
    }

    public void process(final Runnable runnable) {
        process(runnable, new Callback(){
            @Override
            public void onMessage(String message) {
                runnable.run();
            }
        }, new Callback() {
            @Override
            public void onMessage(String message) {

            }
        });
    }

    private void doLoadAllData() {
        Data.getInstance().setIsLoading(true);
        if (data.getUser() == null) {
            doGetCurrentUser();
            return;
        }
        if (data.getMembership() == null) {
            doSearchDestinyPlayer(""+ data.getUser().getAccountType(), data.getUser().getAccountName());
            return;
        }
        if (Characters.getInstance().all() == null) {
            doGetAccount(data.getMembership());
            return;
        }
        if (Items.getInstance().allAsMap().size() == 0) {
            doLoadItems(data.getMembership(), Characters.getInstance().all());
            return;
        }

        if(mOnFinish!=null){
            Data.getInstance().setIsLoading(false);
            mOnFinish.run();
        }
    }

    void doGetCurrentUser() {
        mOnMessage.onMessage(act.getString(R.string.loading_user_message));
        // getting current user from bungie
        webView.call("userService.GetCurrentUser").then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                try {
                    data.loadUserFromJson(new JSONObject(result));
                    doLoadAllData();
                } catch (JSONException e) {
                    Log.e(LOG_TAG, "exception", e);
                    onError("Cannot get user data");
                }
            }

            @Override
            public void onError(String result) {
                mOnError.onMessage(result);
            }
        });
    }
    void doSearchDestinyPlayer(String type, String displayName) {
        mOnMessage.onMessage(act.getString(R.string.searching_your_account_message));
        // know that i'm logged in, search me as the player
        webView.call("destinyService.SearchDestinyPlayer", type, displayName).then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                //JSONObject json = null;
                try {
                    Data.getInstance().loadMembershipFromJson(new JSONArray(result).getJSONObject(0));
                    doLoadAllData();
                } catch (JSONException e) {
                    Log.e(LOG_TAG, "exception", e);
                    onError("Cannot get membership data");
                    //Sentry.captureException(e);
                    //e.printStackTrace();
                }
            }

            @Override
            public void onError(String result) {
                Log.e(LOG_TAG, "doSearchDestinyPlayer unsucessfull");
                mOnError.onMessage(result);
            }
        });
    }

    void doGetAccount(final Membership membership) {
        mOnMessage.onMessage(act.getString(R.string.downloading_message));
        // i know my membershipId, so i'm destiny player, get my characters
        webView.call("destinyService.GetAccount", membership.getTigerType(), "" + membership.getId(), "true").then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                try {
                    Characters.getInstance().loadFromJson(new JSONObject(result).getJSONObject("data").getJSONArray("characters"));
                    doLoadAllData();
                } catch (JSONException e) {
                    Log.e(LOG_TAG, "exception", e);
                    onError("Cannot get account data");
                }
            }

            @Override
            public void onError(String result) {
                Log.e(LOG_TAG, "doGetAccount unsucessfull " + result);
                mOnError.onMessage(result);
            }
        });
    }

    private void doLoadItems(Membership membership, List<Character> characters) {
        mOnMessage.onMessage(act.getString(R.string.loading_your_items_message));
        WaitForAll waiter = new WaitForAll() {
            @Override
            public void finished() {
                act.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        doLoadAllData();
                    }
                });

            }
        };
        for (Character c : characters) {
            waiter.increase();
            doGetCharacterInventory(membership, c, waiter);
        }
        waiter.increase();
        doGetVaultInventory(membership, waiter);

    }

    void doGetCharacterInventory(final Membership membership, final Character character, final WaitForAll waiter) {
        mOnMessage.onMessage(String.format(act.getString(R.string.loading_inventory_message), character));
        webView.call("destinyService.GetCharacterInventory", "" + membership.getType(), "" + membership.getId(), character.getId(), "true").then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                try {
                    Items.getInstance().put(character.getId(), Item.fromJson(result));
                } catch (JSONException e) {
                    Log.e(LOG_TAG, "exception", e);
                    onError("Cannot get character inventory data");
                }
                waiter.decrease();
            }

            @Override
            public void onError(String result) {
                Log.e("doGetCharacterInventory", "unsucessfull " + result);
                mOnError.onMessage(result);
            }
        });
    }


    private void doGetVaultInventory(final Membership membership, final WaitForAll waiter) {
        mOnMessage.onMessage(act.getString(R.string.loading_vault_inventory_message));
        webView.call("destinyService.GetVault", "" + membership.getType(), "true", membership.getId()).then(new ClientWebView.Callback() {
            @Override
            public void onAccept(String result) {
                try {
                    Items.getInstance().put(Items.VAULT_ID, Item.fromJson(result, true));
                } catch (JSONException e) {
                    Log.e(LOG_TAG, "exception", e);
                }
                waiter.decrease();
            }

            @Override
            public void onError(String result) {
                mOnError.onMessage(result);
            }
        });
    }
} public class BackgroundDrawable extends Drawable {
    private Paint mPaint;
    private final String mEmblemHash;
    private final String mBackgroupdHash;

    public BackgroundDrawable(String emblemPath, String backgroundPath) {
        mPaint = new Paint();

        mEmblemHash = emblemPath.replace('/', '-');
        mBackgroupdHash = backgroundPath.replace('/', '-');
        if (ImageStorage.getInstance().getImage(mEmblemHash) == null) {
            ImageStorage.getInstance().fetchImage(mEmblemHash, emblemPath, new ImageStorage.UrlFetchWaiter() {
                @Override
                public void onImageFetched(Bitmap bitmap) {
                    invalidateSelf();
                }
            });
        }
        if (ImageStorage.getInstance().getImage(mBackgroupdHash) == null) {
            ImageStorage.getInstance().fetchImage(mBackgroupdHash, backgroundPath, new ImageStorage.UrlFetchWaiter() {
                @Override
                public void onImageFetched(Bitmap bitmap) {
                    invalidateSelf();
                }
            });
        }
    }

    @Override
    public void draw(Canvas canvas) {
        int width = canvas.getWidth();
        int height = canvas.getHeight();
        Bitmap emblem = ImageStorage.getInstance().getImage(mEmblemHash);
        Bitmap background = ImageStorage.getInstance().getImage(mBackgroupdHash);
        if (background != null) {
            canvas.drawBitmap(background, new Rect(0, 0, background.getWidth(), background.getHeight()), new Rect(0, 0, width, height), mPaint);
        }
        if (emblem != null) {
            int margin = (height - emblem.getHeight()) / 2;
            canvas.drawBitmap(emblem, margin, margin, mPaint);
        }
    }

    @Override
    public void setAlpha(int i) {

    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return 0;
    }
} public class ClientWebView extends WebView {
    public void setErrorHandler(ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }

    public interface ErrorHandler {
        boolean processError(ConsoleMessage cm);
    }

    private ErrorHandler errorHandler;
    private final String LOG_TAG = "ClientWebView";
    private boolean mPrepared = false;
    private boolean mInitialized = false;
    private final Map<String, Promise> mPromises = new HashMap<>();
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    private Activity currentActivity;

    public ClientWebView(Context context) {
        super(context);
        init(context);
    }

    public ClientWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ClientWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {

    }

    @SuppressLint("AddJavascriptInterface")
    public void prepare(final Runnable onInit) {

        if (!mPrepared) {

            mPrepared = true;
            setWebChromeClient(new WebChromeClient() {
                public boolean onConsoleMessage(ConsoleMessage cm) {
                    if (errorHandler != null) {
                        errorHandler.processError(cm);
                    }
                    if (errorHandler == null)
                        Log.d(LOG_TAG, "Does not have error handler");
                    else
                        Log.d(LOG_TAG, "Have error handler");
                    Log.d(LOG_TAG + " js", cm.message() + " -- From line "
                            + cm.lineNumber() + " of "
                            + cm.sourceId());
                    return true;
                }
            });

            setWebViewClient(new WebViewClient() {
                @Override
                public void onPageFinished(WebView view, String url) {
                    if (!mInitialized) {
                        mInitialized = true;
                        onInit.run();
                    }
                    super.onPageFinished(view, url);
                }

                @Override
                public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                    if (currentActivity != null) {
                        new AlertDialog.Builder(currentActivity).setTitle(getContext().getString(R.string.critical_error)).setMessage(
                                String.format(getContext().getString(R.string.no_url_error), failingUrl)
                        ).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                System.exit(0);
                            }
                        }).setIcon(android.R.drawable.ic_dialog_alert)
                                .show();
                    } else {
                        System.exit(0);
                    }

                    super.onReceivedError(view, errorCode, description, failingUrl);
                }
            });
            getSettings().setJavaScriptEnabled(true);
            addJavascriptInterface(new JsObject(mPromises), "clientHandler");
            loadUrl("http://www.bungie.net/");

        } else
            onInit.run();

    }


    public boolean isPrepared() {
        return mPrepared;
    }

    public Promise callAny(String javascript) {
        final Promise p = new Promise();
        final int promiseId = identityHashCode(p);
        mPromises.put("" + promiseId, p);
        queueJavascript("window.clientHandler.accept(\"" + promiseId + "\", " + javascript + ")");
        return p;
    }

    public Promise call(String methodName, JSONObject parameters) {
        final Promise p = new Promise();
        final int promiseId = identityHashCode(p);
        mPromises.put("" + promiseId, p);
        queueJavascript("bungieNetPlatform." + methodName + "(" + parameters.toString() + " , function(data){window.clientHandler.accept(\"" + promiseId + "\", JSON.stringify(data))}, function(data){window.clientHandler.error(\"" + promiseId + "\", JSON.stringify(data))})");
        return p;
    }

    public Promise call(String methodName, String... arguments) {
        final Promise p = new Promise();
        final int promiseId = identityHashCode(p);

        String tmp = "";
        for (String argument : arguments) {
            tmp += argument + " ";
        }
        String processedArguments = "";
        for (String arg : arguments) {
            if (!processedArguments.equals("")) {
                processedArguments += ",";
            }
            processedArguments += '"' + arg + '"';
        }
        mPromises.put("" + promiseId, p);
        queueJavascript("bungieNetPlatform." + methodName + "(" + processedArguments + (!processedArguments.equals("") ? ", " : "") + "function(data){window.clientHandler.accept(" + '"' + promiseId + '"' + ", JSON.stringify(data))}, function(data){window.clientHandler.error(\"" + promiseId + "\", JSON.stringify(data))})");
        return p;
    }

    private void queueJavascript(final String javascript) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                loadUrl("javascript:" + "window.clientHandler.log(\"\"+ " + javascript + ")");
            }
        });
    }

    public void queueRunnable(Runnable r) {
        mHandler.post(r);
    }

    public void setCurrentActivity(Activity currentActivity) {
        this.currentActivity = currentActivity;
    }

    public interface Callback {
        public void onAccept(String result);

        public void onError(String result);
    }

    private class JsObject {
        private final Map<String, Promise> mPromises;

        public JsObject(Map<String, Promise> promises) {
            mPromises = promises;
        }

        @JavascriptInterface
        public void accept(String promiseId, String result) {
            mPromises.get(promiseId).accept(result);
            mPromises.remove(promiseId);
        }

        @JavascriptInterface
        public void error(String promiseId, String result) {
            mPromises.get(promiseId).error(result);
            mPromises.remove(promiseId);
        }

        @JavascriptInterface
        public boolean log(String data) {
            return true;
        }

        public String getString(String value) {
            return value;
        }
    }

    public class Promise {
        private Callback mCallback;

        public void then(Callback callback) {
            mCallback = callback;
        }

        public void accept(String result) {
            mCallback.onAccept(result);
        }

        public void error(String result) {
            mCallback.onError(result);
        }
    }
} public class DisableableViewPager extends ViewPager {
    private boolean mDissabled = false;

    public DisableableViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setDisabled(boolean disabled){
        mDissabled=disabled;
        setEnabled(!disabled);
        setClickable(!disabled);
        recurseDisable(this, !disabled);
    }

    private void recurseDisable(ViewGroup v, boolean state) {
        View a;
        for(int i = 0; i < v.getChildCount(); i++) {
            a = v.getChildAt(i);
            if(a instanceof ViewGroup) recurseDisable((ViewGroup) a, state);
            else if(a != null) {
                a.setEnabled(state);
                a.setClickable(state);
            }
        }
        return;
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mDissabled) {
            return true;
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mDissabled) {
            return true;
        }
        return super.onTouchEvent(event);
    }
} public class LabelSelector extends FrameLayout implements AdapterView.OnItemSelectedListener {
    private BaseAdapter adapter;

    public LabelSelector(Context context) {
        super(context);
        init();
    }

    public LabelSelector(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public LabelSelector(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }


    private void init() {
        LayoutInflater.from(getContext()).inflate(R.layout.label_selector, this, true);
        Spinner label_selector = (Spinner) findViewById(R.id.label_selector_spinner);
        adapter = new LabelSelectorAdapter(getContext());
        label_selector.setAdapter(adapter);


        label_selector.setOnItemSelectedListener(this);

    }


    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        Label label = (Label) adapter.getItem(position);
        Labels.getInstance().setCurrent(label.getId());
        findViewById(R.id.label_selector_root).setBackgroundColor((int) label.getColor());
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }

} public class QuantitySelectView extends FrameLayout {
    private static int MIN = 1;
    private TextView mLabel;
    private SeekBar mSeekbar;

    static public void getStackValue(Context context, int stackSize, final OnStackSelectInterface onStackSelectInterface) {
        final QuantitySelectView selectView = new QuantitySelectView(context);
        selectView.setMax(stackSize);

        new AlertDialog.Builder(context)
                .setTitle(R.string.please_select_a_quantity)
                .setView(selectView)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        onStackSelectInterface.onStackSizeSelect(selectView.getValue());
                    }
                })
                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                    }
                })
                .setIcon(android.R.drawable.ic_dialog_info)
                .show();
    }

    public QuantitySelectView(Context context) {
        super(context);
        init();
    }

    public QuantitySelectView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public QuantitySelectView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        LayoutInflater.from(getContext()).inflate(R.layout.quantyty_select, this, true);
        mLabel = (TextView) findViewById(R.id.quantytySelectLabel);
        mSeekbar = (SeekBar) findViewById(R.id.quantytySelectSeekBar);
        setMax(2);
        mSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                mLabel.setText(Integer.toString(i + MIN));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    public void setMax(int max) {
        mSeekbar.setMax(max - MIN);
        mSeekbar.setProgress(max - MIN);
    }

    public int getValue() {
        return mSeekbar.getProgress() + MIN;
    }

    public interface OnStackSelectInterface {
        void onStackSizeSelect(int i);
    }
}
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.gesture.GestureOverlayView;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.internal.view.SupportMenuItem;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.PagerTabStrip;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import org.json.JSONException;
import org.json.JSONObject;
import org.swistowski.vaulthelper.activities.HelpActivity;
import org.swistowski.vaulthelper.activities.ItemDetailActivity;
import org.swistowski.vaulthelper.activities.LabelsEditorActivity;
import org.swistowski.vaulthelper.activities.LoginActivity;
import org.swistowski.vaulthelper.activities.SendLogActivity;
import org.swistowski.vaulthelper.activities.SettingsActivity;
import org.swistowski.vaulthelper.activities.WebViewActivity;
import org.swistowski.vaulthelper.adapters.ItemsFragmentPagerAdapter;
import org.swistowski.vaulthelper.fragments.AdFragment;
import org.swistowski.vaulthelper.fragments.ItemListFragment;
import org.swistowski.vaulthelper.fragments.SettingsFragment;
import org.swistowski.vaulthelper.models.Character;
import org.swistowski.vaulthelper.models.Item;
import org.swistowski.vaulthelper.models.ItemMover;
import org.swistowski.vaulthelper.purchase.IabHelper;
import org.swistowski.vaulthelper.purchase.IabResult;
import org.swistowski.vaulthelper.purchase.Inventory;
import org.swistowski.vaulthelper.purchase.Purchase;
import org.swistowski.vaulthelper.storage.Characters;
import org.swistowski.vaulthelper.storage.FilterMonitor;
import org.swistowski.vaulthelper.storage.Filters;
import org.swistowski.vaulthelper.storage.Data;
import org.swistowski.vaulthelper.storage.Preferences;
import org.swistowski.vaulthelper.util.DataLoader;
import org.swistowski.vaulthelper.views.BackgroundDrawable;
import org.swistowski.vaulthelper.views.ClientWebView;
import org.swistowski.vaulthelper.views.DisableableViewPager;
import org.swistowski.vaulthelper.views.LabelSelector;
import org.swistowski.vaulthelper.views.QuantitySelectView;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
            public boolean onQueryTextChange(String s) {
                Filters.getInstance().setFilterText(s);
                return false;
            }
        });
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        mFilterMenuItem = menu.findItem(R.id.toggle_filters);
        if (mFilterMenuItem != null) {
            //mFilterMenuItem.setChecked(false);
            mFilterMenuItem.setChecked(filtersVisible);
        }
        /*
        MenuItem mShowAllMenuItem = menu.findItem(R.id.show_all);
        if (mShowAllMenuItem != null) {
            mShowAllMenuItem.setChecked(Data.getInstance().showAll());
        }
        */
        return true;
    }

    private void setFiltersVisible(boolean visibility) {
        filtersVisible = visibility;
        View container = findViewById(R.id.fragment_container);
        if (container != null) {
            if (visibility) {
                container.setVisibility(View.VISIBLE);
            } else {
                container.setVisibility(View.GONE);
            }
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            if (filtersVisible) {
                setFiltersVisible(false);
                return true;
            }
        }

        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle presses on the action bar items
        switch (item.getItemId()) {
            case R.id.toggle_filters:
                item.setChecked(!item.isChecked());
                setFiltersVisible(item.isChecked());
                return true;
            case R.id.action_refresh:
                actionRefresh();
                return true;
            /*
            case R.id.send_logs:
                collectAndSendLog();
                return true;
            case R.id.show_login_page:
                goLogin();
                return true;
                */
            case R.id.show_label_editor:
                LabelsEditorActivity.showIntent(this);
                return true;
            case R.id.settings_menu_item:
                SettingsActivity.openSettingsActivity(this);
                return true;
            case R.id.help_menu_item:
                HelpActivity.showIntent(this);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }


    private void actionRefresh() {
        Data.getInstance().cleanCharacters();
        Data.getInstance().setIsLoading(true);
        initUI();
        reloadDatabase();
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }


    @Override
    public void onPageSelected(int position) {
        if (!isPremium()) {
            position -= 1;
        }
        if (position >= 0 && Characters.getInstance().all() != null && position < Characters.getInstance().all().size()) {
            Character c = Characters.getInstance().all().get(position);
            mPageTabs.setBackgroundDrawable(new BackgroundDrawable(c.getEmblemPath(), c.getBackgroundPath()));


        } else {
            mPageTabs.setBackgroundDrawable(new BitmapDrawable(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)));
            mPageTabs.setBackgroundColor(getResources().getColor(R.color.background_material_dark));
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

    @Override
    public void onRequestSupportDev() {
        IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
            @Override
            public void onIabPurchaseFinished(IabResult result, Purchase info) {