com.orange.ocara.ui.activity.ResultAuditActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.orange.ocara.ui.activity.ResultAuditActivity.java

Source

/**
 * Copyright (C) 2015 Orange
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package com.orange.ocara.ui.activity;

import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.ShareCompat;
import android.support.v4.content.FileProvider;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.util.SparseBooleanArray;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.webkit.MimeTypeMap;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;

import com.orange.ocara.model.AccessibilityStats;
import com.orange.ocara.model.Audit;
import com.orange.ocara.model.AuditObject;
import com.orange.ocara.model.Comment;
import com.orange.ocara.model.ModelManager;
import com.orange.ocara.model.RefreshStrategy;
import com.orange.ocara.model.export.AuditExportService;
import com.orange.ocara.model.export.AuditExportService_;
import com.orange.ocara.modelStatic.Handicap;
import com.orange.ocara.modelStatic.Response;
import com.orange.ocara.ui.adapter.AuditObjectsAdapter;
import com.orange.ocara.ui.dialog.AudioPlayerDialog;
import com.orange.ocara.ui.dialog.OcaraDialogBuilder;
import com.orange.ocara.ui.fragment.HandicapPieChartFragment_;
import com.orange.ocara.ui.view.AuditObjectAnomalyView;
import com.orange.ocara.ui.view.AuditObjectAnomalyView_;
import com.orange.ocara.ui.view.AuditObjectsView;
import com.orange.ocara.ui.view.CommentItemView;
import com.orange.ocara.ui.view.CommentItemView_;
import com.orange.ocara.ui.widget.AuditorAutoCompleteView;
import com.orange.ocara.ui.widget.SiteAutoCompleteView;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.RequestCreator;
import com.squareup.picasso.Target;

import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.OnActivityResult;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.Receiver;
import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.ViewById;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.net.URI;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.inject.Inject;

import timber.log.Timber;

@EActivity(resName = "activity_audit_results")
@OptionsMenu(resName = "audit_result")
public class ResultAuditActivity extends BaseActivityManagingAudit {

    /**
     * action for saving a new document.
     */
    private static final int ACTION_SAVE_DOCUMENT = 1;
    /**
     * action for sharing a new document.
     */
    private static final int ACTION_SHARE_DOCUMENT = 2;

    private static final RefreshStrategy STRATEGY = RefreshStrategy.builder()
            .depth(RefreshStrategy.DependencyDepth.RULE_ANSWER).commentsNeeded(true).build();

    @ViewById(resName = "scrollview_container")
    ScrollView scrollContainer;
    @ViewById(resName = "audit_site")
    TextView auditSite;
    @ViewById(resName = "audit_name")
    TextView auditName;
    @ViewById(resName = "audit_user")
    TextView auditUser;
    @ViewById(resName = "audit_type")
    TextView auditType;
    @ViewById(resName = "audit_date")
    TextView auditDate;
    @ViewById(resName = "chart_pager")
    ViewPager auditChartPager;
    @ViewById(resName = "chart_title")
    TextView auditChartTitle;
    @ViewById(resName = "chart_choices")
    GridView auditChartChoices;

    @ViewById(resName = "path_layout")
    ViewGroup pathLayout;
    @ViewById(resName = "audited_objects")
    AuditObjectsView auditObjects;

    @ViewById(resName = "result_resume_table")
    TableLayout resumeTable;

    @ViewById(resName = "audit_comment_layout")
    ViewGroup auditCommentLayout;
    @ViewById(resName = "audit_comment_container")
    ViewGroup auditCommentContainer;

    @ViewById(resName = "anomaly_layout")
    ViewGroup anomalyLayout;

    @Inject
    ModelManager modelManager;
    @Inject
    Picasso picasso;

    private final HandicapAggregator handicapAggregator = new HandicapAggregator();

    private AuditObjectsAdapter auditObjectsAdapter;

    private int exportAction = -1;
    private File exportFile;
    private boolean shouldFinishAfterExport = false;

    @Override
    RefreshStrategy getRefreshStrategy() {
        return STRATEGY;
    }

    @Override
    protected void onResume() {
        super.onResume();
        setTitle(com.orange.ocara.R.string.audit_results_title);
    }

    @Override
    void auditRefreshed() {
        super.auditRefreshed();

        handicapAggregator.update(audit);

        updateDetails(audit);
        updatePath(audit);
        updateResume(audit);
        updateAuditComments(audit);
        updateAnomalies(audit);
    }

    @AfterViews
    void setUpAuditedPath() {
        auditObjectsAdapter = new AuditObjectsAdapter(this);
        auditObjects.setAdapter(auditObjectsAdapter);

        auditObjects.getAuditPath().getViewTreeObserver()
                .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        float pathWitdh = auditObjects.getAuditPath().getWidth();
                        int auditObjectItemWidth = getResources()
                                .getInteger(com.orange.ocara.R.integer.auditObjectItemWidth);
                        int numberAuditedObjects = (int) (pathWitdh / auditObjectItemWidth);
                        float freeSpace = pathWitdh % auditObjectItemWidth;
                        int margin = (int) (freeSpace / (numberAuditedObjects - 1));

                        Timber.v("auditPath setUpAuditedPath " + pathWitdh + "auditObjectItemWitdh "
                                + auditObjectItemWidth + " freeSpace " + freeSpace + " numberAuditedObjects "
                                + numberAuditedObjects + " margin " + margin);

                        auditObjects.getAuditPath().setItemMargin(margin);

                    }
                });
    }

    @AfterViews
    void setUpChart() {
        auditChartPager.setAdapter(chartPagerAdapter);
        auditChartChoices.setAdapter(chartChoicesAdapter);

        auditChartChoices.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);

        checkUniqueChoice(0);

        auditChartPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
                checkUniqueChoice(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });

        auditChartChoices.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                // Case of Total
                if (position == 0) {
                    chartPagerAdapter.setCustomStat(null);
                    auditChartPager.setCurrentItem(0, true);

                    checkUniqueChoice(0);
                    return;
                }

                final SparseBooleanArray checkedItemPositions = auditChartChoices.getCheckedItemPositions();

                List<Integer> selectedItems = new ArrayList<Integer>();
                for (int i = 0; i < checkedItemPositions.size(); i++) {
                    // Item position in adapter
                    int pos = checkedItemPositions.keyAt(i);

                    if (pos != 0 && checkedItemPositions.valueAt(i)) {
                        selectedItems.add(pos);
                    }
                }

                if (selectedItems.isEmpty()) {
                    selectedItems.add(0);
                }

                // One choice checked (other than total)
                if (selectedItems.size() == 1) {
                    int pos = selectedItems.get(0);

                    chartPagerAdapter.setCustomStat(null);
                    auditChartPager.setCurrentItem(pos, true);
                } else {
                    // Multiple choices, build custom stats
                    AccessibilityStats custom = new AccessibilityStats();

                    for (int i : selectedItems) {
                        custom.plus(handicapAggregator.getStat(i - 1));
                    }

                    chartPagerAdapter.setCustomStat(custom);
                    auditChartPager.setCurrentItem(0, true);

                    auditChartChoices.clearChoices();
                    for (int i : selectedItems) {
                        auditChartChoices.setItemChecked(i, true);
                    }
                }
            }
        });
    }

    protected void checkUniqueChoice(int position) {
        auditChartChoices.clearChoices();
        auditChartChoices.setItemChecked(position, true);

        auditChartTitle.setText(chartPagerAdapter.getPageTitle(position));
    }

    private void updateDetails(Audit audit) {
        final DateFormat dateFormat = new SimpleDateFormat("EEEE d MMMM yyyy", Locale.FRANCE);

        auditName.setText(audit.getName());
        auditType.setText(audit.getRuleSet().getType());
        auditDate.setText(dateFormat.format(audit.getDate()));
        auditUser.setText(AuditorAutoCompleteView.format(audit.getAuthor()));
        auditSite.setText(SiteAutoCompleteView.format(audit.getSite(), false));

        updateChart(audit);
    }

    private void updateChart(Audit audit) {
        chartPagerAdapter.notifyDataSetChanged();
        chartChoicesAdapter.notifyDataSetChanged();
        auditChartTitle.setText(chartPagerAdapter.getPageTitle(auditChartPager.getCurrentItem()));
    }

    private void updatePath(Audit audit) {
        auditObjectsAdapter.update(audit.getObjects());
    }

    private void updateResume(Audit audit) {
        resumeTable.removeViews(1, resumeTable.getChildCount() - 1);

        final int doubtColumnIndex = 4;

        if (Audit.Level.BEGINNER.equals(audit.getLevel())) {
            resumeTable.setColumnCollapsed(doubtColumnIndex, false);
            resumeTable.setColumnStretchable(doubtColumnIndex, true);
        } else {
            resumeTable.setColumnCollapsed(doubtColumnIndex, true);

            // Don' set it. It seems to bug
            //resumeTable.setColumnStretchable(doubtColumnIndex, false);
        }

        int rowIndex = 0;

        for (String handicapId : handicapAggregator.getHandicapIds()) {
            // Inflate a new row and fill it
            final AccessibilityStats stats = handicapAggregator.getStat(handicapId);
            final Handicap handicap = handicapAggregator.getHandicap(handicapId);
            resumeTable.addView(buildResumeRow(handicap.getIcon(), handicap.getName(), stats, rowIndex++));
        }

        // Add total row
        resumeTable.addView(buildResumeRow(com.orange.ocara.R.drawable.ic_handicap_all,
                getString(com.orange.ocara.R.string.audit_results_table_all_handicap),
                handicapAggregator.getTotalStat(), rowIndex++));
    }

    private View buildResumeRow(int handicapIconResId, String handicapName, AccessibilityStats stats,
            int rowIndex) {
        return buildResumeRow(picasso.load(handicapIconResId), handicapName, stats, rowIndex);
    }

    private View buildResumeRow(URI handicapIcon, String handicapName, AccessibilityStats stats, int rowIndex) {
        return buildResumeRow(picasso.load(Uri.parse(handicapIcon.toString())), handicapName, stats, rowIndex);
    }

    private View buildResumeRow(RequestCreator handicapIconRequest, String handicapName, AccessibilityStats stats,
            int rowIndex) {

        TableRow row = (TableRow) getLayoutInflater().inflate(com.orange.ocara.R.layout.result_resume_item, null);

        TextView noImpact = (TextView) row.findViewById(com.orange.ocara.R.id.resume_handicap_no_impact);
        TextView annoying = (TextView) row.findViewById(com.orange.ocara.R.id.resume_handicap_annoying);
        TextView blocking = (TextView) row.findViewById(com.orange.ocara.R.id.resume_handicap_blocking);
        TextView doubt = (TextView) row.findViewById(com.orange.ocara.R.id.resume_handicap_doubt);

        noImpact.setText(Integer.toString(stats.getCounter(AccessibilityStats.Type.ACCESSIBLE)));
        annoying.setText(Integer.toString(stats.getCounter(AccessibilityStats.Type.ANNOYING)));
        blocking.setText(Integer.toString(stats.getCounter(AccessibilityStats.Type.BLOCKING)));
        doubt.setText(Integer.toString(stats.getCounter(AccessibilityStats.Type.DOUBT)));

        final TextView handicapType = (TextView) row.findViewById(com.orange.ocara.R.id.resume_handicap_type);
        handicapType.setText(handicapName);
        if (handicapIconRequest != null) {

            Target target = new Target() {
                @Override
                public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                    handicapType.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(getResources(), bitmap),
                            null, null, null);
                }

                @Override
                public void onBitmapFailed(Drawable errorDrawable) {
                }

                @Override
                public void onPrepareLoad(Drawable placeHolderDrawable) {
                }
            };
            final int maxSize = getResources()
                    .getDimensionPixelSize(com.orange.ocara.R.dimen.resultResumeHandicapIconSize);
            handicapIconRequest.placeholder(android.R.color.black).resize(maxSize, maxSize).into(target);
        }

        int backGroundColor = (rowIndex % 2 == 0) ? com.orange.ocara.R.color.resultTableColorEven
                : com.orange.ocara.R.color.resultTableColorOdd;
        row.setBackgroundResource(backGroundColor);

        return row;
    }

    private void updateAnomalies(Audit audit) {

        ViewGroup anomalyContainerView = (ViewGroup) findViewById(com.orange.ocara.R.id.anomaly_container);
        anomalyContainerView.removeAllViews();

        for (AuditObject auditObject : audit.getObjects()) {
            final List<Comment> comments = auditObject.getComments();
            final String auditObjectName = auditObject.getName();
            final Uri auditObjectIcon = Uri.parse(auditObject.getObjectDescription().getIcon().toString());
            if (auditObject.getResponse().equals(Response.NOK) || !auditObject.getComments().isEmpty()) {
                final AuditObjectAnomalyView auditObjectAnomalyView = AuditObjectAnomalyView_
                        .build(getApplicationContext());
                auditObjectAnomalyView.setOnGroupClickListener(new AuditObjectAnomalyView.OnGroupClickListener() {
                    @Override
                    public void onExpand(AuditObjectAnomalyView view) {
                        scrollTo(view);
                    }
                });

                auditObjectAnomalyView
                        .setOnCommentClickListener(new AuditObjectAnomalyView.OnCommentClickListener() {
                            @Override
                            public void onCommentClick(Comment comment, View CommentView) {
                                ResultAuditActivity.this.onCommentClicked(auditObjectIcon, auditObjectName,
                                        comments, comment, CommentView);
                            }
                        });
                auditObjectAnomalyView.bind(auditObject);
                anomalyContainerView.addView(auditObjectAnomalyView);
            }
        }

        anomalyLayout.setVisibility(anomalyContainerView.getChildCount() > 0 ? View.VISIBLE : View.GONE);
    }

    private void scrollTo(final View view) {

        final Rect scrollBounds = new Rect();
        scrollContainer.getHitRect(scrollBounds);

        scrollBounds.set(scrollBounds.left, scrollBounds.top, scrollBounds.right, scrollBounds.bottom - 200);

        view.getLocalVisibleRect(scrollBounds);

        if (scrollBounds.height() >= view.getHeight()) {
            return; // no scroll need
        }

        // Determine where to set the scroll-to to by measuring the distance from the top of the scroll view
        // to the control to focus on by summing the "top" position of each view in the hierarchy.
        int yDistanceToControlsView = 0;
        View parentView = (View) view.getParent();
        while (true) {
            if (parentView.equals(scrollContainer)) {
                break;
            }
            yDistanceToControlsView += parentView.getTop();
            parentView = (View) parentView.getParent();
        }

        // Compute the final position value for the top and bottom of the control in the scroll view.
        final int topInScrollView = yDistanceToControlsView + view.getTop();
        final int bottomInScrollView = yDistanceToControlsView + view.getBottom();

        // Post the scroll action to happen on the scrollView with the UI thread.
        scrollContainer.post(new Runnable() {
            @Override
            public void run() {
                int height = view.getHeight();
                scrollContainer.smoothScrollTo(0, topInScrollView); //((topInScrollView + bottomInScrollView) / 2) - height);
                view.requestFocus();
            }
        });
    }

    private void updateAuditComments(Audit audit) {

        if (audit.getComments().isEmpty()) {
            auditCommentLayout.setVisibility(View.GONE);
            return;
        }

        auditCommentLayout.setVisibility(View.VISIBLE);
        auditCommentContainer.removeAllViews();
        final List<Comment> comments = audit.getComments();
        final String auditName = audit.getName();
        for (final Comment comment : audit.getComments()) {
            CommentItemView commentItemView = CommentItemView_.build(this);
            commentItemView.bind(comment);
            auditCommentContainer.addView(commentItemView);
            commentItemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onCommentClicked(null, auditName, comments, comment, v);
                }
            });
        }

    }

    @Click(resName = "previous_chart_button")
    void onPreviousChartClicked() {
        int currentItem = auditChartPager.getCurrentItem();
        if (currentItem > 0) {
            currentItem--;
        }

        auditChartPager.setCurrentItem(currentItem, true);
    }

    @Click(resName = "next_chart_button")
    void onNextChartClicked() {
        int currentItem = auditChartPager.getCurrentItem();
        if (currentItem < auditChartPager.getAdapter().getCount() - 1) {
            currentItem++;
        }

        auditChartPager.setCurrentItem(currentItem, true);
    }

    private void onCommentClicked(Uri icon, String name, List<Comment> comments, Comment comment,
            View commentView) {

        switch (comment.getType()) {
        case AUDIO:
            startAudioPlayer(comment.getAttachment(), commentView);
            break;

        case TEXT:
            showComment(icon, name, comments, comment);
            break;

        case PHOTO:
            showComment(icon, name, comments, comment);
            break;

        default:
            break;
        }
    }

    private void showComment(Uri icon, String name, List<Comment> commentList, Comment comment) {
        int selectedIndex = 0;
        List<Comment> commentPhotoAndText = extractCommentPhotoAndText(commentList);
        int nbCommentPhotoAndText = commentPhotoAndText.size();
        String[] titles = new String[nbCommentPhotoAndText];
        String[] comments = new String[nbCommentPhotoAndText];
        String[] images = new String[nbCommentPhotoAndText];

        Timber.v("nbCommentPhotoAndText " + nbCommentPhotoAndText);
        for (int i = 0; i < nbCommentPhotoAndText; i++) {
            if (commentPhotoAndText.get(i).equals(comment)) {
                selectedIndex = i;
            }

            if (icon != null) {
                titles[i] = getString(com.orange.ocara.R.string.illustration_activity_comment_auditobject_title,
                        i + 1, name);
            } else {
                titles[i] = getString(com.orange.ocara.R.string.illustration_activity_comment_audit_title, i + 1,
                        name);
            }

            comments[i] = commentPhotoAndText.get(i).getContent();

            if (commentPhotoAndText.get(i).getType().equals(Comment.Type.PHOTO)) {
                images[i] = commentPhotoAndText.get(i).getAttachment();
            }

        }

        IllustrationsActivity_.intent(ResultAuditActivity.this).selectedIndex(selectedIndex).icon(icon)
                .titles(titles).comments(comments).images(images).start();

    }

    private List<Comment> extractCommentPhotoAndText(List<Comment> comments) {
        List<Comment> ret = new ArrayList<Comment>();

        for (Comment comment : comments) {
            if (comment.getType().equals(Comment.Type.PHOTO) || comment.getType().equals(Comment.Type.TEXT)) {
                ret.add(comment);
            }
        }
        return (ret);
    }

    private void startAudioPlayer(String attachment, View commentView) {
        FragmentManager fm = getSupportFragmentManager();

        int[] location = new int[2];
        commentView.getLocationOnScreen(location);

        AudioPlayerDialog audioPlayerDialog = AudioPlayerDialog.newInstance(attachment, -1, location[1]);
        audioPlayerDialog.show(fm, "fragment_edit_name");
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        boolean ret = super.onPrepareOptionsMenu(menu);
        menu.findItem(com.orange.ocara.R.id.action_lock)
                .setVisible(!Audit.Status.TERMINATED.equals(audit.getStatus()));
        return ret;
    }

    @OptionsItem(resName = "action_lock")
    void onLockAuditMenuItem() {

        AlertDialog dialog = new OcaraDialogBuilder(this)
                .setTitle(com.orange.ocara.R.string.audit_results_lock_dialog_title) // title
                .setMessage(com.orange.ocara.R.string.audit_results_lock_dialog_message) // message
                .setNeutralButton(com.orange.ocara.R.string.action_cancel, null).setPositiveButton(
                        com.orange.ocara.R.string.action_close_audit, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                setLoading(true);
                                lockAudit();
                            }
                        })
                .create();

        dialog.show();
    }

    @Background
    void lockAudit() {
        audit.setStatus(Audit.Status.TERMINATED);
        modelManager.updateAudit(audit);

        auditLocked();
    }

    @UiThread(propagation = UiThread.Propagation.REUSE)
    void auditLocked() {
        setLoading(false);
        supportInvalidateOptionsMenu();

        shouldFinishAfterExport = true;

        exportAction = -1;
        exportFile = null;

        AlertDialog dialog = buildExportDialog().create();
        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialogInterface) {
                if (exportAction < 1) {
                    onExportAuditDocxFinished();
                }
            }
        });
        dialog.show();
    }

    @OptionsItem(resName = "action_export_audit_docx")
    void onExportAuditToWordMenuItem() {

        exportAction = -1;
        exportFile = null;

        AlertDialog dialog = buildExportDialog().create();
        dialog.show();
    }

    private AlertDialog.Builder buildExportDialog() {
        int message;
        if (audit.getStatus().equals(Audit.Status.TERMINATED)) {
            message = com.orange.ocara.R.string.export_docx_message_closed_audit;
        } else {
            message = com.orange.ocara.R.string.export_docx_message;
        }

        return new OcaraDialogBuilder(this).setTitle(com.orange.ocara.R.string.export_docx_title) // title
                .setMessage(message) // message
                .setNeutralButton(com.orange.ocara.R.string.action_cancel, null)
                .setNegativeButton(com.orange.ocara.R.string.action_share, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        exportAction = ACTION_SHARE_DOCUMENT;

                        setLoading(true);
                        AuditExportService_.intent(ResultAuditActivity.this).toDocx(audit.getId()).start();
                    }
                }).setPositiveButton(com.orange.ocara.R.string.action_save, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        exportAction = ACTION_SAVE_DOCUMENT;

                        setLoading(true);
                        AuditExportService_.intent(ResultAuditActivity.this).toDocx(audit.getId()).start();
                    }
                });
    }

    @Receiver(actions = AuditExportService.EXPORT_SUCCESS, local = true, registerAt = Receiver.RegisterAt.OnResumeOnPause)
    @UiThread(propagation = UiThread.Propagation.REUSE)
    void onExportAuditDone(@Receiver.Extra String path) {
        setLoading(false);

        exportFile = new File(path);

        switch (exportAction) {
        case ACTION_SHARE_DOCUMENT: {
            shareDocument(path);
            break;
        }

        case ACTION_SAVE_DOCUMENT: {
            saveDocument(path);
            break;
        }
        }
    }

    @Receiver(actions = AuditExportService.EXPORT_FAILED, local = true, registerAt = Receiver.RegisterAt.OnResumeOnPause)
    @UiThread(propagation = UiThread.Propagation.REUSE)
    void onExportAuditDocxFailed() {
        setLoading(false);

        onExportAuditDocxFinished();
    }

    @UiThread(propagation = UiThread.Propagation.REUSE)
    void onExportAuditDocxFinished() {

        if (shouldFinishAfterExport) {
            ListAuditActivity_.intent(this).start();
            finish();
        }
    }

    /**
     * To share a document by its path.<br/>
     * This will send a share intent.
     *
     * @param path the file path to share
     */
    private void shareDocument(String path) {
        // create an intent, so the user can choose which application he/she wants to use to share this file
        final Intent intent = ShareCompat.IntentBuilder.from(this)
                .setType(MimeTypeMap.getSingleton()
                        .getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(path)))
                .setStream(FileProvider.getUriForFile(this, "com.orange.ocara", exportFile))
                .setChooserTitle("How do you want to share?").createChooserIntent()
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        startActivityForResult(intent, ACTION_SHARE_DOCUMENT);
    }

    @OnActivityResult(ACTION_SHARE_DOCUMENT)
    void onDocumentShared(int resultCode, Intent data) {
        onExportAuditDocxFinished();
    }

    /**
     * To savge a document by its path.<br/>
     *
     * @param path the file path to share
     */
    private void saveDocument(String path) {
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType(
                MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(path)));
        startActivityForResult(intent, ACTION_SAVE_DOCUMENT);
    }

    @OnActivityResult(ACTION_SAVE_DOCUMENT)
    void onDocumentSaved(int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            Uri uri = data.getData();
            copyFile(exportFile, uri);
        } else {
            onExportAuditDocxFinished();
        }
    }

    @Background
    void copyFile(File file, Uri uri) {
        try {
            FileUtils.copyFile(file, getContentResolver().openOutputStream(uri, "w"));
            fileCopied(file, uri);
        } catch (Exception e) {
            Timber.e(e, "Failed to copy file");
        }
    }

    @UiThread(propagation = UiThread.Propagation.REUSE)
    void fileCopied(File file, Uri uri) {
        onExportAuditDocxFinished();
    }

    private final ChartPagerAdapter chartPagerAdapter = new ChartPagerAdapter();

    /**
     * Categories pager adapter.
     */
    private class ChartPagerAdapter extends FragmentStatePagerAdapter {

        private AccessibilityStats customStat = null;

        public ChartPagerAdapter() {
            super(getSupportFragmentManager());
        }

        // Returns total number of pages
        @Override
        public int getCount() {
            return customStat != null ? 1 : handicapAggregator.getHandicapIds().size() + 1;
        }

        // Returns the fragment to display for that page
        @Override
        public Fragment getItem(int position) {
            AccessibilityStats stat = new AccessibilityStats();

            if (customStat != null) {
                stat.plus(customStat);
            } else {
                if (position == 0) {
                    stat.plus(handicapAggregator.getTotalStat());
                } else {
                    stat.plus(handicapAggregator.getStat(position - 1));
                }
            }

            HandicapPieChartFragment_.FragmentBuilder_ builder = HandicapPieChartFragment_.builder()
                    .ok(stat.getCounter(AccessibilityStats.Type.ACCESSIBLE))
                    .nok(stat.getCounter(AccessibilityStats.Type.ANNOYING)
                            + stat.getCounter(AccessibilityStats.Type.BLOCKING))
                    .doute(stat.getCounter(AccessibilityStats.Type.DOUBT))
                    .noAnswer(stat.getCounter(AccessibilityStats.Type.NOANSWER));

            if (Audit.Level.BEGINNER.equals(audit.getLevel())) {
                builder = builder.doute(stat.getCounter(AccessibilityStats.Type.DOUBT));
            }

            return builder.build();
        }

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

        @Override
        public CharSequence getPageTitle(int position) {

            if (customStat != null) {
                return getString(com.orange.ocara.R.string.audit_results_custom_handicap);
            } else {
                if (position == 0) {
                    return getString(com.orange.ocara.R.string.audit_results_table_all_handicap);
                }

                final Handicap handicap = handicapAggregator.getHandicap(position - 1);
                return handicap.getName();
            }
        }

        public void setCustomStat(AccessibilityStats custom) {
            this.customStat = custom;

            notifyDataSetChanged();
        }
    }

    private final BaseAdapter chartChoicesAdapter = new BaseAdapter() {
        @Override
        public int getCount() {
            return handicapAggregator.getHandicapIds().size() + 1;
        }

        @Override
        public Object getItem(int position) {
            return position;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            if (convertView == null) {
                convertView = getLayoutInflater().inflate(com.orange.ocara.R.layout.handicap_type_choice, null,
                        false);
            }

            ImageView icon = (ImageView) convertView.findViewById(com.orange.ocara.R.id.handicap_type_icon);
            TextView title = (TextView) convertView.findViewById(com.orange.ocara.R.id.handicap_type_title);

            if (position == 0) {
                icon.setImageResource(com.orange.ocara.R.drawable.ic_handicap_all);
                title.setText(com.orange.ocara.R.string.audit_results_table_all_handicap);
            } else {
                Handicap handicap = handicapAggregator.getHandicap(position - 1);

                picasso.load(Uri.parse(handicap.getIcon().toString())).placeholder(android.R.color.black)
                        .into(icon);
                title.setText(handicap.getName());
            }

            return convertView;
        }
    };

    private static class HandicapAggregator {
        private final List<String> handicapIds = new ArrayList<>();

        private final Map<String, AccessibilityStats> statsById = new LinkedHashMap<String, AccessibilityStats>();
        private final Map<String, Handicap> handicapById = new LinkedHashMap<String, Handicap>();

        private final AccessibilityStats totalStat = new AccessibilityStats();

        void update(Audit audit) {
            handicapIds.clear();

            statsById.clear();
            handicapById.clear();

            statsById.putAll(audit.computeStatsByHandicap());
            handicapById.putAll(audit.getRuleSet().getHandicapsById());
            handicapIds.addAll(handicapById.keySet());

            computeTotal();
        }

        private void computeTotal() {
            totalStat.clear();

            for (Map.Entry<String, Handicap> entry : handicapById.entrySet()) {
                AccessibilityStats stat = statsById.get(entry.getKey());

                if (stat == null) {
                    stat = new AccessibilityStats();
                    statsById.put(entry.getKey(), stat);
                }

                totalStat.plus(stat);
            }
        }

        List<String> getHandicapIds() {
            return handicapIds;
        }

        Handicap getHandicap(int position) {
            return getHandicap(handicapIds.get(position));
        }

        Handicap getHandicap(String handicapId) {
            return handicapById.get(handicapId);
        }

        AccessibilityStats getStat(int position) {
            return getStat(handicapIds.get(position));
        }

        AccessibilityStats getStat(String handicapId) {
            return statsById.get(handicapId);
        }

        AccessibilityStats getTotalStat() {
            return totalStat;
        }
    }
}