biz.bokhorst.xprivacy.ActivityMain.java Source code

Java tutorial

Introduction

Here is the source code for biz.bokhorst.xprivacy.ActivityMain.java

Source

package biz.bokhorst.xprivacy;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Process;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.BaseExpandableListAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RadioGroup;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.SearchView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

public class ActivityMain extends ActivityBase implements OnItemSelectedListener {
    private Spinner spRestriction = null;
    private AppListAdapter mAppAdapter = null;

    private String searchQuery = "";
    private int mSortMode;
    private boolean mSortInvert;
    private int mProgressWidth = 0;
    private int mProgress = 0;

    private Handler mProHandler = new Handler();

    private static final int SORT_BY_NAME = 0;
    private static final int SORT_BY_UID = 1;
    private static final int SORT_BY_INSTALL_TIME = 2;
    private static final int SORT_BY_UPDATE_TIME = 3;
    private static final int SORT_BY_MODIFY_TIME = 4;
    private static final int SORT_BY_STATE = 5;

    private static final int ACTIVITY_LICENSE = 0;
    private static final int LICENSED = 0x0100;
    private static final int NOT_LICENSED = 0x0231;
    private static final int RETRY = 0x0123;

    private static final int ERROR_CONTACTING_SERVER = 0x101;
    private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
    private static final int ERROR_NON_MATCHING_UID = 0x103;

    public static final String cAction = "Action";
    public static final int cActionRefresh = 1;

    public static final Uri cProUri = Uri.parse("http://www.xprivacy.eu/");

    private static ExecutorService mExecutor = Executors
            .newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new PriorityThreadFactory());

    private static class PriorityThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

    private Comparator<ApplicationInfoEx> mSorter = new Comparator<ApplicationInfoEx>() {
        @Override
        public int compare(ApplicationInfoEx appInfo0, ApplicationInfoEx appInfo1) {
            int sortOrder = mSortInvert ? -1 : 1;
            switch (mSortMode) {
            case SORT_BY_NAME:
                return sortOrder * appInfo0.compareTo(appInfo1);
            case SORT_BY_UID:
                // Default lowest first
                return sortOrder * (appInfo0.getUid() - appInfo1.getUid());
            case SORT_BY_INSTALL_TIME:
                // Default newest first
                Long iTime0 = appInfo0.getInstallTime(ActivityMain.this);
                Long iTime1 = appInfo1.getInstallTime(ActivityMain.this);
                return sortOrder * iTime1.compareTo(iTime0);
            case SORT_BY_UPDATE_TIME:
                // Default newest first
                Long uTime0 = appInfo0.getUpdateTime(ActivityMain.this);
                Long uTime1 = appInfo1.getUpdateTime(ActivityMain.this);
                return sortOrder * uTime1.compareTo(uTime0);
            case SORT_BY_MODIFY_TIME:
                // Default newest first
                Long mTime0 = appInfo0.getModificationTime(ActivityMain.this);
                Long mTime1 = appInfo1.getModificationTime(ActivityMain.this);
                return sortOrder * mTime1.compareTo(mTime0);
            case SORT_BY_STATE:
                Integer state0 = appInfo0.getState(ActivityMain.this);
                Integer state1 = appInfo1.getState(ActivityMain.this);
                if (state0.compareTo(state1) == 0)
                    return sortOrder * appInfo0.compareTo(appInfo1);
                else
                    return sortOrder * state0.compareTo(state1);
            }
            return 0;
        }
    };

    private boolean mPackageChangeReceiverRegistered = false;

    private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            ActivityMain.this.recreate();
        }
    };

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

        final int userId = Util.getUserId(Process.myUid());

        // Check privacy service client
        if (!PrivacyService.checkClient())
            return;

        // Import license file
        if (Intent.ACTION_VIEW.equals(getIntent().getAction()))
            if (Util.importProLicense(new File(getIntent().getData().getPath())) != null)
                Toast.makeText(this, getString(R.string.menu_pro), Toast.LENGTH_LONG).show();

        // Set layout
        setContentView(R.layout.mainlist);
        setSupportActionBar((Toolbar) findViewById(R.id.widgetToolbar));
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

        // Set sub title
        if (Util.hasProLicense(this) != null)
            getSupportActionBar().setSubtitle(R.string.menu_pro);

        // Annotate
        Meta.annotate(this.getResources());

        // Get localized restriction name
        List<String> listRestrictionName = new ArrayList<String>(
                PrivacyManager.getRestrictions(this).navigableKeySet());
        listRestrictionName.add(0, getString(R.string.menu_all));

        // Build spinner adapter
        SpinnerAdapter spAdapter = new SpinnerAdapter(this, android.R.layout.simple_spinner_item);
        spAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spAdapter.addAll(listRestrictionName);

        // Handle info
        ImageView imgInfo = (ImageView) findViewById(R.id.imgInfo);
        imgInfo.setOnClickListener(new View.OnClickListener() {
            @SuppressLint("SetJavaScriptEnabled")
            @Override
            public void onClick(View view) {
                int position = spRestriction.getSelectedItemPosition();
                if (position != AdapterView.INVALID_POSITION) {
                    String query = (position == 0 ? "restrictions"
                            : (String) PrivacyManager.getRestrictions(ActivityMain.this).values().toArray()[position
                                    - 1]);

                    WebView webview = new WebView(ActivityMain.this);
                    webview.getSettings().setUserAgentString("Mozilla/5.0");
                    // needed for hashtag
                    webview.getSettings().setJavaScriptEnabled(true);
                    webview.loadUrl("https://github.com/M66B/XPrivacy#" + query);

                    AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(ActivityMain.this);
                    alertDialogBuilder.setTitle((String) spRestriction.getSelectedItem());
                    alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
                    alertDialogBuilder.setView(webview);
                    alertDialogBuilder.setCancelable(true);
                    AlertDialog alertDialog = alertDialogBuilder.create();
                    alertDialog.show();
                }
            }
        });

        // Setup category spinner
        spRestriction = (Spinner) findViewById(R.id.spRestriction);
        spRestriction.setAdapter(spAdapter);
        spRestriction.setOnItemSelectedListener(this);
        int pos = getSelectedCategory(userId);
        spRestriction.setSelection(pos);

        // Setup sort
        mSortMode = Integer.parseInt(PrivacyManager.getSetting(userId, PrivacyManager.cSettingSortMode, "0"));
        mSortInvert = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingSortInverted, false);

        // Start task to get app list
        AppListTask appListTask = new AppListTask();
        appListTask.executeOnExecutor(mExecutor, (Object) null);

        // Check environment
        Requirements.check(this);

        // Licensing
        checkLicense();

        // Listen for package add/remove
        IntentFilter iff = new IntentFilter();
        iff.addAction(Intent.ACTION_PACKAGE_ADDED);
        iff.addAction(Intent.ACTION_PACKAGE_REMOVED);
        iff.addDataScheme("package");
        registerReceiver(mPackageChangeReceiver, iff);
        mPackageChangeReceiverRegistered = true;

        boolean showChangelog = true;

        // First run
        if (PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFirstRun, true)) {
            showChangelog = false;
            optionAbout();
        }

        // Tutorial
        if (!PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingTutorialMain, false)) {
            showChangelog = false;
            ((ScrollView) findViewById(R.id.svTutorialHeader)).setVisibility(View.VISIBLE);
            ((ScrollView) findViewById(R.id.svTutorialDetails)).setVisibility(View.VISIBLE);
        }
        View.OnClickListener listener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ViewParent parent = view.getParent();
                while (!parent.getClass().equals(ScrollView.class))
                    parent = parent.getParent();
                ((View) parent).setVisibility(View.GONE);
                PrivacyManager.setSetting(userId, PrivacyManager.cSettingTutorialMain, Boolean.TRUE.toString());
            }
        };
        ((Button) findViewById(R.id.btnTutorialHeader)).setOnClickListener(listener);
        ((Button) findViewById(R.id.btnTutorialDetails)).setOnClickListener(listener);

        // Legacy
        if (!PrivacyManager.cVersion3) {
            long now = new Date().getTime();
            String legacy = PrivacyManager.getSetting(userId, PrivacyManager.cSettingLegacy, null);
            if (legacy == null || now > Long.parseLong(legacy) + 7 * 24 * 60 * 60 * 1000L) {
                showChangelog = false;
                PrivacyManager.setSetting(userId, PrivacyManager.cSettingLegacy, Long.toString(now));

                AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
                alertDialogBuilder.setTitle(R.string.app_name);
                alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
                alertDialogBuilder.setMessage(R.string.title_update_legacy);
                alertDialogBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Util.viewUri(ActivityMain.this,
                                Uri.parse("https://github.com/M66B/XPrivacy/blob/master/CHANGELOG.md#xprivacy3"));
                    }
                });
                alertDialogBuilder.setNegativeButton(android.R.string.cancel,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // Do nothing
                            }
                        });

                // Show dialog
                AlertDialog alertDialog = alertDialogBuilder.create();
                alertDialog.show();
            }
        }

        // Show changelog
        if (showChangelog) {
            String sVersion = PrivacyManager.getSetting(userId, PrivacyManager.cSettingChangelog, null);
            Version changelogVersion = new Version(sVersion == null ? "0.0" : sVersion);
            Version currentVersion = new Version(Util.getSelfVersionName(this));
            if (sVersion == null || changelogVersion.compareTo(currentVersion) < 0)
                optionChangelog();
        }
    }

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

        // Update category selection
        if (spRestriction != null) {
            int userId = Util.getUserId(Process.myUid());
            int pos = getSelectedCategory(userId);
            spRestriction.setSelection(pos);
        }

        // Update list
        if (mAppAdapter != null)
            mAppAdapter.notifyDataSetChanged();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        // Handle clear XPrivacy data (needs UI refresh)
        Bundle extras = intent.getExtras();
        if (extras != null && extras.containsKey(cAction) && extras.getInt(cAction) == cActionRefresh)
            recreate();
        else {
            // Refresh application list
            if (mAppAdapter != null)
                mAppAdapter.notifyDataSetChanged();

            // Import pro license
            if (Intent.ACTION_VIEW.equals(intent.getAction()))
                Util.importProLicense(new File(intent.getData().getPath()));
        }
    }

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

        if (mPackageChangeReceiverRegistered) {
            unregisterReceiver(mPackageChangeReceiver);
            mPackageChangeReceiverRegistered = false;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent dataIntent) {
        super.onActivityResult(requestCode, resultCode, dataIntent);

        if (requestCode == ACTIVITY_LICENSE) {
            // License check
            if (dataIntent != null) {
                int code = dataIntent.getIntExtra("Code", -1);
                int reason = dataIntent.getIntExtra("Reason", -1);

                String sReason;
                if (reason == LICENSED)
                    sReason = "LICENSED";
                else if (reason == NOT_LICENSED)
                    sReason = "NOT_LICENSED";
                else if (reason == RETRY)
                    sReason = "RETRY";
                else if (reason == ERROR_CONTACTING_SERVER)
                    sReason = "ERROR_CONTACTING_SERVER";
                else if (reason == ERROR_INVALID_PACKAGE_NAME)
                    sReason = "ERROR_INVALID_PACKAGE_NAME";
                else if (reason == ERROR_NON_MATCHING_UID)
                    sReason = "ERROR_NON_MATCHING_UID";
                else
                    sReason = Integer.toString(reason);

                Util.log(null, Log.WARN, "Licensing: code=" + code + " reason=" + sReason);

                if (code > 0) {
                    Util.setPro(true);
                    invalidateOptionsMenu();
                    Toast.makeText(this, getString(R.string.menu_pro), Toast.LENGTH_LONG).show();
                } else if (reason == RETRY) {
                    Util.setPro(false);
                    mProHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            checkLicense();
                        }
                    }, 30 * 1000);
                }
            }
        }
    }

    // Filtering

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        selectRestriction(pos);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        selectRestriction(0);
    }

    private void selectRestriction(int pos) {
        if (mAppAdapter != null) {
            int userId = Util.getUserId(Process.myUid());
            String restrictionName = (pos == 0 ? null
                    : (String) PrivacyManager.getRestrictions(this).values().toArray()[pos - 1]);
            mAppAdapter.setRestrictionName(restrictionName);
            PrivacyManager.setSetting(userId, PrivacyManager.cSettingSelectedCategory, restrictionName);
            applyFilter();
        }
    }

    private void applyFilter() {
        if (mAppAdapter != null) {
            ProgressBar pbFilter = (ProgressBar) findViewById(R.id.pbFilter);
            TextView tvStats = (TextView) findViewById(R.id.tvStats);
            TextView tvState = (TextView) findViewById(R.id.tvState);

            // Get settings
            int userId = Util.getUserId(Process.myUid());
            boolean fUsed = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFUsed, false);
            boolean fInternet = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFInternet, false);
            boolean fRestriction = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFRestriction,
                    false);
            boolean fRestrictionNot = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFRestrictionNot,
                    false);
            boolean fPermission = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFPermission, true);
            boolean fOnDemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFOnDemand, false);
            boolean fOnDemandNot = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFOnDemandNot,
                    false);
            boolean fUser = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFUser, true);
            boolean fSystem = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFSystem, false);

            String filter = String.format("%s\n%b\n%b\n%b\n%b\n%b\n%b\n%b\n%b\n%b", searchQuery, fUsed, fInternet,
                    fRestriction, fRestrictionNot, fPermission, fOnDemand, fOnDemandNot, fUser, fSystem);
            pbFilter.setVisibility(ProgressBar.VISIBLE);
            tvStats.setVisibility(TextView.GONE);

            // Adjust progress state width
            RelativeLayout.LayoutParams tvStateLayout = (RelativeLayout.LayoutParams) tvState.getLayoutParams();
            tvStateLayout.addRule(RelativeLayout.LEFT_OF, R.id.pbFilter);

            mAppAdapter.getFilter().filter(filter);
        }
    }

    private void applySort() {
        if (mAppAdapter != null)
            mAppAdapter.sort();
    }

    // Options

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        if (inflater != null && PrivacyService.checkClient()) {
            // Inflate menu
            inflater.inflate(R.menu.main, menu);

            // Searchable
            SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_search));
            if (searchView != null) {
                searchView.setIconifiedByDefault(false);

                searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                    @Override
                    public boolean onQueryTextChange(String newText) {
                        searchQuery = newText;
                        applyFilter();
                        return true;
                    }

                    @Override
                    public boolean onQueryTextSubmit(String query) {
                        searchQuery = query;
                        applyFilter();
                        return true;
                    }
                });
                searchView.setOnCloseListener(new SearchView.OnCloseListener() {
                    @Override
                    public boolean onClose() {
                        searchQuery = "";
                        applyFilter();
                        return true;
                    }
                });
            }

            return true;
        } else
            return false;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        int userId = Util.getUserId(Process.myUid());
        boolean mounted = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
        boolean updates = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingUpdates, false);

        menu.findItem(R.id.menu_export).setEnabled(mounted);
        menu.findItem(R.id.menu_import).setEnabled(mounted);

        menu.findItem(R.id.menu_submit).setEnabled(Util.hasValidFingerPrint(this));

        menu.findItem(R.id.menu_pro).setVisible(!Util.isProEnabled() && Util.hasProLicense(this) == null);

        menu.findItem(R.id.menu_dump).setVisible(Util.isDebuggable(this));

        menu.findItem(R.id.menu_update).setVisible(updates);
        menu.findItem(R.id.menu_update).setEnabled(mounted);

        // Update filter count

        // Get settings
        boolean fUsed = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFUsed, false);
        boolean fInternet = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFInternet, false);
        boolean fRestriction = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFRestriction, false);
        boolean fPermission = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFPermission, true);
        boolean fOnDemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFOnDemand, false);
        boolean fUser = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFUser, true);
        boolean fSystem = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFSystem, false);

        // Count number of active filters
        int numberOfFilters = 0;
        if (fUsed)
            numberOfFilters++;
        if (fInternet)
            numberOfFilters++;
        if (fRestriction)
            numberOfFilters++;
        if (fPermission)
            numberOfFilters++;
        if (fOnDemand)
            numberOfFilters++;
        if (fUser)
            numberOfFilters++;
        if (fSystem)
            numberOfFilters++;

        if (numberOfFilters > 0) {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_filter)
                    .copy(Bitmap.Config.ARGB_8888, true);

            Paint paint = new Paint();
            paint.setStyle(Style.FILL);
            paint.setColor(Color.GRAY);
            paint.setTextSize(bitmap.getWidth() / 3);
            paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));

            String text = Integer.toString(numberOfFilters);

            Canvas canvas = new Canvas(bitmap);
            canvas.drawText(text, bitmap.getWidth() - paint.measureText(text), bitmap.getHeight(), paint);

            MenuItem fMenu = menu.findItem(R.id.menu_filter);
            fMenu.setIcon(new BitmapDrawable(getResources(), bitmap));
        }

        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        try {
            switch (item.getItemId()) {
            case R.id.menu_sort:
                optionSort();
                return true;
            case R.id.menu_filter:
                optionFilter();
                return true;
            case R.id.menu_usage:
                optionUsage();
                return true;
            case R.id.menu_template:
                optionTemplate();
                return true;
            case R.id.menu_select_all:
                optionSelectAll();
                return true;
            case R.id.menu_toggle:
                optionToggle();
                return true;
            case R.id.menu_export:
                optionExport();
                return true;
            case R.id.menu_import:
                optionImport();
                return true;
            case R.id.menu_submit:
                optionSubmit();
                return true;
            case R.id.menu_fetch:
                optionFetch();
                return true;
            case R.id.menu_pro:
                optionPro();
                return true;
            case R.id.menu_theme:
                optionSwitchTheme();
                return true;
            case R.id.menu_settings:
                optionSettings();
                return true;
            case R.id.menu_dump:
                optionDump();
                return true;
            case R.id.menu_legend:
                optionLegend();
                return true;
            case R.id.menu_tutorial:
                optionTutorial();
                return true;
            case R.id.menu_changelog:
                optionChangelog();
                return true;
            case R.id.menu_update:
                optionUpdate();
                return true;
            case R.id.menu_report:
                optionReportIssue();
                return true;
            case R.id.menu_about:
                optionAbout();
                return true;
            default:
                return super.onOptionsItemSelected(item);
            }
        } catch (Throwable ex) {
            Util.bug(null, ex);
            return true;
        }
    }

    @SuppressLint("InflateParams")
    private void optionSort() {
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.sort, null);
        final RadioGroup rgSMode = (RadioGroup) view.findViewById(R.id.rgSMode);
        final CheckBox cbSInvert = (CheckBox) view.findViewById(R.id.cbSInvert);

        // Initialise controls
        switch (mSortMode) {
        case SORT_BY_NAME:
            rgSMode.check(R.id.rbSName);
            break;
        case SORT_BY_UID:
            rgSMode.check(R.id.rbSUid);
            break;
        case SORT_BY_INSTALL_TIME:
            rgSMode.check(R.id.rbSInstalled);
            break;
        case SORT_BY_UPDATE_TIME:
            rgSMode.check(R.id.rbSUpdated);
            break;
        case SORT_BY_MODIFY_TIME:
            rgSMode.check(R.id.rbSModified);
            break;
        case SORT_BY_STATE:
            rgSMode.check(R.id.rbSState);
            break;
        }
        cbSInvert.setChecked(mSortInvert);

        // Build dialog
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(ActivityMain.this);
        alertDialogBuilder.setTitle(R.string.menu_sort);
        alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
        alertDialogBuilder.setView(view);
        alertDialogBuilder.setPositiveButton(ActivityMain.this.getString(android.R.string.ok),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        switch (rgSMode.getCheckedRadioButtonId()) {
                        case R.id.rbSName:
                            mSortMode = SORT_BY_NAME;
                            break;
                        case R.id.rbSUid:
                            mSortMode = SORT_BY_UID;
                            break;
                        case R.id.rbSInstalled:
                            mSortMode = SORT_BY_INSTALL_TIME;
                            break;
                        case R.id.rbSUpdated:
                            mSortMode = SORT_BY_UPDATE_TIME;
                            break;
                        case R.id.rbSModified:
                            mSortMode = SORT_BY_MODIFY_TIME;
                            break;
                        case R.id.rbSState:
                            mSortMode = SORT_BY_STATE;
                            break;
                        }
                        mSortInvert = cbSInvert.isChecked();

                        int userId = Util.getUserId(Process.myUid());
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingSortMode,
                                Integer.toString(mSortMode));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingSortInverted,
                                Boolean.toString(mSortInvert));

                        applySort();
                    }
                });
        alertDialogBuilder.setNegativeButton(ActivityMain.this.getString(android.R.string.cancel),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Do nothing
                    }
                });

        // Show dialog
        AlertDialog alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }

    @SuppressLint("InflateParams")
    private void optionFilter() {
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.filters, null);
        final CheckBox cbFUsed = (CheckBox) view.findViewById(R.id.cbFUsed);
        final CheckBox cbFInternet = (CheckBox) view.findViewById(R.id.cbFInternet);
        final CheckBox cbFPermission = (CheckBox) view.findViewById(R.id.cbFPermission);
        final CheckBox cbFRestriction = (CheckBox) view.findViewById(R.id.cbFRestriction);
        final CheckBox cbFRestrictionNot = (CheckBox) view.findViewById(R.id.cbFRestrictionNot);
        final CheckBox cbFOnDemand = (CheckBox) view.findViewById(R.id.cbFOnDemand);
        final CheckBox cbFOnDemandNot = (CheckBox) view.findViewById(R.id.cbFOnDemandNot);
        final CheckBox cbFUser = (CheckBox) view.findViewById(R.id.cbFUser);
        final CheckBox cbFSystem = (CheckBox) view.findViewById(R.id.cbFSystem);
        final Button btnDefault = (Button) view.findViewById(R.id.btnDefault);

        // Get settings
        final int userId = Util.getUserId(Process.myUid());
        boolean fUsed = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFUsed, false);
        boolean fInternet = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFInternet, false);
        boolean fPermission = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFPermission, true);
        boolean fRestriction = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFRestriction, false);
        boolean fRestrictionNot = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFRestrictionNot,
                false);
        boolean fOnDemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFOnDemand, false);
        boolean fOnDemandNot = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFOnDemandNot, false);
        boolean fUser = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFUser, true);
        boolean fSystem = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFSystem, false);

        boolean ondemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingOnDemand, true);

        // Setup checkboxes
        cbFUsed.setChecked(fUsed);
        cbFInternet.setChecked(fInternet);
        cbFPermission.setChecked(fPermission);
        cbFRestriction.setChecked(fRestriction);
        cbFRestrictionNot.setChecked(fRestrictionNot);
        cbFOnDemand.setChecked(fOnDemand && ondemand);
        cbFOnDemandNot.setChecked(fOnDemandNot && ondemand);
        cbFUser.setChecked(fUser);
        cbFSystem.setChecked(fSystem);

        cbFRestrictionNot.setEnabled(fRestriction);

        cbFOnDemand.setEnabled(ondemand);
        cbFOnDemandNot.setEnabled(fOnDemand && ondemand);

        // Manage user/system filter exclusivity
        OnCheckedChangeListener checkListener = new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (buttonView == cbFUser) {
                    if (isChecked)
                        cbFSystem.setChecked(false);
                } else if (buttonView == cbFSystem) {
                    if (isChecked)
                        cbFUser.setChecked(false);
                } else if (buttonView == cbFRestriction)
                    cbFRestrictionNot.setEnabled(cbFRestriction.isChecked());
                else if (buttonView == cbFOnDemand)
                    cbFOnDemandNot.setEnabled(cbFOnDemand.isChecked());
            }
        };
        cbFUser.setOnCheckedChangeListener(checkListener);
        cbFSystem.setOnCheckedChangeListener(checkListener);
        cbFRestriction.setOnCheckedChangeListener(checkListener);
        cbFOnDemand.setOnCheckedChangeListener(checkListener);

        // Clear button
        btnDefault.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                cbFUsed.setChecked(false);
                cbFInternet.setChecked(false);
                cbFPermission.setChecked(true);
                cbFRestriction.setChecked(false);
                cbFRestrictionNot.setChecked(false);
                cbFOnDemand.setChecked(false);
                cbFOnDemandNot.setChecked(false);
                cbFUser.setChecked(true);
                cbFSystem.setChecked(false);
            }
        });

        // Build dialog
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(ActivityMain.this);
        alertDialogBuilder.setTitle(R.string.menu_filter);
        alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
        alertDialogBuilder.setView(view);
        alertDialogBuilder.setPositiveButton(ActivityMain.this.getString(android.R.string.ok),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFUsed,
                                Boolean.toString(cbFUsed.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFInternet,
                                Boolean.toString(cbFInternet.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFRestriction,
                                Boolean.toString(cbFRestriction.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFRestrictionNot,
                                Boolean.toString(cbFRestrictionNot.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFPermission,
                                Boolean.toString(cbFPermission.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFOnDemand,
                                Boolean.toString(cbFOnDemand.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFOnDemandNot,
                                Boolean.toString(cbFOnDemandNot.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFUser,
                                Boolean.toString(cbFUser.isChecked()));
                        PrivacyManager.setSetting(userId, PrivacyManager.cSettingFSystem,
                                Boolean.toString(cbFSystem.isChecked()));

                        invalidateOptionsMenu();
                        applyFilter();
                    }
                });
        alertDialogBuilder.setNegativeButton(ActivityMain.this.getString(android.R.string.cancel),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                });

        // Show dialog
        AlertDialog alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }

    private void optionUsage() {
        Intent intent = new Intent(this, ActivityUsage.class);
        if (mAppAdapter != null && mAppAdapter.getRestrictionName() != null)
            intent.putExtra(ActivityUsage.cRestriction, mAppAdapter.getRestrictionName());
        startActivity(intent);
    }

    @SuppressLint("InflateParams")
    private void optionTemplate() {
        final int userId = Util.getUserId(Process.myUid());

        // Build view
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.template, null);
        final Spinner spTemplate = (Spinner) view.findViewById(R.id.spTemplate);
        Button btnRename = (Button) view.findViewById(R.id.btnRename);
        ExpandableListView elvTemplate = (ExpandableListView) view.findViewById(R.id.elvTemplate);

        // Template selector
        final SpinnerAdapter spAdapter = new SpinnerAdapter(this, android.R.layout.simple_spinner_item);
        spAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        String defaultName = PrivacyManager.getSetting(userId, Meta.cTypeTemplateName, "0",
                getString(R.string.title_default));
        spAdapter.add(defaultName);
        for (int i = 1; i <= 4; i++) {
            String alternateName = PrivacyManager.getSetting(userId, Meta.cTypeTemplateName, Integer.toString(i),
                    getString(R.string.title_alternate) + " " + i);
            spAdapter.add(alternateName);
        }
        spTemplate.setAdapter(spAdapter);

        // Template definition
        final TemplateListAdapter templateAdapter = new TemplateListAdapter(this, view, R.layout.templateentry);
        elvTemplate.setAdapter(templateAdapter);
        elvTemplate.setGroupIndicator(null);

        spTemplate.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
                templateAdapter.notifyDataSetChanged();
            }

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
                templateAdapter.notifyDataSetChanged();
            }
        });

        btnRename.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (Util.hasProLicense(ActivityMain.this) == null) {
                    // Redirect to pro page
                    Util.viewUri(ActivityMain.this, cProUri);
                    return;
                }

                final int templateId = spTemplate.getSelectedItemPosition();
                if (templateId == AdapterView.INVALID_POSITION)
                    return;

                AlertDialog.Builder dlgRename = new AlertDialog.Builder(spTemplate.getContext());
                dlgRename.setTitle(R.string.title_rename);

                final String original = (templateId == 0 ? getString(R.string.title_default)
                        : getString(R.string.title_alternate) + " " + templateId);
                dlgRename.setMessage(original);

                final EditText input = new EditText(spTemplate.getContext());
                dlgRename.setView(input);

                dlgRename.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        String name = input.getText().toString();
                        if (TextUtils.isEmpty(name)) {
                            PrivacyManager.setSetting(userId, Meta.cTypeTemplateName, Integer.toString(templateId),
                                    null);
                            name = original;
                        } else {
                            PrivacyManager.setSetting(userId, Meta.cTypeTemplateName, Integer.toString(templateId),
                                    name);
                        }
                        spAdapter.remove(spAdapter.getItem(templateId));
                        spAdapter.insert(name, templateId);
                        spAdapter.notifyDataSetChanged();
                    }
                });

                dlgRename.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // Do nothing
                    }
                });

                dlgRename.create().show();
            }
        });

        // Build dialog
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
        alertDialogBuilder.setTitle(R.string.menu_template);
        alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
        alertDialogBuilder.setView(view);
        alertDialogBuilder.setPositiveButton(getString(R.string.msg_done), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // Do nothing
            }
        });

        // Show dialog
        AlertDialog alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }

    private void optionSelectAll() {
        // Select all visible apps
        if (mAppAdapter != null)
            mAppAdapter.selectAllVisible();
    }

    private void optionToggle() {
        if (mAppAdapter != null) {
            Intent intent = new Intent(ActivityShare.ACTION_TOGGLE);
            intent.putExtra(ActivityShare.cInteractive, true);
            intent.putExtra(ActivityShare.cUidList,
                    mAppAdapter == null ? new int[0] : mAppAdapter.getSelectedOrVisibleUid(0));
            intent.putExtra(ActivityShare.cRestriction, mAppAdapter.getRestrictionName());
            startActivity(intent);
        }
    }

    private void optionExport() {
        Intent intent = new Intent(ActivityShare.ACTION_EXPORT);
        intent.putExtra(ActivityShare.cInteractive, true);
        intent.putExtra(ActivityShare.cUidList, mAppAdapter == null ? new int[0]
                : mAppAdapter.getSelectedOrVisibleUid(AppListAdapter.cSelectAppAll));
        startActivity(intent);
    }

    private void optionImport() {
        Intent intent = new Intent(ActivityShare.ACTION_IMPORT);
        intent.putExtra(ActivityShare.cInteractive, true);
        intent.putExtra(ActivityShare.cUidList,
                mAppAdapter == null ? new int[0] : mAppAdapter.getSelectedOrVisibleUid(0));
        startActivity(intent);
    }

    private void optionSubmit() {
        if (ActivityShare.registerDevice(this)) {
            int[] uid = (mAppAdapter == null ? new int[0]
                    : mAppAdapter.getSelectedOrVisibleUid(AppListAdapter.cSelectAppNone));
            if (uid.length == 0) {
                AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
                alertDialogBuilder.setTitle(R.string.app_name);
                alertDialogBuilder.setMessage(R.string.msg_select);
                alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
                alertDialogBuilder.setPositiveButton(getString(android.R.string.ok),
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                            }
                        });
                AlertDialog alertDialog = alertDialogBuilder.create();
                alertDialog.show();
            } else if (uid.length <= ActivityShare.cSubmitLimit) {
                if (mAppAdapter != null) {
                    Intent intent = new Intent(ActivityShare.ACTION_SUBMIT);
                    intent.putExtra(ActivityShare.cInteractive, true);
                    intent.putExtra(ActivityShare.cUidList, uid);
                    startActivity(intent);
                }
            } else {
                AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
                alertDialogBuilder.setTitle(R.string.app_name);
                alertDialogBuilder.setMessage(getString(R.string.msg_limit, ActivityShare.cSubmitLimit + 1));
                alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
                alertDialogBuilder.setPositiveButton(getString(android.R.string.ok),
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                            }
                        });
                AlertDialog alertDialog = alertDialogBuilder.create();
                alertDialog.show();
            }
        }
    }

    private void optionFetch() {
        if (Util.hasProLicense(this) == null) {
            // Redirect to pro page
            Util.viewUri(this, cProUri);
        } else {
            if (mAppAdapter != null) {

                Intent intent = new Intent(ActivityShare.ACTION_FETCH);
                intent.putExtra(ActivityShare.cInteractive, true);
                intent.putExtra(ActivityShare.cUidList,
                        mAppAdapter == null ? new int[0] : mAppAdapter.getSelectedOrVisibleUid(0));
                startActivity(intent);
            }
        }
    }

    private void optionPro() {
        // Redirect to pro page
        Util.viewUri(this, cProUri);
    }

    private void optionSwitchTheme() {
        int userId = Util.getUserId(Process.myUid());
        String themeName = PrivacyManager.getSetting(userId, PrivacyManager.cSettingTheme, "");
        themeName = (themeName.equals("Dark") ? "Light" : "Dark");
        PrivacyManager.setSetting(userId, PrivacyManager.cSettingTheme, themeName);
        this.recreate();
    }

    private void optionSettings() {
        Intent intent = new Intent(this, ActivitySettings.class);
        startActivity(intent);
    }

    private void optionDump() {
        try {
            PrivacyService.getClient().dump(0);
        } catch (Throwable ex) {
            Util.bug(null, ex);
        }
    }

    private void optionLegend() {
        // Show help
        Dialog dialog = new Dialog(ActivityMain.this);
        dialog.requestWindowFeature(Window.FEATURE_LEFT_ICON);
        dialog.setTitle(R.string.menu_legend);
        dialog.setContentView(R.layout.legend);
        dialog.setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, getThemed(R.attr.icon_launcher));

        ((ImageView) dialog.findViewById(R.id.imgHelpHalf)).setImageBitmap(getHalfCheckBox());
        ((ImageView) dialog.findViewById(R.id.imgHelpOnDemand)).setImageBitmap(getOnDemandCheckBox());

        for (View child : Util.getViewsByTag((ViewGroup) dialog.findViewById(android.R.id.content), "details"))
            child.setVisibility(View.GONE);

        ((LinearLayout) dialog.findViewById(R.id.llUnsafe))
                .setVisibility(PrivacyManager.cVersion3 ? View.VISIBLE : View.GONE);

        dialog.setCancelable(true);
        dialog.show();
    }

    private void optionTutorial() {
        ((ScrollView) findViewById(R.id.svTutorialHeader)).setVisibility(View.VISIBLE);
        ((ScrollView) findViewById(R.id.svTutorialDetails)).setVisibility(View.VISIBLE);
        int userId = Util.getUserId(Process.myUid());
        PrivacyManager.setSetting(userId, PrivacyManager.cSettingTutorialMain, Boolean.FALSE.toString());

        Dialog dlgUsage = new Dialog(this);
        dlgUsage.requestWindowFeature(Window.FEATURE_LEFT_ICON);
        dlgUsage.setTitle(R.string.title_usage_header);
        dlgUsage.setContentView(R.layout.usage);
        dlgUsage.setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, getThemed(R.attr.icon_launcher));
        dlgUsage.setCancelable(true);
        dlgUsage.show();
    }

    private void optionChangelog() {
        WebView webview = new WebView(this);
        webview.setWebViewClient(new WebViewClient() {
            public void onPageFinished(WebView view, String url) {
                int userId = Util.getUserId(Process.myUid());
                Version currentVersion = new Version(Util.getSelfVersionName(ActivityMain.this));
                PrivacyManager.setSetting(userId, PrivacyManager.cSettingChangelog, currentVersion.toString());
            }
        });
        webview.loadUrl("https://github.com/M66B/XPrivacy/blob/master/CHANGELOG.md");

        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
        alertDialogBuilder.setTitle(R.string.menu_changelog);
        alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
        alertDialogBuilder.setView(webview);
        AlertDialog alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }

    private void optionUpdate() {
        if (Util.hasProLicense(this) == null)
            Util.viewUri(this, ActivityMain.cProUri);
        else
            new ActivityShare.UpdateTask(this).executeOnExecutor(mExecutor);
    }

    private void optionReportIssue() {
        // Report issue
        Util.viewUri(this, Uri.parse("https://github.com/M66B/XPrivacy#support"));
    }

    @SuppressLint("DefaultLocale")
    private void optionAbout() {
        // About
        Dialog dlgAbout = new Dialog(this);
        dlgAbout.requestWindowFeature(Window.FEATURE_LEFT_ICON);
        dlgAbout.setTitle(R.string.menu_about);
        dlgAbout.setContentView(R.layout.about);
        dlgAbout.setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, getThemed(R.attr.icon_launcher));

        // Show version
        try {
            int userId = Util.getUserId(Process.myUid());
            Version currentVersion = new Version(Util.getSelfVersionName(this));
            Version storedVersion = new Version(
                    PrivacyManager.getSetting(userId, PrivacyManager.cSettingVersion, "0.0"));
            boolean migrated = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingMigrated, false);
            String versionName = currentVersion.toString();
            if (currentVersion.compareTo(storedVersion) != 0)
                versionName += "/" + storedVersion.toString();
            if (!migrated)
                versionName += "!";
            int versionCode = Util.getSelfVersionCode(this);
            TextView tvVersion = (TextView) dlgAbout.findViewById(R.id.tvVersion);
            tvVersion.setText(String.format(getString(R.string.app_version), versionName, versionCode));
        } catch (Throwable ex) {
            Util.bug(null, ex);
        }

        if (!PrivacyManager.cVersion3 || Hook.isAOSP(19))
            ((TextView) dlgAbout.findViewById(R.id.tvCompatibility)).setVisibility(View.GONE);

        // Show license
        String licensed = Util.hasProLicense(this);
        TextView tvLicensed1 = (TextView) dlgAbout.findViewById(R.id.tvLicensed);
        TextView tvLicensed2 = (TextView) dlgAbout.findViewById(R.id.tvLicensedAlt);
        if (licensed == null) {
            tvLicensed1.setText(Environment.getExternalStorageDirectory().getAbsolutePath());
            tvLicensed2.setText(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                    .getAbsolutePath());
        } else {
            tvLicensed1.setText(String.format(getString(R.string.app_licensed), licensed));
            tvLicensed2.setVisibility(View.GONE);
        }

        // Show some build properties
        String android = String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT);
        ((TextView) dlgAbout.findViewById(R.id.tvAndroid)).setText(android);
        ((TextView) dlgAbout.findViewById(R.id.build_brand)).setText(Build.BRAND);
        ((TextView) dlgAbout.findViewById(R.id.build_manufacturer)).setText(Build.MANUFACTURER);
        ((TextView) dlgAbout.findViewById(R.id.build_model)).setText(Build.MODEL);
        ((TextView) dlgAbout.findViewById(R.id.build_product)).setText(Build.PRODUCT);
        ((TextView) dlgAbout.findViewById(R.id.build_device)).setText(Build.DEVICE);
        ((TextView) dlgAbout.findViewById(R.id.build_host)).setText(Build.HOST);
        ((TextView) dlgAbout.findViewById(R.id.build_display)).setText(Build.DISPLAY);
        ((TextView) dlgAbout.findViewById(R.id.build_id)).setText(Build.ID);

        dlgAbout.setCancelable(true);

        final int userId = Util.getUserId(Process.myUid());
        if (PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingFirstRun, true))
            dlgAbout.setOnDismissListener(new DialogInterface.OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface dialog) {
                    Dialog dlgUsage = new Dialog(ActivityMain.this);
                    dlgUsage.requestWindowFeature(Window.FEATURE_LEFT_ICON);
                    dlgUsage.setTitle(R.string.app_name);
                    dlgUsage.setContentView(R.layout.usage);
                    dlgUsage.setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, getThemed(R.attr.icon_launcher));
                    dlgUsage.setCancelable(true);
                    dlgUsage.setOnDismissListener(new DialogInterface.OnDismissListener() {
                        @Override
                        public void onDismiss(DialogInterface dialog) {
                            PrivacyManager.setSetting(userId, PrivacyManager.cSettingFirstRun,
                                    Boolean.FALSE.toString());
                            optionLegend();
                        }
                    });
                    dlgUsage.show();
                }
            });

        dlgAbout.show();
    }

    // Tasks

    private class AppListTask extends AsyncTask<Object, Integer, List<ApplicationInfoEx>> {
        private String mRestrictionName;
        private ProgressDialog mProgressDialog;

        @Override
        protected List<ApplicationInfoEx> doInBackground(Object... params) {
            mRestrictionName = null;

            // Delegate
            return ApplicationInfoEx.getXApplicationList(ActivityMain.this, mProgressDialog);
        }

        @SuppressWarnings("deprecation")
        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            TypedArray ta = getTheme().obtainStyledAttributes(new int[] { R.attr.progress_horizontal });
            int progress_horizontal = ta.getResourceId(0, 0);
            ta.recycle();

            // Show progress dialog
            mProgressDialog = new ProgressDialog(ActivityMain.this);
            mProgressDialog.setMessage(getString(R.string.msg_loading));
            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mProgressDialog.setProgressDrawable(getResources().getDrawable(progress_horizontal));
            mProgressDialog.setProgressNumberFormat(null);
            mProgressDialog.setCancelable(false);
            mProgressDialog.setCanceledOnTouchOutside(false);
            mProgressDialog.show();
        }

        @Override
        protected void onPostExecute(List<ApplicationInfoEx> listApp) {
            if (!ActivityMain.this.isFinishing()) {
                // Display app list
                mAppAdapter = new AppListAdapter(ActivityMain.this, R.layout.mainentry, listApp, mRestrictionName);
                ListView lvApp = (ListView) findViewById(R.id.lvApp);
                lvApp.setAdapter(mAppAdapter);

                // Dismiss progress dialog
                if (mProgressDialog.isShowing())
                    try {
                        mProgressDialog.dismiss();
                    } catch (IllegalArgumentException ignored) {
                    }

                // Restore state
                ActivityMain.this.selectRestriction(spRestriction.getSelectedItemPosition());
            }

            super.onPostExecute(listApp);
        }
    }

    // Adapters

    private class SpinnerAdapter extends ArrayAdapter<String> {
        public SpinnerAdapter(Context context, int textViewResourceId) {
            super(context, textViewResourceId);
        }
    }

    @SuppressLint("DefaultLocale")
    private class TemplateListAdapter extends BaseExpandableListAdapter {
        private View mView;
        private Spinner mSpinner;
        private List<String> listRestrictionName;
        private List<String> listLocalizedTitle;
        private boolean ondemand;
        private Version version;
        private LayoutInflater mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        public TemplateListAdapter(Context context, View view, int resource) {
            mView = view;
            mSpinner = (Spinner) view.findViewById(R.id.spTemplate);

            // Get restriction categories
            TreeMap<String, String> tmRestriction = PrivacyManager.getRestrictions(context);
            listRestrictionName = new ArrayList<String>(tmRestriction.values());
            listLocalizedTitle = new ArrayList<String>(tmRestriction.navigableKeySet());

            int userId = Util.getUserId(Process.myUid());
            ondemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingOnDemand, true);
            version = new Version(Util.getSelfVersionName(context));
        }

        private String getTemplate() {
            if (mSpinner.getSelectedItemPosition() == 0)
                return Meta.cTypeTemplate;
            else
                return Meta.cTypeTemplate + mSpinner.getSelectedItemPosition();
        }

        private class ViewHolder {
            private View row;
            public ImageView imgIndicator;
            public ImageView imgInfo;
            public TextView tvRestriction;
            public ImageView imgUnsafe;
            public ImageView imgCbRestrict;
            public ImageView imgCbAsk;
            public boolean restricted;
            public boolean asked;

            public ViewHolder(View theRow) {
                row = theRow;
                imgIndicator = (ImageView) row.findViewById(R.id.imgIndicator);
                imgInfo = (ImageView) row.findViewById(R.id.imgInfo);
                tvRestriction = (TextView) row.findViewById(R.id.tvRestriction);
                imgUnsafe = (ImageView) row.findViewById(R.id.imgUnsafe);
                imgCbRestrict = (ImageView) row.findViewById(R.id.imgCbRestrict);
                imgCbAsk = (ImageView) row.findViewById(R.id.imgCbAsk);
            }
        }

        @Override
        public Object getGroup(int groupPosition) {
            return listRestrictionName.get(groupPosition);
        }

        @Override
        public int getGroupCount() {
            return listRestrictionName.size();
        }

        @Override
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        @Override
        @SuppressLint("InflateParams")
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            final ViewHolder holder;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.templateentry, null);
                holder = new ViewHolder(convertView);
                convertView.setTag(holder);
            } else
                holder = (ViewHolder) convertView.getTag();

            // Get entry
            final String restrictionName = (String) getGroup(groupPosition);

            // Get info
            final int userId = Util.getUserId(Process.myUid());
            String value = PrivacyManager.getSetting(userId, getTemplate(), restrictionName,
                    Boolean.toString(!ondemand) + "+ask");
            holder.restricted = value.contains("true");
            holder.asked = (!ondemand || value.contains("asked"));

            boolean partialRestricted = false;
            boolean partialAsked = false;
            if (holder.restricted || !holder.asked)
                for (Hook hook : PrivacyManager.getHooks(restrictionName, version)) {
                    String settingName = restrictionName + "." + hook.getName();
                    String childValue = PrivacyManager.getSetting(userId, getTemplate(), settingName, null);
                    if (childValue == null)
                        childValue = Boolean.toString(holder.restricted && !hook.isDangerous())
                                + (holder.asked || (hook.isDangerous() && hook.whitelist() == null) ? "+asked"
                                        : "+ask");
                    if (!childValue.contains("true"))
                        partialRestricted = true;
                    if (childValue.contains("asked"))
                        partialAsked = true;
                }

            Bitmap bmRestricted = (holder.restricted ? partialRestricted ? getHalfCheckBox() : getFullCheckBox()
                    : getOffCheckBox());
            Bitmap bmAsked = (holder.asked ? getOffCheckBox()
                    : partialAsked ? getHalfCheckBox() : getOnDemandCheckBox());

            // Indicator state
            holder.imgIndicator.setImageResource(
                    getThemed(isExpanded ? R.attr.icon_expander_maximized : R.attr.icon_expander_minimized));
            holder.imgIndicator.setVisibility(View.VISIBLE);
            holder.imgInfo.setVisibility(View.GONE);
            holder.imgUnsafe.setVisibility(View.GONE);

            // Set data
            holder.tvRestriction.setTypeface(null, Typeface.BOLD);
            holder.tvRestriction.setText(listLocalizedTitle.get(groupPosition));
            holder.imgCbRestrict.setImageBitmap(bmRestricted);
            holder.imgCbAsk.setImageBitmap(bmAsked);
            holder.imgCbAsk.setVisibility(ondemand ? View.VISIBLE : View.GONE);

            holder.imgCbRestrict.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    // Update setting
                    holder.restricted = !holder.restricted;
                    PrivacyManager.setSetting(userId, getTemplate(), restrictionName,
                            (holder.restricted ? "true" : "false") + "+" + (holder.asked ? "asked" : "ask"));
                    notifyDataSetChanged(); // update childs
                }
            });

            holder.imgCbAsk.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    // Update setting
                    holder.asked = (!ondemand || !holder.asked);
                    PrivacyManager.setSetting(userId, getTemplate(), restrictionName,
                            (holder.restricted ? "true" : "false") + "+" + (holder.asked ? "asked" : "ask"));
                    notifyDataSetChanged(); // update childs
                }
            });

            return convertView;
        }

        @Override
        public Object getChild(int groupPosition, int childPosition) {
            return PrivacyManager.getHooks((String) getGroup(groupPosition), version).get(childPosition);
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        @Override
        public int getChildrenCount(int groupPosition) {
            return PrivacyManager.getHooks((String) getGroup(groupPosition), version).size();
        }

        @Override
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            return false;
        }

        @Override
        @SuppressLint("InflateParams")
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
                ViewGroup parent) {
            final ViewHolder holder;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.templateentry, null);
                holder = new ViewHolder(convertView);
                convertView.setTag(holder);
            } else
                holder = (ViewHolder) convertView.getTag();

            // Get entry
            final int userId = Util.getUserId(Process.myUid());
            final String restrictionName = (String) getGroup(groupPosition);
            final Hook hook = (Hook) getChild(groupPosition, childPosition);
            final String settingName = restrictionName + "." + hook.getName();

            // Get parent info
            String parentValue = PrivacyManager.getSetting(userId, getTemplate(), restrictionName,
                    Boolean.toString(!ondemand) + "+ask");
            boolean parentRestricted = parentValue.contains("true");
            boolean parentAsked = (!ondemand || parentValue.contains("asked"));

            // Get child info
            String value = PrivacyManager.getSetting(userId, getTemplate(), settingName, null);
            // This is to circumvent caching problems
            // The child value depends on the parent value
            if (value == null)
                value = Boolean.toString(parentRestricted && !hook.isDangerous())
                        + (parentAsked || (hook.isDangerous() && hook.whitelist() == null) ? "+asked" : "+ask");
            holder.restricted = value.contains("true");
            holder.asked = (!ondemand || value.contains("asked"));
            Bitmap bmRestricted = (parentRestricted && holder.restricted ? getFullCheckBox() : getOffCheckBox());
            Bitmap bmAsked = (parentAsked || holder.asked ? getOffCheckBox() : getOnDemandCheckBox());

            // Set indicator
            holder.imgIndicator.setVisibility(View.INVISIBLE);

            // Function help
            if (hook.getAnnotation() == null)
                holder.imgInfo.setVisibility(View.GONE);
            else {
                holder.imgInfo.setVisibility(View.VISIBLE);
                holder.imgInfo.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        ActivityApp.showHelp(ActivityMain.this, mView, hook);
                    }
                });
            }
            holder.imgUnsafe.setVisibility(hook.isUnsafe() ? View.VISIBLE : View.GONE);

            // Set data
            if (hook.isDangerous())
                holder.row.setBackgroundColor(getResources().getColor(getThemed(
                        hook.isDangerousDefined() ? R.attr.color_dangerous : R.attr.color_dangerous_user)));
            else
                holder.row.setBackgroundColor(
                        hook.isDangerousDefined() ? getResources().getColor(getThemed(R.attr.color_dangerous_off))
                                : Color.TRANSPARENT);
            holder.tvRestriction.setText(hook.getName());
            holder.imgCbRestrict.setEnabled(parentRestricted);
            holder.imgCbRestrict.setImageBitmap(bmRestricted);
            holder.imgCbAsk.setEnabled(!parentAsked);
            holder.imgCbAsk.setImageBitmap(bmAsked);
            holder.imgCbAsk.setVisibility(ondemand ? View.VISIBLE : View.GONE);

            // Listen for long press
            if (Util.getUserId(Process.myUid()) == 0)
                holder.tvRestriction.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        hook.toggleDangerous();

                        // Change background color
                        if (hook.isDangerous())
                            holder.row.setBackgroundColor(getResources()
                                    .getColor(getThemed(hook.isDangerousDefined() ? R.attr.color_dangerous
                                            : R.attr.color_dangerous_user)));
                        else
                            holder.row.setBackgroundColor(hook.isDangerousDefined()
                                    ? getResources().getColor(getThemed(R.attr.color_dangerous_off))
                                    : Color.TRANSPARENT);

                        notifyDataSetChanged();

                        return true;
                    }
                });

            holder.imgCbRestrict.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    // Update setting
                    holder.restricted = !holder.restricted;
                    PrivacyManager.setSetting(userId, getTemplate(), settingName,
                            (holder.restricted ? "true" : "false") + "+" + (holder.asked ? "asked" : "ask"));
                    notifyDataSetChanged(); // update parent
                }
            });

            holder.imgCbAsk.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    // Update setting
                    holder.asked = !holder.asked;
                    PrivacyManager.setSetting(userId, getTemplate(), settingName,
                            (holder.restricted ? "true" : "false") + "+" + (holder.asked ? "asked" : "ask"));
                    notifyDataSetChanged(); // update parent
                }
            });

            return convertView;
        }

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

    @SuppressLint("DefaultLocale")
    private class AppListAdapter extends ArrayAdapter<ApplicationInfoEx> {
        private Context mContext;
        private boolean mSelecting = false;
        private List<ApplicationInfoEx> mListAppAll;
        private List<ApplicationInfoEx> mListAppSelected = new ArrayList<ApplicationInfoEx>();
        private String mRestrictionName;
        private Version mVersion;
        private LayoutInflater mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        private AtomicInteger mFiltersRunning = new AtomicInteger(0);
        private int mHighlightColor;

        public final static int cSelectAppAll = 1;
        public final static int cSelectAppNone = 2;

        public AppListAdapter(Context context, int resource, List<ApplicationInfoEx> objects,
                String initialRestrictionName) {
            super(context, resource, objects);
            mContext = context;
            mListAppAll = new ArrayList<ApplicationInfoEx>();
            mListAppAll.addAll(objects);
            mRestrictionName = initialRestrictionName;
            mVersion = new Version(Util.getSelfVersionName(context));

            TypedArray ta1 = context.getTheme()
                    .obtainStyledAttributes(new int[] { android.R.attr.colorPressedHighlight });
            mHighlightColor = ta1.getColor(0, 0xFF00FF);
            ta1.recycle();
        }

        public void setRestrictionName(String restrictionName) {
            mRestrictionName = restrictionName;
        }

        public String getRestrictionName() {
            return mRestrictionName;
        }

        public List<ApplicationInfoEx> getSelectedOrVisible(int flags) {
            if (mListAppSelected.size() > 0)
                return mListAppSelected;
            else {
                if (flags == cSelectAppAll)
                    return mListAppAll;
                else {
                    List<ApplicationInfoEx> listApp = new ArrayList<ApplicationInfoEx>();
                    if (flags != cSelectAppNone)
                        for (int i = 0; i < this.getCount(); i++)
                            listApp.add(this.getItem(i));
                    return listApp;
                }
            }
        }

        public int[] getSelectedOrVisibleUid(int flags) {
            List<ApplicationInfoEx> listAppInfo = getSelectedOrVisible(flags);
            int[] uid = new int[listAppInfo.size()];
            for (int pos = 0; pos < listAppInfo.size(); pos++)
                uid[pos] = listAppInfo.get(pos).getUid();
            return uid;
        }

        public void selectAllVisible() {
            // Look through the visible apps to figure out what to do
            mSelecting = false;
            for (int i = 0; i < this.getCount(); i++) {
                if (!mListAppSelected.contains(this.getItem(i))) {
                    mSelecting = true;
                    break;
                }
            }

            if (mSelecting) {
                // Add the visible apps not already selected
                for (int i = 0; i < this.getCount(); i++)
                    if (!mListAppSelected.contains(this.getItem(i)))
                        mListAppSelected.add(this.getItem(i));
            } else
                mListAppSelected.clear();

            this.showStats();
            this.notifyDataSetChanged();
        }

        public void showStats() {
            TextView tvStats = (TextView) findViewById(R.id.tvStats);
            String stats = String.format("%d/%d", this.getCount(), mListAppAll.size());
            if (mListAppSelected.size() > 0)
                stats += String.format(" (%d)", mListAppSelected.size());
            tvStats.setText(stats);
        }

        @Override
        public Filter getFilter() {
            return new AppFilter();
        }

        private class AppFilter extends Filter {
            public AppFilter() {
            }

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                int userId = Util.getUserId(Process.myUid());

                int filtersRunning = mFiltersRunning.addAndGet(1);
                FilterResults results = new FilterResults();

                // Get arguments
                String[] components = ((String) constraint).split("\\n");
                String fName = components[0];
                boolean fUsed = Boolean.parseBoolean(components[1]);
                boolean fInternet = Boolean.parseBoolean(components[2]);
                boolean fRestricted = Boolean.parseBoolean(components[3]);
                boolean fRestrictedNot = Boolean.parseBoolean(components[4]);
                boolean fPermission = Boolean.parseBoolean(components[5]);
                boolean fOnDemand = Boolean.parseBoolean(components[6]);
                boolean fOnDemandNot = Boolean.parseBoolean(components[7]);
                boolean fUser = Boolean.parseBoolean(components[8]);
                boolean fSystem = Boolean.parseBoolean(components[9]);

                // Match applications
                int current = 0;
                int max = AppListAdapter.this.mListAppAll.size();
                List<ApplicationInfoEx> lstApp = new ArrayList<ApplicationInfoEx>();
                for (ApplicationInfoEx xAppInfo : AppListAdapter.this.mListAppAll) {
                    // Check if another filter has been started
                    if (filtersRunning != mFiltersRunning.get())
                        return null;

                    // Send progress info to main activity
                    current++;
                    if (current % 5 == 0) {
                        final int position = current;
                        final int maximum = max;
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                setProgress(getString(R.string.msg_applying), position, maximum);
                            }
                        });
                    }

                    // Get if name contains
                    boolean contains = false;
                    if (!fName.equals(""))
                        contains = (xAppInfo.toString().toLowerCase().contains(((String) fName).toLowerCase()));

                    // Get if used
                    boolean used = false;
                    if (fUsed)
                        used = (PrivacyManager.getUsage(xAppInfo.getUid(), mRestrictionName, null) != 0);

                    // Get if internet
                    boolean internet = false;
                    if (fInternet)
                        internet = xAppInfo.hasInternet(mContext);

                    // Get some restricted
                    boolean someRestricted = false;
                    if (fRestricted)
                        for (PRestriction restriction : PrivacyManager.getRestrictionList(xAppInfo.getUid(),
                                mRestrictionName))
                            if (restriction.restricted) {
                                someRestricted = true;
                                break;
                            }

                    // Get Android permission
                    boolean permission = false;
                    if (fPermission)
                        if (mRestrictionName == null)
                            permission = true;
                        else if (PrivacyManager.hasPermission(mContext, xAppInfo, mRestrictionName, mVersion)
                                || PrivacyManager.getUsage(xAppInfo.getUid(), mRestrictionName, null) > 0)
                            permission = true;

                    // Get if onDemand
                    boolean onDemand = false;
                    boolean isApp = PrivacyManager.isApplication(xAppInfo.getUid());
                    boolean odSystem = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingOnDemandSystem,
                            false);
                    boolean gondemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingOnDemand,
                            true);
                    if (fOnDemand && (isApp || odSystem) && gondemand) {
                        onDemand = PrivacyManager.getSettingBool(-xAppInfo.getUid(),
                                PrivacyManager.cSettingOnDemand, false);
                        if (onDemand && mRestrictionName != null)
                            onDemand = !PrivacyManager.getRestrictionEx(xAppInfo.getUid(), mRestrictionName,
                                    null).asked;
                    }

                    // Get if user
                    boolean user = false;
                    if (fUser)
                        user = !xAppInfo.isSystem();

                    // Get if system
                    boolean system = false;
                    if (fSystem)
                        system = xAppInfo.isSystem();

                    // Apply filters
                    if ((fName.equals("") ? true : contains) && (fUsed ? used : true)
                            && (fInternet ? internet : true)
                            && (fRestricted ? (fRestrictedNot ? !someRestricted : someRestricted) : true)
                            && (fPermission ? permission : true)
                            && (fOnDemand ? (fOnDemandNot ? !onDemand : onDemand) : true) && (fUser ? user : true)
                            && (fSystem ? system : true))
                        lstApp.add(xAppInfo);
                }

                // Check again whether another filter has been started
                if (filtersRunning != mFiltersRunning.get())
                    return null;

                // Apply current sorting
                Collections.sort(lstApp, mSorter);

                // Last check whether another filter has been started
                if (filtersRunning != mFiltersRunning.get())
                    return null;

                synchronized (this) {
                    results.values = lstApp;
                    results.count = lstApp.size();
                }

                return results;
            }

            @Override
            @SuppressWarnings("unchecked")
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null) {
                    clear();
                    TextView tvStats = (TextView) findViewById(R.id.tvStats);
                    TextView tvState = (TextView) findViewById(R.id.tvState);
                    ProgressBar pbFilter = (ProgressBar) findViewById(R.id.pbFilter);
                    pbFilter.setVisibility(ProgressBar.GONE);
                    tvStats.setVisibility(TextView.VISIBLE);

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            setProgress(getString(R.string.title_restrict), 0, 1);
                        }
                    });

                    // Adjust progress state width
                    RelativeLayout.LayoutParams tvStateLayout = (RelativeLayout.LayoutParams) tvState
                            .getLayoutParams();
                    tvStateLayout.addRule(RelativeLayout.LEFT_OF, R.id.tvStats);

                    if (results.values == null)
                        notifyDataSetInvalidated();
                    else {
                        addAll((ArrayList<ApplicationInfoEx>) results.values);
                        notifyDataSetChanged();
                    }
                    AppListAdapter.this.showStats();
                }
            }
        }

        public void sort() {
            sort(mSorter);
        }

        private class ViewHolder {
            private View row;
            private int position;
            public View vwState;
            public LinearLayout llAppType;
            public ImageView imgIcon;
            public ImageView imgUsed;
            public ImageView imgGranted;
            public ImageView imgInternet;
            public ImageView imgFrozen;
            public ImageView imgSettings;
            public LinearLayout llName;
            public TextView tvName;
            public ImageView imgCbRestricted;
            public ProgressBar pbRunning;
            public ImageView imgCbAsk;

            public ViewHolder(View theRow, int thePosition) {
                row = theRow;
                position = thePosition;
                vwState = (View) row.findViewById(R.id.vwState);
                llAppType = (LinearLayout) row.findViewById(R.id.llAppType);
                imgIcon = (ImageView) row.findViewById(R.id.imgIcon);
                imgUsed = (ImageView) row.findViewById(R.id.imgUsed);
                imgGranted = (ImageView) row.findViewById(R.id.imgGranted);
                imgInternet = (ImageView) row.findViewById(R.id.imgInternet);
                imgFrozen = (ImageView) row.findViewById(R.id.imgFrozen);
                imgSettings = (ImageView) row.findViewById(R.id.imgSettings);
                llName = (LinearLayout) row.findViewById(R.id.llName);
                tvName = (TextView) row.findViewById(R.id.tvName);
                imgCbRestricted = (ImageView) row.findViewById(R.id.imgCbRestricted);
                pbRunning = (ProgressBar) row.findViewById(R.id.pbRunning);
                imgCbAsk = (ImageView) row.findViewById(R.id.imgCbAsk);
            }
        }

        private class HolderTask extends AsyncTask<Object, Object, Object> {
            private int position;
            private ViewHolder holder;
            private ApplicationInfoEx xAppInfo = null;
            private int state;
            private Bitmap bicon;
            private Drawable dicon;
            private boolean used;
            private boolean enabled;
            private boolean granted;
            private boolean settings;
            private RState rstate;
            private boolean gondemand;
            private boolean ondemand;
            private boolean can;
            private boolean methodExpert;

            public HolderTask(int thePosition, ViewHolder theHolder, ApplicationInfoEx theAppInfo) {
                position = thePosition;
                holder = theHolder;
                xAppInfo = theAppInfo;
            }

            @Override
            protected Object doInBackground(Object... params) {
                if (xAppInfo != null) {
                    int userId = Util.getUserId(Process.myUid());

                    // Get state
                    state = xAppInfo.getState(ActivityMain.this);

                    // Get icon
                    bicon = xAppInfo.getIconBitmap(ActivityMain.this);
                    if (bicon == null)
                        dicon = xAppInfo.getIcon(ActivityMain.this);

                    // Get if used
                    used = (PrivacyManager.getUsage(xAppInfo.getUid(), mRestrictionName, null) != 0);

                    // Get if enabled
                    enabled = PrivacyManager.getSettingBool(xAppInfo.getUid(), PrivacyManager.cSettingRestricted,
                            true);

                    // Get if on demand
                    gondemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingOnDemand, true);
                    boolean isApp = PrivacyManager.isApplication(xAppInfo.getUid());
                    boolean odSystem = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingOnDemandSystem,
                            false);
                    ondemand = (isApp || odSystem);
                    if (ondemand && mRestrictionName != null)
                        ondemand = PrivacyManager.getSettingBool(-xAppInfo.getUid(),
                                PrivacyManager.cSettingOnDemand, false);

                    // Get if granted
                    granted = true;
                    if (mRestrictionName != null)
                        if (!PrivacyManager.hasPermission(ActivityMain.this, xAppInfo, mRestrictionName, mVersion))
                            granted = false;

                    // Get if application settings
                    settings = PrivacyManager.hasSpecificSettings(xAppInfo.getUid());

                    // Get restriction/ask state
                    rstate = new RState(xAppInfo.getUid(), mRestrictionName, null, mVersion);

                    // Get can restrict
                    can = PrivacyManager.canRestrict(rstate.mUid, Process.myUid(), rstate.mRestrictionName,
                            rstate.mMethodName, true);
                    methodExpert = (mRestrictionName == null
                            || PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingMethodExpert, false));

                    return holder;
                }
                return null;
            }

            @Override
            protected void onPostExecute(Object result) {
                if (holder.position == position && result != null) {
                    // Set background color
                    if (xAppInfo.isSystem())
                        holder.llAppType
                                .setBackgroundColor(getResources().getColor(getThemed(R.attr.color_dangerous)));
                    else
                        holder.llAppType.setBackgroundColor(Color.TRANSPARENT);

                    // Display state
                    if (state == ApplicationInfoEx.STATE_ATTENTION)
                        holder.vwState.setBackgroundColor(
                                getResources().getColor(getThemed(R.attr.color_state_attention)));
                    else if (state == ApplicationInfoEx.STATE_SHARED)
                        holder.vwState
                                .setBackgroundColor(getResources().getColor(getThemed(R.attr.color_state_shared)));
                    else
                        holder.vwState.setBackgroundColor(
                                getResources().getColor(getThemed(R.attr.color_state_restricted)));

                    // Display icon
                    if (bicon == null)
                        holder.imgIcon.setImageDrawable(dicon);
                    else
                        holder.imgIcon.setImageBitmap(bicon);
                    holder.imgIcon.setVisibility(View.VISIBLE);

                    // Display on demand
                    if (gondemand) {
                        if (ondemand) {
                            holder.imgCbAsk.setImageBitmap(getAskBoxImage(rstate, methodExpert));
                            holder.imgCbAsk.setVisibility(View.VISIBLE);
                        } else
                            holder.imgCbAsk.setVisibility(View.INVISIBLE);
                    } else
                        holder.imgCbAsk.setVisibility(View.GONE);

                    // Display usage
                    holder.tvName.setTypeface(null, used ? Typeface.BOLD_ITALIC : Typeface.NORMAL);
                    holder.imgUsed.setVisibility(used ? View.VISIBLE : View.INVISIBLE);

                    // Display if permissions
                    holder.imgGranted.setVisibility(granted ? View.VISIBLE : View.INVISIBLE);

                    // Display if internet access
                    holder.imgInternet
                            .setVisibility(xAppInfo.hasInternet(ActivityMain.this) ? View.VISIBLE : View.INVISIBLE);

                    // Display if frozen
                    holder.imgFrozen
                            .setVisibility(xAppInfo.isFrozen(ActivityMain.this) ? View.VISIBLE : View.INVISIBLE);

                    // Display if settings
                    holder.imgSettings.setVisibility(settings ? View.VISIBLE : View.GONE);

                    // Display restriction
                    holder.imgCbRestricted.setImageBitmap(getCheckBoxImage(rstate, methodExpert));
                    holder.imgCbRestricted.setVisibility(View.VISIBLE);

                    // Display enabled state
                    holder.tvName.setEnabled(enabled && can);
                    holder.imgCbRestricted.setEnabled(enabled && can);
                    holder.imgCbAsk.setEnabled(enabled && can);

                    // Display selection
                    if (mListAppSelected.contains(xAppInfo))
                        holder.row.setBackgroundColor(mHighlightColor);
                    else
                        holder.row.setBackgroundColor(Color.TRANSPARENT);

                    // Handle details click
                    holder.imgIcon.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            Intent intentSettings = new Intent(ActivityMain.this, ActivityApp.class);
                            intentSettings.putExtra(ActivityApp.cUid, xAppInfo.getUid());
                            intentSettings.putExtra(ActivityApp.cRestrictionName, mRestrictionName);
                            ActivityMain.this.startActivity(intentSettings);
                        }
                    });

                    // Listen for restriction changes
                    holder.imgCbRestricted.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            if (mRestrictionName == null && rstate.restricted != false) {
                                AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(ActivityMain.this);
                                alertDialogBuilder.setTitle(R.string.menu_clear_all);
                                alertDialogBuilder.setMessage(R.string.msg_sure);
                                alertDialogBuilder.setIcon(getThemed(R.attr.icon_launcher));
                                alertDialogBuilder.setPositiveButton(getString(android.R.string.ok),
                                        new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                deleteRestrictions();
                                            }
                                        });
                                alertDialogBuilder.setNegativeButton(getString(android.R.string.cancel),
                                        new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                            }
                                        });
                                AlertDialog alertDialog = alertDialogBuilder.create();
                                alertDialog.show();
                            } else
                                toggleRestrictions();
                        }
                    });

                    // Listen for ask changes
                    if (gondemand && ondemand)
                        holder.imgCbAsk.setOnClickListener(new View.OnClickListener() {
                            @Override
                            public void onClick(View view) {
                                holder.imgCbAsk.setVisibility(View.GONE);
                                holder.pbRunning.setVisibility(View.VISIBLE);

                                new AsyncTask<Object, Object, Object>() {
                                    @Override
                                    protected Object doInBackground(Object... arg0) {
                                        rstate.toggleAsked();
                                        rstate = new RState(xAppInfo.getUid(), mRestrictionName, null, mVersion);
                                        return null;
                                    }

                                    @Override
                                    protected void onPostExecute(Object result) {
                                        holder.imgCbAsk.setImageBitmap(getAskBoxImage(rstate, methodExpert));
                                        holder.pbRunning.setVisibility(View.GONE);
                                        holder.imgCbAsk.setVisibility(View.VISIBLE);
                                    }
                                }.executeOnExecutor(mExecutor);
                            }
                        });
                    else
                        holder.imgCbAsk.setClickable(false);
                }
            }

            private void deleteRestrictions() {
                holder.imgCbRestricted.setVisibility(View.GONE);
                holder.pbRunning.setVisibility(View.VISIBLE);

                new AsyncTask<Object, Object, Object>() {
                    private List<Boolean> oldState;

                    @Override
                    protected Object doInBackground(Object... arg0) {
                        // Update restriction
                        oldState = PrivacyManager.getRestartStates(xAppInfo.getUid(), mRestrictionName);
                        PrivacyManager.deleteRestrictions(xAppInfo.getUid(), null, true);
                        PrivacyManager.setSetting(xAppInfo.getUid(), PrivacyManager.cSettingOnDemand,
                                Boolean.toString(true));
                        return null;
                    }

                    @Override
                    protected void onPostExecute(Object result) {
                        // Update visible state
                        holder.vwState.setBackgroundColor(
                                getResources().getColor(getThemed(R.attr.color_state_attention)));

                        // Update stored state
                        rstate = new RState(xAppInfo.getUid(), mRestrictionName, null, mVersion);
                        holder.imgCbRestricted.setImageBitmap(getCheckBoxImage(rstate, methodExpert));
                        holder.imgCbAsk.setImageBitmap(getAskBoxImage(rstate, methodExpert));

                        // Notify restart
                        if (oldState.contains(true))
                            Toast.makeText(ActivityMain.this, getString(R.string.msg_restart), Toast.LENGTH_LONG)
                                    .show();

                        // Display new state
                        showState();

                        holder.pbRunning.setVisibility(View.GONE);
                        holder.imgCbRestricted.setVisibility(View.VISIBLE);
                    }
                }.executeOnExecutor(mExecutor);
            }

            private void toggleRestrictions() {
                holder.imgCbRestricted.setVisibility(View.GONE);
                holder.pbRunning.setVisibility(View.VISIBLE);

                new AsyncTask<Object, Object, Object>() {
                    private List<Boolean> oldState;
                    private List<Boolean> newState;

                    @Override
                    protected Object doInBackground(Object... arg0) {
                        // Change restriction
                        oldState = PrivacyManager.getRestartStates(xAppInfo.getUid(), mRestrictionName);
                        rstate.toggleRestriction();
                        newState = PrivacyManager.getRestartStates(xAppInfo.getUid(), mRestrictionName);
                        return null;
                    }

                    @Override
                    protected void onPostExecute(Object result) {
                        // Update restriction display
                        rstate = new RState(xAppInfo.getUid(), mRestrictionName, null, mVersion);
                        holder.imgCbRestricted.setImageBitmap(getCheckBoxImage(rstate, methodExpert));
                        holder.imgCbAsk.setImageBitmap(getAskBoxImage(rstate, methodExpert));

                        // Notify restart
                        if (!newState.equals(oldState))
                            Toast.makeText(ActivityMain.this, getString(R.string.msg_restart), Toast.LENGTH_LONG)
                                    .show();

                        // Display new state
                        showState();

                        holder.pbRunning.setVisibility(View.GONE);
                        holder.imgCbRestricted.setVisibility(View.VISIBLE);
                    }
                }.executeOnExecutor(mExecutor);
            }

            private void showState() {
                state = xAppInfo.getState(ActivityMain.this);
                if (state == ApplicationInfoEx.STATE_ATTENTION)
                    holder.vwState
                            .setBackgroundColor(getResources().getColor(getThemed(R.attr.color_state_attention)));
                else if (state == ApplicationInfoEx.STATE_SHARED)
                    holder.vwState
                            .setBackgroundColor(getResources().getColor(getThemed(R.attr.color_state_shared)));
                else
                    holder.vwState
                            .setBackgroundColor(getResources().getColor(getThemed(R.attr.color_state_restricted)));
            }
        }

        @Override
        @SuppressLint("InflateParams")
        public View getView(int position, View convertView, ViewGroup parent) {
            final ViewHolder holder;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.mainentry, null);
                holder = new ViewHolder(convertView, position);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
                holder.position = position;
            }

            // Get info
            final ApplicationInfoEx xAppInfo = getItem(holder.position);

            // Set data
            holder.row.setBackgroundColor(Color.TRANSPARENT);
            holder.vwState.setBackgroundColor(Color.TRANSPARENT);
            holder.llAppType.setBackgroundColor(Color.TRANSPARENT);
            holder.imgIcon.setVisibility(View.INVISIBLE);
            holder.tvName.setText(xAppInfo.toString());
            holder.tvName.setTypeface(null, Typeface.NORMAL);
            holder.imgUsed.setVisibility(View.INVISIBLE);
            holder.imgGranted.setVisibility(View.INVISIBLE);
            holder.imgInternet.setVisibility(View.INVISIBLE);
            holder.imgFrozen.setVisibility(View.INVISIBLE);
            holder.imgSettings.setVisibility(View.GONE);
            holder.imgCbRestricted.setVisibility(View.INVISIBLE);
            holder.imgCbAsk.setVisibility(View.INVISIBLE);
            holder.tvName.setEnabled(false);
            holder.imgCbRestricted.setEnabled(false);

            holder.imgIcon.setClickable(false);
            holder.imgCbRestricted.setClickable(false);
            holder.imgCbAsk.setClickable(false);

            // Listen for multiple select
            holder.llName.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    if (mListAppSelected.contains(xAppInfo)) {
                        mSelecting = false;
                        mListAppSelected.clear();
                        mAppAdapter.notifyDataSetChanged();
                    } else {
                        mSelecting = true;
                        mListAppSelected.add(xAppInfo);
                        holder.row.setBackgroundColor(mHighlightColor);
                    }
                    showStats();
                    return true;
                }
            });

            // Listen for application selection
            holder.llName.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(final View view) {
                    if (mSelecting) {
                        if (mListAppSelected.contains(xAppInfo)) {
                            mListAppSelected.remove(xAppInfo);
                            holder.row.setBackgroundColor(Color.TRANSPARENT);
                            if (mListAppSelected.size() == 0)
                                mSelecting = false;
                        } else {
                            mListAppSelected.add(xAppInfo);
                            holder.row.setBackgroundColor(mHighlightColor);
                        }
                        showStats();
                    } else {
                        Intent intentSettings = new Intent(ActivityMain.this, ActivityApp.class);
                        intentSettings.putExtra(ActivityApp.cUid, xAppInfo.getUid());
                        intentSettings.putExtra(ActivityApp.cRestrictionName, mRestrictionName);
                        ActivityMain.this.startActivity(intentSettings);
                    }
                }
            });

            // Async update
            new HolderTask(position, holder, xAppInfo).executeOnExecutor(mExecutor, (Object) null);

            return convertView;
        }
    }

    // Helper methods

    private void setProgress(String text, int progress, int max) {
        // Set up the progress bar
        if (mProgressWidth == 0) {
            final View vProgressEmpty = (View) findViewById(R.id.vProgressEmpty);
            mProgressWidth = vProgressEmpty.getMeasuredWidth();
        }
        // Display stuff
        TextView tvState = (TextView) findViewById(R.id.tvState);
        if (text != null)
            tvState.setText(text);
        if (max == 0)
            max = 1;
        mProgress = (int) ((float) mProgressWidth) * progress / max;

        View vProgressFull = (View) findViewById(R.id.vProgressFull);
        vProgressFull.getLayoutParams().width = mProgress;
    }

    private int getSelectedCategory(final int userId) {
        int pos = 0;
        String restrictionName = PrivacyManager.getSetting(userId, PrivacyManager.cSettingSelectedCategory, null);
        if (restrictionName != null)
            for (String restriction : PrivacyManager.getRestrictions(this).values()) {
                pos++;
                if (restrictionName.equals(restriction))
                    break;
            }
        return pos;
    }

    private void checkLicense() {
        if (!Util.isProEnabled() && Util.hasProLicense(this) == null)
            if (Util.isProEnablerInstalled(this))
                try {
                    int uid = getPackageManager().getPackageInfo("biz.bokhorst.xprivacy.pro",
                            0).applicationInfo.uid;
                    PrivacyManager.deleteRestrictions(uid, null, true);
                    Util.log(null, Log.INFO, "Licensing: check");
                    startActivityForResult(new Intent("biz.bokhorst.xprivacy.pro.CHECK"), ACTIVITY_LICENSE);
                } catch (Throwable ex) {
                    Util.bug(null, ex);
                }
    }
}