com.bluros.updater.UpdatesSettings.java Source code

Java tutorial

Introduction

Here is the source code for com.bluros.updater.UpdatesSettings.java

Source

/*
 * Copyright (C) 2016 BlurOS
 *
 * * Licensed under the GNU GPLv2 license
 *
 * The text of the license can be found in the LICENSE file
 * or at https://www.gnu.org/licenses/gpl-2.0.txt
 */

package com.bluros.updater;

import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

/*import com.android.internal.util.cm.ScreenType;*/

import com.bluros.updater.misc.Constants;
import com.bluros.updater.misc.State;
import com.bluros.updater.misc.UpdateInfo;
import com.bluros.updater.receiver.DownloadReceiver;
import com.bluros.updater.service.UpdateCheckService;
import com.bluros.updater.utils.UpdateFilter;
import com.bluros.updater.utils.Utils;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;

public class UpdatesSettings extends PreferenceActivity
        implements OnPreferenceChangeListener, UpdatePreference.OnReadyListener, UpdatePreference.OnActionListener,
        ActivityCompat.OnRequestPermissionsResultCallback {
    private static String TAG = "UpdatesSettings";

    // intent extras
    public static final String EXTRA_UPDATE_LIST_UPDATED = "update_list_updated";
    public static final String EXTRA_FINISHED_DOWNLOAD_ID = "download_id";
    public static final String EXTRA_FINISHED_DOWNLOAD_PATH = "download_path";
    public static final String EXTRA_FINISHED_DOWNLOAD_INCREMENTAL_FOR = "download_incremental_for";

    public static final String KEY_SYSTEM_INFO = "system_info";
    private static final String KEY_DELETE_ALL = "delete_all";

    private static final String UPDATES_CATEGORY = "updates_category";

    private static final int MENU_REFRESH = 0;
    private static final int MENU_DELETE_ALL = 1;
    private static final int MENU_SYSTEM_INFO = 2;

    private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;

    private static final int PERMISSIONS_REQUEST_FOR_DOWNLOAD = 0;
    private static final int PERMISSIONS_REQUEST_FOR_DELETE_ZIP = 1;
    private static final int PERMISSIONS_REQUEST_FOR_DELETE_ALL = 2;
    private static final int PERMISSIONS_REQUEST_FOR_READ_DIR = 3;

    private SharedPreferences mPrefs;
    private ListPreference mUpdateCheck;

    private PreferenceCategory mUpdatesList;
    private UpdatePreference mDownloadingPreference;

    private File mUpdateFolder;

    private boolean mStartUpdateVisible = false;
    private ProgressDialog mProgressDialog;

    private DownloadManager mDownloadManager;
    private boolean mDownloading = false;
    private long mDownloadId;
    private String mDownloadFileName;

    private String mDeleteFileName = "";

    private int mPermissionsReqType;
    private boolean mPermissionsReqInProgress = false;

    private Handler mUpdateHandler = new Handler();

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (DownloadReceiver.ACTION_DOWNLOAD_STARTED.equals(action)) {
                mDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                mUpdateHandler.post(mUpdateProgress);
            } else if (UpdateCheckService.ACTION_CHECK_FINISHED.equals(action)) {
                if (mProgressDialog != null) {
                    mProgressDialog.dismiss();
                    mProgressDialog = null;

                    int count = intent.getIntExtra(UpdateCheckService.EXTRA_NEW_UPDATE_COUNT, -1);
                    if (count == 0) {
                        Toast.makeText(UpdatesSettings.this, R.string.no_updates_found, Toast.LENGTH_SHORT).show();
                    } else if (count < 0) {
                        Toast.makeText(UpdatesSettings.this, R.string.update_check_failed, Toast.LENGTH_LONG)
                                .show();
                    }
                }
                requestUpdateLayout();
            }
        }
    };

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

        mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

        // Load the layouts
        if (!Utils.hasLeanback(this)) {
            addPreferencesFromResource(R.xml.main);
        } else {
            addPreferencesFromResource(R.xml.main_tv);
        }
        mUpdatesList = (PreferenceCategory) findPreference(UPDATES_CATEGORY);
        mUpdateCheck = (ListPreference) findPreference(Constants.UPDATE_CHECK_PREF);

        // Load the stored preference data
        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        if (mUpdateCheck != null) {
            int check = mPrefs.getInt(Constants.UPDATE_CHECK_PREF, Constants.UPDATE_FREQ_WEEKLY);
            mUpdateCheck.setValue(String.valueOf(check));
            mUpdateCheck.setSummary(mapCheckValue(check));
            mUpdateCheck.setOnPreferenceChangeListener(this);
        }

        // Force a refresh if UPDATE_TYPE_PREF does not match release type
        int updateType = Utils.getUpdateType();
        int updateTypePref = mPrefs.getInt(Constants.UPDATE_TYPE_PREF, Constants.UPDATE_TYPE_SNAPSHOT);
        if (updateTypePref != updateType) {
            updateUpdatesType(updateType);
        }

        // Set 'HomeAsUp' feature of the actionbar to fit better into Settings
        if (!Utils.hasLeanback(this)) {
            final ActionBar bar = getActionBar();
            if (bar != null) {
                bar.setDisplayHomeAsUpEnabled(true);
            }

            // Turn on the Options Menu
            invalidateOptionsMenu();
        }
    }

    @Override
    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
        if (preference == findPreference(KEY_SYSTEM_INFO)) {
            checkForUpdates();
        } else if (preference == findPreference(KEY_DELETE_ALL)) {
            confirmDeleteAll();
        }
        return super.onPreferenceTreeClick(preferenceScreen, preference);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, MENU_REFRESH, 0, R.string.menu_refresh).setIcon(R.drawable.ic_menu_refresh)
                .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);

        menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all)
                .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);

        menu.add(0, MENU_SYSTEM_INFO, 0, R.string.menu_system_info)
                .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case MENU_REFRESH:
            checkForUpdates();
            return true;

        case MENU_DELETE_ALL:
            confirmDeleteAll();
            return true;

        case MENU_SYSTEM_INFO:
            showSysInfo();
            return true;

        case android.R.id.home:
            onBackPressed();
            return true;
        }
        return false;
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        // Check if we need to refresh the screen to show new updates
        if (intent.getBooleanExtra(EXTRA_UPDATE_LIST_UPDATED, false)) {
            requestUpdateLayout();
        }

        checkForDownloadCompleted(intent);
    }

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

        // If running on a phone, remove padding around the listview
        /*if (!ScreenType.isTablet(this)) {
        getListView().setPadding(0, 0, 0, 0);
        }*/
    }

    @Override
    public void onReady(UpdatePreference pref) {
        pref.setOnReadyListener(null);
        mUpdateHandler.post(mUpdateProgress);
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        if (preference == mUpdateCheck) {
            int value = Integer.valueOf((String) newValue);
            mPrefs.edit().putInt(Constants.UPDATE_CHECK_PREF, value).apply();
            mUpdateCheck.setSummary(mapCheckValue(value));
            Utils.scheduleUpdateService(this, value * 1000);
            return true;
        }

        return false;
    }

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

        // Determine if there are any in-progress downloads
        mDownloadId = mPrefs.getLong(Constants.DOWNLOAD_ID, -1);
        if (mDownloadId >= 0) {
            Cursor c = mDownloadManager.query(new DownloadManager.Query().setFilterById(mDownloadId));
            if (c == null || !c.moveToFirst()) {
                Toast.makeText(this, R.string.download_not_found, Toast.LENGTH_LONG).show();
            } else {
                int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                Uri uri = Uri.parse(c.getString(c.getColumnIndex(DownloadManager.COLUMN_URI)));
                if (status == DownloadManager.STATUS_PENDING || status == DownloadManager.STATUS_RUNNING
                        || status == DownloadManager.STATUS_PAUSED) {
                    mDownloadFileName = uri.getLastPathSegment();
                }
            }
            if (c != null) {
                c.close();
            }
        }
        if (mDownloadId < 0 || mDownloadFileName == null) {
            resetDownloadState();
        }

        requestUpdateLayout();

        IntentFilter filter = new IntentFilter(UpdateCheckService.ACTION_CHECK_FINISHED);
        filter.addAction(DownloadReceiver.ACTION_DOWNLOAD_STARTED);
        registerReceiver(mReceiver, filter);

        checkForDownloadCompleted(getIntent());
        setIntent(null);
    }

    @Override
    protected void onStop() {
        super.onStop();
        mUpdateHandler.removeCallbacks(mUpdateProgress);
        unregisterReceiver(mReceiver);
        if (mProgressDialog != null) {
            mProgressDialog.cancel();
            mProgressDialog = null;
        }
    }

    @Override
    public void onStartDownload(UpdatePreference pref) {
        // If there is no internet connection, display a message and return.
        if (!Utils.isOnline(this)) {
            Toast.makeText(this, R.string.data_connection_required, Toast.LENGTH_SHORT).show();
            return;
        }

        if (mDownloading) {
            Toast.makeText(this, R.string.download_already_running, Toast.LENGTH_LONG).show();
            return;
        }

        // We have a match, get ready to trigger the download
        mDownloadingPreference = pref;

        mPermissionsReqType = PERMISSIONS_REQUEST_FOR_DOWNLOAD;
        requestStoragePermission(pref.getContext());
    }

    private Runnable mUpdateProgress = new Runnable() {
        public void run() {
            if (!mDownloading || mDownloadingPreference == null || mDownloadId < 0) {
                return;
            }

            ProgressBar progressBar = mDownloadingPreference.getProgressBar();
            if (progressBar == null) {
                return;
            }

            ImageView updatesButton = mDownloadingPreference.getUpdatesButton();
            if (updatesButton == null) {
                return;
            }

            // Enable updates button
            updatesButton.setEnabled(true);

            DownloadManager.Query q = new DownloadManager.Query();
            q.setFilterById(mDownloadId);

            Cursor cursor = mDownloadManager.query(q);
            int status;

            if (cursor == null || !cursor.moveToFirst()) {
                // DownloadReceiver has likely already removed the download
                // from the DB due to failure or MD5 mismatch
                status = DownloadManager.STATUS_FAILED;
            } else {
                status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
            }

            switch (status) {
            case DownloadManager.STATUS_PENDING:
                progressBar.setIndeterminate(true);
                break;
            case DownloadManager.STATUS_PAUSED:
            case DownloadManager.STATUS_RUNNING:
                int downloadedBytes = cursor
                        .getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
                int totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));

                if (totalBytes < 0) {
                    progressBar.setIndeterminate(true);
                } else {
                    progressBar.setIndeterminate(false);
                    progressBar.setMax(totalBytes);
                    progressBar.setProgress(downloadedBytes);
                }
                break;
            case DownloadManager.STATUS_FAILED:
                mDownloadingPreference.setStyle(UpdatePreference.STYLE_NEW);
                resetDownloadState();
                break;
            }

            if (cursor != null) {
                cursor.close();
            }
            if (status != DownloadManager.STATUS_FAILED) {
                mUpdateHandler.postDelayed(this, 1000);
            }
        }
    };

    @Override
    public void onStopDownload(final UpdatePreference pref) {
        if (!mDownloading || mDownloadFileName == null || mDownloadId < 0) {
            pref.setStyle(UpdatePreference.STYLE_NEW);
            resetDownloadState();
            return;
        }

        new AlertDialog.Builder(this).setTitle(R.string.confirm_download_cancelation_dialog_title)
                .setMessage(R.string.confirm_download_cancelation_dialog_message)
                .setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Set the preference back to new style
                        pref.setStyle(UpdatePreference.STYLE_NEW);

                        // We are OK to stop download, trigger it
                        mDownloadManager.remove(mDownloadId);
                        mUpdateHandler.removeCallbacks(mUpdateProgress);
                        resetDownloadState();

                        // Clear the stored data from shared preferences
                        mPrefs.edit().remove(Constants.DOWNLOAD_ID).remove(Constants.DOWNLOAD_MD5).apply();

                        Toast.makeText(UpdatesSettings.this, R.string.download_cancelled, Toast.LENGTH_SHORT)
                                .show();
                    }
                }).setNegativeButton(R.string.dialog_cancel, null).show();
    }

    private void updateUpdatesType(int type) {
        mPrefs.edit().putInt(Constants.UPDATE_TYPE_PREF, type).apply();
        checkForUpdates();
    }

    private void checkForDownloadCompleted(Intent intent) {
        if (intent == null) {
            return;
        }

        long downloadId = intent.getLongExtra(EXTRA_FINISHED_DOWNLOAD_ID, -1);
        if (downloadId < 0) {
            return;
        }

        String fullPathName = intent.getStringExtra(EXTRA_FINISHED_DOWNLOAD_PATH);
        if (fullPathName == null) {
            return;
        }

        String fileName = new File(fullPathName).getName();

        // If this is an incremental, find matching target and mark it as downloaded.
        String incrementalFor = intent.getStringExtra(EXTRA_FINISHED_DOWNLOAD_INCREMENTAL_FOR);
        if (incrementalFor != null) {
            UpdatePreference pref = (UpdatePreference) mUpdatesList.findPreference(incrementalFor);
            if (pref != null) {
                pref.setStyle(UpdatePreference.STYLE_DOWNLOADED);
                pref.getUpdateInfo().setFileName(fileName);
                onStartUpdate(pref);
            }
        } else {
            // Find the matching preference so we can retrieve the UpdateInfo
            UpdatePreference pref = (UpdatePreference) mUpdatesList.findPreference(fileName);
            if (pref != null) {
                pref.setStyle(UpdatePreference.STYLE_DOWNLOADED);
                onStartUpdate(pref);
            }
        }

        resetDownloadState();
    }

    private void resetDownloadState() {
        mDownloadId = -1;
        mDownloadFileName = null;
        mDownloading = false;
        mDownloadingPreference = null;
    }

    private String mapCheckValue(Integer value) {
        Resources resources = getResources();
        String[] checkNames = resources.getStringArray(R.array.update_check_entries);
        String[] checkValues = resources.getStringArray(R.array.update_check_values);
        for (int i = 0; i < checkValues.length; i++) {
            if (Integer.decode(checkValues[i]).equals(value)) {
                return checkNames[i];
            }
        }
        return getString(R.string.unknown);
    }

    private void checkForUpdates() {
        if (mProgressDialog != null) {
            return;
        }

        // If there is no internet connection, display a message and return.
        if (!Utils.isOnline(this)) {
            Toast.makeText(this, R.string.data_connection_required, Toast.LENGTH_SHORT).show();
            return;
        }

        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setTitle(R.string.checking_for_updates);
        mProgressDialog.setMessage(getString(R.string.checking_for_updates));
        mProgressDialog.setIndeterminate(true);
        mProgressDialog.setCancelable(true);
        mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                Intent cancelIntent = new Intent(UpdatesSettings.this, UpdateCheckService.class);
                cancelIntent.setAction(UpdateCheckService.ACTION_CANCEL_CHECK);
                startService(cancelIntent);
                mProgressDialog = null;
            }
        });

        Intent checkIntent = new Intent(UpdatesSettings.this, UpdateCheckService.class);
        checkIntent.setAction(UpdateCheckService.ACTION_CHECK);
        startService(checkIntent);

        mProgressDialog.show();
    }

    private void requestUpdateLayout() {
        // Clear the notification if one exists
        Utils.cancelNotification(this);

        if (!mPermissionsReqInProgress) {
            mPermissionsReqType = PERMISSIONS_REQUEST_FOR_READ_DIR;
            requestStoragePermission(getApplicationContext());
        }
    }

    private void updateLayout() {
        // Read existing Updates
        LinkedList<String> existingFiles = new LinkedList<String>();

        mUpdateFolder = Utils.makeUpdateFolder();
        File[] files = mUpdateFolder.listFiles(new UpdateFilter(".zip"));

        if (mUpdateFolder.exists() && mUpdateFolder.isDirectory() && files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    existingFiles.add(file.getName());
                }
            }
        }

        // Build list of updates
        LinkedList<UpdateInfo> availableUpdates = State.loadState(this);
        final LinkedList<UpdateInfo> updates = new LinkedList<UpdateInfo>();

        for (String fileName : existingFiles) {
            updates.add(new UpdateInfo.Builder().setFileName(fileName).build());
        }
        for (UpdateInfo update : availableUpdates) {
            // Only add updates to the list that are not already downloaded
            if (existingFiles.contains(update.getFileName())) {
                continue;
            }
            updates.add(update);
        }

        Collections.sort(updates, new Comparator<UpdateInfo>() {
            @Override
            public int compare(UpdateInfo lhs, UpdateInfo rhs) {
                // sort in descending 'UI name' order (newest first)
                return -lhs.getName().compareTo(rhs.getName());
            }
        });

        // Update the preference list
        refreshPreferences(updates);

        // Prune obsolete change log files
        new Thread() {
            @Override
            public void run() {
                File cacheDir = getCacheDir();
                if (cacheDir == null) {
                    return;
                }

                File[] files = cacheDir.listFiles(new UpdateFilter(UpdateInfo.CHANGELOG_EXTENSION));
                if (files == null) {
                    return;
                }

                for (File file : files) {
                    boolean updateExists = false;
                    for (UpdateInfo info : updates) {
                        if (file.getName().startsWith(info.getFileName())) {
                            updateExists = true;
                            break;
                        }
                    }
                    if (!updateExists) {
                        file.delete();
                    }
                }
            }
        }.start();
    }

    private void refreshPreferences(LinkedList<UpdateInfo> updates) {
        if (mUpdatesList == null) {
            return;
        }

        // Clear the list
        mUpdatesList.removeAll();

        // Convert the installed version name to the associated filename
        String installedZip = "cm-" + Utils.getInstalledVersion() + ".zip";

        // Determine installed incremental
        String installedIncremental = Utils.getIncremental();

        // Convert LinkedList to HashMap, keyed on filename.
        HashMap<String, UpdateInfo> updatesMap = new HashMap<String, UpdateInfo>();
        for (UpdateInfo ui : updates) {
            updatesMap.put(ui.getFileName(), ui);
        }

        // Add the updates
        for (UpdateInfo ui : updates) {
            // Skip if this is an incremental
            if (ui.isIncremental()) {
                continue;
            }

            // Check to see if there is an incremental
            boolean haveIncremental = false;
            String incrementalFile = "incremental-" + installedIncremental + "-" + ui.getIncremental() + ".zip";
            if (updatesMap.containsKey(incrementalFile)) {
                haveIncremental = true;
                ui.setFileName(incrementalFile);
            }

            // Determine the preference style and create the preference
            boolean isDownloading = ui.getFileName().equals(mDownloadFileName);
            int style;

            if (isDownloading) {
                // In progress download
                style = UpdatePreference.STYLE_DOWNLOADING;
            } else if (haveIncremental) {
                style = UpdatePreference.STYLE_DOWNLOADED;
            } else if (ui.getFileName().equals(installedZip)) {
                // This is the currently installed version
                style = UpdatePreference.STYLE_INSTALLED;
            } else if (ui.getDownloadUrl() != null) {
                style = UpdatePreference.STYLE_NEW;
            } else {
                style = UpdatePreference.STYLE_DOWNLOADED;
            }

            UpdatePreference up = new UpdatePreference(this, ui, style);
            up.setOnActionListener(this);
            up.setKey(ui.getFileName());

            // If we have an in progress download, link the preference
            if (isDownloading) {
                mDownloadingPreference = up;
                up.setOnReadyListener(this);
                mDownloading = true;
            }

            // Add to the list
            mUpdatesList.addPreference(up);
        }

        // If no updates are in the list, show the default message
        if (mUpdatesList.getPreferenceCount() == 0) {
            Preference pref = new Preference(this);
            pref.setLayoutResource(R.layout.preference_empty_list);
            if (!Utils.hasLeanback(this)) {
                pref.setTitle(R.string.no_available_updates_intro);
            } else {
                pref.setTitle(R.string.no_available_updates_intro_tv);
            }
            pref.setEnabled(false);
            mUpdatesList.addPreference(pref);
        }
    }

    private void deleteZipUpdate() {
        if (mUpdateFolder.exists() && mUpdateFolder.isDirectory()) {
            File zipFileToDelete = new File(mUpdateFolder, mDeleteFileName);
            if (zipFileToDelete.exists()) {
                zipFileToDelete.delete();
            } else {
                Log.d(TAG, "Update to delete not found");
                return;
            }

            String message = getString(R.string.delete_single_update_success_message, mDeleteFileName);
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
        } else if (!mUpdateFolder.exists()) {
            Toast.makeText(this, R.string.delete_updates_noFolder_message, Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, R.string.delete_updates_failure_message, Toast.LENGTH_SHORT).show();
        }

        // Update the list
        requestUpdateLayout();
    }

    @Override
    public void onDeleteUpdate(UpdatePreference pref) {
        mDeleteFileName = pref.getKey();
        if (mDeleteFileName.isEmpty()) {
            Log.e(TAG, "deleteZipUpdate: File name not specified");
            return;
        }
        mPermissionsReqType = PERMISSIONS_REQUEST_FOR_DELETE_ZIP;
        requestStoragePermission(pref.getContext());
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        mPermissionsReqInProgress = false;
        switch (requestCode) {
        case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: {
            // if request is cancelled, the result arrays are empty
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted, yay!
                switch (mPermissionsReqType) {
                case PERMISSIONS_REQUEST_FOR_DOWNLOAD:
                    startDownload();
                    break;
                case PERMISSIONS_REQUEST_FOR_DELETE_ZIP:
                    deleteZipUpdate();
                    break;
                case PERMISSIONS_REQUEST_FOR_DELETE_ALL:
                    deleteOldUpdates();
                    break;
                case PERMISSIONS_REQUEST_FOR_READ_DIR:
                    updateLayout();
                    break;
                default:
                    break;
                }
            } else {
                // permission was not granted
                switch (mPermissionsReqType) {
                case PERMISSIONS_REQUEST_FOR_DOWNLOAD:
                    mDownloadingPreference = null;
                    break;
                case PERMISSIONS_REQUEST_FOR_DELETE_ZIP:
                    mDeleteFileName = "";
                    Log.e(TAG, "deleteZipUpdate: Permission not granted");
                    break;
                case PERMISSIONS_REQUEST_FOR_DELETE_ALL:
                    Log.e(TAG, "deleteOldUpdates: Permission not granted");
                    break;
                case PERMISSIONS_REQUEST_FOR_READ_DIR:
                    Log.e(TAG, "updateLayout: Permission not granted");
                    break;
                default:
                    break;
                }
                new AlertDialog.Builder(this).setTitle(R.string.permission_not_granted_dialog_title)
                        .setMessage(R.string.permission_not_granted_dialog_message)
                        .setPositiveButton(R.string.dialog_ok, null).show();
                return;
            }
            break;
        }
        }
    }

    private void startDownload() {
        UpdateInfo ui = mDownloadingPreference.getUpdateInfo();
        if (ui == null) {
            return;
        }

        mDownloadingPreference.setStyle(UpdatePreference.STYLE_DOWNLOADING);

        // Set progress bar to indeterminate while incremental check runs
        ProgressBar progressBar = mDownloadingPreference.getProgressBar();
        progressBar.setIndeterminate(true);

        // Disable cancel button while incremental check runs
        ImageView updatesButton = mDownloadingPreference.getUpdatesButton();
        updatesButton.setEnabled(false);

        mDownloadFileName = ui.getFileName();
        mDownloading = true;

        // Start the download
        Intent intent = new Intent(this, DownloadReceiver.class);
        intent.setAction(DownloadReceiver.ACTION_START_DOWNLOAD);
        intent.putExtra(DownloadReceiver.EXTRA_UPDATE_INFO, (Parcelable) ui);
        sendBroadcast(intent);

        mUpdateHandler.post(mUpdateProgress);
    }

    private void confirmDeleteAll() {
        new AlertDialog.Builder(this).setTitle(R.string.confirm_delete_dialog_title)
                .setMessage(R.string.confirm_delete_all_dialog_message)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // We are OK to delete, trigger it
                        mPermissionsReqType = PERMISSIONS_REQUEST_FOR_DELETE_ALL;
                        requestStoragePermission(getApplicationContext());
                    }
                }).setNegativeButton(R.string.dialog_cancel, null).show();
    }

    private void requestStoragePermission(Context context) {
        int permissionCheck = ContextCompat.checkSelfPermission(context,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
            // permission already granted, go ahead
            switch (mPermissionsReqType) {
            case PERMISSIONS_REQUEST_FOR_DOWNLOAD:
                startDownload();
                break;
            case PERMISSIONS_REQUEST_FOR_DELETE_ZIP:
                deleteZipUpdate();
                break;
            case PERMISSIONS_REQUEST_FOR_DELETE_ALL:
                deleteOldUpdates();
                break;
            case PERMISSIONS_REQUEST_FOR_READ_DIR:
                updateLayout();
                break;
            default:
                break;
            }
        } else {
            // permission not granted, request it from the user
            mPermissionsReqInProgress = true;
            ActivityCompat.requestPermissions(this,
                    new String[] { android.Manifest.permission.WRITE_EXTERNAL_STORAGE },
                    PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
        }
    }

    private boolean deleteOldUpdates() {
        boolean success;

        // If storage permissions have been denied on application start and the first time they
        // are granted is after choosing "Delete downloads", then updateLayout() will not have
        // have been called yet to define mUpdateFolder. Set it now to prevent a null exception
        // when .exists() is called.
        if (mUpdateFolder == null) {
            mUpdateFolder = Utils.makeUpdateFolder();
        }
        if (mUpdateFolder.exists() && mUpdateFolder.isDirectory()) {
            deleteDir(mUpdateFolder);
            mUpdateFolder.mkdir();
            success = true;
            Toast.makeText(this, R.string.delete_updates_success_message, Toast.LENGTH_SHORT).show();
        } else if (!mUpdateFolder.exists()) {
            success = false;
            Toast.makeText(this, R.string.delete_updates_noFolder_message, Toast.LENGTH_SHORT).show();
        } else {
            success = false;
            Toast.makeText(this, R.string.delete_updates_failure_message, Toast.LENGTH_SHORT).show();
        }
        requestUpdateLayout();
        return success;
    }

    private static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            if (children == null) {
                // An IO or permissions failure; don't crash
                Log.e(TAG, "deleteDir: dir.list() failed, check storage permissions");
                return false;
            }
            for (String aChildren : children) {
                boolean success = deleteDir(new File(dir, aChildren));
                if (!success) {
                    return false;
                }
            }
        }
        // The directory is now empty so delete it
        return dir.delete();
    }

    private void showSysInfo() {
        // Build the message
        Date lastCheck = new Date(mPrefs.getLong(Constants.LAST_UPDATE_CHECK_PREF, 0));
        String date = DateFormat.getLongDateFormat(this).format(lastCheck);
        String time = DateFormat.getTimeFormat(this).format(lastCheck);

        String cmReleaseType = Constants.CM_RELEASETYPE_NIGHTLY;
        int updateType = Utils.getUpdateType();
        if (updateType == Constants.UPDATE_TYPE_SNAPSHOT) {
            cmReleaseType = Constants.CM_RELEASETYPE_SNAPSHOT;
        }

        String message = getString(R.string.sysinfo_device) + " " + Utils.getDeviceType() + "\n\n"
                + getString(R.string.sysinfo_running) + " " + Utils.getInstalledVersion() + "\n\n"
                + getString(R.string.sysinfo_update_channel) + " " + cmReleaseType + "\n\n"
                + getString(R.string.sysinfo_last_check) + " " + date + " " + time;

        AlertDialog.Builder builder = new AlertDialog.Builder(this).setTitle(R.string.menu_system_info)
                .setMessage(message).setPositiveButton(R.string.dialog_ok, null);

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

        TextView messageView = (TextView) dialog.findViewById(android.R.id.message);
        messageView.setTextAppearance(this, android.R.style.TextAppearance_DeviceDefault_Small);
    }

    @Override
    public void onStartUpdate(UpdatePreference pref) {
        final UpdateInfo updateInfo = pref.getUpdateInfo();

        // Prevent the dialog from being triggered more than once
        if (mStartUpdateVisible) {
            return;
        }

        mStartUpdateVisible = true;

        // Get the message body right
        String dialogBody = getString(R.string.apply_update_dialog_text, updateInfo.getName());

        // Display the dialog
        new AlertDialog.Builder(this).setTitle(R.string.apply_update_dialog_title).setMessage(dialogBody)
                .setPositiveButton(R.string.dialog_update, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        try {
                            Utils.triggerUpdate(UpdatesSettings.this, updateInfo.getFileName());
                        } catch (IOException e) {
                            Log.e(TAG, "Unable to reboot into recovery mode", e);
                            Toast.makeText(UpdatesSettings.this, R.string.apply_unable_to_reboot_toast,
                                    Toast.LENGTH_SHORT).show();
                        }
                    }
                }).setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Do nothing and allow the dialog to be dismissed
                    }
                }).setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        mStartUpdateVisible = false;
                    }
                }).show();
    }
}