gt:         if (errorStateString != null) {
context: /* 
   * Copyright (C) 2013-2014 Jorrit "Chainfire" Jongma
   * Copyright (C) 2013-2015 The OmniROM Project
   */
  /* 
   * This file is part of OpenDelta.
   * 
   * OpenDelta is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * 
   * OpenDelta is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public License
   * along with OpenDelta. If not, see <http://www.gnu.org/licenses/>.
   */
  
  package eu.chainfire.opendelta;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.UnsupportedEncodingException;
  import java.lang.reflect.Method;
  import java.math.BigInteger;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.net.UnknownHostException;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  
  import org.json.JSONArray;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import android.annotation.SuppressLint;
  import android.app.AlarmManager;
  import android.app.Notification;
  import android.app.NotificationManager;
  import android.app.PendingIntent;
  import android.app.Service;
  import android.content.Context;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
  import android.content.pm.PackageManager;
  import android.net.wifi.WifiManager;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.os.IBinder;
  import android.os.PowerManager;
  import android.os.Process;
  import android.os.StatFs;
  import android.os.SystemClock;
  import android.preference.PreferenceManager;
  import android.text.TextUtils;
  import eu.chainfire.opendelta.BatteryState.OnBatteryStateListener;
  import eu.chainfire.opendelta.DeltaInfo.ProgressListener;
  import eu.chainfire.opendelta.NetworkState.OnNetworkStateListener;
  import eu.chainfire.opendelta.Scheduler.OnWantUpdateCheckListener;
  import eu.chainfire.opendelta.ScreenState.OnScreenStateListener;
  
  public class UpdateService extends Service implements OnNetworkStateListener,
  OnBatteryStateListener, OnScreenStateListener,
  OnWantUpdateCheckListener, OnSharedPreferenceChangeListener {
      private static final int HTTP_SOCKET_TIMEOUT = 30000;
      private static final int HTTP_CONNECTION_TIMEOUT = 30000;
  
      public static void start(Context context) {
          start(context, null);
      }
  
      public static void startCheck(Context context) {
          start(context, ACTION_CHECK);
      }
  
      public static void startFlash(Context context) {
          start(context, ACTION_FLASH);
      }
  
      public static void startBuild(Context context) {
          start(context, ACTION_BUILD);
      }
  
      public static void startUpdate(Context context) {
          start(context, ACTION_UPDATE);
      }
  
      private static void start(Context context, String action) {
          Intent i = new Intent(context, UpdateService.class);
          i.setAction(action);
          context.startService(i);
      }
  
      public static PendingIntent alarmPending(Context context, int id) {
          Intent intent = new Intent(context, UpdateService.class);
          intent.setAction(ACTION_ALARM);
          intent.putExtra(EXTRA_ALARM_ID, id);
          return PendingIntent.getService(context, id, intent, 0);
      }
  
      public static final String ACTION_SYSTEM_UPDATE_SETTINGS = "android.settings.SYSTEM_UPDATE_SETTINGS";
      public static final String PERMISSION_ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
      public static final String PERMISSION_REBOOT = "android.permission.REBOOT";
  
      public static final String BROADCAST_INTENT = "eu.chainfire.opendelta.intent.BROADCAST_STATE";
      public static final String EXTRA_STATE = "eu.chainfire.opendelta.extra.ACTION_STATE";
      public static final String EXTRA_LAST_CHECK = "eu.chainfire.opendelta.extra.LAST_CHECK";
      public static final String EXTRA_PROGRESS = "eu.chainfire.opendelta.extra.PROGRESS";
      public static final String EXTRA_CURRENT = "eu.chainfire.opendelta.extra.CURRENT";
      public static final String EXTRA_TOTAL = "eu.chainfire.opendelta.extra.TOTAL";
      public static final String EXTRA_FILENAME = "eu.chainfire.opendelta.extra.FILENAME";
      public static final String EXTRA_MS = "eu.chainfire.opendelta.extra.MS";
  
      public static final String STATE_ACTION_NONE = "action_none";
      public static final String STATE_ACTION_CHECKING = "action_checking";
      public static final String STATE_ACTION_CHECKING_MD5 = "action_checking_md5";
      public static final String STATE_ACTION_SEARCHING = "action_searching";
      public static final String STATE_ACTION_SEARCHING_MD5 = "action_searching_md5";
      public static final String STATE_ACTION_DOWNLOADING = "action_downloading";
      public static final String STATE_ACTION_APPLYING = "action_applying";
      public static final String STATE_ACTION_APPLYING_PATCH = "action_applying_patch";
      public static final String STATE_ACTION_APPLYING_MD5 = "action_applying_md5";
      public static final String STATE_ACTION_READY = "action_ready";
      public static final String STATE_ERROR_DISK_SPACE = "error_disk_space";
      public static final String STATE_ERROR_UNKNOWN = "error_unknown";
      public static final String STATE_ERROR_UNOFFICIAL = "error_unofficial";
      public static final String STATE_ACTION_BUILD = "action_build";
      public static final String STATE_ERROR_DOWNLOAD = "error_download";
      public static final String STATE_ERROR_CONNECTION = "error_connection";
  
      private static final String ACTION_CHECK = "eu.chainfire.opendelta.action.CHECK";
      private static final String ACTION_FLASH = "eu.chainfire.opendelta.action.FLASH";
      private static final String ACTION_ALARM = "eu.chainfire.opendelta.action.ALARM";
      private static final String EXTRA_ALARM_ID = "eu.chainfire.opendelta.extra.ALARM_ID";
      private static final String ACTION_NOTIFICATION_DELETED = "eu.chainfire.opendelta.action.NOTIFICATION_DELETED";
      private static final String ACTION_BUILD = "eu.chainfire.opendelta.action.BUILD";
      private static final String ACTION_UPDATE = "eu.chainfire.opendelta.action.UPDATE";
  
      private static final int NOTIFICATION_BUSY = 1;
      private static final int NOTIFICATION_UPDATE = 2;
      private static final int NOTIFICATION_ERROR = 3;
  
      public static final String PREF_READY_FILENAME_NAME = "ready_filename";
      public static final String PREF_READY_FILENAME_DEFAULT = null;
  
      private static final String PREF_LAST_CHECK_TIME_NAME = "last_check_time";
      private static final long PREF_LAST_CHECK_TIME_DEFAULT = 0L;
  
      private static final String PREF_LAST_SNOOZE_TIME_NAME = "last_snooze_time";
      private static final long PREF_LAST_SNOOZE_TIME_DEFAULT = 0L;
      // we only snooze until a new build
      private static final String PREF_SNOOZE_UPDATE_NAME = "last_snooze_update";
  
      private static final long SNOOZE_MS = 24 * AlarmManager.INTERVAL_HOUR;
  
      public static final String PREF_AUTO_UPDATE_NETWORKS_NAME = "auto_update_networks";
      public static final int PREF_AUTO_UPDATE_NETWORKS_DEFAULT = NetworkState.ALLOW_WIFI
              | NetworkState.ALLOW_ETHERNET;
  
      public static final String PREF_LATEST_FULL_NAME = "latest_full_name";
      public static final String PREF_LATEST_DELTA_NAME = "latest_delta_name";
      public static final String PREF_STOP_DOWNLOAD = "stop_download";
      public static final String PREF_DOWNLOAD_SIZE = "download_size";
      public static final String PREF_DELTA_SIGNATURE = "delta_signature";
      public static final String PREF_INITIAL_FILE = "initial_file";
  
      public static final int PREF_AUTO_DOWNLOAD_DISABLED = 0;
      public static final int PREF_AUTO_DOWNLOAD_CHECK = 1;
      public static final int PREF_AUTO_DOWNLOAD_DELTA = 2;
      public static final int PREF_AUTO_DOWNLOAD_FULL = 3;
  
      public static final String PREF_AUTO_DOWNLOAD_CHECK_STRING = String.valueOf(PREF_AUTO_DOWNLOAD_CHECK);
      public static final String PREF_AUTO_DOWNLOAD_DISABLED_STRING = String.valueOf(PREF_AUTO_DOWNLOAD_DISABLED);
  
      private Config config;
  
      private HandlerThread handlerThread;
      private Handler handler;
  
      private String state = STATE_ACTION_NONE;
  
      private NetworkState networkState = null;
      private BatteryState batteryState = null;
      private ScreenState screenState = null;
  
      private Scheduler scheduler = null;
  
      private PowerManager.WakeLock wakeLock = null;
      private WifiManager.WifiLock wifiLock = null;
  
      private NotificationManager notificationManager = null;
      private boolean stopDownload;
      private boolean updateRunning;
      private int failedUpdateCount;
      private SharedPreferences prefs = null;
  
      /*
       * Using reflection voodoo instead calling the hidden class directly, to
       * dev/test outside of AOSP tree
       */
      private boolean setPermissions(String path, int mode, int uid, int gid) {
          try {
              Class<?> FileUtils = getClassLoader().loadClass(
                      "android.os.FileUtils");
              Method setPermissions = FileUtils.getDeclaredMethod(
                      "setPermissions", new Class[] { String.class, int.class,
                              int.class, int.class });
              return ((Integer) setPermissions.invoke(
                      null,
                      new Object[] { path, Integer.valueOf(mode),
                              Integer.valueOf(uid), Integer.valueOf(gid) }) == 0);
          } catch (Exception e) {
              // A lot of voodoo could go wrong here, return failure instead of
              // crash
              Logger.ex(e);
          }
          return false;
      }
  
      @Override
      public IBinder onBind(Intent intent) {
          return null;
      }
  
      @SuppressWarnings("deprecation")
      @Override
      public void onCreate() {
          super.onCreate();
  
          config = Config.getInstance(this);
  
          wakeLock = ((PowerManager) getSystemService(POWER_SERVICE))
                  .newWakeLock(
                          config.getKeepScreenOn() ? PowerManager.SCREEN_DIM_WAKE_LOCK
                                  | PowerManager.ACQUIRE_CAUSES_WAKEUP
                                  : PowerManager.PARTIAL_WAKE_LOCK,
                          "OpenDelta WakeLock");
          wifiLock = ((WifiManager) getSystemService(WIFI_SERVICE))
                  .createWifiLock(WifiManager.WIFI_MODE_FULL,
                          "OpenDelta WifiLock");
  
          handlerThread = new HandlerThread("OpenDelta Service Thread");
          handlerThread.start();
          handler = new Handler(handlerThread.getLooper());
  
          notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
          prefs = PreferenceManager.getDefaultSharedPreferences(this);
  
          scheduler = new Scheduler(this, this);
          int autoDownload = getAutoDownloadValue();
          if (autoDownload != PREF_AUTO_DOWNLOAD_DISABLED) {
              scheduler.start();
          }
          networkState = new NetworkState();
          networkState.start(this, this, prefs.getInt(
                  PREF_AUTO_UPDATE_NETWORKS_NAME,
                  PREF_AUTO_UPDATE_NETWORKS_DEFAULT));
  
          batteryState = new BatteryState();
          batteryState.start(this, this,
                  Integer.valueOf(prefs.getString(SettingsActivity.PREF_BATTERY_LEVEL, "50")).intValue(),
                  prefs.getBoolean(SettingsActivity.PREF_CHARGE_ONLY, true));
  
          screenState = new ScreenState();
          screenState.start(this, this);
  
          prefs.registerOnSharedPreferenceChangeListener(this);
  
          autoState(false, PREF_AUTO_DOWNLOAD_CHECK, false);
      }
  
      @Override
      public void onDestroy() {
          prefs.unregisterOnSharedPreferenceChangeListener(this);
          networkState.stop();
          batteryState.stop();
          screenState.stop();
          handlerThread.quitSafely();
  
          super.onDestroy();
      }
  
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
          if (intent != null) {
              if (ACTION_CHECK.equals(intent.getAction())) {
                  checkForUpdates(true, PREF_AUTO_DOWNLOAD_CHECK);
              } else if (ACTION_FLASH.equals(intent.getAction())) {
                  flashUpdate();
              } else if (ACTION_ALARM.equals(intent.getAction())) {
                  scheduler.alarm(intent.getIntExtra(EXTRA_ALARM_ID, -1));
              } else if (ACTION_NOTIFICATION_DELETED.equals(intent.getAction())) {
                  prefs.edit().putLong(PREF_LAST_SNOOZE_TIME_NAME,
                          System.currentTimeMillis()).commit();
                  String lastBuild = prefs.getString(PREF_LATEST_FULL_NAME, PREF_READY_FILENAME_DEFAULT);
                  if (lastBuild != PREF_READY_FILENAME_DEFAULT) {
                      // only snooze until no newer build is available
                      Logger.i("Snoozing notification for " + lastBuild);
                      prefs.edit().putString(PREF_SNOOZE_UPDATE_NAME, lastBuild).commit();
                  }
              } else if (ACTION_BUILD.equals(intent.getAction())) {
                  checkForUpdates(true, PREF_AUTO_DOWNLOAD_FULL);
              } else if (ACTION_UPDATE.equals(intent.getAction())) {
                  autoState(true, PREF_AUTO_DOWNLOAD_CHECK, false);
              }
          }
  
          return START_STICKY;
      }
  
      private synchronized void updateState(String state, Float progress,
              Long current, Long total, String filename, Long ms) {
          this.state = state;
  
          Intent i = new Intent(BROADCAST_INTENT);
          i.putExtra(EXTRA_STATE, state);
          if (progress != null)
              i.putExtra(EXTRA_PROGRESS, progress);
          if (current != null)
              i.putExtra(EXTRA_CURRENT, current);
          if (total != null)
              i.putExtra(EXTRA_TOTAL, total);
          if (filename != null)
              i.putExtra(EXTRA_FILENAME, filename);
          if (ms != null)
              i.putExtra(EXTRA_MS, ms);
  
          sendStickyBroadcast(i);
      }
  
      @Override
      public void onNetworkState(boolean state) {
          Logger.d("network state --> %d", state ? 1 : 0);
      }
  
      @Override
      public void onBatteryState(boolean state) {
          Logger.d("battery state --> %d", state ? 1 : 0);
      }
  
      @Override
      public void onScreenState(boolean state) {
          Logger.d("screen state --> %d", state ? 1 : 0);
          scheduler.onScreenState(state);
      }
  
      @Override
      public boolean onWantUpdateCheck() {
          Logger.i("Scheduler requests check for updates");
          int autoDownload = getAutoDownloadValue();
          if (autoDownload != PREF_AUTO_DOWNLOAD_DISABLED) {
              return checkForUpdates(false, autoDownload);
          }
          return false;
      }
  
      @Override
      public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
              String key) {
          Logger.d("onSharedPreferenceChanged " + key);
  
          if (PREF_AUTO_UPDATE_NETWORKS_NAME.equals(key)) {
              networkState.updateFlags(sharedPreferences.getInt(
                      PREF_AUTO_UPDATE_NETWORKS_NAME,
                      PREF_AUTO_UPDATE_NETWORKS_DEFAULT));
          }
          if (PREF_STOP_DOWNLOAD.equals(key)) {
              stopDownload = true;
          }
          if (SettingsActivity.PREF_AUTO_DOWNLOAD.equals(key)) {
              int autoDownload = getAutoDownloadValue();
              if (autoDownload == PREF_AUTO_DOWNLOAD_DISABLED) {
                  scheduler.stop();
              } else {
                  scheduler.start();
              }
          }
          if (batteryState != null) {
              batteryState.onSharedPreferenceChanged(sharedPreferences, key);
          }
          if (scheduler != null) {
          	scheduler.onSharedPreferenceChanged(sharedPreferences, key);
          }
      }
  
      private void autoState(boolean userInitiated, int checkOnly, boolean notify) {
          Logger.d("autoState state = " + this.state + " userInitiated = " + userInitiated + " checkOnly = " + checkOnly);
  
          if (isErrorState(this.state)) {
              return;
          }
          if (stopDownload) {
              // stop download is only possible in the download step
              // that means must have done a check step before 
              // so just fall back to this instead to show none state
              // which is just confusing
              checkOnly = PREF_AUTO_DOWNLOAD_CHECK;
          }
          String filename = prefs.getString(PREF_READY_FILENAME_NAME,
                  PREF_READY_FILENAME_DEFAULT);
  
          if (filename != null) {
              if (!(new File(filename)).exists()) {
                  filename = null;
              }
          }
  
          boolean updateAvilable = updateAvailable();
          // if the file has been downloaded or creates anytime before
          // this will aways be more important
          if (checkOnly == PREF_AUTO_DOWNLOAD_CHECK && filename == null) {
              Logger.d("Checking step done");
              if (!updateAvilable) {
                  Logger.d("System up to date");
                  updateState(STATE_ACTION_NONE, null, null, null, null,
                          prefs.getLong(PREF_LAST_CHECK_TIME_NAME,
                                  PREF_LAST_CHECK_TIME_DEFAULT));
              } else {
                  Logger.d("Update available");
                  updateState(STATE_ACTION_BUILD, null, null, null, null,
                          prefs.getLong(PREF_LAST_CHECK_TIME_NAME,
                                  PREF_LAST_CHECK_TIME_DEFAULT));
                  if (!userInitiated && notify) {
                      if (!isSnoozeNotification()) {
                          startNotification(checkOnly);
                      } else {
                          Logger.d("notification snoozed");
                      }
                  }
              }
              return;
          }
  
          if (filename == null) {
              Logger.d("System up to date");
              updateState(STATE_ACTION_NONE, null, null, null, null,
                      prefs.getLong(PREF_LAST_CHECK_TIME_NAME,
                              PREF_LAST_CHECK_TIME_DEFAULT));
          } else {
              Logger.d("Update found: %s", filename);
              updateState(STATE_ACTION_READY, null, null, null, (new File(
                      filename)).getName(), prefs.getLong(
                      PREF_LAST_CHECK_TIME_NAME, PREF_LAST_CHECK_TIME_DEFAULT));
  
              if (!userInitiated && notify) {
                  if (!isSnoozeNotification()) {
                      startNotification(checkOnly);
                  } else {
                      Logger.d("notification snoozed");
                  }
              }
          }
      }
  
      private PendingIntent getNotificationIntent(boolean delete) {
          if (delete) {
              Intent notificationIntent = new Intent(this, UpdateService.class);
              notificationIntent.setAction(ACTION_NOTIFICATION_DELETED);
              return PendingIntent.getService(this, 0, notificationIntent, 0);
          } else {
              Intent notificationIntent = new Intent(this, MainActivity.class);
              notificationIntent.setAction(ACTION_SYSTEM_UPDATE_SETTINGS);
              return PendingIntent.getActivity(this, 0, notificationIntent, 0);
          }
      }
  
      private void startNotification(int checkOnly) {
          final String latestFull = prefs.getString(PREF_LATEST_FULL_NAME, PREF_READY_FILENAME_DEFAULT);
          if (latestFull == PREF_READY_FILENAME_DEFAULT) {
              return;
          }
          String flashFilename = prefs.getString(PREF_READY_FILENAME_NAME, PREF_READY_FILENAME_DEFAULT);
          final boolean readyToFlash = flashFilename != PREF_READY_FILENAME_DEFAULT;
          if (readyToFlash) {
              flashFilename = new File(flashFilename).getName();
              flashFilename.substring(0, flashFilename.lastIndexOf('.'));
          }
  
          String notifyFileName = readyToFlash ? flashFilename : latestFull.substring(0, latestFull.lastIndexOf('.'));
  
          notificationManager.notify(
                  NOTIFICATION_UPDATE,
                  (new Notification.Builder(this))
                          .setSmallIcon(R.drawable.stat_notify_update)
                          .setContentTitle(readyToFlash ? getString(R.string.notify_title_flash) : getString(R.string.notify_title_download))
                          .setShowWhen(true)
                          .setContentIntent(getNotificationIntent(false))
                          .setDeleteIntent(getNotificationIntent(true))
                          .setContentText(notifyFileName).build());
      }
  
      private void stopNotification() {
          notificationManager.cancel(NOTIFICATION_UPDATE);
      }
  
      private void startErrorNotification() {
          String errorStateString = null;
          try {
              errorStateString = getString(getResources().getIdentifier(
                      "state_" + state, "string", getPackageName()));
          } catch (Exception e) {
              // String for this state could not be found (displays empty string)
              Logger.ex(e);
          }
  