/**
   * Saves the settings object fields into DB and into plain text files where applicable. The DB
   * changes will not be made persistent if saving settings to plain text files fails.
   *
   * @param s settings object
   * @return true if settings were saved successfully, false otherwise
   */
  public synchronized boolean saveSettings(PrivacySettings s) {
    boolean result = true;
    String packageName = s.getPackageName();
    //        Integer uid = s.getUid();
    //        Log.d(TAG, "saveSettings - settings save request : " + s);

    if (packageName == null || packageName.isEmpty() /* || uid == null*/) {
      Log.e(TAG, "saveSettings - either package name, UID or both is missing");
      return false;
    }

    ContentValues values = new ContentValues();
    values.put("packageName", packageName);
    //        values.put("uid", uid);
    values.put("uid", DUMMY_UID);

    values.put("deviceIdSetting", s.getDeviceIdSetting());
    values.put("deviceId", s.getDeviceId());

    values.put("line1NumberSetting", s.getLine1NumberSetting());
    values.put("line1Number", s.getLine1Number());

    values.put("locationGpsSetting", s.getLocationGpsSetting());
    values.put("locationGpsLat", s.getLocationGpsLat());
    values.put("locationGpsLon", s.getLocationGpsLon());

    values.put("locationNetworkSetting", s.getLocationNetworkSetting());
    values.put("locationNetworkLat", s.getLocationNetworkLat());
    values.put("locationNetworkLon", s.getLocationNetworkLon());

    values.put("networkInfoSetting", s.getNetworkInfoSetting());
    values.put("simInfoSetting", s.getSimInfoSetting());

    values.put("simSerialNumberSetting", s.getSimSerialNumberSetting());
    values.put("simSerialNumber", s.getSimSerialNumber());
    values.put("subscriberIdSetting", s.getSubscriberIdSetting());
    values.put("subscriberId", s.getSubscriberId());

    values.put("accountsSetting", s.getAccountsSetting());
    values.put("accountsAuthTokensSetting", s.getAccountsAuthTokensSetting());
    values.put("outgoingCallsSetting", s.getOutgoingCallsSetting());
    values.put("incomingCallsSetting", s.getIncomingCallsSetting());

    values.put("contactsSetting", s.getContactsSetting());
    values.put("calendarSetting", s.getCalendarSetting());
    values.put("mmsSetting", s.getMmsSetting());
    values.put("smsSetting", s.getSmsSetting());
    values.put("callLogSetting", s.getCallLogSetting());
    values.put("bookmarksSetting", s.getBookmarksSetting());
    values.put("systemLogsSetting", s.getSystemLogsSetting());
    values.put("notificationSetting", s.getNotificationSetting());
    values.put("intentBootCompletedSetting", s.getIntentBootCompletedSetting());
    //        values.put("externalStorageSetting", s.getExternalStorageSetting());
    //        values.put("cameraSetting", s.getCameraSetting());
    //        values.put("recordAudioSetting", s.getRecordAudioSetting());

    readingThreads++;
    SQLiteDatabase db = getWritableDatabase();
    db.beginTransaction(); // make sure this ends up in a consistent state (DB and plain text files)
    Cursor c = null;
    try {
      // save settings to the DB
      //            Log.d(TAG, "saveSettings - checking if entry exists already");
      Integer id = s.get_id();
      if (id != null) { // existing entry -> update
        //                Log.d(TAG, "saveSettings - updating existing entry");
        if (db.update(TABLE_SETTINGS, values, "_id=?", new String[] {id.toString()}) < 1) {
          throw new Exception("saveSettings - failed to update database entry");
        }

        db.delete(TABLE_ALLOWED_CONTACTS, "settings_id=?", new String[] {id.toString()});
        int[] allowedContacts = s.getAllowedContacts();
        if (allowedContacts != null) {
          ContentValues contactsValues = new ContentValues();
          for (int i = 0; i < allowedContacts.length; i++) {
            contactsValues.put("settings_id", id);
            contactsValues.put("contact_id", allowedContacts[i]);
            if (db.insert(TABLE_ALLOWED_CONTACTS, null, contactsValues) == -1)
              throw new Exception("saveSettings - failed to update database entry (contacts)");
          }
        }

      } else { // new entry -> insert if no duplicates exist
        //                Log.d(TAG, "saveSettings - new entry; verifying if duplicates exist");
        c =
            db.query(
                TABLE_SETTINGS,
                new String[] {"_id"},
                "packageName=?",
                new String[] {s.getPackageName()},
                null,
                null,
                null);

        if (c != null) {
          if (c.getCount() == 1) { // exactly one entry
            // exists -> update
            //                        Log.d(TAG, "saveSettings - updating existing entry");
            if (db.update(
                    TABLE_SETTINGS, values, "packageName=?", new String[] {s.getPackageName()})
                < 1) {
              throw new Exception("saveSettings - failed to update database entry");
            }

            if (c.moveToFirst()) {
              Integer idAlt = c.getInt(0); // id of the found duplicate entry
              db.delete(TABLE_ALLOWED_CONTACTS, "settings_id=?", new String[] {idAlt.toString()});
              int[] allowedContacts = s.getAllowedContacts();
              if (allowedContacts != null) {
                ContentValues contactsValues = new ContentValues();
                for (int i = 0; i < allowedContacts.length; i++) {
                  contactsValues.put("settings_id", idAlt);
                  contactsValues.put("contact_id", allowedContacts[i]);
                  if (db.insert(TABLE_ALLOWED_CONTACTS, null, contactsValues) == -1)
                    throw new Exception(
                        "saveSettings - failed to update database entry (contacts)");
                }
              }
            }
          } else if (c.getCount() == 0) { // no entries -> insert
            //                        Log.d(TAG, "saveSettings - inserting new entry");
            long rowId = db.insert(TABLE_SETTINGS, null, values);
            if (rowId == -1) {
              throw new Exception("saveSettings - failed to insert new record into DB");
            }

            db.delete(TABLE_ALLOWED_CONTACTS, "settings_id=?", new String[] {Long.toString(rowId)});
            int[] allowedContacts = s.getAllowedContacts();
            if (allowedContacts != null) {
              ContentValues contactsValues = new ContentValues();
              for (int i = 0; i < allowedContacts.length; i++) {
                contactsValues.put("settings_id", rowId);
                contactsValues.put("contact_id", allowedContacts[i]);
                if (db.insert(TABLE_ALLOWED_CONTACTS, null, contactsValues) == -1)
                  throw new Exception("saveSettings - failed to update database entry (contacts)");
              }
            }
          } else { // something went totally wrong and there are multiple entries for same
                   // identifier
            result = false;
            throw new Exception("saveSettings - duplicate entries in the privacy.db");
          }
        } else {
          result = false;
          // jump to catch block to avoid marking transaction as successful
          throw new Exception("saveSettings - cursor is null, database access failed");
        }
      }

      // save settings to plain text file (for access from core libraries)
      //            Log.d(TAG, "saveSettings - saving to plain text file");
      //            File settingsUidDir = new File("/data/system/privacy/" + packageName + "/" + uid
      // + "/");
      File settingsPackageDir = new File("/data/system/privacy/" + packageName + "/");
      File systemLogsSettingFile =
          new File("/data/system/privacy/" + packageName + "/" + "/systemLogsSetting");
      try {
        // create all parent directories on the file path
        //                settingsUidDir.mkdirs();
        // make the directory readable (requires it to be executable as well)
        //                settingsUidDir.setReadable(true, false);
        //                settingsUidDir.setExecutable(true, false);
        // make the parent directory readable (requires it to be executable as well)
        settingsPackageDir.mkdirs();
        settingsPackageDir.setReadable(true, false);
        settingsPackageDir.setExecutable(true, false);
        // create the setting files and make them readable
        systemLogsSettingFile.createNewFile();
        systemLogsSettingFile.setReadable(true, false);
        // write settings to files
        //                Log.d(TAG, "saveSettings - writing to file");
        OutputStreamWriter writer =
            new OutputStreamWriter(new FileOutputStream(systemLogsSettingFile));
        writer.append(s.getSystemLogsSetting() + "");
        writer.flush();
        writer.close();
      } catch (IOException e) {
        result = false;
        // jump to catch block to avoid marking transaction as successful
        throw new Exception("saveSettings - could not write settings to file", e);
      }
      // mark DB transaction successful (commit the changes)
      db.setTransactionSuccessful();
      //            Log.d(TAG, "saveSettings - completing transaction");
    } catch (Exception e) {
      result = false;
      //            Log.d(TAG, "saveSettings - could not save settings", e);
    } finally {
      db.endTransaction();
      if (c != null) c.close();
      synchronized (readingThreads) {
        readingThreads--;
        // only close DB if no other threads are reading
        if (readingThreads == 0 && db != null && db.isOpen()) {
          db.close();
        }
      }
    }

    return result;
  }