public class BugDroidApplication extends Application implements OnSharedPreferenceChangeListener {

    // Android
    public static Context mContext;
    private static SharedPreferences mPrefs;

    // Objects
    public static Instance mCurrentInstance;

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

        mContext = getApplicationContext();
        mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        mPrefs.registerOnSharedPreferenceChangeListener(this);

        setDefaultInstance();
    }

    private static void setDefaultInstance() {

        int instanceId = mPrefs.getInt(InstancesListFragment.INSTANCE_ID, -1);
        int accountId = mPrefs.getInt(InstancesListFragment.ACCOUNT_ID, -1);

        Account account = null;

        if (accountId >= 0) {
            Uri uriAccount = Uri.withAppendedPath(Accounts.CONTENT_URI, String.valueOf(accountId));
            Cursor cursorAccount = mContext.getContentResolver().query(uriAccount, Accounts.PROJECTION, null,
                null, null);
            if (cursorAccount.getCount() > 0) {
                cursorAccount.moveToFirst();
                account = Account.toAccount(cursorAccount);
            }
            cursorAccount.close();
        }

        if (instanceId >= 0) {
            Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, String.valueOf(instanceId));
            Cursor cursorInstance = mContext.getContentResolver().query(uri, Instances.PROJECTION, null,
                null, null);
            if (cursorInstance.getCount() > 0) {
                cursorInstance.moveToFirst();
                mCurrentInstance = Instance.toInstance(cursorInstance);
                mCurrentInstance.account = account;
            }
            cursorInstance.close();
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (key.equals(InstancesListFragment.ACCOUNT_ID) || key.equals(InstancesListFragment.INSTANCE_ID)) {
            setDefaultInstance();
        }
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
    }
} public class Account implements Serializable {

    private static final long serialVersionUID = -2274154012859845136L;
    public int _id;
    public int instances_id;
    public String username;
    public String password;

    public static Account toAccount(Cursor cursor) {
        Account account = new Account();
        account._id = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.ID.getName()));
        account.instances_id = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.INSTANCES_ID.getName()));
        account.username = cursor.getString(cursor.getColumnIndexOrThrow(Columns.EMAIL.getName()));
        account.password = cursor.getString(cursor.getColumnIndexOrThrow(Columns.PASSWORD.getName()));
        return account;
    }
} public class Bug implements Serializable {

    private static final long serialVersionUID = 2541841490838164840L;
    public int _id;
    public int instances_id;
    public int accounts_id;
    public boolean isBookmarked;
    @SerializedName("assigned_to")
    public User assignedTo;
    @SerializedName("attachments")
    public List<Attachment> attachments = new ArrayList<Attachment>();
    @SerializedName("blocks")
    public List<Integer> blocks = new ArrayList<Integer>();
    @SerializedName("cc")
    public List<Cc> cc = new ArrayList<Cc>();
    @SerializedName("classification")
    public String classification;
    @SerializedName("comments")
    public List<Comment> comments = new ArrayList<Comment>();
    @SerializedName("component")
    public String component;
    @SerializedName("creation_time")
    public String creationTime;
    @SerializedName("creator")
    public User creator;
    @SerializedName("depends_on")
    public List<Integer> dependsOn = new ArrayList<Integer>();
    @SerializedName("flags")
    public List<Flag> flags = new ArrayList<Flag>();
    @SerializedName("history")
    public List<History> history = new ArrayList<History>();
    @SerializedName("id")
    public int bugId;
    @SerializedName("is_cc_accessible")
    public boolean isCcAccessible;
    @SerializedName("is_confirmed")
    public boolean isConfirmed;
    @SerializedName("is_creator_accessible")
    public boolean isCreatorAccessible;
    @SerializedName("keywords")
    public List<String> keywords = new ArrayList<String>();
    @SerializedName("last_change_time")
    public String lastChangeTime;
    @SerializedName("op_sys")
    public String opSys;
    @SerializedName("platform")
    public String platform;
    @SerializedName("priority")
    public String priority;
    @SerializedName("product")
    public String product;
    @SerializedName("qa_contact")
    public User qaContact;
    @SerializedName("ref")
    public String ref;
    @SerializedName("resolution")
    public String resolution;
    @SerializedName("severity")
    public String severity;
    @SerializedName("status")
    public String status;
    @SerializedName("summary")
    public String summary;
    @SerializedName("target_milestone")
    public String targetMilestone;
    @SerializedName("version")
    public String version;
    @SerializedName("whiteboard")
    public String whiteboard;

    public ContentValues toContentValues() {
        ContentValues values = new ContentValues();
        values.put(Columns.BUG_ID.getName(), bugId);
        values.put(Columns.INSTANCES_ID.getName(), instances_id);
        values.put(Columns.ACCOUNTS_ID.getName(), accounts_id);
        values.put(Columns.ASSIGNED_TO_NAME.getName(), assignedTo.name);
        values.put(Columns.ASSIGNED_TO_REAL_NAME.getName(), assignedTo.realName);
        values.put(Columns.CLASSIFICATION.getName(), classification);
        values.put(Columns.COMPONENT.getName(), component);
        values.put(Columns.CREATION_TIME.getName(), creationTime);
        values.put(Columns.CREATOR_NAME.getName(), creator.name);
        values.put(Columns.CREATOR_REAL_NAME.getName(), creator.realName);
        values.put(Columns.LAST_CHANGE_TIME.getName(), lastChangeTime);
        values.put(Columns.OP_SYS.getName(), opSys);
        values.put(Columns.PLATFORM.getName(), platform);
        values.put(Columns.PRIORITY.getName(), priority);
        values.put(Columns.PRODUCT.getName(), product);
        values.put(Columns.QA_CONTACT_NAME.getName(), qaContact.name);
        values.put(Columns.QA_CONTACT_REAL_NAME.getName(), assignedTo.realName);
        values.put(Columns.REF.getName(), ref);
        values.put(Columns.RESOLUTION.getName(), resolution);
        values.put(Columns.SEVERITY.getName(), severity);
        values.put(Columns.STATUS.getName(), status);
        values.put(Columns.SUMMARY.getName(), summary);
        values.put(Columns.TARGET_MILESTONE.getName(), targetMilestone);
        values.put(Columns.VERSION.getName(), version);
        values.put(Columns.WHITEBOARD.getName(), whiteboard);
        return values;
    }

    public static Bug toBug(Cursor cursor) {
        Bug bug = new Bug();
        bug._id = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.ID.getName()));
        bug.bugId = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.BUG_ID.getName()));
        bug.isBookmarked = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.BOOKMARK.getName())) == 1;
        bug.instances_id = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.INSTANCES_ID.getName()));
        bug.accounts_id = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.ACCOUNTS_ID.getName()));
        bug.assignedTo = new User();
        bug.assignedTo.name = cursor.getString(cursor.getColumnIndexOrThrow(Columns.ASSIGNED_TO_NAME.getName()));
        bug.assignedTo.realName = cursor.getString(cursor.getColumnIndexOrThrow(Columns.ASSIGNED_TO_REAL_NAME.getName()));
        bug.attachments = Attachment.getAttachmentsForBug(bug._id);
        bug.classification = cursor.getString(cursor.getColumnIndexOrThrow(Columns.CLASSIFICATION.getName()));
        bug.cc = Cc.getCcsForBug(bug._id);
        bug.component = cursor.getString(cursor.getColumnIndexOrThrow(Columns.COMPONENT.getName()));
        bug.comments = Comment.getCommentsForBug(bug._id);
        bug.creationTime = cursor.getString(cursor.getColumnIndexOrThrow(Columns.CREATION_TIME.getName()));
        bug.creator = new User();
        bug.creator.name = cursor.getString(cursor.getColumnIndexOrThrow(Columns.CREATOR_NAME.getName()));
        bug.creator.realName = cursor.getString(cursor.getColumnIndexOrThrow(Columns.CREATOR_REAL_NAME.getName()));
        bug.lastChangeTime = cursor.getString(cursor.getColumnIndexOrThrow(Columns.LAST_CHANGE_TIME.getName()));
        bug.opSys = cursor.getString(cursor.getColumnIndexOrThrow(Columns.OP_SYS.getName()));
        bug.platform = cursor.getString(cursor.getColumnIndexOrThrow(Columns.PLATFORM.getName()));
        bug.priority = cursor.getString(cursor.getColumnIndexOrThrow(Columns.PRIORITY.getName()));
        bug.product = cursor.getString(cursor.getColumnIndexOrThrow(Columns.PRODUCT.getName()));
        bug.qaContact = new User();
        bug.qaContact.name = cursor.getString(cursor.getColumnIndexOrThrow(Columns.QA_CONTACT_NAME.getName()));
        bug.qaContact.realName = cursor.getString(cursor.getColumnIndexOrThrow(Columns.QA_CONTACT_REAL_NAME.getName()));
        bug.ref = cursor.getString(cursor.getColumnIndexOrThrow(Columns.REF.getName()));
        bug.resolution = cursor.getString(cursor.getColumnIndexOrThrow(Columns.RESOLUTION.getName()));
        bug.severity = cursor.getString(cursor.getColumnIndexOrThrow(Columns.SEVERITY.getName()));
        bug.status = cursor.getString(cursor.getColumnIndexOrThrow(Columns.STATUS.getName()));
        bug.summary = cursor.getString(cursor.getColumnIndexOrThrow(Columns.SUMMARY.getName()));
        bug.targetMilestone = cursor.getString(cursor.getColumnIndexOrThrow(Columns.TARGET_MILESTONE.getName()));
        bug.version = cursor.getString(cursor.getColumnIndexOrThrow(Columns.VERSION.getName()));
        bug.whiteboard = cursor.getString(cursor.getColumnIndexOrThrow(Columns.WHITEBOARD.getName()));
        return bug;
    }

    @Override
    public String toString() {

        Context context = BugDroidApplication.mContext;
        Resources res = context.getResources();

        StringBuffer string = new StringBuffer();
        string.append(res.getString(R.string.bug_title) + ":" + summary + "\n");
        string.append(res.getString(R.string.bug_status) + ":" + status + "\n");
        string.append(res.getString(R.string.bug_resolution) + ":" + resolution + "\n");
        string.append(res.getString(R.string.bug_product) + ":" + product + "\n");
        string.append(res.getString(R.string.bug_component) + ":" + component + "\n");
        string.append(res.getString(R.string.bug_version) + ":" + version + "\n");
        string.append(res.getString(R.string.bug_platform) + ":" + platform + "\n");
        string.append(res.getString(R.string.bug_importance) + ":" + severity + "\n");
        string.append(res.getString(R.string.bug_assigned_to) + ":" + assignedTo.toString() + "\n");
        string.append(res.getString(R.string.bug_qa_contact) + ":" + qaContact.toString() + "\n");
        string.append(res.getString(R.string.bug_target_mil) + ":" + targetMilestone);

        return string.toString();
    }

    public static void bookmark(int _id, boolean newState) {
        Context context = BugDroidApplication.mContext;

        Uri uri = Uri.withAppendedPath(Bugs.CONTENT_URI, String.valueOf(_id));
        ContentValues values = new ContentValues();
        values.put(Columns.BOOKMARK.getName(), newState);
        context.getContentResolver().update(uri, values, null, null);
    }
} public class Instance implements Serializable {

    private static final long serialVersionUID = 6357426966223304678L;
    public int _id;
    public String name;
    public String url;
    public Account account;

    public ContentValues toContentValues() {
        ContentValues contentValues = new ContentValues();
        contentValues.put(Instances.Columns.ID.getName(), _id);
        contentValues.put(Instances.Columns.NAME.getName(), name);
        contentValues.put(Instances.Columns.URL.getName(), url);
        return contentValues;
    }

    public static Instance toInstance(Cursor cursor) {
        Instance instance = new Instance();
        instance._id = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.ID.getName()));
        instance.name = cursor.getString(cursor.getColumnIndexOrThrow(Columns.NAME.getName()));
        instance.url = cursor.getString(cursor.getColumnIndexOrThrow(Columns.URL.getName()));
        return instance;
    }
} public abstract class Search implements Serializable {

    private static final long serialVersionUID = 4902715531248845223L;
    // Vars
    public static final String BUG_SUFFIX = "bug?";
    public static String CHANGED_AFTER_SUFFIX = "&changed_after=%s";
    private static final String ACCOUNT_SUFFIX = "username=%s&password=%s";

    // Search queries
    private static final String BUGS_QUICK_SEARCH = "&quicksearch=%s";
    // TODO : Limit fields
    private static final String BUGS_PRODUCT_SUFFIX = "&product=%s&changed_after=12h";
    private static final String DASHBOARD_QUERY_TO_REVIEW = "&status=NEW&status=UNCONFIRMED&status=ASSIGNED&status=REOPENED&flag.requestee=%s"; // 2012-09-21T23%3A08%3A19Z
    private static final String DASHBOARD_QUERY_REPORTED = "&status=NEW&status=UNCONFIRMED&status=ASSIGNED&status=REOPENED&email1=%s&email1_type=equals&email1_creator=1&email2=%s&email2_type=not_equals&email2_assigned_to=1";
    private static final String DASHBOARD_QUERY_ASSIGNED = "&status=NEW&status=UNCONFIRMED&status=ASSIGNED&status=REOPENED&email1=%s&email1_type=equals&email1_assigned_to=1";
    private static final String DASHBOARD_QUERY_CCD = "&status=NEW&status=UNCONFIRMED&status=ASSIGNED&status=REOPENED&email1=%s&email1_type=equals&email1_cc=1&email2=%s&email2_type=not_equals&email2_assigned_to=1&email2_creator=1";
    private static final String DASHBOARD_QUERY_RECENTLY_FIXED = "&resolution=FIXED&email1=%s&email1_type=equals&email1_assigned_to=1&email1_creator=1&email1_cc=1";

    // Search types
    public static final int QUICK_SEARCH = 0;
    public static final int PRODUCT_NAME = QUICK_SEARCH + 1;
    public static final int DASHBOARD_TO_REVIEW = PRODUCT_NAME + 1;
    public static final int DASHBOARD_REPORTED = DASHBOARD_TO_REVIEW + 1;
    public static final int DASHBOARD_ASSIGNED = DASHBOARD_REPORTED + 1;
    public static final int DASHBOARD_CCD = DASHBOARD_ASSIGNED + 1;
    public static final int DASHBOARD_RECENTLY_FIXED = DASHBOARD_CCD + 1;

    public String name = "";
    public int requestCode;
    private Instance instance;
    private static Account account;

    public Search(int requestCode, Instance instance) {
        super();
        this.requestCode = requestCode;
        this.instance = instance;
        this.account = getAccount();
    }

    public Search(String name, int requestCode, Instance instance) {
        super();
        this.name = name;
        this.requestCode = requestCode;
        this.instance = instance;
        this.account = getAccount();
    }

    private Account getAccount() {
        return instance.account;
    }

    protected abstract String buildQuery();

    public String getQuery() {
        String accountSuffix = "";
        if (account != null) {
            accountSuffix = String.format(ACCOUNT_SUFFIX, account.username, account.password);  
        }

        Context context = BugDroidApplication.mContext;
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        String changedAfterValue = prefs.getString(SettingsActivity.KEY_SEARCH_PERIOD, "7d");
        String changedAfter = String.format(CHANGED_AFTER_SUFFIX, changedAfterValue);

        StringBuilder sb = new StringBuilder();
        sb.append(instance.url);
        sb.append(BUG_SUFFIX);
        sb.append(accountSuffix);
        sb.append(changedAfter);
        sb.append(buildQuery());
        return sb.toString();
    }

    public static class ProductSearch extends Search {

        private static final long serialVersionUID = 3847198290311299655L;
        public String productName;

        public ProductSearch(int requestCode, Instance instance, String productName) {
            super(productName, requestCode, instance);
            this.productName = productName;
        }

        @Override
        protected String buildQuery() {
            productName = URLEncoder.encode(productName);
            return String.format(BUGS_PRODUCT_SUFFIX, productName);
        }
    }

    public static class QuickSearch extends Search {

        private static final long serialVersionUID = -6877783643462458265L;
        public String keywords;

        public QuickSearch(int requestCode, Instance instance, String keywords) {
            super(keywords, requestCode, instance);
            this.keywords = keywords;
        }

        @Override
        protected String buildQuery() {
            keywords = URLEncoder.encode(keywords);
            return String.format(BUGS_QUICK_SEARCH, keywords);
        }
    }

    public static class DashboardSearch extends Search {

        private static final long serialVersionUID = -7918981619210024917L;
        int searchType;

        public DashboardSearch(String name, int requestCode, Instance instance, int searchType) {
            super(requestCode, instance);
            this.searchType = searchType;
        }

        @Override
        protected String buildQuery() {
            String username = account.username;
            switch (searchType) {
            case DASHBOARD_TO_REVIEW:
                return String.format(DASHBOARD_QUERY_TO_REVIEW, username);
            case DASHBOARD_REPORTED:
                return String.format(DASHBOARD_QUERY_REPORTED, username, username);
            case DASHBOARD_ASSIGNED:
                return String.format(DASHBOARD_QUERY_ASSIGNED, username);
            case DASHBOARD_CCD:
                return String.format(DASHBOARD_QUERY_CCD, username, username);
            case DASHBOARD_RECENTLY_FIXED:
                return String.format(DASHBOARD_QUERY_RECENTLY_FIXED, username);
            default:
                return "";
            }
        }
    }

    public static class FormSearch extends Search {

        private static final long serialVersionUID = 9127989443996952914L;
        ArrayList<Pair<String, String>> formValues;

        public FormSearch(String name, int requestCode, Instance instance,
            ArrayList<Pair<String, String>> formValues) {
            super(name, requestCode, instance);
            this.formValues = formValues;
        }

        @Override
        protected String buildQuery() {
            StringBuilder sb = new StringBuilder();
            for (Pair<String, String> pair : formValues) {
                sb.append("&");
                sb.append(pair.key);
                sb.append("=");
                sb.append(pair.value);
            }
            return sb.toString();
        }
    }
} public static class DashboardSearch extends Search {

    private static final long serialVersionUID = -7918981619210024917L;
    int searchType;

    public DashboardSearch(String name, int requestCode, Instance instance, int searchType) {
        super(requestCode, instance);
        this.searchType = searchType;
    }

    @Override
    protected String buildQuery() {
        String username = account.username;
        switch (searchType) {
        case DASHBOARD_TO_REVIEW:
            return String.format(DASHBOARD_QUERY_TO_REVIEW, username);
        case DASHBOARD_REPORTED:
            return String.format(DASHBOARD_QUERY_REPORTED, username, username);
        case DASHBOARD_ASSIGNED:
            return String.format(DASHBOARD_QUERY_ASSIGNED, username);
        case DASHBOARD_CCD:
            return String.format(DASHBOARD_QUERY_CCD, username, username);
        case DASHBOARD_RECENTLY_FIXED:
            return String.format(DASHBOARD_QUERY_RECENTLY_FIXED, username);
        default:
            return "";
        }
    }
} public class BugIntentService extends IntentService {

    public static final String BUG = "bug";
    private static final String QUERY = "query";
    private static final String INSTANCES_ID = "instances_id";
    private static final String ACCOUNTS_ID = "accounts_id";
    private static final String BUG_ID = "bugId";
    public static final String FROM_CACHE = "fromCache";
    public static final String MESSENGER = "messenger";

    // TODO : Limit fields?
    private static final String BUG_SUFFIX = "bug/%d?include_fields=_all"; 
    private static final String ACCOUNT_SUFFIX = "&username=%s&password=%s";

    public BugIntentService() {
        super(BugIntentService.class.getSimpleName());
    }

    public static Intent getIntent(Context context, Instance instance, int bugId, boolean fromCache) {
        Intent intent = new Intent(context, BugIntentService.class);
        String accountSuffix = "";
        Account account = instance.account;
        if (account != null) {
            intent.putExtra(ACCOUNTS_ID, account._id);
            accountSuffix = String.format(ACCOUNT_SUFFIX, account.username, account.password);
        }

        String bugSuffix = String.format(BUG_SUFFIX, bugId);
        String query = instance.url.concat(bugSuffix).concat(accountSuffix);

        intent.putExtra(QUERY, query);
        intent.putExtra(INSTANCES_ID, instance._id);
        intent.putExtra(BUG_ID, bugId);
        intent.putExtra(FROM_CACHE, fromCache);
        return intent;
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Context context = getApplicationContext();

        Bundle bundle = intent.getExtras();
        String query = bundle.getString(QUERY);
        int instances_id = bundle.getInt(INSTANCES_ID);
        int accounts_id = bundle.getInt(ACCOUNTS_ID, -1);
        int bugId = bundle.getInt(BUG_ID);
        boolean fromCache = bundle.getBoolean(FROM_CACHE);

        try {
            String selection = Bugs.Columns.INSTANCES_ID.getName() + "=" + instances_id
                + " AND " + Bugs.Columns.ACCOUNTS_ID.getName() + "=" + accounts_id
                + " AND " + Bugs.Columns.BUG_ID.getName() + "=" + bugId;
            Cursor cursor = context.getContentResolver().query(Bugs.CONTENT_URI, Bugs.PROJECTION, selection, null, null);
            Bug bugFromCache = null;
            if (cursor.getCount() > 0) {
                cursor.moveToFirst();
                bugFromCache = Bug.toBug(cursor);
                cursor.close();
            }

            if (fromCache && bugFromCache != null) {   
                sendResult(intent, bugFromCache, true);
                return;
            }

            //Get bug from web and parse it
            String jsonString = NetworkUtils.readJson(query).result;
            Gson gson = new Gson();
            Bug bugFromWeb = gson.fromJson(jsonString, Bug.class);
            if (bugFromWeb != null) {
                bugFromWeb.instances_id = instances_id;
                bugFromWeb.accounts_id = accounts_id;
                
                int bugs_id;
                if (bugFromCache != null) {
                    bugs_id = bugFromCache._id;
                    bugFromWeb.isBookmarked = bugFromCache.isBookmarked;

                    // Delete old data for bug
                    String selectionComments = Comments.Columns.BUGS_ID + "=" + bugs_id;
                    context.getContentResolver().delete(Comments.CONTENT_URI, selectionComments, null);
                    String selectionAttachments = Attachments.Columns.BUGS_ID + "=" + bugs_id;
                    context.getContentResolver().delete(Attachments.CONTENT_URI, selectionAttachments, null);
                    String selectionCcs = Ccs.Columns.BUGS_ID + "=" + bugs_id;
                    context.getContentResolver().delete(Ccs.CONTENT_URI, selectionCcs, null);

                    Uri uri = Uri.withAppendedPath(Bugs.CONTENT_URI, String.valueOf(bugs_id));
                    context.getContentResolver().update(uri, bugFromCache.toContentValues(), null, null);
                } else {
                    Uri uriInserted = context.getContentResolver().insert(Bugs.CONTENT_URI, bugFromWeb.toContentValues());
                    bugs_id = Integer.parseInt(uriInserted.getLastPathSegment());
                    bugFromWeb._id = bugs_id;
                }

                //Insert new data
                context.getContentResolver().bulkInsert(Comments.CONTENT_URI, Comment.toContentValues(bugFromWeb.comments, bugs_id));
                context.getContentResolver().bulkInsert(Attachments.CONTENT_URI, Attachment.toContentValues(bugFromWeb.attachments, bugs_id));
                context.getContentResolver().bulkInsert(Ccs.CONTENT_URI, Cc.toContentValues(bugFromWeb.cc, bugs_id));
            }
 
            //Send bug to activity
            sendResult(intent, bugFromWeb, false);
        } catch (JsonParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void sendResult(Intent intent, Bug bug, boolean fromCache) {
        Bundle extras = intent.getExtras();
        Messenger messenger = (Messenger) extras.get(MESSENGER);
        if (messenger != null) {
            Message msg = Message.obtain();
            Bundle data = new Bundle();
            data.putBoolean(FROM_CACHE, fromCache);
            data.putSerializable(BUG, bug);
            msg.setData(data);
            try {
                messenger.send(msg);
            } catch (android.os.RemoteException e1) {
                Log.w(getClass().getName(), "Exception sending message", e1);
            }
        }
    }
} public class BugsIntentService extends IntentService {

    public static final String BUGS = "bugs";
    public static final String SEARCH_TYPE = "searchType";
    private static final String SEARCH = "query";
    public static final String MESSENGER = "messenger";
    public static final String REQUEST_CODE = "requestCode";

    public BugsIntentService() {
        super(BugsIntentService.class.getSimpleName());
    }

    public static Intent getIntent(Context context, Search search) {
        Intent intent = new Intent(context, BugsIntentService.class);
        intent.putExtra(SEARCH, search);
        return intent;
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle bundle = intent.getExtras();
        Search search = (Search) bundle.getSerializable(SEARCH);
        String query = search.getQuery();
        int requestCode = search.requestCode;
        String jsonString = NetworkUtils.readJson(query).result;
        try {
            Gson gson = new Gson();
            Bugs bugs = gson.fromJson(jsonString, Bugs.class);

            Bundle extras = intent.getExtras();
            Messenger messenger = (Messenger) extras.get(MESSENGER);
            if (messenger != null) {
                Message msg = Message.obtain();
                Bundle data = new Bundle();
                data.putInt(REQUEST_CODE, requestCode);
                if (bugs != null) {
                    data.putSerializable(BUGS, (ArrayList<Bug>) bugs.bugs); 
                }
                msg.setData(data);
                try {
                    messenger.send(msg);
                } catch (android.os.RemoteException e1) {
                    Log.w(getClass().getName(), "Exception sending message", e1);
                }
            }
        } catch (JsonParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

} public class BugActivity extends SherlockFragmentActivity implements ActionBar.TabListener,
    ViewPager.OnPageChangeListener, BugFragment.BugLoadingListener {

    private static final String BUG_ID = "bugId";
    private static final String BUG_TITLE = "bugTitle";

    // Android
    private Tab mTabDetails, mTabComments, mTabAttachments, mTabCcs;
    public BugFragment mBugFragment;
    private ActionBar mActionBar;

    // Objects
    private int mBugId;
    private String mBugTitle;

    public static Intent getIntent(Context context, int bugId, String bugTitle) {
        Intent intent = new Intent(context, BugActivity.class);
        intent.putExtra(BUG_ID, bugId);
        intent.putExtra(BUG_TITLE, bugTitle);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        setContentView(R.layout.activity_singlepane_empty);

        Bundle bundle = getIntent().getExtras();
        if (bundle != null) {
            mBugId = bundle.getInt(BUG_ID);
            mBugTitle = bundle.getString(BUG_TITLE);
        } else {
            mBugId = savedInstanceState.getInt(BUG_ID);
            mBugTitle = savedInstanceState.getString(BUG_TITLE);
        }

        mActionBar = getSupportActionBar();
        mActionBar.setDisplayHomeAsUpEnabled(true);
        mActionBar.setTitle(String.valueOf(mBugId));

        // We use tab navigation
        mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        mTabDetails = mActionBar.newTab().setText("Details").setTabListener(this);
        mActionBar.addTab(mTabDetails);
        mTabComments = mActionBar.newTab().setText("Comments").setTabListener(this);
        mActionBar.addTab(mTabComments);
        mTabAttachments = mActionBar.newTab().setText("Attachments").setTabListener(this);
        mActionBar.addTab(mTabAttachments);
        mTabCcs = mActionBar.newTab().setText("Ccs").setTabListener(this);
        mActionBar.addTab(mTabCcs);

        mBugFragment = (BugFragment) getSupportFragmentManager().findFragmentByTag(
            BugFragment.class.getSimpleName());
        if (mBugFragment == null) {
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            mBugFragment = BugFragment.newInstance(mBugId, mBugTitle);
            ft.add(R.id.root_container, mBugFragment, BugFragment.class.getSimpleName());
            ft.commit();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putInt(BUG_ID, mBugId);
        outState.putString(BUG_TITLE, mBugTitle);
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (mBugFragment != null && mBugFragment.mViewPager != null) {
            mBugFragment.mViewPager.setCurrentItem(tab.getPosition());
        }
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPageScrollStateChanged(int arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPageSelected(int position) {
        mActionBar.setSelectedNavigationItem(position);

        mActionBar.getTabAt(position).select();
        ViewParent root = findViewById(android.R.id.content).getParent();
        findAndUpdateSpinner(root, position);
    }

    /**
     * Searches the view hierarchy excluding the content view for a possible
     * Spinner in the ActionBar.
     * 
     * @param root
     *            The parent of the content view
     * @param position
     *            The position that should be selected
     * @return if the spinner was found and adjusted
     */
    private boolean findAndUpdateSpinner(Object root, int position) {
        if (root instanceof android.widget.Spinner) {
            // Found the Spinner
            Spinner spinner = (Spinner) root;
            spinner.setSelection(position);
            return true;
        } else if (root instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) root;
            if (group.getId() != android.R.id.content) {
                // Found a container that isn't the container holding our screen
                // layout
                for (int i = 0; i < group.getChildCount(); i++) {
                    if (findAndUpdateSpinner(group.getChildAt(i), position)) {
                        // Found and done searching the View tree
                        return true;
                    }
                }
            }
        }
        // Nothing found
        return false;
    }

    @Override
    public void onBugLoading(int bugId) {
        setSupportProgressBarIndeterminateVisibility(true);
    }

    @Override
    public void onBugLoaded(Bug bug) {
        setSupportProgressBarIndeterminateVisibility(false);
    }
} public class BugMultiPaneActivity extends SherlockFragmentActivity {

    private static final String BUG_ID = "bugId";
    private static final String BUG_TITLE = "bugTitle";
    private static final String BUG = "bug";

    //UI
    private MenuItem mRefreshMenu, mBookmarkMenu;
    private FrameLayout mBugDetails, mBugComments, mBugAttachments, mBugCcs;

    // Objects
    private int mBugId;
    private String mBugTitle;
    private Bug mBug;
    private Instance mInstance;

    public static Intent getIntent(Context context, int bugId, String bugTitle) {
        Intent intent = new Intent(context, BugMultiPaneActivity.class);
        intent.putExtra(BUG_ID, bugId);
        intent.putExtra(BUG_TITLE, bugTitle);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        setContentView(R.layout.activity_bug_multipane);

        mInstance = BugDroidApplication.mCurrentInstance;

        Bundle bundle = getIntent().getExtras();
        if (savedInstanceState != null) {
            mBugId = savedInstanceState.getInt(BUG_ID);
            mBugTitle = savedInstanceState.getString(BUG_TITLE);
            mBug = (Bug) savedInstanceState.getSerializable(BUG);
            bindBug();
        } else {
            mBugId = bundle.getInt(BUG_ID);
            mBugTitle = bundle.getString(BUG_TITLE);
            loadBug(true);
        }

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        mBugDetails = (FrameLayout) findViewById(R.id.bugDetails);
        mBugComments = (FrameLayout) findViewById(R.id.bugComments);
        mBugAttachments = (FrameLayout) findViewById(R.id.bugAttachments);
        mBugCcs = (FrameLayout) findViewById(R.id.bugCcs);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        getSupportMenuInflater().inflate(R.menu.bug_menu_items, menu);

        mRefreshMenu = menu.findItem(R.id.menu_refresh);
        mBookmarkMenu = menu.findItem(R.id.menu_bookmark);

        if (mBug != null) {
            setBookmarkMenu(mBug.isBookmarked);
        }
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
        case android.R.id.home:
            finish();
            break;
        case R.id.menu_refresh:
            refresh();
            break;
        case R.id.menu_bookmark:
            bookmark();
            break;
            // case R.id.menu_edit:
            // break;
        case R.id.menu_share:
            share();
            break;
        default:
//            Toast.makeText(this, "Not yet implemented", Toast.LENGTH_SHORT).show();
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putInt(BUG_ID, mBugId);
        outState.putString(BUG_TITLE, mBugTitle);
        outState.putSerializable(BUG, mBug);
    }

    public void refresh() {
        loadBug(false);
    }

    public void bookmark() {
        if (mBookmarkMenu == null) {
            return;
        }

        if (mBookmarkMenu.isChecked()) {
            Bug.bookmark(mBug._id, false);
            setBookmarkMenu(false);
        } else {
            Bug.bookmark(mBug._id, true);
            setBookmarkMenu(true);
        }
    }

    public void share() {
        if (mBug != null) {
            Intent intent = new Intent(android.content.Intent.ACTION_SEND);
            intent.setType("text/plain");

            String subject = "[" + mBug.product + "] " + mBug.bugId + " - " + mBug.summary;
            String body = mBug.toString();

            intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
            intent.putExtra(android.content.Intent.EXTRA_TEXT, body);
            startActivity(intent);
        }
    }

    private void setBookmarkMenu(boolean bookmark) {
        if (mBookmarkMenu == null) {
            return;
        }

        if (bookmark) {
            mBookmarkMenu.setIcon(R.drawable.ic_action_star_on);
        } else {
            mBookmarkMenu.setIcon(R.drawable.ic_action_star_off);
        }
        mBookmarkMenu.setChecked(bookmark);
    }

    public void loadBug(boolean fromCache) {
        if (mRefreshMenu != null) {
            mRefreshMenu.setVisible(false);
        }
        setSupportProgressBarIndeterminateVisibility(true);
        Intent intent = BugIntentService.getIntent(this, mInstance, mBugId, fromCache);
        Messenger messenger = new Messenger(onBugReceivedHandler);
        intent.putExtra(BugIntentService.MESSENGER, messenger);
        startService(intent);
    }

    private void bindBug() {
        if (mRefreshMenu != null) {
            mRefreshMenu.setVisible(true);
        }
        setSupportProgressBarIndeterminateVisibility(false);
        setBugTitle(mBug.summary);

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        BugDetailsFragment bugDetailsFragment = BugDetailsFragment.newInstance(mBug);
        ft.replace(R.id.bugDetails, bugDetailsFragment, BugDetailsFragment.class.getSimpleName());

        BugCommentsFragment bugCommentsFragment = BugCommentsFragment.newInstance(mBug);
        ft.replace(R.id.bugComments, bugCommentsFragment, BugCommentsFragment.class.getSimpleName());

        if (mBug.attachments.size() > 0) {
            BugAttachmentsFragment bugAttachmentsFragment = BugAttachmentsFragment.newInstance(mBug);
            ft.replace(R.id.bugAttachments, bugAttachmentsFragment, BugAttachmentsFragment.class.getSimpleName()); 
        } else {
            mBugAttachments.setVisibility(View.GONE);
        }

        if (mBug.cc.size() > 0) {
            BugCcsFragment bugCcsFragment = BugCcsFragment.newInstance(mBug);
            ft.replace(R.id.bugCcs, bugCcsFragment, BugCcsFragment.class.getSimpleName()); 
        } else {
            mBugCcs.setVisibility(View.GONE);
        }
        ft.commit();
        setBookmarkMenu(mBug.isBookmarked);
    }

    private void setBugTitle(String title) {
        getSupportActionBar().setTitle(String.valueOf(mBugId));
        getSupportActionBar().setSubtitle(title);
    }

    Handler onBugReceivedHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {

            boolean fromCache = msg.getData().getBoolean(BugIntentService.FROM_CACHE);
            if (fromCache) {
                Toast.makeText(BugMultiPaneActivity.this, "Bug from cache", Toast.LENGTH_SHORT).show();
            }
            mBug = (Bug) msg.getData().getSerializable(BugIntentService.BUG);
            bindBug();
            return false;
        }
    });
} public class UIUtils {
    public static boolean isTablet(Context context) {
        return isLarge(context);
    }

    @SuppressLint("InlinedApi")
    public static boolean isLarge(Context context) {
        return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
    }

    @SuppressLint("InlinedApi")
    public static boolean isXLarge(Context context) {
        return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
    }
}
import java.util.ArrayList;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.util.SparseArrayCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockFragment;
import fr.julienvermet.bugdroid.R;
import fr.julienvermet.bugdroid.application.BugDroidApplication;
import fr.julienvermet.bugdroid.model.Account;
import fr.julienvermet.bugdroid.model.Bug;
import fr.julienvermet.bugdroid.model.Instance;
import fr.julienvermet.bugdroid.model.Search;
import fr.julienvermet.bugdroid.model.Search.DashboardSearch;
import fr.julienvermet.bugdroid.provider.BugDroidContent.Bugs;
import fr.julienvermet.bugdroid.service.BugIntentService;
import fr.julienvermet.bugdroid.service.BugsIntentService;
import fr.julienvermet.bugdroid.ui.phone.BugActivity;
import fr.julienvermet.bugdroid.ui.tablet.BugMultiPaneActivity;
import fr.julienvermet.bugdroid.util.UIUtils;
/*
* Copyright (C) 2013 Julien Vermet
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.julienvermet.bugdroid.ui;





public class DashboardFragment extends SherlockFragment {
    
    private ExpandableListView mExpandableListView;
    private SparseArrayCompat<ArrayList<Bug>> mBugs = new SparseArrayCompat<ArrayList<Bug>>();
    private DashboardAdapter mDashboardAdapter;
    private Instance mInstance;
    private int mAccounts_id;

    private enum DashboardElements {
        BOOKMARKS("Bookmarks", 0), 
        THINGS_TO_REVIEW("Things To Review", Search.DASHBOARD_TO_REVIEW), 