public final class ProfileEditFragment extends PreferenceFragment implements
OnPreferenceClickListener, 
SharedPreferences.OnSharedPreferenceChangeListener {

    String filename = String.valueOf(System.currentTimeMillis());
    static String name;
    boolean isSavedProfile = false;
    boolean prefChange = true;
    boolean uiRefreshWarning = false;
    boolean taskbarSettingsPrefEnabled = false;

    /* The activity that creates an instance of this fragment must
     * implement this interface in order to receive event call backs. */
    public interface Listener {
        void showDeleteDialog();
        void showExpertModeDialog();
        void showReloadDialog(String filename, boolean isEdit, boolean returnToList);
        String getProfileTitle(String filename) throws IOException;
        void setDefaultDensity();
        String generateBlurb(String key, String value);
        void setEmptyTitle(String title);
        void showUiRefreshDialog(String filename, boolean isEdit, boolean returnToList);
    }

    // Use this instance of the interface to deliver action events
    static Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Set values
        setRetainInstance(true);
        setHasOptionsMenu(true);

        // Show the Up button in the action bar.
        ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // Animate elevation change
            if(getActivity().findViewById(R.id.layoutMain).getTag().equals("main-layout-large")) {
                LinearLayout profileViewEdit = getActivity().findViewById(R.id.profileViewEdit);
                LinearLayout profileList = getActivity().findViewById(R.id.profileList);
                profileList.animate().z(0f);
                profileViewEdit.animate().z(getResources().getDimensionPixelSize(R.dimen.profile_view_edit_elevation));
            }

            // Remove dividers
            View rootView = getView();
            if(rootView != null) {
                ListView list = rootView.findViewById(android.R.id.list);
                if(list != null) list.setDivider(null);
            }
        }
    }

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

        // Get filename
        if(getArguments() != null)
            if(getArguments().getString("filename") != null) {
                filename = getArguments().getString("filename");
                isSavedProfile = true;
            }

        SharedPreferences prefNew = U.getPrefNew(getActivity());
        SharedPreferences prefMain = U.getPrefMain(getActivity());

        // If editing a previously saved profile, begin loading saved settings
        if(isSavedProfile) {
            SharedPreferences prefSaved = U.getPrefSaved(getActivity(), filename);
            SharedPreferences.Editor editor = prefNew.edit();

            if("fallback".equals(prefSaved.getString("rotation_lock_new", "fallback")) && prefSaved.getBoolean("rotation_lock", false))
                editor.putString("rotation_lock_new", "landscape");
            else
                editor.putString("rotation_lock_new", prefSaved.getString("rotation_lock_new", "do-nothing"));

            if("fallback".equals(prefSaved.getString("immersive_new", "fallback")) && prefSaved.getBoolean("immersive", false))
                editor.putString("immersive_new", "immersive-mode");
            else
                editor.putString("immersive_new", prefSaved.getString("immersive_new", "do-nothing"));

            if(prefSaved.getBoolean("overscan", false)) {
                editor.putBoolean("overscan", true);
                editor.putInt("overscan_left", prefSaved.getInt("overscan_left", 0));
                editor.putInt("overscan_right", prefSaved.getInt("overscan_right", 0));
                editor.putInt("overscan_top", prefSaved.getInt("overscan_top", 0));
                editor.putInt("overscan_bottom", prefSaved.getInt("overscan_bottom", 0));
            } else {
                editor.putBoolean("overscan", false);
                editor.putInt("overscan_left", 0);
                editor.putInt("overscan_right", 0);
                editor.putInt("overscan_top", 0);
                editor.putInt("overscan_bottom", 0);
            }

            if(prefMain.getBoolean("expert_mode", false)) {
                if("reset".equals(prefSaved.getString("size", "reset"))) {
                    editor.putString("size", Integer.toString(prefMain.getInt("width", 0))
                            + "x"
                            + Integer.toString(prefMain.getInt("height", 0)));

                    editor.putBoolean("size-reset", true);
                } else
                    editor.putString("size", prefSaved.getString("size", "reset"));

                if("reset".equals(prefSaved.getString("density", "reset"))) {
                    editor.putString("density", Integer.toString(U.getSystemProperty("ro.sf.lcd_density", prefMain.getInt("density", 0))));
                    editor.putBoolean("density-reset", true);
                } else
                    editor.putString("density", prefSaved.getString("density", "reset"));
            } else {
                if(prefSaved.getBoolean("size-reset", false)) {
                    editor.remove("size-reset");

                    String nativeRes = Integer.toString(prefMain.getInt("width", 0))
                            + "x"
                            + Integer.toString(prefMain.getInt("height", 0));

                    if(nativeRes.equals(prefSaved.getString("size", "reset")))
                        editor.putString("size", "reset");
                    else
                        editor.putString("size", prefSaved.getString("size", "reset"));
                } else
                    editor.putString("size", prefSaved.getString("size", "reset"));

                if(prefSaved.getBoolean("density-reset", false)) {
                    editor.remove("density-reset");

                    if(Integer.toString(U.getSystemProperty("ro.sf.lcd_density", prefMain.getInt("density", 0))).equals(prefSaved.getString("density", "reset")))
                        editor.putString("density", "reset");
                    else
                        editor.putString("density", prefSaved.getString("density", "reset"));
                } else
                    editor.putString("density", prefSaved.getString("density", "reset"));
            }

            if(getActivity().getPackageManager().hasSystemFeature("com.cyanogenmod.android")
                    && Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1
                    && prefSaved.getString("ui_refresh", "do-nothing").equals("system-ui")) {
                editor.putString("ui_refresh", "activity-manager");
            } else
                editor.putString("ui_refresh", prefSaved.getString("ui_refresh", "do-nothing"));

            editor.putString("profile_name", prefSaved.getString("profile_name", getResources().getString(R.string.action_new)));
            editor.putBoolean("bluetooth_on", prefSaved.getBoolean("bluetooth_on", false));
            editor.putBoolean("wifi_on", prefSaved.getBoolean("wifi_on", false));
            editor.putBoolean("daydreams_on", prefSaved.getBoolean("daydreams_on", false));
            editor.putBoolean("show_touches", prefSaved.getBoolean("show_touches", false));
            editor.putBoolean("backlight_off", prefSaved.getBoolean("backlight_off", false));
            editor.putBoolean("vibration_off", prefSaved.getBoolean("vibration_off", false));
            editor.putBoolean("chrome", prefSaved.getBoolean("chrome", false));
            editor.putBoolean("navbar", prefSaved.getBoolean("navbar", false));
            editor.putBoolean("freeform", prefSaved.getBoolean("freeform", false));
            editor.putString("screen_timeout", prefSaved.getString("screen_timeout", "do-nothing"));
            editor.putString("hdmi_rotation", prefSaved.getString("hdmi_rotation", "landscape"));
            editor.putBoolean("taskbar", prefSaved.getBoolean("taskbar", false));
            editor.putBoolean("clear_home", prefSaved.getBoolean("clear_home", false));
            editor.apply();

            name = prefSaved.getString("profile_name", getResources().getString(R.string.action_new));
            prefChange = false;
        } else
            name = prefNew.getString("profile_name", getResources().getString(R.string.action_new));

        // Add preferences
        addPreferencesFromResource(R.xml.profile_name_setting);

        if(prefMain.getBoolean("expert_mode", false))
            addPreferencesFromResource(R.xml.display_settings_expert);
        else
            addPreferencesFromResource(R.xml.display_settings);

        if(U.isPlayStoreInstalled(getActivity())
                && U.isPlayStoreRelease(getActivity())
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            addPreferencesFromResource(R.xml.desktop_optimization);
            findPreference("taskbar_settings").setOnPreferenceClickListener(this);
            taskbarSettingsPrefEnabled = true;
        } else
            addPreferencesFromResource(R.xml.desktop_optimization_alt);

        addPreferencesFromResource(R.xml.additional_settings);

        // Modifications for certain scenarios
        if(!prefMain.getBoolean("expert_mode", false) && prefMain.getBoolean("landscape", false)) {
            ListPreference size = (ListPreference) findPreference("size");
            size.setEntryValues(R.array.pref_resolution_list_values_landscape);
        }

        if(getActivity().getPackageManager().hasSystemFeature("com.cyanogenmod.android")
                && Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
            ListPreference uiRefresh = (ListPreference) findPreference("ui_refresh");
            uiRefresh.setEntries(R.array.pref_ui_refresh_list_alt);
            uiRefresh.setEntryValues(R.array.pref_ui_refresh_list_values_alt);
        }

        if(U.isInNonRootMode(getActivity())) {
            ListPreference uiRefresh = (ListPreference) findPreference("ui_refresh");
            uiRefresh.setEntries(R.array.pref_ui_refresh_list_non_root);
        }

        // Set OnClickListeners for certain preferences
        findPreference("daydreams_on").setOnPreferenceClickListener(this);
        findPreference("overscan_settings").setOnPreferenceClickListener(this);
        findPreference("freeform").setOnPreferenceClickListener(this);

        if(prefMain.getBoolean("expert_mode", false))
            findPreference("size").setOnPreferenceClickListener(this);

        // Bind the summaries of EditText/List/Dialog/Ringtone preferences to
        // their values. When their values change, their summaries are updated
        // to reflect the new value, per the Android Design guidelines.
        U.bindPreferenceSummaryToValue(findPreference("profile_name"), opcl);
        U.bindPreferenceSummaryToValue(findPreference("size"), opcl);
        U.bindPreferenceSummaryToValue(findPreference("density"), opcl);
        U.bindPreferenceSummaryToValue(findPreference("rotation_lock_new"), opcl);
        U.bindPreferenceSummaryToValue(findPreference("ui_refresh"), opcl);
        U.bindPreferenceSummaryToValue(findPreference("screen_timeout"), opcl);
        U.bindPreferenceSummaryToValue(findPreference("immersive_new"), opcl);
        U.bindPreferenceSummaryToValue(findPreference("hdmi_rotation"), opcl);

        // Disable unsupported preferences
        if(!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH))
            disablePreference("bluetooth_on", "additional_settings", true);

        if(!U.canEnableWifi(getActivity()))
            disablePreference("wifi_on", "additional_settings", true);

        if(!getActivity().getPackageManager().hasSystemFeature("com.cyanogenmod.android"))
            disablePreference("navbar", "additional_settings", true);

        if(!U.canEnableOverscan())
            disablePreference("overscan_settings", "display_settings", false);

        if(!U.canEnableImmersiveMode())
            disablePreference("immersive_new", "display_settings", false);

        if(!U.canEnableFreeform(getActivity()))
            disablePreference("freeform", "desktop_optimization", true);

        if(U.getChromePackageName(getActivity()) == null)
            disablePreference("chrome", "desktop_optimization", true);

        if(U.isInNonRootMode(getActivity())) {
            disablePreference("hdmi_rotation", "display_settings", false);
            disablePreference("chrome", "desktop_optimization", false);

            if(!U.hasSupportLibrary(getActivity()))
                disablePreference("show_touches", "additional_settings", false);
        }

        if(U.getTaskbarPackageName(getActivity()) == null || !U.isPlayStoreRelease(getActivity()))
            disablePreference("taskbar", "desktop_optimization", true);

        uiRefreshWarning = true;
    }

    @SuppressWarnings("deprecation")
    @Override
    public void onResume() {
        super.onResume();
        SharedPreferences prefNew = U.getPrefNew(getActivity());

        // Change window title
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
            getActivity().setTitle(prefNew.getString("profile_name", getResources().getString(R.string.action_new)));
        else
            getActivity().setTitle(" " + prefNew.getString("profile_name", getResources().getString(R.string.action_new)));

        if(U.canEnableOverscan()) {
            if(prefNew.getBoolean("overscan", false))
                findPreference("overscan_settings").setSummary(getResources().getString(R.string.enabled));
            else
                findPreference("overscan_settings").setSummary(getResources().getString(R.string.disabled));
        }

        if(taskbarSettingsPrefEnabled) {
            findPreference("taskbar_settings").setTitle(
                    U.getTaskbarPackageName(getActivity()) == null
                            ? R.string.pref_taskbar_settings_title_install
                            : R.string.pref_taskbar_settings_title_open);
        }
    }

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

        // Register listener to check for changed preferences
        PreferenceManager.getDefaultSharedPreferences(getActivity()).registerOnSharedPreferenceChangeListener(this);
    }

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

        // Unregister listener
        PreferenceManager.getDefaultSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.profile_edit, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()) {
            case android.R.id.home:
                // Override default Android "up" behavior to instead mimic the back button
                onBackPressed(filename, false, true);
                return true;

                // Save button
            case R.id.action_save:
                onBackPressed(filename, false, false);
                return true;

                // Delete button
            case R.id.action_delete:
                // Show toast if this is the currently active profile
                SharedPreferences prefCurrent = U.getPrefCurrent(getActivity());
                if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                    SharedPreferences prefSaved = U.getPrefQuickActions(getActivity());
                    if(filename.equals(prefSaved.getString("original_filename", "0")))
                        U.showToast(getActivity(), R.string.deleting_current_profile);
                    else
                        listener.showDeleteDialog();
                } else if(filename.equals(prefCurrent.getString("filename", "0")))
                    U.showToast(getActivity(), R.string.deleting_current_profile);
                else
                    listener.showDeleteDialog();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        prefChange = true;

        switch(key) {
            case "ui_refresh":
                if(uiRefreshWarning && "activity-manager".equals(sharedPreferences.getString(key, "do-nothing")))
                    U.showToastLong(getActivity(), R.string.am_restart_warning);
                break;
            case "density":
                if(sharedPreferences.getString(key, "reset").isEmpty())
                    listener.setDefaultDensity();
                break;
            case "profile_name":
                if(!sharedPreferences.getString(key, getResources().getString(R.string.action_new)).isEmpty()) {
                    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                        getActivity().setTitle(sharedPreferences.getString(key, getResources().getString(R.string.action_new)));
                    else
                        getActivity().setTitle(" " + sharedPreferences.getString(key, getResources().getString(R.string.action_new)));
                }
                break;
            case "rotation_lock_new":
                if(!"do-nothing".equals(sharedPreferences.getString(key, "fallback"))
                        && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                        && !Settings.canDrawOverlays(getActivity())
                        && !U.getPrefMain(getActivity()).getBoolean("dont_show_system_alert_dialog", false)) {
                    DialogFragment fragment = new SystemAlertPermissionDialogFragment();
                    fragment.show(getFragmentManager(), "SystemAlertPermissionDialogFragment");
                }
                break;
            case "hdmi_rotation":
                U.showToastLong(getActivity(), R.string.hdmi_output_toast);
                break;
        }
    }

    /**
     * A preference value change listener that updates the preference's summary
     * to reflect its new value.
     */
    public static Preference.OnPreferenceChangeListener opcl = new Preference.OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference, Object value) {
        String stringValue = value.toString();

        // Damage control if user inputs an empty profile name
        if(stringValue.isEmpty() && preference.getKey().equals("profile_name")) {
            stringValue = name;
            listener.setEmptyTitle(name);
        }

        if(preference instanceof ListPreference) {
            // For list preferences, look up the correct display value in
            // the preference's 'entries' list.
            ListPreference listPreference = (ListPreference) preference;
            int index = listPreference.findIndexOfValue(stringValue);
            CharSequence summary = index >= 0 ? listPreference.getEntries()[index] : null;

            // Set the summary to reflect the new value.
            if(summary == null) {
                stringValue = listener.generateBlurb(preference.getKey(), stringValue);
                preference.setSummary(stringValue);
            } else
                preference.setSummary(summary);
        } else {
            if((preference.getKey().equals("density") && !stringValue.isEmpty())
                    || preference.getKey().equals("size"))
                stringValue = listener.generateBlurb(preference.getKey(), stringValue);

            // For all other preferences, set the summary to the value's
            // simple string representation.
            preference.setSummary(stringValue);
        }

        return true;
        }
    };

    private void saveProfile() throws IOException {
        // Save preferences to XML file
        SharedPreferences prefNew = U.getPrefNew(getActivity());
        SharedPreferences prefSaved = U.getPrefSaved(getActivity(), filename);
        SharedPreferences prefMain = U.getPrefMain(getActivity());
        SharedPreferences.Editor editor = prefSaved.edit();

        if(prefNew.getString("profile_name", getResources().getString(R.string.action_new)).isEmpty())
            editor.putString("profile_name", name);
        else
            editor.putString("profile_name", prefNew.getString("profile_name", getResources().getString(R.string.action_new)));

        editor.putBoolean("overscan", prefNew.getBoolean("overscan", false));
        editor.putInt("overscan_left", prefNew.getInt("overscan_left", 0));
        editor.putInt("overscan_right", prefNew.getInt("overscan_right", 0));
        editor.putInt("overscan_top", prefNew.getInt("overscan_top", 0));
        editor.putInt("overscan_bottom", prefNew.getInt("overscan_bottom", 0));
        editor.putBoolean("bluetooth_on", prefNew.getBoolean("bluetooth_on", false));
        editor.putBoolean("wifi_on", prefNew.getBoolean("wifi_on", false));
        editor.putBoolean("daydreams_on", prefNew.getBoolean("daydreams_on", false));
        editor.putBoolean("show_touches", prefNew.getBoolean("show_touches", false));
        editor.putString("rotation_lock_new", prefNew.getString("rotation_lock_new", "do-nothing"));
        editor.putBoolean("backlight_off", prefNew.getBoolean("backlight_off", false));
        editor.putBoolean("vibration_off", prefNew.getBoolean("vibration_off", false));
        editor.putString("size", prefNew.getString("size", "reset"));
        editor.putString("density", prefNew.getString("density", "reset"));
        editor.putBoolean("chrome", prefNew.getBoolean("chrome", false));
        editor.putString("ui_refresh", prefNew.getString("ui_refresh", "do-nothing"));
        editor.putBoolean("navbar", prefNew.getBoolean("navbar", false));
        editor.putString("screen_timeout", prefNew.getString("screen_timeout", "do-nothing"));
        editor.putString("immersive_new", prefNew.getString("immersive_new", "do-nothing"));
        editor.putBoolean("freeform", prefNew.getBoolean("freeform", false));
        editor.putString("hdmi_rotation", prefNew.getString("hdmi_rotation", "landscape"));
        editor.putBoolean("taskbar", prefNew.getBoolean("taskbar", false));
        editor.putBoolean("clear_home", prefNew.getBoolean("clear_home", false));

        if(prefMain.getBoolean("expert_mode", false)) {
            if(prefNew.getBoolean("size-reset", false))
                editor.putBoolean("size-reset", true);

            if(prefNew.getBoolean("density-reset", false))
                editor.putBoolean("density-reset", true);
        }

        editor.apply();

        // Save profile name to file, for use with MainActivity
        FileOutputStream output = getActivity().openFileOutput(filename, Context.MODE_PRIVATE);
        output.write(prefSaved.getString("profile_name", getResources().getString(R.string.action_new)).getBytes());
        output.close();

        // Refresh list of profiles
        U.listProfilesBroadcast(getActivity());

        U.showToast(getActivity(), R.string.profile_saved);

        if(prefMain.getBoolean("show-welcome-message", false)) {
            SharedPreferences.Editor prefMainEditor = prefMain.edit();
            prefMainEditor.remove("show-welcome-message");
            prefMainEditor.apply();
        }
    }

    public void onBackPressed(String filename, boolean isEdit, boolean returnToList) {
        // Unregister listener
        PreferenceManager.getDefaultSharedPreferences(getActivity()).unregisterOnSharedPreferenceChangeListener(this);

        if(prefChange) {
            SharedPreferences prefNew = U.getPrefNew(getActivity());
            SharedPreferences prefMain = U.getPrefMain(getActivity());

            String requestedRes = prefNew.getString("size", "reset");
            String requestedDpi = prefNew.getString("density", "reset");

            // Check to see if the user is trying to set a blacklisted resolution/DPI combo
            boolean blacklisted = U.isBlacklisted(getActivity(), requestedRes, requestedDpi);

            if(blacklisted && !prefMain.getBoolean("expert_mode", false))
                U.showToastLong(getActivity(), R.string.blacklisted);
            else if(!("reset".equals(requestedRes)
                    && "reset".equals(requestedDpi))
                    && "do-nothing".equals(prefNew.getString("ui_refresh", "do-nothing"))
                    && !prefMain.getBoolean("expert_mode", false)
                    && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                listener.showUiRefreshDialog(filename, isEdit, returnToList);
            } else
                preSave(filename, isEdit, returnToList);
        } else {
            // Cleanup
            SharedPreferences prefNew = U.getPrefNew(getActivity());
            SharedPreferences.Editor prefNewEditor = prefNew.edit();
            prefNewEditor.clear();
            prefNewEditor.apply();

            finish(filename, isEdit, returnToList);
        }
    }

    @Override
    public boolean onPreferenceClick(Preference p) {
        SharedPreferences prefNew = U.getPrefNew(getActivity());
        SharedPreferences prefMain = U.getPrefMain(getActivity());

        switch(p.getKey()) {
            case "daydreams_on":
                if(prefNew.getBoolean("daydreams_on", true))
                    U.showToastLong(getActivity(), R.string.configure_daydreams);
                break;
            case "overscan_settings":
                Intent intent = new Intent(getActivity(), FragmentContainerActivity.class);
                intent.putExtra("tag", "OverscanFragment");
                intent.putExtra("filename", filename);
                getActivity().startActivityForResult(intent, 42);
                break;
            case "size":
                if(prefMain.getBoolean("expert_mode", false))
                    listener.showExpertModeDialog();
                break;
            case "freeform":
                if(prefNew.getBoolean("freeform", true) && !U.hasFreeformSupport(getActivity())) {
                    if(!U.isInNonRootMode(getActivity())
                            && "do-nothing".equals(prefNew.getString("ui_refresh", "do-nothing")))
                        U.showToastLong(getActivity(), R.string.freeform_message);
                    else if(U.isInNonRootMode(getActivity())
                            && !"activity-manager".equals(prefNew.getString("ui_refresh", "do-nothing")))
                        U.showToastLong(getActivity(), R.string.freeform_message_non_root);
                }
                break;
            case "taskbar_settings":
                PackageManager packageManager = getActivity().getPackageManager();
                String packageName = U.getTaskbarPackageName(getActivity());
                Intent intent2;

                if(packageName == null) {
                    intent2 = new Intent(Intent.ACTION_VIEW);
                    intent2.setData(Uri.parse("https://play.google.com/store/apps/details?id=com.farmerbb.taskbar"));
                } else
                    intent2 = packageManager.getLaunchIntentForPackage(packageName);

                if(intent2 != null) {
                    intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                    try {
                        startActivity(intent2);
                    } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
                }

                break;
        }

        return true;
    }

    public void onReloadDialogPositiveClick(String filename, boolean isEdit, boolean returnToList) {
        SharedPreferences prefNew = U.getPrefNew(getActivity());
        SharedPreferences prefCurrent = U.getPrefCurrent(getActivity());
        SharedPreferences prefSaved = U.getPrefSaved(getActivity(), prefCurrent.getString("filename", "0"));
        SharedPreferences.Editor editor = prefCurrent.edit();

        if(!prefNew.getString("ui_refresh", "do-nothing").equals(prefSaved.getString("ui_refresh", "do-nothing"))) {
            editor.putBoolean("force_ui_refresh", true);
            editor.apply();
        }

        try {
            saveProfile();
        } catch(IOException e) {
            // Show error message as toast if file fails to save
            U.showToast(getActivity(), R.string.failed_to_save);
        }

        U.loadProfile(getActivity(), filename);

        // Cleanup
        SharedPreferences.Editor prefNewEditor = prefNew.edit();
        prefNewEditor.clear();
        prefNewEditor.apply();

        finish(filename, isEdit, returnToList);
    }

    public void onReloadDialogNegativeClick(String filename, boolean isEdit, boolean returnToList) {
        try {
            saveProfile();
        } catch(IOException e) {
            // Show error message as toast if file fails to save
            U.showToast(getActivity(), R.string.failed_to_save);
        }

        // Cleanup
        SharedPreferences prefNew = U.getPrefNew(getActivity());
        SharedPreferences.Editor prefNewEditor = prefNew.edit();
        prefNewEditor.clear();
        prefNewEditor.apply();

        finish(filename, isEdit, returnToList);
    }

    public void deleteProfile() {
        // Build the pathname to delete file, then perform delete operation
        File fileToDelete = new File(getActivity().getFilesDir() + File.separator + filename);
        fileToDelete.delete();

        File xmlFileToDelete = new File(getActivity().getFilesDir().getParent() + File.separator + "shared_prefs" + File.separator + filename + ".xml");
        xmlFileToDelete.delete();

        U.showToast(getActivity(), R.string.profile_deleted);

        // Cleanup
        SharedPreferences prefNew = U.getPrefNew(getActivity());
        SharedPreferences.Editor prefNewEditor = prefNew.edit();
        prefNewEditor.clear();
        prefNewEditor.apply();

        SharedPreferences prefMain = U.getPrefMain(getActivity());

        Fragment fragment;

        if(prefMain.getBoolean("show-welcome-message", false)) {
            Bundle bundle = new Bundle();
            bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false));

            fragment = new WelcomeFragment();
            fragment.setArguments(bundle);
        } else {
            // Refresh list of profiles
            U.listProfilesBroadcast(getActivity());

            // Add ProfileListFragment or WelcomeFragment
            if(getActivity().findViewById(R.id.layoutMain).getTag().equals("main-layout-normal"))
                fragment = new ProfileListFragment();
            else {
                Bundle bundle = new Bundle();
                bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false));

                fragment = new WelcomeFragment();
                fragment.setArguments(bundle);
            }
        }

        getFragmentManager()
                .beginTransaction()
                .replace(R.id.profileViewEdit, fragment, "ProfileListFragment")
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
                .commit();
    }

    private void finish(String filename, boolean isEdit, boolean returnToList) {
        Fragment fragment;
        String id;
        int transition;

        // returnToList is true if we are editing a profile by long-pressing an entry in ProfileListFragment
        // if we were not currently viewing or editing a profile, or if we are creating a new profile.
        // This method should then return the user to ProfileListFragment.
        if(returnToList) {
            if(getActivity().findViewById(R.id.layoutMain).getTag().equals("main-layout-normal"))
                fragment = new ProfileListFragment();
            else {
                SharedPreferences prefMain = U.getPrefMain(getActivity());

                Bundle bundle = new Bundle();
                bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false));

                fragment = new WelcomeFragment();
                fragment.setArguments(bundle);
            }

            id = "ProfileListFragment";
            transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
        }

        // returnToList is false if we are editing a profile by using the Edit button in ProfileViewFragment,
        // or if we are selecting / long-pressing a new entry in ProfileListFragment while viewing or editing
        // an exiting profile. This method should then return the user to either ProfileEditFragment or
        // ProfileViewFragment, depending on if the isEdit value is true or false respectively.

        else {
            Bundle bundle = new Bundle();
            bundle.putString("filename", filename);

            if(isEdit) {
                fragment = new ProfileEditFragment();
                fragment.setArguments(bundle);
                id = "ProfileEditFragment";
                transition = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
            } else {
                try {
                    bundle.putString("title", listener.getProfileTitle(filename));
                } catch (IOException e) { /* Gracefully fail */ }

                fragment = new ProfileViewFragment();
                fragment.setArguments(bundle);
                id = "ProfileViewFragment";
                transition = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
            }
        }

        getFragmentManager()
                .beginTransaction()
                .replace(R.id.profileViewEdit, fragment, id)
                .setTransition(transition)
                .commit();
    }

    public String getFilename() {
        return filename;
    }

    public void setPrefChange(boolean prefChange) {
        if(!this.prefChange)
            this.prefChange = prefChange;
    }

    public void preSave(String filename, boolean isEdit, boolean returnToList) {
        SharedPreferences prefCurrent = U.getPrefCurrent(getActivity());
        SharedPreferences prefNew = U.getPrefNew(getActivity());

        // Show dialog if this is the currently active profile
        if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
            SharedPreferences prefSaved = U.getPrefQuickActions(getActivity());
            if(this.filename.equals(prefSaved.getString("original_filename", "0")))
                listener.showReloadDialog(filename, isEdit, returnToList);
            else {
                try {
                    saveProfile();
                } catch (IOException e) {
                    // Show error message as toast if file fails to save
                    U.showToast(getActivity(), R.string.failed_to_save);
                }

                // Cleanup
                SharedPreferences.Editor prefNewEditor = prefNew.edit();
                prefNewEditor.clear();
                prefNewEditor.apply();

                finish(filename, isEdit, returnToList);
            }
        } else if(this.filename.equals(prefCurrent.getString("filename", "0")))
            listener.showReloadDialog(filename, isEdit, returnToList);
        else {
            try {
                saveProfile();
            } catch(IOException e) {
                // Show error message as toast if file fails to save
                U.showToast(getActivity(), R.string.failed_to_save);
            }

            // Cleanup
            SharedPreferences.Editor prefNewEditor = prefNew.edit();
            prefNewEditor.clear();
            prefNewEditor.apply();

            finish(filename, isEdit, returnToList);
        }
    }

    private void disablePreference(String preferenceName, String categoryName, boolean shouldClear) {
        Preference pref = getPreferenceScreen().findPreference(preferenceName);
        if(pref == null) return;

        SharedPreferences prefNew = U.getPrefNew(getActivity());
        if(shouldClear && prefNew.getBoolean(preferenceName, false)) {
            SharedPreferences.Editor editor = prefNew.edit();
            editor.putBoolean(preferenceName, false);
            editor.apply();
        }

        PreferenceCategory category = (PreferenceCategory) getPreferenceScreen().findPreference(categoryName);
        category.removePreference(pref);
    }
} public final class ProfileListFragment extends Fragment {

    // This is used by other components to refresh the list of profiles
    private final class ListNotesReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(getActivity() != null) listProfiles();
        }
    }

    private final class ProfileListAdapter extends ArrayAdapter<String> {
        private ProfileListAdapter(Context context, ArrayList<String> notes) {
            super(context, R.layout.row_layout, notes);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // Get the data item for this position
            String profile = getItem(position);
            // Check if an existing view is being reused, otherwise inflate the view
            if(convertView == null) {
                convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_layout, parent, false);
            }
            // Lookup view for data population
            TextView profileTitle = convertView.findViewById(R.id.profileTitle);
            // Populate the data into the template view using the data object
            profileTitle.setText(profile);

            // Return the completed view to render on screen
            return convertView;
        }
    }

    IntentFilter filter = new IntentFilter(U.LIST_PROFILES);
    ListNotesReceiver receiver = new ListNotesReceiver();

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs. */
    public interface Listener {
        void viewProfile(String filename);
        void editProfile(String filename);
        SharedPreferences getPrefCurrent();
        SharedPreferences getPrefQuickActions();
        TextView getHelperText();
        boolean isDebugModeEnabled(boolean isClick);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                                         + " must implement Listener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_profile_list, container, false);
    }

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

        // Set values
        setRetainInstance(true);
        setHasOptionsMenu(true);
    }

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

        if(getId() == R.id.profileViewEdit) {
            // Change window title
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                getActivity().setTitle(getResources().getString(R.string.app_name));
            else
                getActivity().setTitle(" " + getResources().getString(R.string.app_name));

            // Don't show the Up button in the action bar, and disable the button
            ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            ((AppCompatActivity) getActivity()).getSupportActionBar().setHomeButtonEnabled(false);
        }

        // Refresh list of profiles onResume (instead of onCreate) to reflect additions/deletions
        listProfiles();
    }

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

        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(receiver, filter);

        // Floating action button
        FloatingActionButton floatingActionButton = getActivity().findViewById(R.id.button_floating_action);
        floatingActionButton.setImageResource(R.drawable.ic_action_new);
        if(getActivity().findViewById(R.id.layoutMain).getTag().equals("main-layout-large"))
            floatingActionButton.hide();

        if(getId() == R.id.profileViewEdit) {
            floatingActionButton.show();
            floatingActionButton.setOnClickListener(v -> {
                DialogFragment newProfileFragment = new NewProfileDialogFragment();
                newProfileFragment.show(getFragmentManager(), "new");
            });
        }
    }

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

        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(receiver);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Inflate action bar menu
        if(getId() == R.id.profileViewEdit)
            inflater.inflate(R.menu.main, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle presses on the action bar items
        switch(item.getItemId()) {
            // Quick Actions button
            case R.id.action_quick:
                Intent intentQuick = new Intent(getActivity(), TaskerQuickActionsActivity.class);
                intentQuick.putExtra("launched-from-app", true);
                startActivity(intentQuick);
                return true;

            // Settings button
            case R.id.action_settings:
                Intent intentSettings = new Intent(getActivity(), FragmentContainerActivity.class);
                intentSettings.putExtra("tag", "SettingsFragment");
                startActivity(intentSettings);
                return true;

            // About button
            case R.id.action_about:
                DialogFragment aboutFragment = new AboutDialogFragment();
                aboutFragment.show(getFragmentManager(), "about");
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void listProfiles() {
        final TextView helper = getActivity().findViewById(R.id.textView1);
        final ListView listView = getActivity().findViewById(R.id.listView1);

        // Get array of profiles
        final String[][] profileList = U.listProfiles(getActivity());

        // If there are no saved profiles, then display the empty view
        if(profileList == null) {
            TextView empty = getActivity().findViewById(R.id.empty);
            empty.setText(getResources().getString(R.string.no_profiles_found));
            empty.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.accent));

            listView.setAdapter(null);
            listView.setEmptyView(empty);

            if(listener.isDebugModeEnabled(false)) {
                helper.setText(R.string.debug_mode_enabled);
                helper.setTextColor(Color.WHITE);
                helper.setBackgroundColor(Color.RED);
            } else {
                helper.setText(" ");
                helper.setBackgroundColor(Color.WHITE);
            }
        } else {
            final int numOfFiles = profileList[1].length;

            // Create ArrayList and populate with list of profiles
            ArrayList<String> arrayList = new ArrayList<>(numOfFiles);
            arrayList.addAll(Arrays.asList(profileList[1]));

            // Create the custom adapter to bind the array to the ListView
            final ProfileListAdapter adapter = new ProfileListAdapter(getActivity(), arrayList);

            // Display the ListView
            listView.setAdapter(adapter);
            listView.setClickable(true);
            listView.setOnItemClickListener((arg0, arg1, position, arg3) -> {
                listener.viewProfile(profileList[0][position]);

                // Update status of indicated item
                SharedPreferences prefCurrent = listener.getPrefCurrent();
                if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                    SharedPreferences prefSaved = listener.getPrefQuickActions();
                    for(int i = 0; i < numOfFiles; i++) {
                        if(profileList[0][i].equals(prefSaved.getString("original_filename", "0")))
                            listView.setItemChecked(i, true);
                        else
                            listView.setItemChecked(i, false);
                    }
                } else {
                    for(int i = 0; i < numOfFiles; i++) {
                        if(profileList[0][i].equals(prefCurrent.getString("filename", "0")))
                            listView.setItemChecked(i, true);
                        else
                            listView.setItemChecked(i, false);
                    }
                }

                // Set helper text based on whether or not a profile is active
                initHelperText(listener.getHelperText(), prefCurrent);
            });

            // Make ListView handle long presses
            listView.setLongClickable(true);
            listView.setOnItemLongClickListener((arg0, arg1, position, arg3) -> {
                listener.editProfile(profileList[0][position]);

                // Update status of indicated item
                SharedPreferences prefCurrent = listener.getPrefCurrent();
                if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                    SharedPreferences prefSaved = listener.getPrefQuickActions();
                    for(int i = 0; i < numOfFiles; i++) {
                        if(profileList[0][i].equals(prefSaved.getString("original_filename", "0")))
                            listView.setItemChecked(i, true);
                        else
                            listView.setItemChecked(i, false);
                    }
                } else {
                    for(int i = 0; i < numOfFiles; i++) {
                        if(profileList[0][i].equals(prefCurrent.getString("filename", "0")))
                            listView.setItemChecked(i, true);
                        else
                            listView.setItemChecked(i, false);
                    }
                }

                // Set helper text based on whether or not a profile is active
                initHelperText(listener.getHelperText(), prefCurrent);

                return true;
            });

            // Prepare the ListView for indicating the currently loaded profile
            listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

            SharedPreferences prefCurrent = listener.getPrefCurrent();
            if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                SharedPreferences prefSaved = listener.getPrefQuickActions();
                for(int i = 0; i < numOfFiles; i++) {
                    if(profileList[0][i].equals(prefSaved.getString("original_filename", "0")))
                        listView.setItemChecked(i, true);
                    else
                        listView.setItemChecked(i, false);
                }
            } else {
                for(int i = 0; i < numOfFiles; i++) {
                    if(profileList[0][i].equals(prefCurrent.getString("filename", "0")))
                        listView.setItemChecked(i, true);
                    else
                        listView.setItemChecked(i, false);
                }
            }

            // Set helper text based on whether or not a profile is active
            initHelperText(helper, prefCurrent);
        }

        // Allow clicking on helper text to enable debug mode / show debug menu
        helper.setOnClickListener(v -> {
            if(listener.isDebugModeEnabled(true)) {
                if(profileList == null)
                    helper.setText(R.string.debug_mode_enabled);
                else
                    helper.setText(R.string.profile_helper_text_debug);

                helper.setTextColor(Color.WHITE);
                helper.setBackgroundColor(Color.RED);
            } else {
                if(profileList == null) {
                    helper.setText(" ");
                    helper.setBackgroundColor(Color.WHITE);
                } else {
                    SharedPreferences prefCurrent = listener.getPrefCurrent();
                    if("0".equals(prefCurrent.getString("filename", "0")))
                        helper.setText(R.string.profile_helper_text);
                    else {
                        if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                            SharedPreferences prefSaved = listener.getPrefQuickActions();
                            if("0".equals(prefSaved.getString("original_filename", "0")))
                                helper.setText(R.string.profile_helper_text);
                            else
                                helper.setText(R.string.profile_helper_text_alt);
                        } else
                            helper.setText(R.string.profile_helper_text_alt);
                    }

                    helper.setTextColor(ContextCompat.getColor(getActivity(), R.color.text_color_secondary));
                    helper.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.accent));
                }
            }
        });

        helper.setOnLongClickListener(v -> {
            if(listener.isDebugModeEnabled(false)) {
                Intent intent = new Intent(getActivity(), DebugModeActivity.class);
                startActivity(intent);
            }

            return false;
        });
    }

    private void initHelperText(TextView helper, SharedPreferences prefCurrent) {
        if("0".equals(prefCurrent.getString("filename", "0"))) {
            if(listener.isDebugModeEnabled(false)) {
                helper.setText(R.string.profile_helper_text_debug);
                helper.setTextColor(Color.WHITE);
                helper.setBackgroundColor(Color.RED);
            } else {
                helper.setText(R.string.profile_helper_text);
                helper.setTextColor(ContextCompat.getColor(getActivity(), R.color.text_color_secondary));
                helper.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.accent));
            }
        } else {
            if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                SharedPreferences prefSaved = listener.getPrefQuickActions();
                if("0".equals(prefSaved.getString("original_filename", "0"))) {
                    if(listener.isDebugModeEnabled(false)) {
                        helper.setText(R.string.profile_helper_text_debug);
                        helper.setTextColor(Color.WHITE);
                        helper.setBackgroundColor(Color.RED);
                    } else {
                        helper.setText(R.string.profile_helper_text);
                        helper.setTextColor(ContextCompat.getColor(getActivity(), R.color.text_color_secondary));
                        helper.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.accent));
                    }
                } else {
                    if(listener.isDebugModeEnabled(false)) {
                        helper.setText(R.string.profile_helper_text_debug);
                        helper.setTextColor(Color.WHITE);
                        helper.setBackgroundColor(Color.RED);
                    } else {
                        helper.setText(R.string.profile_helper_text_alt);
                        helper.setTextColor(ContextCompat.getColor(getActivity(), R.color.text_color_secondary));
                        helper.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.accent));
                    }
                }
            } else {
                if(listener.isDebugModeEnabled(false)) {
                    helper.setText(R.string.profile_helper_text_debug);
                    helper.setTextColor(Color.WHITE);
                    helper.setBackgroundColor(Color.RED);
                } else {
                    helper.setText(R.string.profile_helper_text_alt);
                    helper.setTextColor(ContextCompat.getColor(getActivity(), R.color.text_color_secondary));
                    helper.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.accent));
                }
            }
        }
    }
} public final class ProfileViewFragment extends Fragment {

    String filename = "";
    String left;
    String right;
    int n;

    /* The activity that creates an instance of this fragment must
     * implement this interface in order to receive event call backs. */
    public interface Listener {
        void showDeleteDialog();
        void onLoadProfileButtonClick(String filename);
        void onTurnOffProfileButtonClick();
        String generateBlurb(String key, String value);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_profile_view, container, false);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @SuppressWarnings("deprecation")
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        new Handler().post(this::showUI);
    }

    private void showUI() {
        // Set values
        setRetainInstance(true);
        setHasOptionsMenu(true);

        // Change window title
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
            getActivity().setTitle(getArguments().getString("title"));
        else
            getActivity().setTitle(" " + getArguments().getString("title"));

        // Show the Up button in the action bar.
        ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        // Animate elevation change
        if(getActivity().findViewById(R.id.layoutMain).getTag().equals("main-layout-large")
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            LinearLayout profileViewEdit = getActivity().findViewById(R.id.profileViewEdit);
            LinearLayout profileList = getActivity().findViewById(R.id.profileList);
            profileList.animate().z(0f);
            profileViewEdit.animate().z(getResources().getDimensionPixelSize(R.dimen.profile_view_edit_elevation));
        }

        // Get filename of saved note
        filename = getArguments().getString("filename");

        SharedPreferences prefSaved = U.getPrefSaved(getActivity(), filename);
        SharedPreferences prefCurrent = U.getPrefCurrent(getActivity());
        Button button = getActivity().findViewById(R.id.pvButton);
        TextView resolution = getActivity().findViewById(R.id.pvResolution);
        TextView density = getActivity().findViewById(R.id.pvDensity);
        TextView profileSettingsLeft = getActivity().findViewById(R.id.pvProfileSettingsLeft);
        TextView profileSettingsRight = getActivity().findViewById(R.id.pvProfileSettingsRight);

        // Change color and/or background of the Load/Turn Off button
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
            button.setBackground(getResources().getDrawable(R.drawable.pl_button));
        else
            button.getBackground().setColorFilter(ContextCompat.getColor(getActivity(), R.color.pl_selected), PorterDuff.Mode.SRC);

        // Set listeners for Load/Turn Off button
        if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
            SharedPreferences prefQuick = U.getPrefQuickActions(getActivity());
            if(filename.equals(prefQuick.getString("original_filename", "0"))) {
                button.setText(getResources().getString(R.string.action_turn_off_alt, getArguments().getString("title")));
                button.setOnClickListener(view -> listener.onTurnOffProfileButtonClick());
            } else {
                button.setText(getResources().getString(R.string.action_load, getArguments().getString("title")));
                button.setOnClickListener(view -> listener.onLoadProfileButtonClick(filename));
            }
        } else if(filename.equals(prefCurrent.getString("filename", "0"))) {
            button.setText(getResources().getString(R.string.action_turn_off_alt, getArguments().getString("title")));
            button.setOnClickListener(view -> listener.onTurnOffProfileButtonClick());
        } else {
            button.setText(getResources().getString(R.string.action_load, getArguments().getString("title")));
            button.setOnClickListener(view -> listener.onLoadProfileButtonClick(filename));
        }

        // Generate a brief overview of this profile's settings to display within the fragment
        // NOTE:  these statements must remain in order
        resolution.setText(listener.generateBlurb("size", prefSaved.getString("size", "reset")));
        density.setText(listener.generateBlurb("density", prefSaved.getString("density", "reset")));

        left = "";
        right = "";
        n = 0;

        generateProfileSettings(prefSaved.getBoolean("bluetooth_on", false), R.string.profile_view_bluetooth_on);
        generateProfileSettings(prefSaved.getBoolean("chrome", false), R.string.quick_chrome);
        generateProfileSettings(prefSaved.getBoolean("clear_home", false), R.string.quick_clear_home);

        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
            generateProfileSettings(prefSaved.getBoolean("daydreams_on", false), R.string.profile_view_daydreams_on);

        generateProfileSettings(prefSaved.getBoolean("navbar", false), R.string.profile_view_navbar);
        generateProfileSettings(prefSaved.getBoolean("freeform", false), R.string.profile_view_freeform);

        switch(prefSaved.getString("hdmi_rotation", "landscape")) {
            case "portrait":
                generateProfileSettings(true, R.string.profile_view_hdmi_output);
                break;
        }

        if(U.canEnableOverscan())
            generateProfileSettings(prefSaved.getBoolean("overscan", false), R.string.quick_overscan);

        switch(prefSaved.getString("rotation_lock_new", "fallback")) {
            case "fallback":
                if(prefSaved.getBoolean("rotation_lock", false))
                    generateProfileSettings(true, R.string.profile_view_rotation_landscape);
                break;
            case "landscape":
                generateProfileSettings(true, R.string.profile_view_rotation_landscape);
                break;
            case "auto-rotate":
                generateProfileSettings(true, R.string.profile_view_rotation_autorotate);
                break;
            case "portrait":
                generateProfileSettings(true, R.string.profile_view_rotation_portrait);
                break;
        }

        generateProfileSettings(prefSaved.getBoolean("backlight_off", false), R.string.pref_title_backlight_off);
        generateProfileSettings(prefSaved.getBoolean("vibration_off", false), R.string.pref_title_vibration_off);

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
            generateProfileSettings(prefSaved.getBoolean("daydreams_on", false), R.string.profile_view_daydreams_on);

        generateProfileSettings(prefSaved.getBoolean("show_touches", false), R.string.pref_title_show_touches);

        if(U.canEnableImmersiveMode()) {
            switch(prefSaved.getString("immersive_new", "fallback")) {
                case "fallback":
                    if(prefSaved.getBoolean("immersive", false))
                        generateProfileSettings(true, R.string.pref_title_immersive);
                    break;
                case "status-only":
                case "immersive-mode":
                    generateProfileSettings(true, R.string.pref_title_immersive);
                    break;
            }
        }

        generateProfileSettings(prefSaved.getBoolean("taskbar", false), R.string.quick_taskbar);

        switch(prefSaved.getString("screen_timeout", "do-nothing")) {
            case "always-on":
                generateProfileSettings(true, R.string.profile_view_screen_timeout_always_on);
                break;
            case "always-on-charging":
                generateProfileSettings(true, R.string.profile_view_screen_timeout_always_on_charging);
                break;
        }

        switch(prefSaved.getString("ui_refresh", "do-nothing")) {
            case "system-ui":
                if(getActivity().getPackageManager().hasSystemFeature("com.cyanogenmod.android")
                        && Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1)
                    generateProfileSettings(true, R.string.profile_view_ui_refresh_soft_reboot);
                else if(U.isInNonRootMode(getActivity()))
                    generateProfileSettings(true, R.string.profile_view_ui_refresh_systemui_alt);
                else
                    generateProfileSettings(true, R.string.profile_view_ui_refresh_systemui);
                break;
            case "activity-manager":
                if(U.isInNonRootMode(getActivity()))
                    generateProfileSettings(true, R.string.profile_view_ui_refresh_soft_reboot_alt);
                else
                    generateProfileSettings(true, R.string.profile_view_ui_refresh_soft_reboot);
                break;
        }

        generateProfileSettings(prefSaved.getBoolean("wifi_on", false), R.string.profile_view_wifi_on);

        if(!left.isEmpty())
            profileSettingsLeft.setText(left);

        if(!right.isEmpty())
            profileSettingsRight.setText(right);

        if(n == 0) {
            TextView header = getActivity().findViewById(R.id.pvHeaderProfileSettings);
            header.setText(" ");
            header.setBackgroundColor(Color.WHITE);
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.profile_view, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                // Override default Android "up" behavior to instead mimic the back button
                onBackPressed();
                return true;

            // Edit button
            case R.id.action_edit:
                Bundle bundle = new Bundle();
                bundle.putString("filename", filename);

                Fragment fragment = new ProfileEditFragment();
                fragment.setArguments(bundle);

                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.profileViewEdit, fragment, "ProfileEditFragment")
                        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                        .commit();

                return true;

            // Delete button
            case R.id.action_delete_2:
                // Show toast if this is the currently active profile
                SharedPreferences prefCurrent = U.getPrefCurrent(getActivity());
                if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                    SharedPreferences prefSaved = U.getPrefQuickActions(getActivity());
                    if(filename.equals(prefSaved.getString("original_filename", "0")))
                        U.showToast(getActivity(), R.string.deleting_current_profile);
                    else
                        listener.showDeleteDialog();
                } else if(filename.equals(prefCurrent.getString("filename", "0")))
                    U.showToast(getActivity(), R.string.deleting_current_profile);
                else
                    listener.showDeleteDialog();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    public void deleteProfile() {
        // Build the pathname to delete file, then perform delete operation
        File fileToDelete = new File(getActivity().getFilesDir() + File.separator + filename);
        fileToDelete.delete();

        File xmlFileToDelete = new File(getActivity().getFilesDir().getParent() + File.separator + "shared_prefs" + File.separator + filename + ".xml");
        xmlFileToDelete.delete();

        U.showToast(getActivity(), R.string.profile_deleted);

        // Cleanup
        SharedPreferences prefNew = U.getPrefNew(getActivity());
        SharedPreferences.Editor prefNewEditor = prefNew.edit();
        prefNewEditor.clear();
        prefNewEditor.apply();

        // Refresh list of profiles
        U.listProfilesBroadcast(getActivity());

        onBackPressed();
    }

    public void onBackPressed() {
        // Add ProfileListFragment or WelcomeFragment
        Fragment fragment;
        if(getActivity().findViewById(R.id.layoutMain).getTag().equals("main-layout-normal"))
            fragment = new ProfileListFragment();
        else {
            SharedPreferences prefMain = U.getPrefMain(getActivity());

            Bundle bundle = new Bundle();
            bundle.putBoolean("show-welcome-message", prefMain.getBoolean("show-welcome-message", false));

            fragment = new WelcomeFragment();
            fragment.setArguments(bundle);
        }

        getFragmentManager()
                .beginTransaction()
                .replace(R.id.profileViewEdit, fragment, "ProfileListFragment")
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
                .commit();
    }

    public String getFilename() {
        return getArguments().getString("filename");
    }

    private void generateProfileSettings(boolean optionIsSet, int text) {
        if(optionIsSet) {
            n++;
            if(n % 2 == 0)
                right = right + getResources().getString(R.string.bullet) + " " + getResources().getString(text) + "\n";
            else
                left = left + getResources().getString(R.string.bullet) + " " + getResources().getString(text) + "\n";
        }
    }
} public final class WelcomeFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(getArguments().getBoolean("show-welcome-message", true))
            return inflater.inflate(R.layout.fragment_welcome, container, false);
        else
            return inflater.inflate(R.layout.fragment_welcome_alt, container, false);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // Set values
        setRetainInstance(true);
        setHasOptionsMenu(true);

        // Animate elevation change
        if(getActivity().findViewById(R.id.layoutMain).getTag().equals("main-layout-large")
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            LinearLayout profileViewEdit = getActivity().findViewById(R.id.profileViewEdit);
            LinearLayout profileList = getActivity().findViewById(R.id.profileList);

            if(getArguments().getBoolean("show-welcome-message", true)) {
                profileList.animate().z(0f);
                profileViewEdit.animate().z(getResources().getDimensionPixelSize(R.dimen.profile_view_edit_elevation));
            } else {
                profileViewEdit.animate().z(0f);
                profileList.animate().z(getResources().getDimensionPixelSize(R.dimen.profile_list_elevation));
            }
        }
    }

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

        // Change window title
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
            getActivity().setTitle(getResources().getString(R.string.app_name));
        else
            getActivity().setTitle(" " + getResources().getString(R.string.app_name));

        // Don't show the Up button in the action bar, and disable the button
        ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
        ((AppCompatActivity) getActivity()).getSupportActionBar().setHomeButtonEnabled(false);

        // Floating action button
        FloatingActionButton floatingActionButton = getActivity().findViewById(R.id.button_floating_action_welcome);
        floatingActionButton.setImageResource(R.drawable.ic_action_new);
        floatingActionButton.setOnClickListener(v -> {
            DialogFragment newProfileFragment = new NewProfileDialogFragment();
            newProfileFragment.show(getFragmentManager(), "new");
        });
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.main, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle presses on the action bar items
        switch(item.getItemId()) {
            // Quick Actions button
            case R.id.action_quick:
                Intent intentQuick = new Intent(getActivity(), TaskerQuickActionsActivity.class);
                intentQuick.putExtra("launched-from-app", true);
                startActivity(intentQuick);
                return true;

            // Settings button
            case R.id.action_settings:
                Intent intentSettings = new Intent(getActivity(), FragmentContainerActivity.class);
                intentSettings.putExtra("tag", "SettingsFragment");
                startActivity(intentSettings);
                return true;

            // About button
            case R.id.action_about:
                DialogFragment aboutFragment = new AboutDialogFragment();
                aboutFragment.show(getFragmentManager(), "about");
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
} public final class AboutDialogFragment extends DialogFragment {

    LinearLayout checkbox;
    TextView textView;

/* The activity that creates an instance of this dialog fragment must
 * implement this interface in order to receive event call backs.
 * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onAboutDialogNegativeClick(DialogFragment dialog);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        // Get the layout inflater
        final LayoutInflater inflater = getActivity().getLayoutInflater();

        // Inflate and set the layout for the dialog
        // Pass null as the parent view because its going in the dialog layout
        final View view = inflater.inflate(R.layout.fragment_dialogs, null);

        builder.setView(view)
        .setTitle(R.string.dialog_about_title)
        .setPositiveButton(R.string.action_close, (dialog, id) -> {});

        if(!BuildConfig.DEBUG) {
            builder.setNegativeButton(R.string.check_for_update, (dialog, id) -> listener.onAboutDialogNegativeClick(this));
        }

        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Denver"));
        calendar.setTimeInMillis(BuildConfig.TIMESTAMP);

        int year = calendar.get(Calendar.YEAR);

        textView = view.findViewById(R.id.dialogMessage);
        textView.setText(getString(R.string.dialog_about_message, year));
        textView.setMovementMethod(LinkMovementMethod.getInstance());

        checkbox = view.findViewById(R.id.checkBoxLayout);
        checkbox.setVisibility(View.GONE);

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class AndroidUpgradeDialogFragment extends DialogFragment {

/* The activity that creates an instance of this dialog fragment must
 * implement this interface in order to receive event call backs.
 * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onUpgradeDialogPositiveClick(DialogFragment dialog);
        void onUpgradeDialogNegativeClick(DialogFragment dialog);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_upgrade_message)
        .setTitle(R.string.safeguard_title)
        .setPositiveButton(R.string.action_ok, (dialog, id) -> listener.onUpgradeDialogPositiveClick(this))
        .setNegativeButton(R.string.check_for_update, (dialog, id) -> listener.onUpgradeDialogNegativeClick(this));

        // Prevent the user from cancelling this particular dialog
        setCancelable(false);

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class BusyboxDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onBusyboxDialogNegativeClick();
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.busybox_message)
                .setTitle(R.string.busybox_required)
                .setPositiveButton(R.string.action_ok, (dialog, id) -> {})
                .setNegativeButton(R.string.action_ignore, (dialog, id) -> listener.onBusyboxDialogNegativeClick());

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class DeleteDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onDeleteDialogPositiveClick(DialogFragment dialog);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_are_you_sure)
        .setTitle(R.string.dialog_delete_button_title)
        .setPositiveButton(R.string.action_delete, (dialog, id) -> listener.onDeleteDialogPositiveClick(this))
        .setNegativeButton(R.string.action_cancel, (dialog, id) -> {});

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class ExpertModeSizeDialogFragment extends DialogFragment {

        EditText editText;
        EditText editText2;
        boolean showKeyboard = true;

        /* The activity that creates an instance of this dialog fragment must
         * implement this interface in order to receive event call backs.
         * Each method passes the DialogFragment in case the host needs to query it. */
        public interface Listener {
            void onExpertModeSizePositiveClick(String height, String width);
        }

        // Use this instance of the interface to deliver action events
        Listener listener;

        // Override the Fragment.onAttach() method to instantiate the Listener
        @SuppressWarnings("deprecation")
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            // Verify that the host activity implements the callback interface
            try {
                // Instantiate the Listener so we can send events to the host
                listener = (Listener) activity;
            } catch (ClassCastException e) {
                // The activity doesn't implement the interface, throw exception
                throw new ClassCastException(activity.toString()
                                             + " must implement Listener");
            }
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            // Use the Builder class for convenient dialog construction
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

            // Get the layout inflater
            final LayoutInflater inflater = getActivity().getLayoutInflater();

            // Inflate and set the layout for the dialog
            // Pass null as the parent view because its going in the dialog layout
            final View view = inflater.inflate(R.layout.fragment_expert_mode_size, null);
            builder.setView(view)
                .setTitle(R.string.pref_title_resolution)
                .setPositiveButton(R.string.action_ok, (dialog, id) -> {
                    editText = view.findViewById(R.id.fragmentEditText1);
                    editText2 = view.findViewById(R.id.fragmentEditText2);

                    String height = editText.getText().toString();
                    String width = editText2.getText().toString();

                    listener.onExpertModeSizePositiveClick(height, width);
                })
                .setNegativeButton(R.string.action_cancel, (dialog, id) -> {}
                );

            editText = view.findViewById(R.id.fragmentEditText1);
            editText2 = view.findViewById(R.id.fragmentEditText2);

            editText.setText(getArguments().getString("height"));
            editText2.setText(getArguments().getString("width"));

            editText.selectAll();
            editText.setOnFocusChangeListener((v, hasFocus) -> {
                if(showKeyboard) {
                    showKeyboard = false;
                    editText.post(() -> {
                        InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                        imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
                    });
                }
            });
            editText.requestFocus();

            // Remove padding from layout on pre-Lollipop devices
            if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
                view.setPadding(0, 0, 0, 0);

            // Create the AlertDialog object and return it
            return builder.create();
        }

        @Override
        public void onDismiss(DialogInterface dialog) {
            showKeyboard = true;
            super.onDismiss(dialog);
        }
    } public final class FirstLoadDialogFragment extends DialogFragment {

    CheckBox checkbox;
    TextView textView;

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onFirstLoadPositiveClick(DialogFragment dialog, String filename, boolean isChecked);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        // Get the layout inflater
        final LayoutInflater inflater = getActivity().getLayoutInflater();

        // Inflate and set the layout for the dialog
        // Pass null as the parent view because its going in the dialog layout
        final View view = inflater.inflate(R.layout.fragment_dialogs, null);

        builder.setView(view)
        .setTitle(R.string.dialog_load_profile)
        .setPositiveButton(R.string.action_ok, (dialog, id) -> {
            checkbox = view.findViewById(R.id.firstLoadCheckBox);
            listener.onFirstLoadPositiveClick(this,
                    getArguments().getString("filename"),
                    checkbox.isChecked());
        })
        .setNegativeButton(R.string.action_cancel, (dialog, id) -> {}
        );

        textView = view.findViewById(R.id.dialogMessage);
        textView.setText(R.string.dialog_first_load);

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class FirstRunDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onFirstRunDialogPositiveClick();
        void onFirstRunDialogNegativeClick();
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    private int secondsLeft = 5;
    private boolean isStarted = false;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.first_run)
                .setTitle(R.string.welcome)
                .setPositiveButton(R.string.accept, (dialog, id) -> listener.onFirstRunDialogPositiveClick())
                .setNegativeButton(R.string.decline, (dialog, id) -> listener.onFirstRunDialogNegativeClick());

        // Prevent the user from cancelling this particular dialog
        setCancelable(false);

        // Create the AlertDialog object and return it
        return builder.create();
    }

    @Override
    public void onStart() {
        super.onStart();
        isStarted = true;

        startCountdown((AlertDialog) getDialog());
    }

    @Override
    public void onStop() {
        isStarted = false;
        super.onStop();
    }

    private void startCountdown(AlertDialog dialog) {
        if(!isStarted) return;

        Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
        if(secondsLeft == 0) {
            button.setEnabled(true);
            button.setText(getString(R.string.accept));
            return;
        }

        button.setEnabled(false);
        button.setText(getString(R.string.accept_alt, secondsLeft));
        secondsLeft--;

        new Handler().postDelayed(() -> startCountdown(dialog), 1000);
    }
} public final class MultipleVersionsDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void uninstallPackage(String packageName);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                                         + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.both_versions_installed)
            .setTitle(R.string.safeguard_title)
            .setPositiveButton(R.string.action_uninstall, (dialog, id) -> listener.uninstallPackage(getArguments().getString("package")));

        // Prevent the user from cancelling this particular dialog
        setCancelable(false);

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class NewDeviceDialogFragment extends DialogFragment {

/* The activity that creates an instance of this dialog fragment must
 * implement this interface in order to receive event call backs.
 * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onNewDeviceDialogPositiveClick(DialogFragment dialog);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.new_device_message)
        .setTitle(R.string.safeguard_title)
        .setPositiveButton(R.string.action_ok, (dialog, id) -> listener.onNewDeviceDialogPositiveClick(this));

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class NewProfileDialogFragment extends DialogFragment implements AdapterView.OnItemSelectedListener {

    EditText editText;
    Spinner spinner;
    ArrayAdapter<CharSequence> adapter;
    boolean showKeyboard = true;
    int pos;

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onNewProfilePositiveClick(String name, int pos);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        // Get the layout inflater
        final LayoutInflater inflater = getActivity().getLayoutInflater();

        // Inflate and set the layout for the dialog
        // Pass null as the parent view because its going in the dialog layout
        final View view = inflater.inflate(R.layout.fragment_new_profile, null);

        builder.setView(view)
                .setTitle(R.string.action_new)
                .setPositiveButton(R.string.action_ok, (dialog, id) -> {
                    editText = view.findViewById(R.id.newProfile);
                    String name = editText.getText().toString();
                    listener.onNewProfilePositiveClick(name, pos);
                })
                .setNegativeButton(R.string.action_cancel, (dialog, id) -> {});

        editText = view.findViewById(R.id.newProfile);
        editText.setOnFocusChangeListener((v, hasFocus) -> {
            if(showKeyboard) {
                showKeyboard = false;
                editText.post(() -> {
                    InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
                });
            }
        });
        editText.requestFocus();

        spinner = view.findViewById(R.id.spinner);
        // Create an ArrayAdapter using the string array and a default spinner layout
        adapter = ArrayAdapter.createFromResource(getActivity(),
                R.array.new_profile_templates, android.R.layout.simple_spinner_item);
        // Specify the layout to use when the list of choices appears
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        // Set listener
        spinner.setOnItemSelectedListener(this);
        // Apply the adapter to the spinner
        spinner.setAdapter(adapter);

        // Create the AlertDialog object and return it
        return builder.create();
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        showKeyboard = true;
        super.onDismiss(dialog);
    }

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

    @Override
    public void onNothingSelected(AdapterView<?> parent) {}
} public final class ReloadProfileDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onReloadDialogPositiveClick(String filename, boolean isEdit, boolean returnToList);
        void onReloadDialogNegativeClick(String filename, boolean isEdit, boolean returnToList);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setTitle(R.string.dialog_reload_profile_title)
        .setMessage(R.string.editing_current_profile)
        .setPositiveButton(R.string.action_save_reload, (dialog, id) -> {
            if(getArguments() != null)
                listener.onReloadDialogPositiveClick(getArguments().getString("filename"),
                    getArguments().getBoolean("is-edit"),
                    getArguments().getBoolean("return-to-list"));
            else
                listener.onReloadDialogPositiveClick(null, false, false);
        })
        .setNegativeButton(R.string.action_save_only, (dialog, id) -> {
            if(getArguments() != null)
                listener.onReloadDialogNegativeClick(getArguments().getString("filename"),
                        getArguments().getBoolean("is-edit"),
                        getArguments().getBoolean("return-to-list"));
            else
                listener.onReloadDialogNegativeClick(null, false, false);
        });

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class SystemAlertPermissionDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onSystemAlertPermissionDialogPositiveClick();
        void onSystemAlertPermissionDialogNegativeClick();
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                    + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.system_alert_permission_dialog_message)
                .setTitle(R.string.permission_needed)
                .setPositiveButton(R.string.grant_permission, (dialog, id) -> listener.onSystemAlertPermissionDialogPositiveClick())
                .setNegativeButton(R.string.action_cancel, (dialog, id) -> listener.onSystemAlertPermissionDialogNegativeClick());

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class UiRefreshDialogFragment extends DialogFragment {

    /* The activity that creates an instance of this dialog fragment must
     * implement this interface in order to receive event call backs.
     * Each method passes the DialogFragment in case the host needs to query it. */
    public interface Listener {
        void onUiRefreshDialogPositiveClick(String filename, boolean isEdit, boolean returnToList);
    }

    // Use this instance of the interface to deliver action events
    Listener listener;

    // Override the Fragment.onAttach() method to instantiate the Listener
    @SuppressWarnings("deprecation")
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the Listener so we can send events to the host
            listener = (Listener) activity;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(activity.toString()
                                         + " must implement Listener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_ui_refresh)
                .setTitle(R.string.pref_title_ui_refresh)
                .setPositiveButton(R.string.action_save, (dialog, id) ->
                        listener.onUiRefreshDialogPositiveClick(getArguments().getString("filename"),
                                getArguments().getBoolean("is-edit"),
                                getArguments().getBoolean("return-to-list")))
                .setNegativeButton(R.string.action_cancel, (dialog, id) -> {});

        // Create the AlertDialog object and return it
        return builder.create();
    }
} public final class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // Load preferences
        SharedPreferences prefCurrent = U.getPrefCurrent(context);
        SharedPreferences prefMain = U.getPrefMain(context);
        boolean isDebugMode = prefMain.getBoolean("debug_mode", false);

        if(Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) || isDebugMode) {
            // Initialize preferences on BlissOS
            if(U.isBlissOs(context) && !prefMain.getBoolean("first-run", false))
                U.initPrefs(context);

            // Restore DisplayConnectionService
            if(prefMain.getBoolean("hdmi", true) && prefMain.getBoolean("first-run", false)) {
                Intent serviceIntent = new Intent(context, DisplayConnectionService.class);
                U.startService(context, serviceIntent);
            }

            if(prefCurrent.getInt("external_display_id", -1) > 1)
                prefCurrent.edit().putInt("external_display_id", 1).apply();

            if(!prefCurrent.getBoolean("not_active", true)) {
                if(prefMain.getBoolean("safe_mode", false)
                        && !"activity-manager".equals(prefCurrent.getString("ui_refresh", "do-nothing"))
                        && !prefCurrent.getBoolean("reboot_required", false)
                        && prefCurrent.getLong("time_of_profile_start", 0)
                        < (System.currentTimeMillis() - (isDebugMode ? 0 : SystemClock.elapsedRealtime()))) {
                    SharedPreferences.Editor editor = prefCurrent.edit();
                    editor.putString("ui_refresh", "do-nothing");
                    editor.apply();

                    U.turnOffProfile(context);
                } else if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                    SharedPreferences prefSaved = U.getPrefQuickActions(context);
                    if("0".equals(prefSaved.getString("original_filename", "0"))
                            && !prefCurrent.getBoolean("reboot_required", false)
                            && prefCurrent.getLong("time_of_profile_start", 0)
                            < (System.currentTimeMillis() - (isDebugMode ? 0 : SystemClock.elapsedRealtime()))) {
                        SharedPreferences.Editor editor = prefCurrent.edit();
                        editor.putString("ui_refresh", "do-nothing");
                        editor.apply();

                        U.turnOffProfile(context);
                    } else {
                        prefCurrent.edit().putBoolean("reboot_required", false).apply();

                        // Restore NotificationService
                        Intent serviceIntent = new Intent(context, NotificationService.class);
                        U.startService(context, serviceIntent);

                        // Start BootService to run superuser commands
                        Intent serviceIntent2 = new Intent(context, BootService.class);
                        U.startService(context, serviceIntent2);
                    }
                } else {
                    prefCurrent.edit().putBoolean("reboot_required", false).apply();

                    // Restore NotificationService
                    Intent serviceIntent = new Intent(context, NotificationService.class);
                    U.startService(context, serviceIntent);

                    // Start BootService to run superuser commands
                    Intent serviceIntent2 = new Intent(context, BootService.class);
                    U.startService(context, serviceIntent2);
                }
            }
        }
    }
} public final class PackageUpgradeReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // Load preferences
        SharedPreferences prefCurrent = U.getPrefCurrent(context);
        SharedPreferences prefMain = U.getPrefMain(context);
        boolean isDebugMode = prefMain.getBoolean("debug_mode", false);

        if(Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction()) || isDebugMode) {
            // Restore DisplayConnectionService
            if(prefMain.getBoolean("hdmi", true) && prefMain.getBoolean("first-run", false)) {
                Intent serviceIntent = new Intent(context, DisplayConnectionService.class);
                U.startService(context, serviceIntent);
            }

            // Restore NotificationService
            if(!prefCurrent.getBoolean("not_active", true)) {
                Intent serviceIntent = new Intent(context, NotificationService.class);
                U.startService(context, serviceIntent);
            }
        }
    }
} public final class TaskerActionReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if(U.isExternalAccessDisabled(context)) return;

        BundleScrubber.scrub(intent);

        final Bundle bundle = intent.getBundleExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);
        BundleScrubber.scrub(bundle);

        if(bundle.containsKey(PluginBundleManager.BUNDLE_EXTRA_STRING_MESSAGE)) {
            if(PluginBundleManager.isBundleValid(bundle)) {
                String filename = bundle.getString(PluginBundleManager.BUNDLE_EXTRA_STRING_MESSAGE);

                Intent shortcutIntent = new Intent(context, QuickLaunchActivity.class);
                shortcutIntent.setAction(Intent.ACTION_MAIN);
                shortcutIntent.putExtra(U.NAME, filename);
                shortcutIntent.putExtra("tasker", true);
                shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

                context.startActivity(shortcutIntent);
            }
        } else if(bundle.containsKey(PluginBundleManagerQuickActions.BUNDLE_EXTRA_STRING_KEY)
                && bundle.containsKey(PluginBundleManagerQuickActions.BUNDLE_EXTRA_STRING_VALUE)) {
            if(PluginBundleManagerQuickActions.isBundleValid(bundle)) {
                String key = bundle.getString(PluginBundleManagerQuickActions.BUNDLE_EXTRA_STRING_KEY);
                String value = bundle.getString(PluginBundleManagerQuickActions.BUNDLE_EXTRA_STRING_VALUE);

                Intent shortcutIntent = new Intent(context, TaskerQuickActionsActivity.class);
                shortcutIntent.setAction(Intent.ACTION_MAIN);
                shortcutIntent.putExtra(U.KEY, key);
                shortcutIntent.putExtra(U.VALUE, value);
                shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

                context.startActivity(shortcutIntent);
            }
        }
    }
} public final class TaskerConditionReceiver extends BroadcastReceiver {
    private Bundle lastbundle = null;

    @SuppressWarnings("deprecation")
    @Override
    public void onReceive(Context context, Intent intent) {
        if(U.isExternalAccessDisabled(context)) return;

        if(lastbundle != null && lastbundle.equals(intent.getBundleExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE))) {
            // bundle hasn't changed: we can safely return
            return;
        }
        updateValues(intent);

        BundleScrubber.scrub(intent);

        BundleScrubber.scrub(lastbundle);

        if(PluginBundleManager.isBundleValid(lastbundle)) {
            String filename = lastbundle.getString(PluginBundleManager.BUNDLE_EXTRA_STRING_MESSAGE);
            SharedPreferences prefCurrent = context.getSharedPreferences("current", Context.MODE_MULTI_PROCESS);

            if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                SharedPreferences prefSaved = context.getSharedPreferences("quick_actions", Context.MODE_MULTI_PROCESS);
                if("0".equals(prefSaved.getString("original_filename", "0")))
                    setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_UNSATISFIED);
                else {
                    if("any_profile".equals(filename))
                        setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_SATISFIED);
                    else {
                        if(prefSaved.getString("original_filename", "0").equals(filename))
                            setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_SATISFIED);
                        else
                            setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_UNSATISFIED);
                    }
                }
            } else {
                if("0".equals(prefCurrent.getString("filename", "0")))
                    setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_UNSATISFIED);
                else {
                    if("any_profile".equals(filename))
                        setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_SATISFIED);
                    else {
                        if(prefCurrent.getString("filename", "0").equals(filename))
                            setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_SATISFIED);
                        else
                            setResultCode(com.twofortyfouram.locale.api.Intent.RESULT_CONDITION_UNSATISFIED);
                    }
                }
            }
        }
    }

    private void updateValues(Intent intent) {
        lastbundle = intent.getBundleExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);
    }
} public final class DisplayConnectionService extends Service implements ServiceInterface {

    DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() {
        @Override
        public void onDisplayAdded(int displayId) {
            SharedPreferences prefCurrent = U.getPrefCurrent(DisplayConnectionService.this);
            DisplayManager dm = (DisplayManager) getSystemService(DISPLAY_SERVICE);
            Display[] displays = dm.getDisplays();

            try {
                if(displays[displays.length - 2].getDisplayId() == Display.DEFAULT_DISPLAY
                        && prefCurrent.getBoolean("not_active", true)) {
                    Intent hdmiIntent = new Intent(DisplayConnectionService.this, HdmiActivity.class);
                    hdmiIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(hdmiIntent);
                }
            } catch (ArrayIndexOutOfBoundsException e) { /* Gracefully fail */ }
        }

        @Override
        public void onDisplayChanged(int displayId) {}

        @Override
        public void onDisplayRemoved(int displayId) {
            Intent intent = new Intent();
            intent.setAction(U.SCREEN_DISCONNECT);
            LocalBroadcastManager.getInstance(DisplayConnectionService.this).sendBroadcast(intent);

            SharedPreferences prefCurrent = U.getPrefCurrent(DisplayConnectionService.this);
            DisplayManager dm = (DisplayManager) getSystemService(DISPLAY_SERVICE);
            Display[] displays = dm.getDisplays();

            try {
                if(displays[displays.length - 1].getDisplayId() == Display.DEFAULT_DISPLAY
                        && !prefCurrent.getBoolean("not_active", true)) {
                    SharedPreferences prefMain = U.getPrefMain(DisplayConnectionService.this);
                    if("quick_actions".equals(prefCurrent.getString("filename", "0"))) {
                        SharedPreferences prefSaved = U.getPrefQuickActions(DisplayConnectionService.this);
                        if(prefMain.getString("hdmi_load_profile", "show_list").equals(prefSaved.getString("original_filename", "0")))
                            U.turnOffProfile(DisplayConnectionService.this);
                        else if(prefMain.getBoolean("inactive", true)) {
                            Intent turnOffIntent = new Intent(DisplayConnectionService.this, TurnOffActivity.class);
                            turnOffIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(turnOffIntent);
                        }
                    } else if(prefMain.getString("hdmi_load_profile", "show_list").equals(prefCurrent.getString("filename", "0")))
                        U.turnOffProfile(DisplayConnectionService.this);
                    else if(prefMain.getBoolean("inactive", true)) {
                        Intent turnOffIntent = new Intent(DisplayConnectionService.this, TurnOffActivity.class);
                        turnOffIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(turnOffIntent);
                    }
                }
            } catch (ArrayIndexOutOfBoundsException e) { /* Gracefully fail */ }
        }
    };

    private final ServiceBinder binder = new ServiceBinder() {
        @Override
        public ServiceInterface getService() {
            return DisplayConnectionService.this;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public void onCreate() {
        DisplayManager manager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
        manager.registerDisplayListener(listener, null);
    }

    @Override
    public void startForeground() {
        // Intent to launch MainActivity when notification is clicked
        Intent mainActivityIntent = new Intent(this, MainActivity.class);
        PendingIntent mainActivityPendingIntent = PendingIntent.getActivity(this, 0, mainActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        String id = "DisplayConnectionService";

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        CharSequence name = getString(R.string.auto_start);
        int importance = NotificationManager.IMPORTANCE_MIN;

        mNotificationManager.createNotificationChannel(new NotificationChannel(id, name, importance));

        // Build the notification
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, id)
                .setContentIntent(mainActivityPendingIntent)
                .setSmallIcon(R.drawable.ic_action_dock)
                .setContentTitle(getString(R.string.auto_start_active))
                .setOngoing(true)
                .setShowWhen(false);

        // Set notification color on Lollipop
        mBuilder.setColor(ContextCompat.getColor(this, R.color.primary_dark))
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);

        startForeground(2, mBuilder.build());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if(intent.getBooleanExtra("start_foreground", false))
            startForeground();

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        DisplayManager manager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
        manager.unregisterDisplayListener(listener);
    }
} public final class NotificationService extends RotationLockService {

    NotificationCompat.Builder mBuilder;

    BroadcastReceiver screenOnReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            SharedPreferences prefMain = U.getPrefMain(NotificationService.this);
            SharedPreferences.Editor editor = prefMain.edit();
            editor.putLong("screen_on_time", System.currentTimeMillis());
            editor.apply();

            if(U.castScreenActive(NotificationService.this)) {
                Intent serviceIntent = new Intent(context, TempBacklightOnService.class);
                U.startService(context, serviceIntent);
            } else {
                Intent serviceIntent = new Intent(context, ScreenOnService.class);
                U.startService(context, serviceIntent);
            }
        }
    };

    BroadcastReceiver userPresentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            SharedPreferences prefMain = U.getPrefMain(NotificationService.this);
            long screenOnTime = prefMain.getLong("screen_on_time", 0);

            SharedPreferences.Editor editor = prefMain.edit();
            editor.remove("screen_on_time");
            editor.apply();

            if(U.castScreenActive(NotificationService.this)
                    || screenOnTime < (System.currentTimeMillis() - 5000)) {
                Intent serviceIntent = new Intent(context, ScreenOnService.class);
                U.startService(context, serviceIntent);
            }
        }
    };

    DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() {
        @Override
        public void onDisplayAdded(int displayId) {
            Intent intent = new Intent();
            intent.setAction(U.SCREEN_CONNECT);
            LocalBroadcastManager.getInstance(NotificationService.this).sendBroadcast(intent);

            DisplayManager dm = (DisplayManager) getSystemService(DISPLAY_SERVICE);
            Display[] displays = dm.getDisplays();

            try {
                if(displays[displays.length - 2].getDisplayId() == Display.DEFAULT_DISPLAY) {
                    Intent serviceIntent = new Intent(NotificationService.this, ScreenOnService.class);
                    U.startService(NotificationService.this, serviceIntent);
                }
            } catch (ArrayIndexOutOfBoundsException e) { /* Gracefully fail */ }
        }

        @Override
        public void onDisplayChanged(int displayId) {}

        @Override
        public void onDisplayRemoved(int displayId) {
            DisplayManager dm = (DisplayManager) getSystemService(DISPLAY_SERVICE);
            Display[] displays = dm.getDisplays();

            try {
                if(displays[displays.length - 1].getDisplayId() == Display.DEFAULT_DISPLAY) {
                    Intent serviceIntent = new Intent(NotificationService.this, TempBacklightOnService.class);
                    U.startService(NotificationService.this, serviceIntent);

                    SharedPreferences prefMain = U.getPrefMain(NotificationService.this);
                    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
                    boolean displayConnectionServiceRunning = false;

                    for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
                        if(DisplayConnectionService.class.getName().equals(service.service.getClassName()))
                            displayConnectionServiceRunning = true;
                    }

                    if(prefMain.getBoolean("inactive", true) && !displayConnectionServiceRunning) {
                        Intent turnOffIntent = new Intent(NotificationService.this, TurnOffActivity.class);
                        turnOffIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(turnOffIntent);
                    }
                }
            } catch (ArrayIndexOutOfBoundsException e) { /* Gracefully fail */ }
        }
    };

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onCreate() {
        // Register broadcast receivers for screen on and user present
        final IntentFilter filter1 = new IntentFilter();
        final IntentFilter filter2 = new IntentFilter();

        filter1.addAction(Intent.ACTION_SCREEN_ON);
        filter1.addAction(Intent.ACTION_DREAMING_STARTED);
        filter2.addAction(Intent.ACTION_USER_PRESENT);

        registerReceiver(screenOnReceiver, filter1);
        registerReceiver(userPresentReceiver, filter2);

        DisplayManager manager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
        manager.registerDisplayListener(listener, null);

        super.onCreate();
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(screenOnReceiver);
        unregisterReceiver(userPresentReceiver);

        DisplayManager manager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
        manager.unregisterDisplayListener(listener);

        super.onDestroy();
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void startService() {
        // Load preferences
        SharedPreferences prefCurrent = U.getPrefCurrent(this);
        SharedPreferences prefMain = U.getPrefMain(this);

        // Intent to launch MainActivity when notification is clicked
        Intent mainActivityIntent = new Intent(this, MainActivity.class);
        PendingIntent mainActivityPendingIntent = PendingIntent.getActivity(this, 0, mainActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        String id = "NotificationService";

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            CharSequence name = getString(R.string.profile_active);

            int importance = prefMain.getBoolean("hide_notification", false)
                    ? NotificationManager.IMPORTANCE_MIN
                    : NotificationManager.IMPORTANCE_LOW;

            mNotificationManager.createNotificationChannel(new NotificationChannel(id, name, importance));
        }

        // Build the notification
        mBuilder = new NotificationCompat.Builder(this, id)
                .setContentIntent(mainActivityPendingIntent)
                .setSmallIcon(R.drawable.ic_action_dock)
                .setContentTitle(getResources().getString(R.string.notification))
                .setContentText(prefCurrent.getString("profile_name", getResources().getString(R.string.action_new)))
                .setOngoing(true)
                .setShowWhen(false);

        // Set action buttons
        setActionButton(prefMain.getString("notification_action_2", "turn-off"), prefCurrent, 0);
        setActionButton(prefMain.getString("notification_action", "lock-device"), prefCurrent, 1);

        // Respect setting to hide notification
        if(prefMain.getBoolean("hide_notification", false))
            mBuilder.setPriority(Notification.PRIORITY_MIN);

        // Set notification color on Lollipop
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mBuilder.setColor(ContextCompat.getColor(this, R.color.primary_dark))
                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
        }

        startForeground(1, mBuilder.build());
    }

    @Override
    protected int getScreenOrientation() {
        return U.hasSupportLibrary(this) ? -1 : U.getScreenOrientation(this);
    }

    private void setActionButton(String key, SharedPreferences prefCurrent, int code) {
        Intent customIntent;
        PendingIntent customPendingIntent = null;
        String customString = null;

        if(key.equals("turn-off")) {
            // Turn Off
            customIntent = new Intent(this, TurnOffActivity.class);
            customIntent.putExtra("notification", true);
            customPendingIntent = PendingIntent.getActivity(this, code, customIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            customString = getResources().getStringArray(R.array.pref_notification_action_list)[0];
        } else if(key.equals("lock-device")) {
            // Lock Device
            customIntent = new Intent(this, LockDeviceService.class);
            customPendingIntent = PendingIntent.getService(this, code, customIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            customString = getResources().getStringArray(R.array.pref_notification_action_list)[2];
        } else if(key.equals("quick-actions")) {
            // Quick Actions
            customIntent = new Intent(this, TaskerQuickActionsActivity.class);
            customIntent.putExtra("launched-from-app", true);
            customPendingIntent = PendingIntent.getActivity(this, code, customIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            customString = getResources().getStringArray(R.array.pref_notification_action_list)[1];
        } else if(key.startsWith("temp_")) {
            // Toggle
            customIntent = new Intent(this, TaskerQuickActionsActivity.class);
            customIntent.putExtra(U.KEY, key.equals("temp_immersive") ? "temp_immersive_new" : key);
            customIntent.putExtra(U.VALUE, "Toggle");
            customPendingIntent = PendingIntent.getActivity(this, code, customIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            String onOffString;

            switch(key) {
                case "temp_backlight_off":
                    if(prefCurrent.getBoolean("backlight_off", false))
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[0];
                    else
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[1];

                    customString = getResources().getString(R.string.quick_backlight) + onOffString;
                    break;
                case "temp_chrome":
                    if(prefCurrent.getBoolean("chrome", false))
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[1];
                    else
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[0];

                    customString = getResources().getString(R.string.desktop) + onOffString;
                    break;
                case "temp_immersive":
                case "temp_immersive_new":
                    switch(prefCurrent.getString("immersive_new", "fallback")) {
                        case "immersive-mode":
                            onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[1];
                            break;
                        default:
                            onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[0];
                            break;
                    }

                    customString = getResources().getString(R.string.immersive) + onOffString;
                    break;
                case "temp_overscan":
                    if(prefCurrent.getBoolean("overscan", false))
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[1];
                    else
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[0];

                    customString = getResources().getString(R.string.quick_overscan) + onOffString;
                    break;
                case "temp_vibration_off":
                    if(prefCurrent.getBoolean("vibration_off", false))
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[0];
                    else
                        onOffString = " " + getResources().getStringArray(R.array.pref_quick_actions)[1];

                    customString = getResources().getString(R.string.quick_vibration) + onOffString;
                    break;
            }
        }

        // Add action to notification builder
        mBuilder.addAction(0, customString, customPendingIntent);
    }
} public final class U {

    private U() {}

    // Intents and extras
    // NOTE: these intents are only sent in certain scenarios and should not be relied upon by third-party apps

    // Re-lists the profiles in ProfileListActivity
    public static final String LIST_PROFILES = "com.farmerbb.secondscreen.LIST_PROFILES";

    // Sent when SecondScreen detects a screen connection
    public static final String SCREEN_CONNECT = "com.farmerbb.secondscreen.SCREEN_CONNECT";

    // Sent when SecondScreen detects a screen disconnection
    public static final String SCREEN_DISCONNECT = "com.farmerbb.secondscreen.SCREEN_DISCONNECT";

    // Used by debug mode
    public static final String SIMULATE_REBOOT = "com.farmerbb.secondscreen.SIMULATE_REBOOT";
    public static final String SIMULATE_APP_UPGRADE = "com.farmerbb.secondscreen.SIMULATE_APP_UPGRADE";

    // Extras for intents sent via homescreen shortcuts or Tasker
    public static final String NAME = "com.farmerbb.secondscreen.NAME";
    public static final String KEY = "com.farmerbb.secondscreen.KEY";
    public static final String VALUE = "com.farmerbb.secondscreen.VALUE";

    // Path to the Chrome command line file
    private static final String CHROME_COMMAND_LINE = "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE)
            ? "/data/local/tmp/chrome-command-line"
            : "/data/local/chrome-command-line";

    // Arrays of sysfs files that turn the backlight or vibration off.
    // Add new files to the end of the respective array to add backlight/vibration off support to a device

    public static final File[] backlightOff = {
            new File("/sys/class/leds/lcd-backlight", "brightness"),
            new File("/sys/class/backlight/pwm-backlight", "brightness"),
            new File("/sys/class/backlight/intel_backlight", "brightness"),
            new File("/sys/class/backlight/tegra-dsi-backlight.0", "brightness"),
            new File("/sys/devices/platform/i2c-gpio.24/i2c-24/24-002c/backlight/panel", "brightness")};

    public static final File[] vibrationOff = {
            new File("/sys/class/timed_output/vibrator", "amp"),
            new File("/sys/drv2605", "rtp_strength")};

    // Tests if backlight/vibration off files exist
    public static boolean filesExist(File[] array) {
        boolean exists = false;

        for(File file : array) {
            if(file.exists())
                exists = true;
        }

        return exists;
    }

    // Superuser commands

    // Commands for features with boolean values.
    // "true" to turn a feature on, "false" to turn it off
    private static final String navbarCommand = "settings put secure dev_force_show_navbar ";
    public static String navbarCommand(boolean checked) {
        if(checked)
            return navbarCommand + "1";
        else
            return navbarCommand + "0";
    }

    private static final String showTouchesCommand = "settings put system show_touches ";
    public static String showTouchesCommand(boolean checked) {
        if(checked)
            return showTouchesCommand + "1";
        else
            return showTouchesCommand + "0";
    }

    private static final String daydreamsCommand = "settings put secure screensaver_enabled ";
    public static String daydreamsCommand(boolean checked) {
        if(checked)
            return daydreamsCommand + "1";
        else
            return daydreamsCommand + "0";
    }

    private static final String daydreamsChargingCommand = "settings put secure screensaver_activate_on_sleep ";
    public static String daydreamsChargingCommand(boolean checked) {
        if(checked)
            return daydreamsChargingCommand + "1";
        else
            return daydreamsChargingCommand + "0";
    }

    private static final String freeformCommand = "settings put global enable_freeform_support ";
    public static String freeformCommand(boolean checked) {
        if(checked)
            return freeformCommand + "1";
        else
            return freeformCommand + "0";
    }

    // Non-boolean commands.  Most of these take a variable value either as an argument to the method,
    // or by tacking the argument onto the end of the string.
    public static final String chromeCommandRemove = "rm " + CHROME_COMMAND_LINE;
    public static final String rotationCommand = "am broadcast -a android.intent.action.DOCK_EVENT --ei android.intent.extra.DOCK_STATE ";
    public static final String rotationPrePostCommands = "settings put secure screensaver_activate_on_dock ";
    public static final String stayOnCommand = "settings put global stay_on_while_plugged_in ";
    public static final String timeoutCommand = "settings put secure lock_screen_lock_after_timeout ";
    public static final String hdmiRotationCommand = "setprop persist.demo.hdmirotation ";
    public static final String setHomeActivityCommand = "cmd package set-home-activity ";

    public static String safeModeSizeCommand(String args) {
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
            return "settings put global display_size_forced " + args;
        else
            return "settings put secure display_size_forced " + args;
    }

    public static String safeModeDensityCommand(String args) {
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
            return "settings put global display_density_forced " + args;
        else
            return "settings put secure display_density_forced " + args;
    }

    public static String sizeCommand(Context context, String args) {
        if(isDesktopModeActive(context))
            return "wm size " + args + " -d " + getExternalDisplayID(context);
        else if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1)
            return "wm size " + args;
        else
            return "am display-size " + args;
    }

    public static String densityCommand(Context context, String args) {
        if(isDesktopModeActive(context))
            return "wm density " + args + " -d " + getExternalDisplayID(context);
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1)
            return "wm density " + args;
        else
            return "am display-density " + args;
    }

    public static String overscanCommand(Context context, String args) {
        if(isDesktopModeActive(context))
            return "wm overscan " + args + " -d " + getExternalDisplayID(context);
        else
            return "wm overscan " + args;
    }

    public static String chromeCommand(Context context) {
        return "echo 'chrome --user-agent=\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + getChromeVersion(context) + " Safari/537.36\"' > " + CHROME_COMMAND_LINE + " && chmod 644 " + CHROME_COMMAND_LINE;
    }

    public static String chromeCommand2(Context context) {
        return "am force-stop " + getChromePackageName(context);
    }

    public static String immersiveCommand(String pref) {
        String returnCommand = "settings put global policy_control ";

        switch(pref) {
            case "status-only":
                returnCommand = returnCommand + "immersive.navigation=*";
                break;
            case "immersive-mode":
                returnCommand = returnCommand + "immersive.full=*";
                break;
            case "do-nothing":
                returnCommand = returnCommand + "null";
                break;
        }

        return returnCommand;
    }

    public static String uiRefreshCommand(Context context, boolean restartActivityManager) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> pids = am.getRunningAppProcesses();
        int processid = 0;

        if(restartActivityManager) {
            // Kill surfaceflinger if on a Jelly Bean device; run "am restart" if on KitKat or later
            if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                for(ActivityManager.RunningAppProcessInfo process : pids) {
                    if(process.processName.equalsIgnoreCase("/system/bin/surfaceflinger"))
                        processid = process.pid;
                }

                return "sleep 1 && kill " + Integer.toString(processid);
            } else
                return "sleep 1 && am restart";
        } else {
            // Get SystemUI pid
            for(ActivityManager.RunningAppProcessInfo process : pids) {
                if(process.processName.equalsIgnoreCase("com.android.systemui"))
                    processid = process.pid;
            }

            // Starting with 5.1.1 LMY48I, RunningAppProcessInfo no longer returns valid data,
            // which means we won't be able to use the "kill" command with the pid of SystemUI.
            // Thus, if the SystemUI pid gets returned as 0, we need to use the "pkill" command
            // instead, and hope that the user has that command available on their device.
            // Starting with 7.0, pkill doesn't work, so use "kill" and "pidof" instead.
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                return "sleep 2 && kill `pidof com.android.systemui`";
            else if(processid == 0)
                return "sleep 2 && pkill com.android.systemui";
            else
                return "sleep 2 && kill " + Integer.toString(processid);
        }
    }

    public static String uiRefreshCommand2(Context context, boolean shouldClearHome) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

        // For better reliability, we execute the UI refresh while on the home screen
        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
        homeIntent.addCategory(Intent.CATEGORY_HOME);
        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        if(!shouldClearHome) {
            try {
                context.startActivity(homeIntent);
            } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
        }

        // Kill all background processes, in order to fully refresh UI
        PackageManager pm = context.getPackageManager();
        List<ApplicationInfo> packages = pm.getInstalledApplications(0);
        for(ApplicationInfo packageInfo : packages) {
            if(!packageInfo.packageName.equalsIgnoreCase(context.getPackageName()))
                am.killBackgroundProcesses(packageInfo.packageName);
        }

        // Get launcher package name
        final ResolveInfo mInfo = pm.resolveActivity(homeIntent, 0);
        final String launcherPackageName = mInfo.activityInfo.applicationInfo.packageName;

        if(launcherPackageName.equals(getTaskbarPackageName(context))
                || launcherPackageName.equals("android"))
            return "sleep 1";
        else
            return "sleep 1 && am force-stop " + launcherPackageName;
    }

    // Runs checks to determine if size or density commands need to be run.
    // Don't run these commands if we don't need to.
    public static boolean runSizeCommand(Context context, String requestedRes) {
        if(isDesktopModeActive(context))
            return true;

        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display disp = wm.getDefaultDisplay();
        disp.getRealMetrics(metrics);

        SharedPreferences prefMain = getPrefMain(context);
        String currentRes = " ";
        String nativeRes = Integer.toString(prefMain.getInt("width", 0))
                + "x"
                + Integer.toString(prefMain.getInt("height", 0));

        if(prefMain.getBoolean("debug_mode", false)) {
            SharedPreferences prefCurrent = getPrefCurrent(context);
            currentRes = prefCurrent.getString("size", "reset");

            if("reset".equals(currentRes))
                currentRes = nativeRes;
        } else {
            if((context.getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && !prefMain.getBoolean("landscape", false))
                    || (context.getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && prefMain.getBoolean("landscape", false))) {
                currentRes = Integer.toString(metrics.widthPixels)
                        + "x"
                        + Integer.toString(metrics.heightPixels);
            } else if((context.getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && !prefMain.getBoolean("landscape", false))
                    || (context.getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && prefMain.getBoolean("landscape", false))) {
                currentRes = Integer.toString(metrics.heightPixels)
                        + "x"
                        + Integer.toString(metrics.widthPixels);
            }
        }

        if(requestedRes.equals("reset"))
            requestedRes = nativeRes;

        return !requestedRes.equals(currentRes);
    }

    public static boolean runDensityCommand(Context context, String requestedDpi) {
        if(isDesktopModeActive(context))
            return true;

        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display disp = wm.getDefaultDisplay();
        disp.getRealMetrics(metrics);

        SharedPreferences prefMain = getPrefMain(context);
        String currentDpi;
        String nativeDpi = Integer.toString(getSystemProperty("ro.sf.lcd_density", prefMain.getInt("density", 0)));

        if(prefMain.getBoolean("debug_mode", false)) {
            SharedPreferences prefCurrent = getPrefCurrent(context);
            currentDpi = prefCurrent.getString("density", "reset");

            if("reset".equals(currentDpi))
                currentDpi = nativeDpi;
        } else
            currentDpi = Integer.toString(metrics.densityDpi);

        if(requestedDpi.equals("reset"))
            requestedDpi = nativeDpi;

        return !requestedDpi.equals(currentDpi);
    }

    // Methods used to retrieve SharedPreferences objects

    // XML file containing the main application preferences; normally set to the MainActivity preferences file.
    public static SharedPreferences getPrefMain(Context context) {
        SharedPreferences prefMain;
        if(context.getPackageName().equals("com.farmerbb.secondscreen"))
            prefMain = context.getSharedPreferences(MainActivity.class.getName().replace("com.farmerbb.secondscreen.", ""), Context.MODE_PRIVATE);
        else
            prefMain = context.getSharedPreferences(MainActivity.class.getName(), Context.MODE_PRIVATE);

        return prefMain;
    }

    // XML file containing the current state of any running profiles
    public static SharedPreferences getPrefCurrent(Context context) {
        return getPrefSaved(context, "current");
    }

    // XML file containing the current Quick Actions temporary profile
    public static SharedPreferences getPrefQuickActions(Context context) {
        return getPrefSaved(context, "quick_actions");
    }

    // XML file containing any changes to profiles that have not yet been saved;
    // normally set to the global application preferences file due to the way PreferenceActivity works.
    public static SharedPreferences getPrefNew(Context context) {
        return getPrefSaved(context, context.getPackageName() + "_preferences");
    }

    // Gets any XML file corresponding to a saved profile filename.
    public static SharedPreferences getPrefSaved(Context context, String filename) {
        return context.getSharedPreferences(filename, Context.MODE_PRIVATE);
    }

    // Methods used for generating the list of saved profiles

    // Returns list of filenames in /data/data/com.farmerbb.secondscreen/files/, plus a fake entry for "Turn Off"
    private static String[] getListOfProfiles(File file) {
        return file.list();
    }

    private static String[] getListOfProfiles(File file, String fakeEntry) {
        String[] list = file.list();
        String[] realList = new String[list.length + 1];

        System.arraycopy(list, 0, realList, 0, list.length);

        realList[list.length] = fakeEntry;

        return realList;
    }

    // Returns an integer with number of files in /data/data/com.farmerbb.secondscreen/files/
    public static int getNumOfFiles(File file) {
        return new File(file.getPath()).list().length;
    }

    // Loads first line of a profile for display in the ListView
    public static String getProfileTitle(Context context, String filename) throws IOException {
        // Open the file on disk
        FileInputStream input = context.openFileInput(filename);
        InputStreamReader reader = new InputStreamReader(input);
        BufferedReader buffer = new BufferedReader(reader);

        // Load the file
        String line = buffer.readLine();

        // Close file on disk
        reader.close();

        return(line);
    }

    // Gathers list of profile names and their corresponding filenames, optionally with fake entries
    public static String[][] listProfiles(Context context) {
        return listProfiles(context, false, null, 0);
    }

    public static String[][] listProfiles(Context context, String fakeEntryValue, int fakeEntryTitle) {
        return listProfiles(context, true, fakeEntryValue, fakeEntryTitle);
    }

    private static String[][] listProfiles(Context context, boolean fakeEntry, String fakeEntryValue, int fakeEntryTitle) {
        // Get number of files
        int numOfFiles = getNumOfFiles(context.getFilesDir());
        int numOfProfiles = numOfFiles;

        // Get array of filenames
        String[] listOfFiles;
        ArrayList<String> listOfProfiles = new ArrayList<>();

        if(fakeEntry)
            listOfFiles = getListOfProfiles(context.getFilesDir(), fakeEntryValue);
        else
            listOfFiles = getListOfProfiles(context.getFilesDir());

        // Remove any files from the list that aren't profiles
        for(int i = 0; i < numOfFiles; i++) {
            if(isValidFilename(listOfFiles[i]) && !listOfFiles[i].equals(fakeEntryValue))
                listOfProfiles.add(listOfFiles[i]);
            else
                numOfProfiles--;
        }

        if(numOfProfiles == 0)
            return null;
        else {
            // Get "fake" number of files, if applicable
            int fakeNumOfProfiles;
            if(fakeEntry)
                fakeNumOfProfiles = numOfProfiles + 1;
            else
                fakeNumOfProfiles = numOfProfiles;

            // Create arrays of profile lists
            String[] listOfProfilesByDate = new String[fakeNumOfProfiles];
            String[] listOfProfilesByName = new String[fakeNumOfProfiles];

            String[] listOfTitlesByDate = new String[fakeNumOfProfiles];
            String[] listOfTitlesByName = new String[fakeNumOfProfiles];

            for(int i = 0; i < numOfProfiles; i++) {
                listOfProfilesByDate[i] = listOfProfiles.get(i);
            }

            // Get array of first lines of each profile
            for(int i = 0; i < numOfProfiles; i++) {
                try {
                    listOfTitlesByDate[i] = getProfileTitle(context, listOfProfilesByDate[i]);
                } catch (IOException e) {
                    showToast(context, R.string.error_loading_list);
                }
            }

            // Add fake entry, if applicable
            if(fakeEntry)
                listOfTitlesByDate[numOfProfiles] = " " + context.getResources().getString(R.string.bullet) + " " + context.getResources().getString(fakeEntryTitle) + " " + context.getResources().getString(R.string.bullet);

            // Sort alphabetically
            // Copy titles array
            System.arraycopy(listOfTitlesByDate, 0, listOfTitlesByName, 0, fakeNumOfProfiles);

            // Sort titles
            Arrays.sort(listOfTitlesByName, (s, t1) -> Collator.getInstance().compare(s, t1));

            // Initialize profiles array
            for(int i = 0; i < fakeNumOfProfiles; i++)
                listOfProfilesByName[i] = "new";

            // Copy filenames array with new sort order of titles and nullify date arrays
            for(int i = 0; i < fakeNumOfProfiles; i++) {
                for(int j = 0; j < fakeNumOfProfiles; j++) {
                    if(listOfTitlesByName[i].equals(listOfTitlesByDate[j]) && listOfProfilesByName[i].equals("new")) {
                        listOfProfilesByName[i] = listOfProfilesByDate[j];
                        listOfProfilesByDate[j] = "";
                        listOfTitlesByDate[j] = "";
                    }
                }
            }

            if(fakeEntry) listOfProfilesByName[0] = fakeEntryValue;

            return new String[][] {listOfProfilesByName, listOfTitlesByName};
        }
    }

    // Sends broadcast to refresh list of profiles
    public static void listProfilesBroadcast(Context context) {
        Intent listProfilesIntent = new Intent();
        listProfilesIntent.setAction(LIST_PROFILES);
        LocalBroadcastManager.getInstance(context).sendBroadcast(listProfilesIntent);
    }

    // Miscellaneous utility methods

    // Checks if elevated permissions are available.
    // If debug mode is enabled, the app acts as if elevated permissions are always available.
    public static boolean hasElevatedPermissions(Context context) {
        return hasElevatedPermissions(context, false);
    }

    public static boolean hasElevatedPermissions(Context context, boolean forceRecheck) {
        return Superuser.getInstance().available(forceRecheck)
                || NonRootUtils.hasWriteSecureSettingsPermission(context)
                || getPrefMain(context).getBoolean("debug_mode", false);
    }

    // Checks if SecondScreen is running in non-root mode.
    public static boolean isInNonRootMode(Context context) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && !(Superuser.getInstance().available()
                || getPrefMain(context).getBoolean("debug_mode", false));
    }

    // Executes multiple commands, either by calling superuser
    // or by writing to the settings database directly.
    public static void runCommands(Context context, String[] commands, boolean rebootRequired) {
        boolean arrayIsEmpty = true;
        for(String command : commands) {
            if(command != null && !command.equals("")) {
                arrayIsEmpty = false;
                break;
            }
        }

        boolean runAsRoot = Superuser.getInstance().available()
                || getPrefMain(context).getBoolean("debug_mode", false);

        if(!arrayIsEmpty) {
            if(runAsRoot) {
                for(String command : commands) {
                    if(!command.isEmpty()) {
                        runSuCommands(context, commands);
                        break;
                    }
                }
            } else
                NonRootUtils.runCommands(context, commands);
        }

        CommandDispatcher.getInstance().dispatch(context);

        if(!runAsRoot && rebootRequired) {
            SharedPreferences prefCurrent = getPrefCurrent(context);
            if(!prefCurrent.getBoolean("not_active", true))
                prefCurrent.edit().putBoolean("reboot_required", true).apply();

            Intent intent = new Intent(context, RebootRequiredActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
        }
    }

    // Executes a single command.
    public static void runCommand(Context context, String command) {
        runCommands(context, new String[]{command}, false);
    }

    // Executes multiple superuser commands.
    // If debug mode is enabled, the command is not actually run; instead, this will show a
    // notification containing the command that would have been run instead.
    private static void runSuCommands(Context context, String[] commands) {
        if(getPrefMain(context).getBoolean("debug_mode", false)) {
            StringBuilder dump = new StringBuilder();

            for(String command : commands) {
                if(!command.equals(""))
                    dump.append(context.getResources().getString(R.string.bullet))
                            .append(" ").append(command).append("\n");
            }

            NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            String id = "debug_mode";

            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                CharSequence name = context.getString(R.string.debug_mode);
                int importance = NotificationManager.IMPORTANCE_LOW;

                nm.createNotificationChannel(new NotificationChannel(id, name, importance));
            }

            Notification notification = new NotificationCompat.Builder(context, id)
                    .setSmallIcon(R.drawable.ic_stat_name)
                    .setContentTitle(context.getResources().getString(R.string.debug_mode_enabled))
                    .setContentText(dump.toString())
                    .setStyle(new NotificationCompat.BigTextStyle().bigText(dump.toString()))
                    .build();

            nm.notify(new Random().nextInt(), notification);

            // Some devices (Android TV) don't show notifications, so let's also print the commands
            // to the log just in case.
            System.out.println(dump);
        } else
            Superuser.getInstance().run(commands);
    }

    // Loads a profile with the given filename
    public static void loadProfile(Context context, String filename) {
        SharedPreferences prefSaved = getPrefSaved(context, filename);
        SharedPreferences prefMain = getPrefMain(context);

        String requestedRes = prefSaved.getString("size", "reset");
        String requestedDpi = prefSaved.getString("density", "reset");

        // Check to see if the user is trying to load a profile with a blacklisted resolution/DPI combo
        boolean blacklisted = isBlacklisted(context, requestedRes, requestedDpi);

        if(blacklisted && !prefMain.getBoolean("expert_mode", false)) {
            showToastLong(context, R.string.blacklisted);
            return;
        }
        
        // Initialize the support library
        if(hasSupportLibrary(context)) {
            try {
                Intent intent = new Intent();
                intent.setComponent(ComponentName.unflattenFromString(BuildConfig.SUPPORT_APPLICATION_ID + "/.InitActivity"));
                context.startActivity(intent);
            } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
        }

        if(NonRootUtils.hasWriteSettingsPermission(context)) {
            // Set filename in current.xml
            SharedPreferences prefCurrent = getPrefCurrent(context);
            SharedPreferences.Editor editor = prefCurrent.edit();
            editor.putString("filename", filename);
            editor.apply();

            // Start ProfileLoadService
            Intent intent = new Intent(context, ProfileLoadService.class);
            // Get filename of selected profile
            intent.putExtra(NAME, filename);
            startService(context, intent);
        } else {
            Intent intent = new Intent(context, WriteSettingsPermissionActivity.class);
            intent.putExtra("action", "load-profile");
            intent.putExtra("filename", filename);
            context.startActivity(intent);
        }
    }

    // Turns off the currently active profile
    public static void turnOffProfile(Context context) {
        if(NonRootUtils.hasWriteSettingsPermission(context)) {
            // Set filename in current.xml
            SharedPreferences prefCurrent = getPrefCurrent(context);
            SharedPreferences.Editor editor = prefCurrent.edit();
            editor.putString("filename_backup", prefCurrent.getString("filename", "0"));
            editor.putString("filename", "0");
            editor.apply();

            // Start TurnOffService
            Intent intent = new Intent(context, TurnOffService.class);
            startService(context, intent);
        } else {
            Intent intent = new Intent(context, WriteSettingsPermissionActivity.class);
            intent.putExtra("action", "turn-off-profile");
            context.startActivity(intent);
        }
    }

    // Shows toast notifications
    public static void showToast(Context context, int message) {
        showToast(context, context.getString(message), Toast.LENGTH_SHORT);
    }

    public static void showToastLong(Context context, int message) {
        showToast(context, context.getString(message), Toast.LENGTH_LONG);
    }

    public static void showToast(Context context, String message, int length) {
        cancelToast();

        ToastInterface toast = createToast(context.getApplicationContext(), message, length);
        toast.show();

        ToastHelper.getInstance().setLastToast(toast);
    }

    public static void cancelToast() {
        ToastInterface toast = ToastHelper.getInstance().getLastToast();
        if(toast != null) toast.cancel();
    }

    private static ToastInterface createToast(Context context, String message, int length) {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
            return new ToastFrameworkImpl(context, message, length);
        else
            return new ToastCompatImpl(context, message, length);
    }

    // Generates blurb text for profile options, used in various places in the UI
    public static String generateBlurb(Activity a, String key, String value, boolean isNotification) {
        String blurb = " ";

        if(a instanceof TaskerQuickActionsActivity) {
            blurb = a.getResources().getStringArray(R.array.pref_notification_action_list)[1];

            // If this blurb is being generated for the notification, and the value is "Toggle",
            // set value to the actual "On" or "Off" state
            if(isNotification && value.equals("Toggle")) {
                SharedPreferences prefCurrent = getPrefCurrent(a);
                switch(key) {
                    case "temp_backlight_off":
                        if(prefCurrent.getBoolean("backlight_off", false))
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[0];
                        else
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[1];
                        break;
                    case "temp_chrome":
                        if(prefCurrent.getBoolean("chrome", false))
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[1];
                        else
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[0];
                        break;
                    case "temp_immersive":
                    case "temp_immersive_new":
                        if(key.equals("temp_immersive_new"))
                            key = "temp_immersive";

                        switch(prefCurrent.getString("immersive_new", "fallback")) {
                            case "immersive-mode":
                                value = a.getResources().getStringArray(R.array.pref_quick_actions)[1];
                                break;
                            default:
                                value = a.getResources().getStringArray(R.array.pref_quick_actions)[0];
                                break;
                        }
                        break;
                    case "temp_overscan":
                        if(prefCurrent.getBoolean("overscan", false))
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[1];
                        else
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[0];
                        break;
                    case "temp_vibration_off":
                        if(prefCurrent.getBoolean("vibration_off", false))
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[0];
                        else
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[1];
                        break;
                    case "temp_freeform":
                        if(prefCurrent.getBoolean("freeform", false))
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[1];
                        else
                            value = a.getResources().getStringArray(R.array.pref_quick_actions)[0];
                        break;
                    case "temp_hdmi_rotation":
                        switch(prefCurrent.getString("hdmi_rotation", "landscape")) {
                            case "portrait":
                                value = a.getResources().getStringArray(R.array.pref_hdmi_rotation_list)[1];
                                break;
                            case "landscape":
                                value = a.getResources().getStringArray(R.array.pref_hdmi_rotation_list)[0];
                                break;
                        }
                        break;
                }
            }

            // Modifications for non-English locales
            if(value.equals(a.getResources().getStringArray(R.array.pref_quick_actions_values)[0]))
                value = a.getResources().getStringArray(R.array.pref_quick_actions)[0];
            else if(value.equals(a.getResources().getStringArray(R.array.pref_quick_actions_values)[1])) {
                if(key.equals("temp_overscan"))
                    value = a.getResources().getStringArray(R.array.pref_quick_actions_overscan)[0];
                else
                    value = a.getResources().getStringArray(R.array.pref_quick_actions)[1];
            }
        }

        switch(key) {
            case "turn_off":
                blurb = a.getResources().getString(R.string.quick_turn_off);
                break;
            case "lock_device":
                blurb = a.getResources().getStringArray(R.array.pref_notification_action_list)[2];
                break;
            case "temp_backlight_off":
                blurb = a.getResources().getString(R.string.quick_backlight) + " " + value;
                break;
            case "temp_chrome":
                blurb = a.getResources().getString(R.string.quick_chrome) + " " + value;
                break;
            case "temp_immersive":
                blurb = a.getResources().getString(R.string.quick_immersive) + " " + value;
                break;
            case "temp_immersive_new":
                switch(value) {
                    case "do-nothing":
                        blurb = a.getResources().getStringArray(R.array.pref_immersive_list_alt)[0];
                        break;
                    case "status-only":
                        blurb = a.getResources().getStringArray(R.array.pref_immersive_list_alt)[1];
                        break;
                    case "immersive-mode":
                        blurb = a.getResources().getStringArray(R.array.pref_immersive_list_alt)[2];
                        break;
                    case "Toggle":
                        blurb = a.getResources().getStringArray(R.array.pref_immersive_list_alt)[3];
                        break;
                }
                break;
            case "density":
            case "temp_density":
                switch(value) {
                    case "reset":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[0];
                        break;
                    case "120":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[1];
                        break;
                    case "160":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[2];
                        break;
                    case "213":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[3];
                        break;
                    case "240":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[4];
                        break;
                    case "320":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[5];
                        break;
                    case "480":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[6];
                        break;
                    case "640":
                        blurb = a.getResources().getStringArray(R.array.pref_dpi_list)[7];
                        break;
                    default:
                        blurb = value + a.getResources().getString(R.string.dpi);
                        break;
                }
                break;
            case "temp_overscan":
                blurb = a.getResources().getString(R.string.quick_overscan) + " " + value;
                break;
            case "size":
            case "temp_size":
                SharedPreferences prefMain = getPrefMain(a);
                if(value.equals("reset"))
                    blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[0];
                else if(prefMain.getBoolean("landscape", false)) {
                    switch(value) {
                        case "3840x2160":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[1];
                            break;
                        case "1920x1080":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[2];
                            break;
                        case "1280x720":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[3];
                            break;
                        case "854x480":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[4];
                            break;
                        default:
                            blurb = value;
                            break;
                    }
                } else {
                    switch(value) {
                        case "2160x3840":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[1];
                            break;
                        case "1080x1920":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[2];
                            break;
                        case "720x1280":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[3];
                            break;
                        case "480x854":
                            blurb = a.getResources().getStringArray(R.array.pref_resolution_list)[4];
                            break;
                        default:
                            Scanner scanner = new Scanner(value);
                            scanner.useDelimiter("x");

                            int height = scanner.nextInt();
                            int width = scanner.nextInt();
                            scanner.close();

                            blurb = Integer.toString(width) + "x" + Integer.toString(height);
                            break;
                    }
                }
                break;
            case "temp_rotation_lock_new":
                switch(value) {
                    case "do-nothing":
                        blurb = a.getResources().getStringArray(R.array.pref_rotation_list)[0];
                        break;
                    case "auto-rotate":
                        blurb = a.getResources().getStringArray(R.array.pref_rotation_list)[1];
                        break;
                    case "landscape":
                        blurb = a.getResources().getStringArray(R.array.pref_rotation_list)[2];
                        break;
                    case "portrait":
                        blurb = a.getResources().getStringArray(R.array.pref_rotation_list)[3];
                        break;
                }
                break;
            case "temp_vibration_off":
                blurb = a.getResources().getString(R.string.quick_vibration) + " " + value;
                break;
            case "temp_freeform":
                blurb = a.getResources().getString(R.string.quick_freeform) + " " + value;
                break;
            case "temp_hdmi_rotation":
                blurb = a.getResources().getString(R.string.quick_hdmi_rotation) + " " + value;
                break;
        }

        return blurb;
    }

    // Updates a preference summary with the currently set value
    public static void bindPreferenceSummaryToValue(Preference preference, Preference.OnPreferenceChangeListener listener) {
        // Set the listener to watch for value changes.
        preference.setOnPreferenceChangeListener(listener);

        // Trigger the listener immediately with the preference's current value.
        listener.onPreferenceChange(preference, PreferenceManager.getDefaultSharedPreferences(preference.getContext()).getString(preference.getKey(), ""));
    }

    // Determine if a resolution/density combo is blacklisted (unsafe to use under normal circumstances)
    public static boolean isBlacklisted(Context context, String requestedRes, String requestedDpi) {
        boolean blacklisted = false;

        SharedPreferences prefMain = getPrefMain(context);
        int defaultHeight = prefMain.getInt("height", 0);
        int defaultWidth = prefMain.getInt("width", 0);
        int defaultDpi = getSystemProperty("ro.sf.lcd_density", prefMain.getInt("density", 0));

        int height, width, density;
        if("reset".equals(requestedRes)) {
            height = defaultHeight;
            width = defaultWidth;
        } else {
            Scanner scanner = new Scanner(requestedRes);
            scanner.useDelimiter("x");

            width = scanner.nextInt();
            height = scanner.nextInt();

            scanner.close();
        }

        if("reset".equals(requestedDpi))
            density = defaultDpi;
        else
            density = Integer.parseInt(requestedDpi);

        // Blacklist DPI values that result in a smallest width that's too low or too high
        int smallestWidth = (DisplayMetrics.DENSITY_DEFAULT * Math.min(height, width)) / density;
        if(smallestWidth < 320 || smallestWidth > 1280)
            blacklisted = true;

        // On Android 10, blacklist resolutions that are larger than the device's native resolution
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && (height > defaultHeight || width > defaultWidth))
            blacklisted = true;

        return blacklisted;
    }

    // Detects if we are currently casting the screen using Chromecast
    public static boolean castScreenActive(Context context) {
        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
        Display[] displays = dm.getDisplays();

        for(Display display : displays) {
            try {
                if(Class.forName("android.view.Display")
                        .getMethod("getOwnerPackageName")
                        .invoke(display)
                        .equals("com.google.android.gms"))
                    return true;
            } catch (Exception e) { /* Gracefully fail */ }
        }

        return false;
    }

    // Directs the user to check for updates
    public static void checkForUpdates(Context context) {
        String url;
        if(isPlayStoreRelease(context)) {
            if(BuildConfig.APPLICATION_ID.equals("com.farmerbb.secondscreen.free")
                    && !isPlayStoreInstalled(context))
                url = "https://github.com/farmerbb/SecondScreen/releases";
            else
                url = "https://play.google.com/store/apps/details?id=" + BuildConfig.APPLICATION_ID;
        } else
            url = "https://f-droid.org/repository/browse/?fdid=" + BuildConfig.APPLICATION_ID;

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(url));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        try {
            context.startActivity(intent);
        } catch (ActivityNotFoundException e) { /* Gracefully fail */ }
    }

    // Shows an error dialog when permissions cannot be granted
    public static void showErrorDialog(final Context context, String appopCmd) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(R.string.error_dialog_title)
                .setMessage(context.getString(R.string.error_dialog_message, BuildConfig.APPLICATION_ID, appopCmd))
                .setPositiveButton(R.string.action_ok, null);

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

    // Clears the default home screen
    public static void clearDefaultHome(Context context) {
        PackageManager packageManager = context.getPackageManager();
        ComponentName componentName = new ComponentName(context, DummyLauncherActivity.class.getName());
        packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

        goHome(context);

        packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    }

    public static void goHome(Context context) {
        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
        homeIntent.addCategory(Intent.CATEGORY_HOME);
        homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(homeIntent);
    }

    // Locks the screen
    public static void lockDevice(Context context) {
        if(isInNonRootMode(context)) {
            ComponentName component = new ComponentName(context, LockDeviceReceiver.class);
            context.getPackageManager().setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                    PackageManager.DONT_KILL_APP);

            DevicePolicyManager mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
            if(mDevicePolicyManager.isAdminActive(component))
                mDevicePolicyManager.lockNow();
            else {
                Intent intent = new Intent(context, LockDeviceActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(intent);

                if(context instanceof Activity)
                    ((Activity) context).overridePendingTransition(0, 0);
            }
        } else
            runCommand(context, "input keyevent 26");
    }

    private static boolean isValidFilename(String filename) {
        // Handle legacy default profiles from version 1.x.x
        switch(filename) {
            case "monitor_1080p":
            case "monitor_720p":
            case "tv_1080p":
            case "tv_720p":
                return true;
        }

        return NumberUtils.isCreatable(filename);
    }

    private static boolean isSystemApp(Context context) {
        try {
            ApplicationInfo info = context.getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
            int mask = ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
            return (info.flags & mask) != 0;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    public static boolean isBlissOs(Context context) {
        String blissVersion = getSystemProperty("ro.bliss.version");
        return blissVersion != null && !blissVersion.isEmpty()
                && BuildConfig.APPLICATION_ID.equals("com.farmerbb.secondscreen.free")
                && isSystemApp(context);
    }

    private static void getDisplayMetrics(Context context) {
        SharedPreferences prefMain = getPrefMain(context);
        SharedPreferences.Editor editor = prefMain.edit();

        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display disp = wm.getDefaultDisplay();
        disp.getRealMetrics(metrics);

        editor.putInt("density", getSystemProperty("ro.sf.lcd_density", metrics.densityDpi));

        if(context.getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
            if(wm.getDefaultDisplay().getRotation() == Surface.ROTATION_90
                    || wm.getDefaultDisplay().getRotation() == Surface.ROTATION_270) {
                editor.putBoolean("landscape", true);
                editor.putInt("height", metrics.widthPixels);
                editor.putInt("width", metrics.heightPixels);
            } else {
                editor.putBoolean("landscape", false);
                editor.putInt("height", metrics.heightPixels);
                editor.putInt("width", metrics.widthPixels);
            }
        } else if(context.getApplicationContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            if(wm.getDefaultDisplay().getRotation() == Surface.ROTATION_0
                    || wm.getDefaultDisplay().getRotation() == Surface.ROTATION_180) {
                editor.putBoolean("landscape", true);
                editor.putInt("height", metrics.heightPixels);
                editor.putInt("width", metrics.widthPixels);
            } else {
                editor.putBoolean("landscape", false);
                editor.putInt("height", metrics.widthPixels);
                editor.putInt("width", metrics.heightPixels);
            }
        }

        editor.apply();
    }

    @SuppressWarnings("HardwareIds")
    public static void initPrefs(Context context) {
        // Gather display metrics
        getDisplayMetrics(context);

        // Set some default preferences
        SharedPreferences prefMain = getPrefMain(context);
        SharedPreferences.Editor editor = prefMain.edit();
        editor.putBoolean("first-run", true);
        editor.putBoolean("safe_mode", true);
        editor.putString("android_id", Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID));
        editor.putBoolean("hdmi", true);
        editor.putString("notification_action", "quick-actions");
        editor.putBoolean("show-welcome-message", !isBlissOs(context));
        editor.apply();

        // Create default profile for BlissOS
        if(isBlissOs(context)) {
            String filename = String.valueOf(System.currentTimeMillis());
            String profileName = context.getResources().getString(R.string.blissos_default);

            createProfileFromTemplate(context, profileName, 3, getPrefSaved(context, filename));

            // Write the String to a new file with filename of current milliseconds of Unix time
            try {
                FileOutputStream output = context.openFileOutput(filename, Context.MODE_PRIVATE);
                output.write(profileName.getBytes());
                output.close();
            } catch (IOException e) { /* Gracefully fail */ }
        }
    }

    public static String getTaskbarPackageName(Context context) {
        return getInstalledPackage(context, Arrays.asList(
                "com.farmerbb.taskbar.paid",
                "com.farmerbb.taskbar"));
    }

    public static String getChromePackageName(Context context) {
        return getInstalledPackage(context, Arrays.asList(
                "com.chrome.canary",
                "com.chrome.dev",
                "com.chrome.beta",
                "com.android.chrome"));
    }

    // Returns the name of an installed package from a list of package names, in order of preference
    private static String getInstalledPackage(Context context, List<String> packageNames) {
        if(packageNames == null || packageNames.isEmpty())
            return null;

        List<String> packages = packageNames instanceof ArrayList ? packageNames : new ArrayList<>(packageNames);
        String packageName = packages.get(0);

        try {
            context.getPackageManager().getPackageInfo(packageName, 0);
            return packageName;
        } catch (PackageManager.NameNotFoundException e) {
            packages.remove(0);
            return getInstalledPackage(context, packages);
        }
    }

    private static String getChromeVersion(Context context) {
        String packageName = getChromePackageName(context);
        if(packageName != null) {
            try {
                PackageInfo pInfo = context.getPackageManager().getPackageInfo(packageName, 0);
                return pInfo.versionName;
            } catch (PackageManager.NameNotFoundException e) { /* Gracefully fail */ }
        }

        return null;
    }
    
    public static void createProfileFromTemplate(Context context, String name, int pos, SharedPreferences profileToCreate) {
        SharedPreferences.Editor editor = profileToCreate.edit();
        SharedPreferences prefMain = getPrefMain(context);

        if(name.isEmpty())
            if(pos == 5)
                editor.putString("profile_name", context.getResources().getString(R.string.action_new));
            else
                editor.putString("profile_name", context.getResources().getStringArray(R.array.new_profile_templates)[pos]);
        else
            editor.putString("profile_name", name);

        switch(pos) {
            // TV (1080p)
            case 0:
                if(prefMain.getBoolean("landscape", false))
                    editor.putString("size", "1920x1080");
                else
                    editor.putString("size", "1080x1920");

                editor.putString("rotation_lock_new", "landscape");
                editor.putString("density", "240");

                if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                    editor.putString("ui_refresh",
                            isInNonRootMode(context) ? "activity-manager" : "system-ui");
                }

                editor.putBoolean("chrome", getChromePackageName(context) != null);
                break;

            // TV (4K)
            case 1:
                if(prefMain.getBoolean("landscape", false))
                    editor.putString("size", "3840x2160");
                else
                    editor.putString("size", "2160x3840");

                editor.putString("rotation_lock_new", "landscape");
                editor.putString("density", "480");

                if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                    editor.putString("ui_refresh",
                            isInNonRootMode(context) ? "activity-manager" : "system-ui");
                }

                editor.putBoolean("chrome", getChromePackageName(context) != null);
                break;

            // TV (720p)
            case 2:
                if(prefMain.getBoolean("landscape", false))
                    editor.putString("size", "1280x720");
                else
                    editor.putString("size", "720x1280");

                editor.putString("rotation_lock_new", "landscape");
                editor.putString("density", "160");

                if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                    editor.putString("ui_refresh",
                            isInNonRootMode(context) ? "activity-manager" : "system-ui");
                }

                editor.putBoolean("chrome", getChromePackageName(context) != null);
                break;

            // Monitor (1080p)
            case 3:
                if(prefMain.getBoolean("landscape", false))
                    editor.putString("size", "1920x1080");
                else
                    editor.putString("size", "1080x1920");

                editor.putString("rotation_lock_new", "landscape");
                editor.putString("density", "160");

                if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                    editor.putString("ui_refresh",
                            isInNonRootMode(context) ? "activity-manager" : "system-ui");
                }

                editor.putBoolean("chrome", getChromePackageName(context) != null);

                if(canEnableFreeform(context)
                        && getTaskbarPackageName(context) != null
                        && isPlayStoreRelease(context)) {
                    editor.putBoolean("taskbar", true);
                    editor.putBoolean("freeform", true);
                    editor.putBoolean("clear_home", true);
                }

                break;

            // AppRadio
            case 4:
                if(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH))
                    editor.putBoolean("bluetooth_on", true);

                editor.putBoolean("backlight_off", true);

                if(filesExist(vibrationOff))
                    editor.putBoolean("vibration_off", true);

                if(prefMain.getBoolean("expert_mode", false)) {
                    editor.putString("size", Integer.toString(prefMain.getInt("width", 0))
                            + "x"
                            + Integer.toString(prefMain.getInt("height", 0)));

                    editor.putString("density", Integer.toString(getSystemProperty("ro.sf.lcd_density", prefMain.getInt("density", 0))));

                    editor.putBoolean("size-reset", true);
                    editor.putBoolean("density-reset", true);
                }

                break;

            // Other / None
            case 5:
                if(prefMain.getBoolean("expert_mode", false)) {
                    editor.putString("size", Integer.toString(prefMain.getInt("width", 0))
                            + "x"
                            + Integer.toString(prefMain.getInt("height", 0)));

                    editor.putString("density", Integer.toString(getSystemProperty("ro.sf.lcd_density", prefMain.getInt("density", 0))));

                    editor.putBoolean("size-reset", true);
                    editor.putBoolean("density-reset", true);
                }

                break;
        }

        editor.apply();
    }

    public static boolean canEnableFreeform(Context context) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                || Build.VERSION.SDK_INT != Build.VERSION_CODES.P
                || (getTaskbarPackageName(context) != null && isPlayStoreRelease(context)));
    }

    @TargetApi(Build.VERSION_CODES.N)
    public static boolean hasFreeformSupport(Context context) {
        return canEnableFreeform(context)
                && (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                || Settings.Global.getInt(context.getContentResolver(), "enable_freeform_support", 0) != 0
                || (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
                && Settings.Global.getInt(context.getContentResolver(), "force_resizable_activities", 0) != 0
                && (getTaskbarPackageName(context) != null && isPlayStoreRelease(context))));
    }

    public static boolean isUntestedAndroidVersion(Context context) {
        SharedPreferences prefMain = getPrefMain(context);

        return getCurrentApiVersion() > Math.max(
                BuildConfig.TESTED_API_VERSION,
                prefMain.getFloat("current_api_version_new", BuildConfig.TESTED_API_VERSION)
        );
    }

    public static float getCurrentApiVersion() {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            return Float.valueOf(Build.VERSION.SDK_INT + "." + Build.VERSION.PREVIEW_SDK_INT);
        else
            return (float) Build.VERSION.SDK_INT;
    }

    @SuppressLint("PackageManagerGetSignatures")
    public static boolean isPlayStoreRelease(Context context) {
        Signature playStoreSignature = new Signature(context.getString(R.string.signature));
        try {
            PackageManager pm = context.getPackageManager();
            PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
            for(Signature signature : info.signatures) {
                if(signature.equals(playStoreSignature))
                    return true;
            }
        } catch (Exception e) { /* Gracefully fail */ }

        return false;
    }

    public static boolean isExternalAccessDisabled(Context context) {
        SharedPreferences prefMain = getPrefMain(context);
        return !prefMain.getBoolean("tasker_enabled", true);
    }

    public static int getScreenOrientation(Context context) {
        SharedPreferences prefCurrent = getPrefCurrent(context);
        String rotationLockPref = prefCurrent.getString("rotation_lock_new", "fallback");

        switch(rotationLockPref) {
            case "landscape":
                return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
            case "auto-rotate":
                return ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
            case "portrait":
                return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
            default:
                return -1;
        }
    }

    public static boolean hasSupportLibrary(Context context) {
        PackageManager pm = context.getPackageManager();
        try {
            pm.getPackageInfo(BuildConfig.SUPPORT_APPLICATION_ID, 0);
            return pm.checkSignatures(BuildConfig.SUPPORT_APPLICATION_ID, BuildConfig.APPLICATION_ID)
                    == PackageManager.SIGNATURE_MATCH;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    public static boolean isPlayStoreInstalled(Context context) {
        try {
            context.getPackageManager().getPackageInfo("com.android.vending", 0);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    public static void startService(Context context, Intent intent) {
        SupportUtils.startService(context, intent);
    }

    @SuppressLint("PrivateApi")
    public static String getSystemProperty(String key) {
        try {
            Class<?> cls = Class.forName("android.os.SystemProperties");
            return cls.getMethod("get", String.class).invoke(null, key).toString();
        } catch (Exception e) {
            return null;
        }
    }

    @SuppressLint("PrivateApi")
    public static int getSystemProperty(String key, int def) {
        try {
            Class<?> cls = Class.forName("android.os.SystemProperties");
            return (int) cls.getMethod("getInt", String.class, int.class).invoke(null, key, def);
        } catch (Exception e) {
            return def;
        }
    }

    public static boolean isDesktopModeActive(Context context) {
        boolean desktopModePrefEnabled;

        try {
            desktopModePrefEnabled = Settings.Global.getInt(context.getContentResolver(), "force_desktop_mode_on_external_displays") == 1;
        } catch (Settings.SettingNotFoundException e) {
            desktopModePrefEnabled = false;
        }

        return desktopModePrefEnabled && getExternalDisplayID(context) != Display.DEFAULT_DISPLAY;
    }

    public static int getExternalDisplayID(Context context) {
        SharedPreferences prefCurrent = getPrefCurrent(context);
        int savedID = prefCurrent.getInt("external_display_id", -1);

        if(savedID != -1)
            return savedID;

        DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
        Display[] displays = dm.getDisplays();

        return displays[displays.length - 1].getDisplayId();
    }

    public static boolean canEnableOverscan() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && U.getCurrentApiVersion() <= 29.0f;
    }

    public static boolean canEnableImmersiveMode() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && U.getCurrentApiVersion() <= 29.0f;
    }

    public static boolean canEnableWifi(Context context) {
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
                && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
    }
}
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.appcompat.app.AppCompatActivity;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.farmerbb.secondscreen.BuildConfig;
import com.farmerbb.secondscreen.R;
import com.farmerbb.secondscreen.fragment.ProfileEditFragment;
import com.farmerbb.secondscreen.fragment.ProfileListFragment;
import com.farmerbb.secondscreen.fragment.ProfileViewFragment;
import com.farmerbb.secondscreen.fragment.WelcomeFragment;
import com.farmerbb.secondscreen.fragment.dialog.AboutDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.AndroidUpgradeDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.BusyboxDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.DeleteDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.ExpertModeSizeDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.FirstLoadDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.FirstRunDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.MultipleVersionsDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.NewDeviceDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.NewProfileDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.ReloadProfileDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.SystemAlertPermissionDialogFragment;
import com.farmerbb.secondscreen.fragment.dialog.UiRefreshDialogFragment;
import com.farmerbb.secondscreen.receiver.BootReceiver;
import com.farmerbb.secondscreen.receiver.PackageUpgradeReceiver;
import com.farmerbb.secondscreen.receiver.TaskerActionReceiver;
import com.farmerbb.secondscreen.receiver.TaskerConditionReceiver;
import com.farmerbb.secondscreen.service.DisplayConnectionService;
import com.farmerbb.secondscreen.service.NotificationService;
import com.farmerbb.secondscreen.util.U;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
/* Copyright 2015 Braden Farmer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.farmerbb.secondscreen.activity;




// This is the main activity of the application.  This is the activity that is launched when
// SecondScreen is selected in the user's application launcher.
// It shows fragments in a single-pane layout on phones and a dual-pane layout on tablets and larger
// screens.  The left pane will always show the ProfileListFragment.  The right pane will show,
// depending on where the user is in the application flow, either the WelcomeFragment,
// ProfileViewFragment, or ProfileEditFragment.
// The single-pane layout follows the flow of the dual-pane layout's right pane, with
// ProfileListFragment shown in place of WelcomeFragment (except initially on the first run).
//
// The MainActivity also takes care of some basic application initialization, such as running a
// safeguard check, testing for superuser access, setting some initial preferences, gathering basic
// info about the user's device, launching services, and displaying the warning dialog on first run.
public final class MainActivity extends AppCompatActivity implements
FirstRunDialogFragment.Listener,
FirstLoadDialogFragment.Listener,
NewProfileDialogFragment.Listener,