FPII-2286: Dynamically compute the offset compensation

Compute the offset compensation based on the current calibration and
on the values read.
Re-factor the app globally and bump version number to 2.0 (20099).

Change-Id: Ic43456ae85b72f76804767fd54111cc22191d5b3
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..816e37e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+The app version (a.k.a Version Code) is based on the following scheme:
+
+* Major version number
+* Minor version number
+* Release status number (snapshot/release candidate/public release)
+
+The rules are as follow:
+
+* The major version uses two digits
+* The minor version uses two digits
+* The release status uses two digits
+* The major version should be bumped when introducing new features
+* The minor version should be bumped whenever there is a (simple/hot) fix
+* The release status must be 0 while developing (snapshot)
+* The release status must be in [1;98] while testing releases (release candidate)
+* The release status must be 99 for a public release
+
+The readable app version (a.k.a Version Name) is based on the app version with the following schemes:
+
+* Snapshots are named after <major version>.<minor version>-SNAPSHOT
+* Release candidates are named after <major version>.<minor version>-RC<release status>
+* Public releases are named after <major version>.<minor version>
+
+TODO: implement an automatic translation from app version to readable app version
+TODO: implement an automatic version bumping task when pushing/merging/integrating features/fixes
diff --git a/app/build.gradle b/app/build.gradle
index 770ac31..b7fad15 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,14 +1,14 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 23
+    compileSdkVersion 22
     buildToolsVersion '22.0.1'
     defaultConfig {
         applicationId "com.fairphone.psensor"
         minSdkVersion 22
         targetSdkVersion 22
-        versionCode 1
-        versionName "1.0"
+        versionCode 20099
+        versionName "2.0"
     }
     buildTypes {
         release {
@@ -25,6 +25,6 @@
 dependencies {
     compile fileTree(include: ['*.jar'], dir: 'libs')
     testCompile 'junit:junit:4.12'
-    compile 'com.android.support:appcompat-v7:23.4.0'
-    compile 'com.android.support:support-v13:24.1.0'
+    compile 'com.android.support:support-v13:22.2.1'
+    compile 'com.android.support:support-v4:22.2.1'
 }
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ca67335..0d76418 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,12 +3,12 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.fairphone.psensor"
     android:sharedUserId="android.uid.system"
-    android:versionCode="1"
-    android:versionName="1.0">
+    android:versionCode="20099"
+    android:versionName="2.0">
 
     <uses-sdk
-        android:minSdkVersion="17"
-        android:targetSdkVersion="21" />
+        android:minSdkVersion="22"
+        android:targetSdkVersion="22" />
 
      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
      <uses-permission android:name="android.permission.READ_PHONE_STATE" />
@@ -21,7 +21,8 @@
     <application
         android:allowBackup="true"
         android:label="@string/app_name"
-        android:theme="@style/AppTheme">
+        android:theme="@style/AppTheme"
+        android:icon="@mipmap/ic_launcher_settings">
 
         <activity android:name="com.fairphone.psensor.UpdateFinalizerActivity"
             android:enabled="false"
diff --git a/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java b/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java
index 62e4249..ffe9fcf 100644
--- a/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java
+++ b/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java
@@ -6,6 +6,7 @@
 import android.content.Intent;
 import android.provider.Settings;
 
+import com.fairphone.psensor.helpers.CalibrationStatusHelper;
 
 
 public class BootUpReceiver extends BroadcastReceiver {
@@ -19,6 +20,10 @@
     public void onReceive(Context context, Intent intent) {
         if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
             UpdateFinalizerService.startActionBootUp(context);
+
+            if (CalibrationStatusHelper.isCalibrationPending(context)) {
+                CalibrationStatusHelper.setCalibrationCompleted(context);
+            }
         }
         if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
             UpdateFinalizerService.startActionShutdown(context);
diff --git a/app/src/main/java/com/fairphone/psensor/CalibrationActivity.java b/app/src/main/java/com/fairphone/psensor/CalibrationActivity.java
index 7f874fa..2ad977f 100644
--- a/app/src/main/java/com/fairphone/psensor/CalibrationActivity.java
+++ b/app/src/main/java/com/fairphone/psensor/CalibrationActivity.java
@@ -1,9 +1,11 @@
-
 package com.fairphone.psensor;
 
 import android.app.Activity;
+import android.app.DialogFragment;
+import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -15,294 +17,356 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
-import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.ViewFlipper;
 
 import com.fairphone.psensor.CalibrationContract.CalibrationData;
+import com.fairphone.psensor.fragments.IncompatibleDeviceDialog;
+import com.fairphone.psensor.helpers.CalibrationStatusHelper;
+import com.fairphone.psensor.helpers.ProximitySensorHelper;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
+import java.util.Locale;
 
 /**
- * 1. Hint user block sensor and read value. if less than 230, hint and wait confirm. <BR>
- * 2. Hint user unblock sensor and read value. if greater than 96, hint and wait confirm. <BR>
- * 3. Use the value of unblock to do the calibration. value+30 as far, value+60 as near. <BR>
- * 4. Write far and near value to /persist/sns.reg binary file. <BR>
- * 5. The file of sns.reg content as "0000100: 0a3c 0000 <near> <far> 6400 6400 01c0 0000" <BR>
+ * Activity to start the calibration process.<br>
+ * <br>
+ * The calibration steps are:
+ * <ol>
+ * <li>Ask for a blocked sensor and read the (blocked) value.</li>
+ * <li>Ask for a non-blocked sensor and read the (non-blocked) value.</li>
+ * <li>Compute a new calibration (near and far threshold as well as the offset compensation) and persist it into the
+ * memory.</li>
+ * </ol>
+ * <br>
+ * The offset compensation is -was- 0 out of factory and could cause issues because certain devices require a higher
+ * compensation.<br>
+ * <br>
+ * The dynamic offset compensation is computed from the non-blocked value read at step 2.<br>
+ * The rules are as follow:
+ * <ol>
+ * <li>The read value is reduced by approx. 32 (sensor units) for each offset compensation increment (from the
+ * specification).</li>
+ * <li>According to the vendor, the value read must be above 0 when non-blocked, so we use the offset value directly
+ * lower than floor("value read"/32) to be on the safe side.</li>
+ * <li>This also allows to take into account a dirty current state. The non-blocked value then belongs to [32;63] in
+ * the current conditions.</li>
+ * <li>If the value read is already 0, we lower the persisted offset by 2 to reach a similar non-blocked range than
+ * above.</li>
+ * <li>The proximity sensor offset compensation belongs to [{@link ProximitySensorConfiguration#MIN_OFFSET_COMPENSATION}, {@link ProximitySensorConfiguration#MAX_OFFSET_COMPENSATION}].</li>
+ * </ol>
  */
-public class CalibrationActivity extends Activity {
+public class CalibrationActivity extends Activity implements IncompatibleDeviceDialog.IncompatibleDeviceDialogListener {
     private static final String TAG = CalibrationActivity.class.getSimpleName();
 
-    private static final String CALIBRATION_FILE = "/persist/sns.reg";
-    private static final String CMD = "senread";
-    private static final String RESULT_PREFIX = "[RESULT]";
-    private static final int DEFAULT_OFFSET = 0x01;
-    protected static final int OFFSET_FAR = 30;
-    protected static final int OFFSET_NEAR = 30;
-    protected static final int READ_MIN_LIMIT = 0;
-    protected static final int READ_MAX_LIMIT = 255;
-    protected static final int BLOCK_LIMIT = 235;
-    protected static final int UNBLOCK_LIMIT = 180;
-    private static final int SEEK_NEAR = 0x00000100 + 4;
-    private static final int SEEK_FAR = 0x00000100 + 6;
-    private static final int SEEK_OFFSET = 0x00000128;
+    /* Calibration step status */
+    private static final int STEP_CURRENT = 0;
+    private static final int STEP_IN_PROGRESS = 1;
+    private static final int STEP_ERROR = 2;
+    private static final int STEP_OK = 3;
 
-    private static final int STATE_START = 0;
-    private static final int STATE_BLOCK = 11;
-    private static final int STATE_BLOCK_READ = 12;
-    private static final int STATE_BLOCK_WARN = 13;
-    private static final int STATE_UNBLOCK = 21;
-    private static final int STATE_UNBLOCK_READ = 22;
-    private static final int STATE_UNBLOCK_WARN = 23;
-    private static final int STATE_CAL = 3;
-    private static final int STATE_SUCCESS = 4;
-    private static final int STATE_FAIL = 5;
-    private static final int STATE_FAIL_STEP_2 = 6;
+    /**
+     * Value to compute the near threshold from the blocked value (in sensor units).
+     */
+    public static final int NEAR_THRESHOLD_FROM_BLOCKED_VALUE = 30;
+    /**
+     * Value to compute the far threshold from the near threshold (in sensor units).
+     */
+    public static final int FAR_THRESHOLD_FROM_NEAR_THRESHOLD = 30;
+    /**
+     * Minimal accepted value for the blocked value (in sensor units).
+     */
+    public static final int BLOCKED_MINIMAL_VALUE = 235;
+    /**
+     * Maximal accepted value for the non-blocked value in relation to the read blocked value (in sensor units).
+     */
+    public static final int NON_BLOCKED_MAXIMAL_VALUE_FROM_BLOCKED_VALUE = 5;
+    /**
+     * Delay to emulate a long calibration (in ms).
+     */
+    public static final int CALIBRATION_DELAY_MS = 3000;
 
-    protected static final int READ_N_TIMES = 3;
-    protected static final int READ_DELAY = 500;
+    private ProximitySensorConfiguration mPersistedConfiguration;
+    private ProximitySensorConfiguration mCalibratedConfiguration;
+
+    private int mBlockedValue;
+    private int mNonBlockedValue;
 
     private Handler mHandler;
-    private TextView mStep1;
-    private TextView mText1;
-    private Button mButton1;
-    private TextView mStep2;
-    private TextView mText2;
-    private Button mButton2;
-    private TextView mStep3;
-    private TextView mText3;
-    private Button mButton3;
-
-    private int mPersistedDataFar;
-    private int mPersistedDataNear;
-    private int mPersistedDataOffset;
-    private int mDataFar;
-    private int mDataNear;
-    private int mDataOffset;
-    private int mState = STATE_START;
 
     private ViewFlipper mFlipper;
     private View mViewStep1;
     private View mViewStep2;
     private View mViewStep3;
-    private ProgressBar mProgressBar1;
-    private ProgressBar mProgressBar2;
 
+    private final View.OnClickListener actionReboot = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+            powerManager.reboot(null);
+        }
+
+    };
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        getPersistedValues();
-
-        mHandler=new Handler();
+        mHandler = new Handler();
 
         setContentView(R.layout.activity_calibration);
 
-        mFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
+        if (!ProximitySensorHelper.canReadProximitySensorValue()) {
+            Log.w(TAG, "Proximity sensor value not read-able, aborting.");
 
+            showIncompatibleDeviceDialog();
+        } else if (!ProximitySensorConfiguration.canReadFromAndPersistToMemory()) {
+            Log.w(TAG, "Proximity sensor configuration not accessible (R/W), aborting.");
+
+            showIncompatibleDeviceDialog();
+        } else {
+            init();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (CalibrationStatusHelper.isCalibrationPending(this)) {
+            updateCalibrationStepView(mViewStep3, STEP_CURRENT, R.string.step_3, R.string.msg_calibration_success, -1, actionReboot, R.string.reboot);
+            if (mFlipper.getDisplayedChild() != 2) {
+                mFlipper.setDisplayedChild(2);
+            }
+        } else {
+            reset();
+        }
+    }
+
+    private void init() {
+        mFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
         mFlipper.setInAnimation(this, R.anim.slide_in_from_left);
         mFlipper.setOutAnimation(this, R.anim.slide_out_to_right);
 
-
-        LayoutInflater inflater = LayoutInflater.from(this);
-        mViewStep1 = inflater.inflate(R.layout.view_calibration_step,null);
-        mViewStep2 = inflater.inflate(R.layout.view_calibration_step,null);
-        mViewStep3 = inflater.inflate(R.layout.view_calibration_step,null);
+        final LayoutInflater inflater = LayoutInflater.from(this);
+        mViewStep1 = inflater.inflate(R.layout.view_calibration_step, mFlipper, false);
+        mViewStep2 = inflater.inflate(R.layout.view_calibration_step, mFlipper, false);
+        mViewStep3 = inflater.inflate(R.layout.view_calibration_step, mFlipper, false);
 
         mFlipper.addView(mViewStep1);
         mFlipper.addView(mViewStep2);
         mFlipper.addView(mViewStep3);
-
-
-        mStep1 = (TextView) mViewStep1.findViewById(R.id.textview_heading);
-        mText1 = (TextView) mViewStep1.findViewById(R.id.maintext);
-        mButton1 = (Button) mViewStep1.findViewById(R.id.button);
-        mProgressBar1 = (ProgressBar) mViewStep1.findViewById(R.id.progressBar);
-        mProgressBar1.setVisibility(View.INVISIBLE);
-
-        mButton1.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                changeState(STATE_BLOCK_READ);
-            }
-        });
-        mStep2 = (TextView) mViewStep2.findViewById(R.id.textview_heading);
-        mText2 = (TextView) mViewStep2.findViewById(R.id.maintext);
-        mButton2 = (Button) mViewStep2.findViewById(R.id.button);
-        mStep2.setText(getText(R.string.step_2));
-        mText2.setText(getText(R.string.msg_unblock));
-        mProgressBar2 = (ProgressBar) mViewStep2.findViewById(R.id.progressBar);
-        mProgressBar1.setVisibility(View.INVISIBLE);
-
-        mButton2.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                changeState(STATE_UNBLOCK_READ);
-            }
-        });
-        mStep3 = (TextView) mViewStep3.findViewById(R.id.textview_heading);
-        mText3 = (TextView) mViewStep3.findViewById(R.id.maintext);
-        mButton3 = (Button) mViewStep3.findViewById(R.id.button);
-        mStep3.setText(getText(R.string.step_3));
-        mText3.setText(getText(R.string.msg_calibration_success));
-        mButton3.setText(R.string.reboot);
-
-        mButton3.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mFlipper.showNext();
-                PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
-                powerManager.reboot(null);            }
-        });
     }
 
-    private void changeState(int state) {
-        mState = state;
-        update();
+    private void reset() {
+        mPersistedConfiguration = ProximitySensorConfiguration.readFromMemory();
+        mCalibratedConfiguration = new ProximitySensorConfiguration();
+
+        updateCalibrationStepView(mViewStep1, STEP_CURRENT, R.string.step_1, R.string.msg_block, -1, new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                doReadBlockedValue();
+            }
+        }, R.string.next);
+
+        updateCalibrationStepView(mViewStep2, STEP_CURRENT, R.string.step_2, R.string.msg_unblock, -1, new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                doReadNonBlockedValue();
+            }
+        }, R.string.next);
+
+        updateCalibrationStepView(mViewStep3, STEP_CURRENT, R.string.step_3, R.string.msg_cal, -1, actionReboot, R.string.reboot);
+
+        mFlipper.setDisplayedChild(0);
     }
 
-    private void update() {
-        switch (mState) {
-            case STATE_START:
-                changeState(STATE_BLOCK);
+    private void updateCalibrationStepView(View stepView, int viewStatus, int title, int instructions, int errorNotice, View.OnClickListener action, int actionLabel) {
+        final TextView viewTitle = (TextView) stepView.findViewById(R.id.current_step);
+        final TextView viewInstructions = (TextView) stepView.findViewById(R.id.instructions);
+        final TextView viewErrorNotice = (TextView) stepView.findViewById(R.id.error_notice);
+        final View viewInProgress = stepView.findViewById(R.id.progress_bar);
+        final Button buttonAction = (Button) stepView.findViewById(R.id.button);
+
+        switch (viewStatus) {
+            case STEP_CURRENT:
+                viewErrorNotice.setVisibility(View.GONE);
+                viewInProgress.setVisibility(View.GONE);
+
+                buttonAction.setEnabled(true);
                 break;
-            case STATE_BLOCK:
-                updateToBlock();
+
+            case STEP_IN_PROGRESS:
+                viewErrorNotice.setVisibility(View.GONE);
+                viewInProgress.setVisibility(View.VISIBLE);
+
+                buttonAction.setEnabled(false);
                 break;
-            case STATE_BLOCK_READ:
-                updateToBlockRead();
+
+            case STEP_ERROR:
+                viewErrorNotice.setVisibility(View.VISIBLE);
+                viewInProgress.setVisibility(View.GONE);
+
+                buttonAction.setEnabled(true);
                 break;
-            case STATE_BLOCK_WARN:
-            case STATE_UNBLOCK:
-                updateToUnblock();
+
+            case STEP_OK:
+                viewErrorNotice.setVisibility(View.GONE);
+                viewInProgress.setVisibility(View.GONE);
+
+                buttonAction.setEnabled(false);
                 break;
-            case STATE_UNBLOCK_READ:
-                updateToUnblockRead();
-                break;
-            case STATE_UNBLOCK_WARN:
-            case STATE_CAL:
-                updateToCal();
-                break;
-            case STATE_SUCCESS:
-                updateToSuccess();
-                break;
-            case STATE_FAIL:
-                updateToFail();
-                break;
-            case STATE_FAIL_STEP_2:
-                updateToFailStep2();
-                break;
+
             default:
-                break;
+                Log.wtf(TAG, "Unknown calibration step reached: " + viewStatus);
+        }
+
+        if (title != -1) {
+            viewTitle.setText(title);
+        }
+
+        if (instructions != -1) {
+            viewInstructions.setText(instructions);
+        }
+
+        if (errorNotice != -1) {
+            viewErrorNotice.setText(errorNotice);
+        }
+
+        if (action != null) {
+            buttonAction.setOnClickListener(action);
+        }
+
+        if (actionLabel != -1) {
+            buttonAction.setText(actionLabel);
         }
     }
 
-    private void updateToBlock() {
-        //mText1.setText(getString(R.string.msg_block));
-        mFlipper.setDisplayedChild(0);
-
-        mStep1.setEnabled(true);
-        mText1.setEnabled(true);
-        mButton1.setEnabled(true);
-        mProgressBar1.setVisibility(View.INVISIBLE);
+    private void updateCalibrationStepView(View stepView, int viewStatus, int instructions) {
+        updateCalibrationStepView(stepView, viewStatus, -1, instructions, -1, null, -1);
     }
 
-    private void updateToBlockRead() {
-        mText1.setText(getString(R.string.msg_reading));
-        mButton1.setEnabled(false);
-        mProgressBar1.setVisibility(View.VISIBLE);
+    private void updateCalibrationStepView(View stepView, int viewStatus, int instructions, int errorNotice) {
+        updateCalibrationStepView(stepView, viewStatus, -1, instructions, errorNotice, null, -1);
+    }
+
+    private void updateCalibrationStepView(View stepView, int viewStatus, int instructions, int errorNotice, View.OnClickListener action, int actionLabel) {
+        updateCalibrationStepView(stepView, viewStatus, -1, instructions, errorNotice, action, actionLabel);
+    }
+
+    private void doReadBlockedValue() {
+        updateCalibrationStepView(mViewStep1, STEP_IN_PROGRESS, R.string.msg_reading);
+
         new Thread(new Runnable() {
             @Override
             public void run() {
-                final int value = read(BLOCK_LIMIT, READ_MAX_LIMIT);
+                final int value = ProximitySensorHelper.read(BLOCKED_MINIMAL_VALUE, ProximitySensorHelper.READ_MAX_LIMIT);
 
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        Log.d(TAG, "block value   = " + String.format("%3d", value) + " (" + String.format("0x%04x", value) + ")");
+                        Log.d(TAG, "    blocked value = " + String.format(Locale.ENGLISH, "%3d", value));
 
-                        if (value >= BLOCK_LIMIT) {
-                            mDataNear = value - OFFSET_NEAR;
-                            changeState(STATE_UNBLOCK);
-                        } else {
-                            mText1.setText(getString(R.string.msg_fail_block));
-                            changeState(STATE_FAIL);
-                        }
+                        doSaveBlockedValue(value);
                     }
                 });
             }
         }).start();
     }
 
-    private void updateToCal() {
-        mText2.setText(R.string.msg_step_success);
-        mStep3.setEnabled(true);
-        mText3.setEnabled(true);
-        mText3.setText(getString(R.string.msg_cal));
-        mHandler.post(new Runnable() {
+    private void doSaveBlockedValue(int value) {
+        if (value >= 0) {
+            mBlockedValue = value;
+
+            updateCalibrationStepView(mViewStep1, STEP_OK, R.string.msg_step_success);
+            mFlipper.setDisplayedChild(1);
+        } else {
+            updateCalibrationStepView(mViewStep1, STEP_ERROR, R.string.msg_block, R.string.msg_fail_block);
+            mFlipper.setDisplayedChild(0);
+        }
+    }
+
+    private void doReadNonBlockedValue() {
+        updateCalibrationStepView(mViewStep2, STEP_IN_PROGRESS, R.string.msg_reading);
+
+        new Thread(new Runnable() {
             @Override
             public void run() {
-                if (write()) {
-                    mText3.setText(getString(R.string.msg_calibration_success));
-                    mButton3.setEnabled(true);
-                    changeState(STATE_SUCCESS);
-                } else {
-                    mText3.setText(getString(R.string.msg_fail_write_sns));
-                    changeState(STATE_FAIL);
+                final int value = ProximitySensorHelper.read(ProximitySensorHelper.READ_MIN_LIMIT, (mBlockedValue - NON_BLOCKED_MAXIMAL_VALUE_FROM_BLOCKED_VALUE));
+
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Log.d(TAG, "non-blocked value = " + String.format(Locale.ENGLISH, "%3d", value));
+
+                        doSaveNonBlockedValue(value);
+                    }
+                });
+            }
+        }).start();
+    }
+
+    private void doSaveNonBlockedValue(int value) {
+        if (value >= 0) {
+            mNonBlockedValue = value;
+
+            updateCalibrationStepView(mViewStep2, STEP_OK, R.string.msg_step_success);
+            mFlipper.setDisplayedChild(2);
+
+            doCalibrate();
+        } else {
+            updateCalibrationStepView(mViewStep2, STEP_ERROR, R.string.msg_unblock, R.string.msg_fail_unlock);
+            mFlipper.setDisplayedChild(1);
+        }
+    }
+
+    public void doCalibrate() {
+        updateCalibrationStepView(mViewStep3, STEP_IN_PROGRESS, R.string.msg_cal);
+
+        mCalibratedConfiguration.nearThreshold = mBlockedValue - NEAR_THRESHOLD_FROM_BLOCKED_VALUE;
+        mCalibratedConfiguration.farThreshold = mCalibratedConfiguration.nearThreshold - FAR_THRESHOLD_FROM_NEAR_THRESHOLD;
+
+        if (mNonBlockedValue == 0) {
+            mCalibratedConfiguration.offsetCompensation = Math.min(Math.max(mPersistedConfiguration.offsetCompensation - 2, ProximitySensorConfiguration.MIN_OFFSET_COMPENSATION), ProximitySensorConfiguration.MAX_OFFSET_COMPENSATION);
+            Log.d(TAG, "New offset based on current offset only");
+        } else {
+            mCalibratedConfiguration.offsetCompensation = Math.min(Math.max(mPersistedConfiguration.offsetCompensation + (int)Math.floor(mNonBlockedValue / 32) - 1, ProximitySensorConfiguration.MIN_OFFSET_COMPENSATION), ProximitySensorConfiguration.MAX_OFFSET_COMPENSATION);
+            Log.d(TAG, "New offset based on unblock value and current offset");
+        }
+
+        if (mCalibratedConfiguration.persistToMemory()) {
+            storeCalibrationData();
+            CalibrationStatusHelper.setCalibrationSuccessful(this);
+
+            // wait a bit because the calibration is otherwise too fast
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        Thread.sleep(CALIBRATION_DELAY_MS);
+                    } catch (InterruptedException e) {
+                        // Log but ignore interruption.
+                        Log.e(TAG, e.getMessage());
+                    }
+
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            updateCalibrationStepView(mViewStep3, STEP_CURRENT, R.string.msg_calibration_success);
+                            mFlipper.setDisplayedChild(2);
+                        }
+                    });
                 }
-            }
-        });
-    }
+            }).start();
+        } else {
+            updateCalibrationStepView(mViewStep3, STEP_ERROR, R.string.msg_cal, R.string.msg_fail_write_sns, new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    reset();
+                    startFairphoneUpdaterActivity();
+                }
+            }, R.string.go_to_updater);
+        }
 
-    private void updateToFail() {
-        mButton1.setEnabled(true);
-        changeState(STATE_BLOCK);
-    }
-
-    private void updateToFailStep2() {
-        mText2.setText(getString(R.string.msg_fail_unlock));
-        mButton2.setEnabled(true);
-        changeState(STATE_UNBLOCK);
-    }
-
-    private void updateToSuccess() {
-        mFlipper.setDisplayedChild(2);
-        setSuccesfullyCalibrated(this, true);
-        mText2.setText(R.string.msg_step_success);
-        mButton3.setEnabled(true);
-    }
-
-    private void updateToUnblockRead() {
-        mText2.setText(getString(R.string.msg_reading));
-        mButton2.setEnabled(false);
-        mProgressBar2.setVisibility(View.VISIBLE);
-        new Thread(new Runnable() {
-            @Override
-            public void run() {
-                final int value = read(READ_MIN_LIMIT, (mDataNear + OFFSET_NEAR - 5));
-
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        Log.d(TAG, "unblock value = " + String.format("%3d", value) + " (" + String.format("0x%04x", value) + ")");
-                        if (value >= 0 && value <= (mDataNear + OFFSET_NEAR - 5)) {
-                            mDataFar = mDataNear - OFFSET_FAR;
-                            mDataOffset = DEFAULT_OFFSET;
-                            changeState(STATE_CAL);
-                        } else {
-                            mText1.setText(getString(R.string.msg_fail_unlock));
-                            changeState(STATE_FAIL_STEP_2);
-                        }
-                    }
-                });
-            }
-        }).start();
     }
 
     @Override
@@ -311,152 +375,6 @@
         super.onPause();
     }
 
-    private void updateToUnblock() {
-        mFlipper.setDisplayedChild(1);
-        mProgressBar2.setVisibility(View.INVISIBLE);
-        mStep2.setEnabled(true);
-        mText2.setEnabled(true);
-        mButton2.setEnabled(true);
-    }
-
-    /**
-     * Call to read(1, {@link #READ_MIN_LIMIT}, {@link #READ_MAX_LIMIT})
-     *
-     * @return The value read or -1 if read failed.
-     * @see #read(int, int, int)
-     */
-    public static int read() {
-        return read(1, READ_MIN_LIMIT, READ_MAX_LIMIT);
-    }
-
-    /**
-     * Call to read({@link #READ_N_TIMES}, min_value, max_value)
-     *
-     * @param min_value The lower threshold (inclusive) of accepted range.
-     * @param max_value The upper threshold (inclusive) of accepted range.
-     * @return The mean of all the value read (up to {@link #READ_N_TIMES}) or -1 if no read succeeded.
-     * @see #read(int, int, int)
-     */
-    public static int read(int min_value, int max_value) {
-        return read(READ_N_TIMES, min_value, max_value);
-    }
-
-    /**
-     * Read the proximity sensor value read_times times and return the mean value.
-     *
-     * Wait {@link #READ_DELAY} between each read, even if there is only one read planned.
-     *
-     * @param min_value The lower threshold (inclusive) of accepted range.
-     * @param max_value The upper threshold (inclusive) of accepted range.
-     * @return The mean of all the value read (up to {@link #READ_N_TIMES}) or -1 if no read succeeded.
-     */
-    public static int read(int read_times, int min_value, int max_value) {
-        String line;
-        int result;
-        int summed_result = 0;
-        int nb_result_read = 0;
-        int final_result = -1;
-
-        for (int i = 0; i < read_times; i++) {
-            line = exec(CMD);
-
-            if (line != null && line.startsWith(RESULT_PREFIX)) {
-                try {
-                    result = Integer.parseInt( line.replace(RESULT_PREFIX, "").trim() );
-
-                    if (min_value <= result && result <= max_value) {
-                        summed_result += result;
-                        nb_result_read++;
-                    } else {
-                        Log.d(TAG, "Ignored value out of accepted range (" + result + " not in [" + min_value + "," + max_value + "])");
-                    }
-                } catch (Exception e) {
-                    Log.wtf(TAG, e);
-                }
-            }
-
-            // wait a bit between two sensor read
-            try {
-                Thread.sleep(READ_DELAY);
-            } catch (Exception e) {
-                Log.wtf(TAG, e);
-            }
-        }
-
-        if (nb_result_read == 0) {
-            // something went wrong with CMD, are we allowed to execute it?
-            Log.e(TAG, "Could not read sensor value " + read_times + " " + ((read_times==1) ? "time" : "times"));
-
-            // TODO display an error message
-        } else {
-            if (nb_result_read < read_times) {
-                Log.w(TAG, "Read " + nb_result_read + "/" + read_times + " values");
-            }
-
-            final_result = Math.round(summed_result / nb_result_read);
-        }
-
-        return final_result;
-    }
-
-    private void getPersistedValues() {
-        byte[] buffer = new byte[4];
-        buffer[2] = 0x00;
-        buffer[3] = 0x00;
-        try {
-            RandomAccessFile file = new RandomAccessFile(CALIBRATION_FILE, "r");
-
-            file.seek(SEEK_NEAR);
-            file.read(buffer, 0, 2);
-            mPersistedDataNear = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
-            Log.d(getString(R.string.logtag), "persisted near data   = " + String.format("%3d", mPersistedDataNear) + " (" + String.format("0x%02x%02x", buffer[1], buffer[0]) + ")");
-
-            file.seek(SEEK_FAR);
-            file.read(buffer, 0, 2);
-            mPersistedDataFar = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
-            Log.d(getString(R.string.logtag), "persisted far data    = " + String.format("%3d", mPersistedDataFar) + " (" + String.format("0x%02x%02x", buffer[1], buffer[0]) + ")");
-
-            file.seek(SEEK_OFFSET);
-            file.read(buffer, 0, 1);
-            buffer[1] = 0x00;
-            mPersistedDataOffset = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
-            Log.d(getString(R.string.logtag), "persisted offset data = " + String.format("%3d", mPersistedDataOffset) + " (" + String.format("0x%02x", buffer[0]) + ")");
-
-            file.close();
-        } catch (Exception e) {
-            Log.wtf(TAG, e);
-        }
-    }
-
-
-    private boolean write() {
-        byte[] far  = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(mDataFar).array();
-        byte[] near = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(mDataNear).array();
-        byte[] offset = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(mDataOffset).array();
-
-        Log.d(getString(R.string.logtag), "near data   = " + String.format("%3d", mDataNear) + " (" + String.format("0x%02x%02x", near[1], near[0]) + ")");
-        Log.d(getString(R.string.logtag), "far data    = " + String.format("%3d", mDataFar) + " (" + String.format("0x%02x%02x", far[1], far[0]) + ")");
-        Log.d(getString(R.string.logtag), "offset data = " + String.format("%3d", mDataOffset) + " (" + String.format("0x%02x", offset[0]) + ")");
-
-        try {
-            RandomAccessFile file = new RandomAccessFile(CALIBRATION_FILE, "rw");
-            file.seek(SEEK_NEAR);
-            file.writeByte(near[0]);
-            file.writeByte(near[1]);
-            file.seek(SEEK_FAR);
-            file.writeByte(far[0]);
-            file.writeByte(far[1]);
-            file.seek(SEEK_OFFSET);
-            file.writeByte(offset[0]);
-            file.close();
-            storeCalibrationData();
-            return true;
-        } catch (Exception e) {
-            Log.wtf(TAG, e);
-        }
-        return false;
-    }
-
     private void storeCalibrationData() {
         CalibrationDbHelper mDbHelper = new CalibrationDbHelper(this);
 
@@ -465,18 +383,18 @@
 
         // Create a new map of values, where column names are the keys
         ContentValues values = new ContentValues();
-        values.put(CalibrationData.COLUMN_NAME_PREVIOUS_NEAR, mPersistedDataNear);
-        values.put(CalibrationData.COLUMN_NAME_PREVIOUS_FAR, mPersistedDataFar);
-        values.put(CalibrationData.COLUMN_NAME_PREVIOUS_OFFSET, mPersistedDataOffset);
-        values.put(CalibrationData.COLUMN_NAME_NEAR, mDataNear);
-        values.put(CalibrationData.COLUMN_NAME_FAR, mDataFar);
-        values.put(CalibrationData.COLUMN_NAME_OFFSET, mDataOffset);
+        values.put(CalibrationData.COLUMN_NAME_PREVIOUS_NEAR, mPersistedConfiguration.nearThreshold);
+        values.put(CalibrationData.COLUMN_NAME_PREVIOUS_FAR, mPersistedConfiguration.farThreshold);
+        values.put(CalibrationData.COLUMN_NAME_PREVIOUS_OFFSET, mPersistedConfiguration.offsetCompensation);
+        values.put(CalibrationData.COLUMN_NAME_NEAR, mCalibratedConfiguration.nearThreshold);
+        values.put(CalibrationData.COLUMN_NAME_FAR, mCalibratedConfiguration.farThreshold);
+        values.put(CalibrationData.COLUMN_NAME_OFFSET, mCalibratedConfiguration.offsetCompensation);
 
         PackageInfo pInfo = null;
         try {
             pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
         } catch (PackageManager.NameNotFoundException e) {
-            e.printStackTrace();
+            Log.wtf(TAG, e);
         }
         int verCode = pInfo.versionCode;
         values.put(CalibrationData.COLUMN_NAME_APP_VERSION, verCode);
@@ -490,43 +408,30 @@
 
     }
 
-    private static String exec(String cmd) {
-        try {
-            Process proc = Runtime.getRuntime().exec(new String[]{cmd});
-            BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
-            return reader.readLine();
-        } catch (IOException e) {
-            Log.wtf(TAG, "Could not execute command `" + cmd + "`", e);
-            return null;
-        }
+    private void startFairphoneUpdaterActivity() {
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName(getString(R.string.package_fairphone_updater), getString(R.string.activity_fairphone_updater_check_for_updates)));
+        intent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME | Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(intent);
     }
 
-    protected static void setSuccesfullyCalibrated(Context ctx, boolean isSuccessfullyCalibrated) {
-        SharedPreferences sharedPref = ctx.getSharedPreferences(
-                ctx.getString(R.string.preference_file_key), MODE_PRIVATE);
-        SharedPreferences.Editor editor = sharedPref.edit();
-        editor.putBoolean(ctx.getString(R.string.preference_successfully_calibrated),isSuccessfullyCalibrated);
-        editor.apply();
+    private void showIncompatibleDeviceDialog() {
+        final DialogFragment dialog = new IncompatibleDeviceDialog();
+        dialog.show(getFragmentManager(), getString(R.string.fragment_tag_incompatible_device_dialog));
     }
 
-    protected static boolean hasToBeCalibrated(Context ctx) {
-        SharedPreferences sharedPref = ctx.getSharedPreferences(
-                ctx.getString(R.string.preference_file_key), MODE_PRIVATE);
-        boolean wasCalibrated = sharedPref.getBoolean(ctx.getString(R.string.preference_successfully_calibrated),false);
-        boolean wasCalibratedEarlier = false;
-        try {
-            RandomAccessFile file = new RandomAccessFile(CALIBRATION_FILE, "rw");
-            file.seek(SEEK_NEAR);
-            file.seek(SEEK_OFFSET);
-            byte offset0 = file.readByte();
-            byte offset1 = file.readByte();
-            file.close();
-            /* offset is only 0 on devices that have not been calibrated. */
-            wasCalibratedEarlier = (offset0 != 0 || offset1 != 0);
-        } catch (Exception e) {
-            Log.wtf(TAG, e);
-        }
-        return !wasCalibrated;
+    @Override
+    public void onIncompatibleDeviceDialogPositiveAction(DialogFragment dialog) {
+        startFairphoneUpdaterActivity();
     }
 
+    @Override
+    public void onIncompatibleDeviceDialogNegativeAction(DialogFragment dialog) {
+        // fall-through
+    }
+
+    @Override
+    public void onDismissIncompatibleDeviceDialog(DialogFragment dialog) {
+        finish();
+    }
 }
diff --git a/app/src/main/java/com/fairphone/psensor/DiagnosticsActivity.java b/app/src/main/java/com/fairphone/psensor/DiagnosticsActivity.java
index 821b828..d646bea 100644
--- a/app/src/main/java/com/fairphone/psensor/DiagnosticsActivity.java
+++ b/app/src/main/java/com/fairphone/psensor/DiagnosticsActivity.java
@@ -11,6 +11,8 @@
 import android.util.Log;
 import android.widget.TextView;
 
+import com.fairphone.psensor.helpers.ProximitySensorHelper;
+
 public class DiagnosticsActivity extends Activity {
 
     private int mSensorChangeCount = 0;
@@ -26,10 +28,16 @@
 
     private Handler mHandler;
 
+    private ProximitySensorConfiguration mPersistedConfiguration;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        mPersistedConfiguration = ProximitySensorConfiguration.readFromMemory();
         mHandler = new Handler();
+
+
         setContentView(R.layout.activity_diagnostics);
 
 //        mActionBar = getActionBar();
@@ -42,8 +50,8 @@
         mBlockValueTextView = (TextView)findViewById(R.id.blockValue);
         mUnblockValueTextView = (TextView)findViewById(R.id.unblockValue);
 
-        mBlockValueTextView.setText(String.valueOf(CalibrationActivity.BLOCK_LIMIT - CalibrationActivity.OFFSET_NEAR));
-        mUnblockValueTextView.setText(String.valueOf(CalibrationActivity.UNBLOCK_LIMIT - CalibrationActivity.OFFSET_FAR));
+        mBlockValueTextView.setText(mPersistedConfiguration.nearThreshold);
+        mUnblockValueTextView.setText(mPersistedConfiguration.farThreshold);
 
         getProximitySensor();
         setupSensorStateListener();
@@ -54,7 +62,7 @@
         @Override
         public void run() {
             try {
-                sensorValue = CalibrationActivity.read();
+                sensorValue = ProximitySensorHelper.read();
                 Log.i(DiagnosticsActivity.class.getName(), String.valueOf(sensorValue));
                 runOnUiThread(new Runnable() {
                     @Override
diff --git a/app/src/main/java/com/fairphone/psensor/ProximitySensorConfiguration.java b/app/src/main/java/com/fairphone/psensor/ProximitySensorConfiguration.java
new file mode 100644
index 0000000..4bea788
--- /dev/null
+++ b/app/src/main/java/com/fairphone/psensor/ProximitySensorConfiguration.java
@@ -0,0 +1,222 @@
+package com.fairphone.psensor;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Locale;
+
+/**
+ * Configuration fields for the proximity sensor. <br>
+ * <br>
+ * Using the vendor wording, the "near threshold" is the "Proximity Interrupt High threshold", the "far threshold"
+ * is the "Proximity Interrupt LOW threshold", and the "offset compensation" is the "Proximity Offset Compensation".
+ */
+public class ProximitySensorConfiguration {
+    private static final String TAG = ProximitySensorConfiguration.class.getSimpleName();
+
+    /**
+     * Minimal offset compensation value allowed to be persisted. <br>
+     * The minimal value accepted in the registry is <code>0x0000</code>, whereas this is the minimal value considered
+     * sane from a tuning perspective.
+     */
+    public static final int MIN_OFFSET_COMPENSATION = 0x0000;
+    /**
+     * Maximal offset compensation value allowed to be persisted. <br>
+     * The maximal value accepted in the registry is <code>0xFFFF</code>, whereas this is the maximal value considered
+     * sane from a tuning perspective.
+     */
+    public static final int MAX_OFFSET_COMPENSATION = 0x00FF;
+    /**
+     * Minimal near threshold value allowed to be persisted. <br>
+     * The minimal value accepted in the registry is <code>0x0000</code>, whereas this is the minimal value considered
+     * sane from a tuning perspective.
+     */
+    public static final int MIN_NEAR_THRESHOLD = 0x0000;
+    /**
+     * Maximal near threshold value allowed to be persisted. <br>
+     * The maximal value accepted in the registry is <code>0xFFFF</code>, whereas this is the maximal value considered
+     * sane from a tuning perspective.
+     */
+    public static final int MAX_NEAR_THRESHOLD = 0x00FF;
+    /**
+     * Minimal far threshold value allowed to be persisted. <br>
+     * The minimal value accepted in the registry is <code>0x0000</code>, whereas this is the minimal value considered
+     * sane from a tuning perspective.
+     */
+    public static final int MIN_FAR_THRESHOLD = 0x0000;
+    /**
+     * Maximal far threshold value allowed to be persisted. <br>
+     * The maximal value accepted in the registry is <code>0xFFFF</code>, whereas this is the maximal value considered
+     * sane from a tuning perspective.
+     */
+    public static final int MAX_FAR_THRESHOLD = 0x00FF;
+
+    /**
+     * Default value for the offset compensation.
+     */
+    private static final int DEFAULT_OFFSET_COMPENSATION = 0x0001;
+    /**
+     * Default value for the near threshold.
+     */
+    private static final int DEFAULT_NEAR_THRESHOLD = 0x00FF;
+    /**
+     * Default value for the far threshold.
+     */
+    private static final int DEFAULT_FAR_THRESHOLD = 0x0000;
+
+    /**
+     * Path to the persisted calibration file.
+     */
+    private static final String CALIBRATION_FILE = "/persist/sns.reg";
+    /**
+     * Offset in the calibration file to reach the offset compensation value.
+     */
+    private static final int OFFSET_COMPENSATION_OFFSET = 0x00000120 + 8;
+    /**
+     * Offset in the calibration file to reach the near threshold value.
+     */
+    private static final int NEAR_THRESHOLD_OFFSET = 0x00000100 + 4;
+    /**
+     * Offset in the calibration file to reach the far threshold value.
+     */
+    private static final int FAR_THRESHOLD_OFFSET = 0x00000100 + 6;
+
+    /**
+     * The proximity sensor offset compensation.
+     */
+    public int offsetCompensation;
+    /**
+     * The proximity sensor interrupt high threshold, to change state from free to blocked.
+     */
+    public int nearThreshold;
+    /**
+     * The proximity sensor interrupt low threshold, to change state from blocked to free.
+     */
+    public int farThreshold;
+
+    /**
+     * Default constructor based on the default values.
+     *
+     * @see #DEFAULT_OFFSET_COMPENSATION
+     * @see #DEFAULT_NEAR_THRESHOLD
+     * @see #DEFAULT_FAR_THRESHOLD
+     */
+    public ProximitySensorConfiguration() {
+        offsetCompensation = DEFAULT_OFFSET_COMPENSATION;
+        nearThreshold = DEFAULT_NEAR_THRESHOLD;
+        farThreshold = DEFAULT_FAR_THRESHOLD;
+    }
+
+    public String toString() {
+        return String.format(Locale.ENGLISH, "{offset compensation=%d, near threshold=%d, far threshold=%d}", offsetCompensation, nearThreshold, farThreshold);
+    }
+
+    /**
+     * Determine whether the memory can be read from and persisted by trying to open a handle to it.
+     *
+     * @return <code>true</code> if the memory is both readable and writable, <code>false</code> if not.
+     */
+    public static boolean canReadFromAndPersistToMemory() {
+        final File calibrationFile = new File(CALIBRATION_FILE);
+
+        return calibrationFile.canRead() && calibrationFile.canWrite();
+    }
+
+    /**
+     * Read the configuration persisted into memory.
+     *
+     * @return The persisted configuration or <code>null</code> if not accessible.
+     */
+    public static ProximitySensorConfiguration readFromMemory() {
+        ProximitySensorConfiguration configuration = new ProximitySensorConfiguration();
+
+        try {
+            byte[] buffer = new byte[4];
+            RandomAccessFile file = new RandomAccessFile(CALIBRATION_FILE, "r");
+
+            file.seek(OFFSET_COMPENSATION_OFFSET);
+            file.read(buffer, 0, 2);
+            configuration.offsetCompensation = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
+
+            file.seek(NEAR_THRESHOLD_OFFSET);
+            file.read(buffer, 0, 2);
+            configuration.nearThreshold = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
+
+            file.seek(FAR_THRESHOLD_OFFSET);
+            file.read(buffer, 0, 2);
+            configuration.farThreshold = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
+
+            file.close();
+
+            Log.d(TAG, "Configuration " + configuration.toString() + " read from `" + CALIBRATION_FILE + "`");
+        } catch (Exception e) {
+            Log.wtf(TAG, e);
+
+            configuration = null;
+        }
+
+        return configuration;
+    }
+
+    /**
+     * Persist the configuration into memory. <br>
+     * <br>
+     * This method <strong>does not</strong> update the current configuration of the proximity sensor. It does set the
+     * values that live in the <code>/persist/</code> directory only.
+     *
+     * @throws IllegalArgumentException if one of the configuration element does not respect the acceptable range
+     * (see {@link #MIN_OFFSET_COMPENSATION}, {@link #MAX_OFFSET_COMPENSATION}, {@link #MIN_NEAR_THRESHOLD},
+     * {@link #MAX_NEAR_THRESHOLD}, {@link #MIN_FAR_THRESHOLD}, and {@link #MAX_FAR_THRESHOLD}).
+     * @return <code>true</code> if the configuration could be persisted, <code>false</code> if it failed.
+     */
+    public boolean persistToMemory() throws IllegalArgumentException {
+        boolean success = false;
+        byte[] buffer;
+        RandomAccessFile file = null;
+
+        if (offsetCompensation < MIN_OFFSET_COMPENSATION || MAX_OFFSET_COMPENSATION < offsetCompensation) {
+            throw new IllegalArgumentException(String.format(Locale.ENGLISH, "Offset compensation (%d) not in the acceptable range [%d;%d]", offsetCompensation, MIN_OFFSET_COMPENSATION, MAX_OFFSET_COMPENSATION));
+        } else if (nearThreshold < MIN_NEAR_THRESHOLD || MAX_NEAR_THRESHOLD < nearThreshold) {
+            throw new IllegalArgumentException(String.format(Locale.ENGLISH, "Near threshold (%d) not in the acceptable range [%d;%d]", nearThreshold, MIN_NEAR_THRESHOLD, MAX_NEAR_THRESHOLD));
+        }if (farThreshold < MIN_FAR_THRESHOLD || MAX_FAR_THRESHOLD < farThreshold) {
+            throw new IllegalArgumentException(String.format(Locale.ENGLISH, "Far threshold (%d) not in the acceptable range [%d;%d]", farThreshold, MIN_FAR_THRESHOLD, MAX_FAR_THRESHOLD));
+        }
+
+        try {
+            file = new RandomAccessFile(CALIBRATION_FILE, "rw");
+
+            file.seek(OFFSET_COMPENSATION_OFFSET);
+            buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(offsetCompensation).array();
+            file.write(buffer, 0, 2);
+
+            file.seek(NEAR_THRESHOLD_OFFSET);
+            buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(nearThreshold).array();
+            file.write(buffer, 0, 2);
+
+            file.seek(FAR_THRESHOLD_OFFSET);
+            buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(farThreshold).array();
+            file.write(buffer, 0, 2);
+
+            file.close();
+            success = true;
+
+            Log.d(TAG, "Configuration " + this.toString() + " persisted to `" + CALIBRATION_FILE + "`");
+        } catch (Exception e) {
+            Log.wtf(TAG, e);
+        } finally {
+            if (file != null) {
+                try {
+                    file.close();
+                } catch (IOException e) {
+                    // fall-through
+                }
+            }
+        }
+
+        return success;
+    }
+}
diff --git a/app/src/main/java/com/fairphone/psensor/UpdateFinalizerActivity.java b/app/src/main/java/com/fairphone/psensor/UpdateFinalizerActivity.java
index c3051d3..5bf3bc0 100644
--- a/app/src/main/java/com/fairphone/psensor/UpdateFinalizerActivity.java
+++ b/app/src/main/java/com/fairphone/psensor/UpdateFinalizerActivity.java
@@ -22,7 +22,6 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.app.AlertDialog;
 import android.app.Activity;
@@ -35,6 +34,8 @@
 import android.widget.CompoundButton;
 import android.widget.TextView;
 
+import com.fairphone.psensor.helpers.CalibrationStatusHelper;
+
 
 public class UpdateFinalizerActivity extends Activity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
 
@@ -65,14 +66,14 @@
 
         }
 
-        mTextViewMain = (TextView) findViewById(R.id.maintext);
+        mTextViewMain = (TextView) findViewById(R.id.instructions);
         mTextViewMain.setText(Html.fromHtml(getString(R.string.Text)));
 
         setAlreadyShown();
 
         UpdateFinalizerService.startActionClearNotification(this);
 
-        if (isWizard() && !CalibrationActivity.hasToBeCalibrated(this)) {
+        if (isWizard() && !CalibrationStatusHelper.hasToBeCalibrated(this)) {
             disable(this);
             finish();
         }
diff --git a/app/src/main/java/com/fairphone/psensor/UpdateFinalizerService.java b/app/src/main/java/com/fairphone/psensor/UpdateFinalizerService.java
index d660376..5fada84 100644
--- a/app/src/main/java/com/fairphone/psensor/UpdateFinalizerService.java
+++ b/app/src/main/java/com/fairphone/psensor/UpdateFinalizerService.java
@@ -11,6 +11,8 @@
 import android.support.v4.app.NotificationCompat;
 import android.util.Log;
 
+import com.fairphone.psensor.helpers.CalibrationStatusHelper;
+
 public class UpdateFinalizerService extends IntentService {
 
     private static final String ACTION_BOOTUP_COMPLETE   = "com.fairphone.updatefinalizer.action.BOOT_COMPLETED";
@@ -81,7 +83,7 @@
 
     private void handleCheckCalibrationPending() {
         final Context ctx = this;
-        if (!UpdateFinalizerActivityFromNotification.isNotShowAnymore(this) && CalibrationActivity.hasToBeCalibrated(this)) {
+        if (!UpdateFinalizerActivityFromNotification.isNotShowAnymore(this) && CalibrationStatusHelper.hasToBeCalibrated(this)) {
             showNotification();
             setAlarm();
         }
@@ -107,7 +109,7 @@
                 .bigText(getString(R.string.NotificationText)));
         notBuilder.setSmallIcon(R.drawable.ic_stat_action_info);
         notBuilder.setContentIntent(pendingIntent);
-        notBuilder.setColor(getResources().getColor(R.color.colorPrimary));
+        notBuilder.setColor(getResources().getColor(R.color.theme_primary));
         NotificationManager notificationManager =  (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
         notificationManager.notify(mNotificationIDPleaseCalibrate, notBuilder.build());
     }
diff --git a/app/src/main/java/com/fairphone/psensor/fragments/IncompatibleDeviceDialog.java b/app/src/main/java/com/fairphone/psensor/fragments/IncompatibleDeviceDialog.java
new file mode 100644
index 0000000..0e92365
--- /dev/null
+++ b/app/src/main/java/com/fairphone/psensor/fragments/IncompatibleDeviceDialog.java
@@ -0,0 +1,73 @@
+package com.fairphone.psensor.fragments;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import com.fairphone.psensor.R;
+
+public class IncompatibleDeviceDialog extends DialogFragment {
+
+    /* The activity that creates an instance of this dialog fragment must
+     * implement this interface in order to receive event callbacks.
+     * Each method passes the DialogFragment in case the host needs to query it. */
+    public interface IncompatibleDeviceDialogListener {
+        void onIncompatibleDeviceDialogPositiveAction(DialogFragment dialog);
+
+        void onIncompatibleDeviceDialogNegativeAction(DialogFragment dialog);
+
+        void onDismissIncompatibleDeviceDialog(DialogFragment dialog);
+    }
+
+    // Use this instance of the interface to deliver action events
+    IncompatibleDeviceDialogListener mListener;
+
+    public IncompatibleDeviceDialog() {
+        // default constructor for easy instantiation
+    }
+
+    // Override the Fragment.onAttach() method to instantiate the EnterTheBetaDialogListener
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        // Verify that the host activity implements the callback interface
+        try {
+            // Instantiate the NoticeDialogListener so we can send events to the host
+            mListener = (IncompatibleDeviceDialogListener) activity;
+        } catch (ClassCastException e) {
+            // The activity doesn't implement the interface, throw exception
+            throw new ClassCastException(activity.toString() + " must implement IncompatibleDeviceDialogListener");
+        }
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+        builder.setTitle(R.string.incompatible_device);
+        builder.setMessage(R.string.device_cannot_run_calibration_tool);
+
+        builder.setPositiveButton(R.string.go_to_updater, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int id) {
+                mListener.onIncompatibleDeviceDialogPositiveAction(IncompatibleDeviceDialog.this);
+            }
+        });
+
+        builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int id) {
+                mListener.onIncompatibleDeviceDialogNegativeAction(IncompatibleDeviceDialog.this);
+            }
+        });
+        builder.setCancelable(false);
+
+        return builder.create();
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        mListener.onDismissIncompatibleDeviceDialog(this);
+    }
+}
diff --git a/app/src/main/java/com/fairphone/psensor/helpers/CalibrationStatusHelper.java b/app/src/main/java/com/fairphone/psensor/helpers/CalibrationStatusHelper.java
new file mode 100644
index 0000000..22797d7
--- /dev/null
+++ b/app/src/main/java/com/fairphone/psensor/helpers/CalibrationStatusHelper.java
@@ -0,0 +1,94 @@
+package com.fairphone.psensor.helpers;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.fairphone.psensor.ProximitySensorConfiguration;
+import com.fairphone.psensor.R;
+
+/**
+ * Helper methods to access the shared preferences of the app.
+ */
+public class CalibrationStatusHelper {
+
+    /**
+     * Empty constructor to avoid instantiation.
+     */
+    private CalibrationStatusHelper() {
+    }
+
+    /**
+     * Determine if the current device needs to be calibrated.<br>
+     * <br>
+     * The conditions are as follows:
+     * <ol>
+     * <li>The memory needs to be accessible (R/W).</li>
+     * <li>Either: there must not be an evidence that the device has been calibrated in the shared preferences.</li>
+     * <li>Or either: the persisted offset compensation must be equal to 0.</li>
+     * </ol>
+     *
+     * @param context The context.
+     * @param calibrateNullCompensation Flag to decide if the logic is based on the current compensation offset or on
+     * the shared preferences.
+     * @return <em>true</em> if a calibration should take place, <em>false</em> if the device has been calibrated at
+     * one point or if the memory is not accessible.
+     */
+    public static boolean hasToBeCalibrated(Context context, boolean calibrateNullCompensation) {
+        boolean hasToBeCalibrated;
+
+        if (ProximitySensorConfiguration.canReadFromAndPersistToMemory()) {
+            if (calibrateNullCompensation) {
+                final ProximitySensorConfiguration persistedConfiguration = ProximitySensorConfiguration.readFromMemory();
+                hasToBeCalibrated = (persistedConfiguration != null) && (persistedConfiguration.offsetCompensation == 0);
+            } else {
+                final SharedPreferences sharedPref = context.getSharedPreferences(
+                        context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+                hasToBeCalibrated = !sharedPref.getBoolean(context.getString(R.string.preference_successfully_calibrated), false);
+            }
+        } else {
+            /* Memory is not accessible, so no calibration is required. */
+            hasToBeCalibrated = false;
+        }
+
+        return hasToBeCalibrated;
+    }
+
+    /**
+     * Call to <code>hasToBeCalibrated(context, false)</code>.
+     *
+     * @param context The context.
+     * @return <em>true</em> if a calibration should take place, <em>false</em> if the device has been calibrated at
+     * one point.
+     * @see CalibrationStatusHelper#hasToBeCalibrated(Context, boolean)
+     */
+    public static boolean hasToBeCalibrated(Context context) {
+        return hasToBeCalibrated(context, false);
+    }
+
+    public static boolean isCalibrationPending(Context context) {
+        final SharedPreferences sharedPreferences = context.getSharedPreferences(
+            context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+        return sharedPreferences.getBoolean(context.getString(R.string.preference_pending_calibration), false);
+    }
+
+    public static void setCalibrationCompleted(Context context) {
+        final SharedPreferences sharedPreferences = context.getSharedPreferences(
+            context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+        final SharedPreferences.Editor editor = sharedPreferences.edit();
+
+        editor.putBoolean(context.getString(R.string.preference_pending_calibration), false);
+
+        editor.apply();
+    }
+
+    public static void setCalibrationSuccessful(Context context) {
+        final SharedPreferences sharedPreferences = context.getSharedPreferences(
+            context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+        final SharedPreferences.Editor editor = sharedPreferences.edit();
+
+        editor.putBoolean(context.getString(R.string.preference_successfully_calibrated), true);
+        editor.putBoolean(context.getString(R.string.preference_pending_calibration), true);
+
+        editor.apply();
+    }
+}
diff --git a/app/src/main/java/com/fairphone/psensor/helpers/ProximitySensorHelper.java b/app/src/main/java/com/fairphone/psensor/helpers/ProximitySensorHelper.java
new file mode 100644
index 0000000..aa560a9
--- /dev/null
+++ b/app/src/main/java/com/fairphone/psensor/helpers/ProximitySensorHelper.java
@@ -0,0 +1,161 @@
+package com.fairphone.psensor.helpers;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Helper methods to access the proximity sensor.
+ */
+public class ProximitySensorHelper {
+    private static final String TAG = ProximitySensorHelper.class.getSimpleName();
+    
+    /**
+     * Minimal value to accept as valid from the sensor reading (in sensor units).
+     */
+    public static final int READ_MIN_LIMIT = 0;
+    /**
+     * Maximal value to accept as valid from the sensor reading (in sensor units).
+     */
+    public static final int READ_MAX_LIMIT = 255;
+
+    /**
+     * Command to read the sensor value.
+     */
+    private static final String READ_COMMAND = "/system/bin/senread";
+    /**
+     * Result prefix returned by the reading command.
+     */
+    private static final String READ_COMMAND_RESULT_PREFIX = "[RESULT]";
+
+    /**
+     * Amount of times to perform a sensor reading.
+     */
+    private static final int READ_N_TIMES = 3;
+    /**
+     * Time to wait between two sensor readings (in milliseconds).
+     */
+    private static final int READ_DELAY_MS = 500;
+
+    /**
+     * Empty constructor to avoid instantiation.
+     */
+    private ProximitySensorHelper() {
+    }
+
+    /**
+     * Determine whether the proximity sensor value is readable or not based on the reading tool availability. <br>
+     * <br>
+     * The reading tool is tested to exist and be executable.
+     *
+     * @return <code>true</code> if the proximity sensor value is readable, <code>false</code> if not.
+     */
+    public static boolean canReadProximitySensorValue() {
+        final File tool = new File(READ_COMMAND);
+
+        return tool.exists() && tool.canExecute();
+    }
+
+    /**
+     * Read the proximity sensor value read_times times and return the mean value. <br>
+     * <br>
+     * Wait {@link #READ_DELAY_MS} between each read, even if there is only one read planned.
+     *
+     * @param min_value The lower threshold (inclusive) of accepted range.
+     * @param max_value The upper threshold (inclusive) of accepted range.
+     * @return The mean of all the value read (up to {@link #READ_N_TIMES}) or -1 if no read succeeded.
+     */
+    public static int read(int read_times, int min_value, int max_value) {
+        int result;
+        int summed_result = 0;
+        int nb_result_read = 0;
+        int final_result = -1;
+
+        for (int i = 0; i < read_times; i++) {
+            result = readProximitySensorValue();
+
+            if (min_value <= result && result <= max_value) {
+                summed_result += result;
+                nb_result_read++;
+            } else {
+                Log.d(TAG, "Ignored value out of accepted range (" + result + " not in [" + min_value + "," + max_value + "])");
+            }
+
+            // wait a bit between two sensor reading
+            try {
+                Thread.sleep(READ_DELAY_MS);
+            } catch (Exception e) {
+                Log.wtf(TAG, e);
+            }
+        }
+
+        if (nb_result_read == 0) {
+            // something went wrong with READ_COMMAND, are we allowed to execute it?
+            Log.e(TAG, "Could not read sensor value " + read_times + " " + ((read_times == 1) ? "time" : "times"));
+        } else {
+            if (nb_result_read < read_times) {
+                Log.w(TAG, "Read " + nb_result_read + "/" + read_times + " values");
+            }
+
+            final_result = Math.round(summed_result / nb_result_read);
+        }
+
+        return final_result;
+    }
+
+    /**
+     * Call to read(1, {@link #READ_MIN_LIMIT}, {@link #READ_MAX_LIMIT})
+     *
+     * @return The value read or -1 if read failed.
+     * @see ProximitySensorHelper#read(int, int, int)
+     */
+    public static int read() {
+        return read(1, READ_MIN_LIMIT, READ_MAX_LIMIT);
+    }
+
+    /**
+     * Call to read({@link #READ_N_TIMES}, min_value, max_value)
+     *
+     * @param min_value The lower threshold (inclusive) of accepted range.
+     * @param max_value The upper threshold (inclusive) of accepted range.
+     * @return The mean of all the value read (up to {@link #READ_N_TIMES}) or -1 if no read succeeded.
+     * @see ProximitySensorHelper#read(int, int, int)
+     */
+    public static int read(int min_value, int max_value) {
+        return read(READ_N_TIMES, min_value, max_value);
+    }
+
+    /**
+     * Read the proximity sensor value using an external command ({@link #READ_COMMAND}).
+     *
+     * @return the proximity sensor value (>= {@link #READ_MIN_LIMIT and <= {@link #READ_MAX_LIMIT}}) or
+     * <code>-1</code> if there was an error (parsing the value or using the external command).
+     */
+    private static int readProximitySensorValue() {
+        int value = -1;
+        Process process = null;
+
+        try {
+            process = Runtime.getRuntime().exec(new String[]{READ_COMMAND});
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            final String line = reader.readLine();
+
+            if (line != null && line.startsWith(READ_COMMAND_RESULT_PREFIX)) {
+                value = Integer.parseInt(line.replace(READ_COMMAND_RESULT_PREFIX, "").trim());
+            }
+        } catch (IOException e) {
+            Log.wtf(TAG, "Could not execute command `" + READ_COMMAND + "`", e);
+        } catch (NumberFormatException e) {
+            Log.wtf(TAG, e);
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
+        }
+
+        return value;
+    }
+}
diff --git a/app/src/main/res/drawable/button_blue_background.xml b/app/src/main/res/drawable/button_blue_background.xml
new file mode 100644
index 0000000..5adfbd8
--- /dev/null
+++ b/app/src/main/res/drawable/button_blue_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:drawable="@color/blue_light" android:state_enabled="false"/>
+    <item android:drawable="@color/blue_dark" android:state_pressed="true"/>
+    <item android:drawable="@color/blue"/>
+
+</selector>
diff --git a/app/src/main/res/drawable/button_grey_background.xml b/app/src/main/res/drawable/button_grey_background.xml
new file mode 100644
index 0000000..57c942f
--- /dev/null
+++ b/app/src/main/res/drawable/button_grey_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:drawable="@color/grey_transparent" android:state_enabled="false"/>
+    <item android:drawable="@color/grey" android:state_pressed="true"/>
+    <item android:drawable="@color/grey_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/button_pink_transparent_background.xml b/app/src/main/res/drawable/button_pink_transparent_background.xml
new file mode 100644
index 0000000..0ea5c09
--- /dev/null
+++ b/app/src/main/res/drawable/button_pink_transparent_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:drawable="@color/transparent" android:state_enabled="false"/>
+    <item android:drawable="@color/pink_dark" android:state_pressed="true"/>
+    <item android:drawable="@color/pink_light"/>
+
+</selector>
diff --git a/app/src/main/res/drawable/fairphone_button.xml b/app/src/main/res/drawable/fairphone_button.xml
deleted file mode 100644
index a403d23..0000000
--- a/app/src/main/res/drawable/fairphone_button.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<selector
-    xmlns:android="http://schemas.android.com/apk/res/android">
-
-<item android:state_enabled="true">
-<shape
-        xmlns:android="http://schemas.android.com/apk/res/android">
-
-        <padding
-            android:left="1dp"
-            android:right="1dp"
-            android:top="1dp"
-            android:bottom="1dp"/>
-
-        <solid android:color="@color/theme_primary"/>
-   </shape>
-</item>
-
-    <item android:state_enabled="false">
-        <shape
-            xmlns:android="http://schemas.android.com/apk/res/android">
-
-            <padding
-                android:left="1dp"
-                android:right="1dp"
-                android:top="1dp"
-                android:bottom="1dp"/>
-
-            <solid android:color="@color/colorFP2Gray"/>
-        </shape>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_calibration.xml b/app/src/main/res/layout/activity_calibration.xml
index d9fa826..ebb4f89 100644
--- a/app/src/main/res/layout/activity_calibration.xml
+++ b/app/src/main/res/layout/activity_calibration.xml
@@ -1,32 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:orientation="vertical"
-    android:layout_height="match_parent"
     android:layout_width="match_parent"
-    android:background="@color/colorFP2Background">
+    android:layout_height="match_parent"
+    android:orientation="vertical">
 
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="100dp"
-        android:background="@color/colorFP2HeaderBackground">
-
-        <TextView
-            android:text="@string/app_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerHorizontal="true"
-            android:layout_centerVertical="true"
-            android:fontFamily="sans-serif-light"
-            android:textColor="@color/colorPrimary"
-            android:textSize="30sp"/>
-
-    </RelativeLayout>
+    <include layout="@layout/header_app" />
 
     <ViewFlipper
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
         android:id="@+id/viewFlipper"
-        android:background="@color/colorFP2Background" />
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/app/src/main/res/layout/activity_update_finalizer.xml b/app/src/main/res/layout/activity_update_finalizer.xml
index f11a4c8..c1c43b3 100644
--- a/app/src/main/res/layout/activity_update_finalizer.xml
+++ b/app/src/main/res/layout/activity_update_finalizer.xml
@@ -4,102 +4,55 @@
     android:id="@+id/activity_update_finalizer"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:orientation="vertical"
     tools:context="com.fairphone.psensor.UpdateFinalizerActivity">
 
-    <LinearLayout
+    <include layout="@layout/header_wizard" />
+
+    <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:orientation="vertical">
+        android:background="@color/background"
+        android:gravity="center_horizontal"
+        android:orientation="vertical"
+        android:padding="@dimen/main_padding">
 
-        <RelativeLayout
+        <TextView
+            android:id="@+id/heading"
+            style="@style/TextBold16BlueDark"
             android:layout_width="match_parent"
-            android:layout_height="100dp"
-            android:background="@color/colorFP2HeaderBackground">
+            android:layout_height="wrap_content"
+            android:text="@string/greating"
+            android:textIsSelectable="false" />
 
-            <TextView
-                android:id="@+id/textView4"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:layout_centerVertical="true"
-                android:fontFamily="sans-serif-light"
-                android:textColor="@color/colorPrimary"
-                android:textSize="36sp"
-                android:text="@string/Heading"
-                tools:text="@string/Heading" />
-        </RelativeLayout>
-
-        <RelativeLayout
+        <TextView
+            android:id="@+id/instructions"
+            style="@style/TextRegular14BlueDark"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@color/colorFP2Background">
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/heading"
+            android:layout_marginTop="@dimen/main_small_text_below_heading_margin"
+            android:text="@string/Text" />
 
-            <Button
-                android:id="@+id/button_next"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_alignParentBottom="true"
-                android:layout_centerHorizontal="true"
-                android:layout_marginBottom="34dp"
-                android:layout_marginLeft="20dp"
-                android:layout_marginRight="20dp"
-                android:background="@color/colorPrimary"
-                android:fontFamily="sans-serif-light"
-                android:text="@string/next"
-                android:textColor="@color/colorFP2ButtonText"
-                android:textSize="24sp" />
+        <CheckBox
+            android:id="@+id/checkBoxSuppress"
+            style="@style/TextRegular14BlueDark"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_above="@+id/button_next"
+            android:text="@string/dontshowagain"
+            android:layout_marginBottom="@dimen/main_margin_small"
+            android:enabled="false" />
 
-            <RelativeLayout
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_above="@+id/button_next"
-                android:layout_alignParentEnd="true"
-                android:layout_marginBottom="32dp">
+        <Button
+            android:id="@+id/button_next"
+            style="@style/ButtonWhiteBlue"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:text="@string/next" />
 
-                <TextView
-                    android:id="@+id/textView2"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentStart="true"
-                    android:layout_alignParentTop="true"
-                    android:layout_marginEnd="30dp"
-                    android:layout_marginStart="30dp"
-                    android:layout_marginTop="50dp"
-                    android:text="@string/greating"
-                    android:textAllCaps="false"
-                    android:textColor="@color/colorFP2Text"
-                    android:textIsSelectable="false"
-                    android:textSize="15sp"
-                    android:textStyle="bold"
-                    android:typeface="sans" />
-
-                <TextView
-                    android:id="@+id/maintext"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_alignStart="@+id/textView2"
-                    android:layout_below="@+id/textView2"
-                    android:layout_marginEnd="30dp"
-                    android:layout_marginTop="20dp"
-                    android:fontFamily="sans-serif-light"
-                    android:lineSpacingExtra="3sp"
-                    android:text="@string/Text"
-                    android:textColor="@color/colorFP2Text"
-                    android:textSize="15sp" />
-
-                <CheckBox
-                    android:text="@string/dontshowagain"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/checkBoxSuppress"
-                    android:fontFamily="sans-serif-light"
-                    android:textColor="@color/colorFP2Text"
-                    android:layout_alignParentBottom="true"
-                    android:layout_alignEnd="@+id/maintext" />
-            </RelativeLayout>
-
-        </RelativeLayout>
-
-    </LinearLayout>
+    </RelativeLayout>
 
 </LinearLayout>
diff --git a/app/src/main/res/layout/header_app.xml b/app/src/main/res/layout/header_app.xml
new file mode 100644
index 0000000..ecfc79b
--- /dev/null
+++ b/app/src/main/res/layout/header_app.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/background_header">
+
+    <TextView
+        style="@style/TitleLightPrimary"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/header_big_height"
+        android:paddingStart="@dimen/header_padding"
+        android:paddingEnd="@dimen/header_padding"
+        android:ellipsize="end"
+        android:gravity="center_vertical|center_horizontal"
+        android:lines="2"
+        android:text="@string/app_name" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/header_wizard.xml b/app/src/main/res/layout/header_wizard.xml
new file mode 100644
index 0000000..7f2853e
--- /dev/null
+++ b/app/src/main/res/layout/header_wizard.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/background_header">
+
+    <TextView
+        style="@style/TitleLightPrimary"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/header_big_height"
+        android:ellipsize="end"
+        android:gravity="center_vertical|center_horizontal"
+        android:lines="2"
+        android:text="@string/Heading" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/layout/view_calibration_step.xml b/app/src/main/res/layout/view_calibration_step.xml
index 5ad76a3..7657336 100644
--- a/app/src/main/res/layout/view_calibration_step.xml
+++ b/app/src/main/res/layout/view_calibration_step.xml
@@ -1,74 +1,62 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:orientation="vertical" android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/colorFP2Background">
-
-    <TextView
-        android:id="@+id/maintext"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="20dp"
-        android:fontFamily="sans-serif-light"
-        android:lineSpacingExtra="3sp"
-        android:text="@string/msg_block"
-        android:textColor="@color/colorFP2Text"
-        android:textSize="15sp"
-        android:layout_marginEnd="30dp"
-
-        android:layout_below="@+id/textview_heading"
-        android:layout_alignStart="@+id/textview_heading"
-        android:layout_alignParentEnd="true" />
-
-    <TextView
-        android:id="@+id/textview_heading"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/step_1"
-        android:textAllCaps="false"
-        android:textColor="@color/colorFP2Text"
-        android:textIsSelectable="false"
-        android:textSize="15sp"
-        android:textStyle="bold"
-        android:typeface="sans"
-        android:layout_below="@+id/instructionImage"
-        android:layout_marginEnd="30dp"
-        android:layout_marginStart="30dp"
-        />
-
-    <Button
-        android:id="@+id/button"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="20dp"
-        android:layout_marginRight="20dp"
-        android:background="@drawable/fairphone_button"
-        android:fontFamily="sans-serif-light"
-        android:text="@string/next"
-        android:textColor="@color/colorFP2ButtonText"
-        android:textSize="24sp"
-        android:layout_alignParentBottom="true"
-        android:layout_centerHorizontal="true"
-        android:layout_marginBottom="30dp" />
+    android:layout_width="match_parent" android:layout_height="match_parent"
+    android:background="@color/background"
+    android:paddingLeft="@dimen/main_padding"
+    android:paddingRight="@dimen/main_padding">
 
     <ImageView
-        android:id="@+id/instructionImage"
-        android:src="@drawable/top_image_psensor"
+        android:id="@+id/instruction_image"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_marginLeft="15dp"
-        android:layout_marginRight="15dp"
         android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true" />
+        android:layout_centerHorizontal="true"
+        android:src="@drawable/top_image_psensor" />
+
+    <TextView
+        android:id="@+id/current_step"
+        style="@style/TextBold16BlueDark"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/instruction_image"
+        android:text="@string/step_1" />
+
+    <TextView
+        android:id="@+id/instructions"
+        style="@style/TextRegular14BlueDark"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/current_step"
+        android:layout_marginTop="@dimen/main_small_text_below_heading_margin"
+        android:text="@string/msg_block" />
+
+    <TextView
+        android:id="@+id/error_notice"
+        style="@style/TextRegular14PinkDark"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/instructions"
+        android:layout_marginTop="@dimen/main_small_text_below_heading_margin"
+        android:text="@string/msg_fail_block" />
 
     <ProgressBar
+        android:id="@+id/progress_bar"
         style="?android:attr/progressBarStyleLarge"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:id="@+id/progressBar"
-        android:layout_marginBottom="57dp"
-        android:visibility="invisible"
         android:layout_above="@+id/button"
-        android:layout_centerHorizontal="true" />
-</RelativeLayout>
\ No newline at end of file
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="@dimen/main_margin"
+        android:indeterminate="true" />
+
+    <Button
+        android:id="@+id/button"
+        style="@style/ButtonWhiteBlue"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="@dimen/main_margin"
+        android:text="@string/next" />
+
+</RelativeLayout>
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_settings.png b/app/src/main/res/mipmap-hdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..252e6b4
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_settings.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_settings.png b/app/src/main/res/mipmap-mdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..d2ecee9
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_settings.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_settings.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..fa8c813
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_settings.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_settings.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..3fa69e9
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_settings.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_settings.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..72946b7
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_settings.png
Binary files differ
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4e89569..4d64b0c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version='1.0' encoding='UTF-8'?>
 <resources>
     <string name="app_name">Näherungssensorwerkzeug</string>
     <string name="Exit">Schließen</string>
@@ -14,27 +14,27 @@
     <string name="cancel">Abbrechen</string>
     <string name="dontshowagain">Diese Meldung künftig nicht mehr zeigen.</string>
     <string name="greating">Näherungssensorkalibrierung</string>
-    <string name="msg_block">Decke den Sensorbereich komplett mit deinem Finger ab (der Sensorbereich ist im obigen Bild hervorgehoben) und drücke gleichzeitig auf OK.</string>
-    <string name="msg_cal">Kalibrieren...</string>
+    <string name="msg_block">Decke den Sensorbereich komplett mit deinem Finger ab (der Sensorbereich ist im obigen Bild hervorgehoben) und drücke gleichzeitig auf Weiter.</string>
+    <string name="msg_cal">Kalibrieren…</string>
     <string name="msg_calibration_success"><![CDATA[Kalibrierung erfolgreich. Drücke auf "OK & Neustart" um die Kalibrierung abzuschließen und dein Fairphone neu zu starten.]]></string>
     <string name="reboot"><![CDATA[OK & Neustart]]></string>
     <string name="msg_fail">Fehler</string>
     <string name="msg_fail_block">Hier ging was schief. Bitte probier es noch einmal. Achte darauf den Sensorbereich komplett abzudecken.</string>
     <string name="msg_fail_cal">Kalibrierung fehlgeschlagen. Bitte überprüfe das Gerät und versuch es noch einmal.</string>
     <string name="msg_fail_unlock">Hier ging was schief. Bitte probier es noch einmal. Stelle sicher, dass der Sensorbereich frei und der Bildschirm sauber sind.</string>
-    <string name="msg_fail_write_sns">Ein unerwartetes Problem mit Rechten ist aufgetreten...</string>
-    <string name="msg_reading">Lesen...</string>
+    <string name="msg_fail_write_sns">Ein unerwartetes Problem mit Rechten ist aufgetreten…</string>
+    <string name="msg_reading">Lesen…</string>
     <string name="msg_step_success">Fertig</string>
-    <string name="msg_unblock">Entferne deinen Finger vom Sensorbereich und drücke danach auf OK.</string>
+    <string name="msg_unblock">Entferne deinen Finger vom Sensorbereich und drücke danach auf Weiter.</string>
     <string name="name_exit">Schließen</string>
     <string name="name_read">OK</string>
     <string name="next">Weiter</string>
     <string name="not_triggered">Nicht aktiviert</string>
     <string name="sensor_value">Sensorwert:</string>
     <string name="state">Status:</string>
-    <string name="step_1">Schritt 1</string>
-    <string name="step_2">Schritt 2</string>
-    <string name="step_3">Schritt 3</string>
+    <string name="step_1">Schritt 1/3</string>
+    <string name="step_2">Schritt 2/3</string>
+    <string name="step_3">Schritt 3/3</string>
     <string name="unblock_value">Wert wenn frei:</string>
     <string name="NotificationTitle">Näherungssensorkalibrierung</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 0134ac7..9cada09 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version='1.0' encoding='UTF-8'?>
 <resources>
     <string name="app_name">Herramienta Sensor de proximidad</string>
     <string name="Exit">Salir</string>
@@ -6,25 +6,25 @@
     <string name="Text"><![CDATA[ <p>Fairphone OS te da la opción de calibrar el sensor de proximidad - esto mejorará el funcionamiento del sensor mientras haces una llamada (la pantalla se encenderá cuando terminas la llamada).</p> <p>Te recomendamos calibrar el sensor de proximidad ahora para tener el mejor funcionamiento - es fácil y rápido!</p>]]></string>
     <string name="activity_calibration">Calibración</string>
     <string name="activity_diagnostics">Diagnóstico</string>
-    <string name="msg_block">Cubre el area del sensor de proximidad con el dedo (el area a cubrir esta indicada en la imagen superior), luego pulsa OK.</string>
+    <string name="msg_block">Cubre el área del sensor de proximidad con el dedo (el área a cubrir esta indicada en la imagen superior), luego pulsa Próximo.</string>
     <string name="msg_cal">Calibrando…</string>
     <string name="msg_calibration_success"><![CDATA[Calibración lista! Pulsa OK para finalizar el proceso y reiniciar tu Fairphone.]]></string>
     <string name="msg_step_success">Listo</string>
     <string name="msg_fail">Fallido</string>
-    <string name="msg_fail_block">Ha habido un error; por favor inténtalo de nuevo. Asegúrate de cubrir el area del sensor completamente. </string>
+    <string name="msg_fail_block">Ha habido un error; por favor inténtalo de nuevo. Asegúrate de cubrir el área del sensor completamente. </string>
     <string name="msg_fail_cal">Calibración fallida. Comprueba el teléfono e inténtalo de nuevo.</string>
     <string name="msg_fail_unlock">Algo ha fallado. Por favor inténtalo de nuevo. Asegúrate de que el sensor de proximidad no está cubierto y la pantalla esta limpia.</string>
-    <string name="msg_fail_write_sns">Problema inesperado de permisos...</string>
-    <string name="msg_reading">En proceso...</string>
-    <string name="msg_unblock">Retira el dedo del sensor y pulsa OK.</string>
+    <string name="msg_fail_write_sns">Problema inesperado de permisos…</string>
+    <string name="msg_reading">En proceso…</string>
+    <string name="msg_unblock">Retira el dedo del sensor y pulsa Próximo.</string>
     <string name="name_exit">Salir</string>
     <string name="name_read">OK</string>
     <string name="next">Próximo</string>
     <string name="dontshowagain">No mostrar esta pantalla de nuevo.</string>
     <string name="unblock_value">Valor sensor libre:</string>
-    <string name="step_1">Paso 1</string>
-    <string name="step_2">Paso 2</string>
-    <string name="step_3">Paso 3</string>
+    <string name="step_1">Paso 1/3</string>
+    <string name="step_2">Paso 2/3</string>
+    <string name="step_3">Paso 3/3</string>
     <string name="state">Estado:</string>
     <string name="sensor_value">Valor sensor:</string>
     <string name="reboot"><![CDATA[Aceptar y Reiniciar]]></string>
@@ -36,5 +36,5 @@
     <string name="ask_really_dont_show_title">¿Estás seguro?</string>
     <string name="ask_really_dont_show_text"><![CDATA[Calibrar el sensor de proximidad es fácil y mejorará tu experiencia cuando llames. <it>Puedes calibrar el sensor de proximidad en otro momento en Ajustes → Mantenimiento  → Herramienta Sensor de Proximidad</it>.]]></string>
     <string name="block_value">Valor de bloqueo:</string>
-    <string name="Heading">Una cosa más...</string>
+    <string name="Heading">Una cosa más…</string>
 </resources>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 326d011..7a0a221 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version='1.0' encoding='UTF-8'?>
 <resources>
     <string name="app_name">Capteur de proximité</string>
     <string name="Exit">Quitter</string>
@@ -8,33 +8,37 @@
     <string name="Text"><![CDATA[ <p>Vous avez désormais la possibilité de calibrer le capteur de proximité depuis Fairphone OS - ceci améliore le comportement du capteur lors d’un appel (l’écran s’éteint/s’allume).</p> <p>Nous recommandons de calibrer le capteur de proximité maintenant pour une meilleure expérience - cela ne vous prendra qu’une minute!</p>]]></string>
     <string name="activity_calibration">Calibration</string>
     <string name="activity_diagnostics">Diagnostique</string>
-    <string name="ask_really_dont_show_title">Êtes-vous sûr?</string>
+    <string name="ask_really_dont_show_title">Êtes-vous sûr ?</string>
     <string name="block_value">Valeur à l\'état «bloqué» :</string>
     <string name="dontshowagain">Ne plus afficher cet écran.</string>
     <string name="greating">Calibration du capteur de proximité</string>
-    <string name="msg_block">Recouvrez la zone du capteur entièrement avec votre doigt (la zone à couvrir est illustrée par l\'image ci-dessus), puis utilisez le bouton OK.</string>
+    <string name="msg_block">Recouvrez la zone du capteur entièrement avec votre doigt (la zone à couvrir est illustrée par l\'image ci-dessus), puis utilisez le bouton Suivant.</string>
     <string name="msg_cal">Calibration en cours…</string>
     <string name="msg_calibration_success"><![CDATA[Calibration terminée ! Utilisez le bouton OK & Redémarrer pour finaliser la procédure et redémarrer votre Fairphone.]]></string>
     <string name="msg_fail">Échec</string>
     <string name="msg_fail_block">Une erreur est survenue, merci de recommencer. Assurez vous de couvrir complètement la zone du capteur.</string>
     <string name="msg_fail_cal">Échec de la calibration. Veuillez vérifier votre téléphone puis recommencer.</string>
     <string name="msg_fail_unlock">Une erreur est survenue, merci de recommencer. Assurez vous d\'emlever votre doigt de la zone du capteur et de vérifier que votre écran est propre.</string>
-    <string name="msg_fail_write_sns">Erreur exceptionnelle de permission…</string>
-    <string name="msg_reading">Lecture en cours…</string>
+    <string name="msg_fail_write_sns">Une erreur est survenue, assurez vous que votre téléphone soit à jour avant de réessayer.</string>
+    <string name="msg_reading">Lecture du capteur de proximité en cours…</string>
     <string name="msg_step_success">Succès</string>
-    <string name="msg_unblock">Enlevez votre doigt de la zone du capteur puis utilisez le bouton OK.</string>
+    <string name="msg_unblock">Enlevez votre doigt de la zone du capteur puis utilisez le bouton Suivant.</string>
     <string name="name_exit">Quitter</string>
     <string name="name_read">OK</string>
     <string name="next">Suivant</string>
     <string name="reboot"><![CDATA[OK & Redémarrer]]></string>
     <string name="sensor_value">Valeur du capteur:</string>
     <string name="state">État:</string>
-    <string name="step_1">Étape 1</string>
-    <string name="step_2">Étape 2</string>
-    <string name="step_3">Étape 3</string>
+    <string name="step_1">Étape 1/3</string>
+    <string name="step_2">Étape 2/3</string>
+    <string name="step_3">Étape 3/3</string>
     <string name="unblock_value">Valeur à l\'état «libre»:</string>
     <string name="cancel">Annuler</string>
     <string name="NotificationTitle">Calibration du capteur de proximité</string>
     <string name="ask_really_dont_show_text"><![CDATA[La calibration du capteur de proximité est simple à réaliser and améliorera votre confort d\'appel. <it>Vous pouvez (re-)calibrer le capteur de proximité à n\'importe quel moment via Paramètres → Maintenance → Capteur de proximité</it>.]]></string>
     <string name="not_triggered">Non activé</string>
-</resources>
+<string name="incompatible_device">Équipement incompatible</string>
+    <string name="device_cannot_run_calibration_tool">L\'outil de calibration ne peut pas être exécuté sur votre Fairphone 2. Assurez vous que le système de votre téléphone soit à jour.</string>
+
+    <string name="go_to_updater">Vérifier les mises à jour</string>
+    </resources>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index da12f74..6e35111 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version='1.0' encoding='UTF-8'?>
 <resources>
     <string name="app_name">Afstandssensor gereedschap</string>
     <string name="Exit">Sluit</string>
@@ -12,7 +12,7 @@
     <string name="cancel">Annuleer</string>
     <string name="dontshowagain">Dit scherm niet meer tonen.</string>
     <string name="greating">Afstandssensor kalibratie</string>
-    <string name="msg_block">Bedek de sensor volledig met je vinger (het sensor gebied is weergegeven in de afbeelding hierboven) en druk vervolgens op de OK knop.</string>
+    <string name="msg_block">Bedek de sensor volledig met je vinger (het sensor gebied is weergegeven in de afbeelding hierboven) en druk vervolgens op de Volgende knop.</string>
     <string name="msg_cal">Kalibreren…</string>
     <string name="msg_calibration_success"><![CDATA[Kalibratie succesvol! Druk op de OK & Herstart knop om de kalibratie af te ronden en je Fairphone te herstarten.]]></string>
     <string name="Text"><![CDATA[ <p>Fairphone OS geeft je nu de mogelijkheid om de afstandssensor te kalibreren - dit verbetert het gedrag van de sensor tijdens een telefoongesprek (scherm uit/aan).</p> <p>We raden je aan de afstandssensor te kaliberen - dit is eenvoudig en zo gepiept!</p>]]></string>
@@ -23,18 +23,18 @@
     <string name="msg_fail_write_sns">Er is onverwacht een permissie probleem opgetreden…</string>
     <string name="msg_reading">Controleren…</string>
     <string name="msg_step_success">Succesvol</string>
-    <string name="msg_unblock">Verwijder je vinger van de sensor en druk op de OK knop.</string>
+    <string name="msg_unblock">Verwijder je vinger van de sensor en druk op de Volgende knop.</string>
     <string name="name_exit">Sluit</string>
     <string name="name_read">OK</string>
     <string name="next">Volgende</string>
     <string name="not_triggered">Niet geactiveerd </string>
     <string name="reboot"><![CDATA[OK & Herstart]]></string>
     <string name="unblock_value">Ontgrendel waarde:</string>
-    <string name="step_3">Stap 3</string>
-    <string name="step_2">Stap 2</string>
-    <string name="step_1">Stap 1</string>
+    <string name="step_3">Stap 3/3</string>
+    <string name="step_2">Stap 2/3</string>
+    <string name="step_1">Stap 1/3</string>
     <string name="state">Status</string>
     <string name="sensor_value">Sensor waarde:</string>
     <string name="ask_really_dont_show_text"><![CDATA[Het kalibreren van de afstandssensor is eenvoudig en verbetert het scherm gedrag tijdens een telefoongesprek (scherm uit/aan). <it>Je kunt de afstandssensor op een later tijdstip kalibreren onder Instellingen → Onderhoud → Afstandssensor gereedschap</it>.]]></string>
     <string name="NotificationTitle">Afstandssensor kalibratie</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 3f3dff6..06f6015 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,16 +7,30 @@
     <color name="theme_primary_dark">#000000</color>
     <color name="theme_accent">#f286b6</color>
 
-    <color name="switchbar_background_color">#2aa8e0</color>
-    <color name="colorPrimary">#2aa8e0</color>
-    <color name="colorPrimaryDark">#000000</color>
-    <color name="colorAccent">#f286b6</color>
+    <!-- BACKGROUND -->
+    <color name="background_header">#F7F7F7</color>
+    <color name="background">#F0F0F0</color>
 
-    <color name="colorFP2HeaderBackground">#f7f7f7</color>
-    <color name="colorFP2Background">#f0f0f0</color>
-    <color name="colorFP2Text">#123c59</color>
-    <color name="colorFP2ButtonText">#f7f7f7</color>
-    <color name="colorFP2Gray">#a7a7a7</color>
+    <!-- MAIN COLORS -->
+    <color name="transparent">#00000000</color>
+    <color name="white">#FFFFFF</color>
+    <color name="white_transparent">#55FFFFFF</color>
+    <color name="grey">#5B5B5B</color>
+    <color name="grey_light">#999999</color>
+    <color name="grey_dark">#333333</color>
+    <color name="grey_transparent">#66999999</color>
+    <color name="blue">#2AA9E0</color>
+    <color name="blue_dark">#123C59</color>
+    <color name="blue_light">#AADCF2</color>
+    <color name="blue_light_transparent">#DBEAF0</color>
+    <color name="green">#6BC1A3</color>
+    <color name="green_dark">#17717A</color>
+    <color name="green_light">#A0D7C4</color>
+    <color name="green_light_transparent">#D8E8E2</color>
+    <color name="pink">#C1454A</color>
+    <color name="pink_dark">#823A3D</color>
+    <color name="pink_light">#F286B6</color>
+    <color name="pink_light_transparent">#F0DBE4</color>
+    <color name="red">#C3474C</color>
 
-
-</resources>
\ No newline at end of file
+</resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index c59b7cf..2c49b92 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,8 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <!-- ActionBar contentInsetStart -->
-    <dimen name="actionbar_contentInsetStart">16dp</dimen>
 
-    <!-- ActionBar height -->
-    <dimen name="actionbar_size">56dip</dimen>
-</resources>
\ No newline at end of file
+    <!-- HEADER -->
+    <dimen name="header_big_height">100dp</dimen>
+    <dimen name="header_small_height">56dp</dimen>
+    <dimen name="header_big_text_size">34sp</dimen>
+    <dimen name="header_small_text_size">20sp</dimen>
+    <dimen name="header_padding">8sp</dimen>
+
+    <!-- MAIN -->
+    <dimen name="main_margin">32dp</dimen>
+    <dimen name="main_margin_small">20dp</dimen>
+    <dimen name="main_padding">32dp</dimen>
+    <dimen name="main_padding_small">20dp</dimen>
+    <dimen name="main_small_text_margin_top">43dp</dimen>
+    <dimen name="main_small_text_below_heading_margin">20dp</dimen>
+    <dimen name="main_button_margin_top">8dp</dimen>
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a3dd27a..089d5e9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -6,10 +6,13 @@
     <string name="activity_diagnostics">Diagnostics</string>
     <string name="activity_calibration">Calibration</string>
 
-    <string name="msg_block">Cover the sensor area completely with your finger (the sensor area is shown in the image above), then press the OK button.</string>
-    <string name="msg_reading">Checking...</string>
-    <string name="msg_unblock">Remove your finger from the sensor area and press the OK button.</string>
-    <string name="msg_cal">Calibrating...</string>
+    <string name="incompatible_device">Incompatible device</string>
+    <string name="device_cannot_run_calibration_tool">The calibration tool cannot be run on your Fairphone 2. Make sure your device is running the latest software update.</string>
+
+    <string name="msg_block">Cover the sensor area completely with your finger (the sensor area is shown in the image above), then press the Next button.</string>
+    <string name="msg_reading">Retrieving information from the proximity sensor…</string>
+    <string name="msg_unblock">Remove your finger from the sensor area and press the Next button.</string>
+    <string name="msg_cal">Calibrating…</string>
     <string name="msg_calibration_success"><![CDATA[Calibration successful! Press the OK & Reboot button to finalize the calibration process and to reboot your Fairphone.]]></string>
     <string name="msg_step_success">Successful</string>
     <string name="msg_fail">Unsuccessful</string>
@@ -18,7 +21,7 @@
     <string name="msg_fail_block">Something went wrong, please try again. Make sure you cover the sensor area completely.</string>
     <string name="msg_fail_cal">Calibration unsuccessful, please check the device and try again.</string>
     <string name="msg_fail_unlock">Something went wrong, please try again. Make sure to remove your finger from the sensor area and that the screen is clean.</string>
-    <string name="msg_fail_write_sns">Encountering unexpected permission issue…</string>
+    <string name="msg_fail_write_sns">Something went wrong, please check that your device is up-to-date and try again.</string>
 
     <string name='not_triggered'>Not triggered</string>
     <string name='state'>State: </string>
@@ -26,11 +29,11 @@
     <string name='block_value'>Block value: </string>
     <string name='unblock_value'>Unlock value: </string>
 
-    <string name="step_1">Step 1</string>
-    <string name="step_2">Step 2</string>
-    <string name="step_3">Step 3</string>
+    <string name="step_1">Step 1/3</string>
+    <string name="step_2">Step 2/3</string>
+    <string name="step_3">Step 3/3</string>
 
-    <string name="Heading">One more thing...</string>
+    <string name="Heading">One more thing…</string>
     <string name="greating">Proximity sensor calibration</string>
     <string name="Text"><![CDATA[ <p>Fairphone OS now gives you the option to calibrate the proximity sensor - this improves the sensor behavior while making a call (screen going off/on).</p> <p>We recommend to calibrate the proximity sensor now to get the best experience - it\'s easy and done in a second!</p>]]></string>
     <string name="next">Next</string>
@@ -44,9 +47,20 @@
     <string name="Exit">Exit</string>
     <string name="OK">OK</string>
     <string name="cancel">Cancel</string>
+    <string name="go_to_updater">Check for updates</string>
     <string name="ask_really_dont_show_title">Are you sure?</string>
     <string name="ask_really_dont_show_text"><![CDATA[Calibrating the proximity sensor is easy and will improve the call experience. <it>You can calibrate the proximity sensor at a later time in Settings → Maintenance → Proximity Sensor Tool</it>.]]></string>
     <string name="preference_successfully_calibrated" translatable="false">preference_successfully_calibrated</string>
     <string name="NotificationTitle">Finalizing Update</string>
 
+    <!-- FRAGMENT TAGS -->
+    <string name="fragment_tag_incompatible_device_dialog" translatable="false">incompatible_device</string>
+
+    <!-- SHARED PREFERENCES -->
+    <string name="preference_pending_calibration" translatable="false">preference_pending_calibration</string>
+
+    <!-- EXTERNAL PACKAGES AND ACTIVITIES -->
+    <string name="package_fairphone_updater" translatable="false">com.fairphone.updater</string>
+    <string name="activity_fairphone_updater_check_for_updates" translatable="false">com.fairphone.updater.FairphoneUpdater</string>
+
 </resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index c531199..737a2c3 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,9 +1,204 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
+    <style name="AppTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
         <!-- Customize your theme here. -->
-        <item name="colorPrimary">@color/colorPrimary</item>
-        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
-        <item name="colorAccent">@color/colorAccent</item>
+        <item name="android:colorPrimary">@color/theme_primary</item>
+        <item name="android:colorPrimaryDark">@color/theme_primary_dark</item>
+        <item name="android:colorAccent">@color/theme_accent</item>
+        <item name="android:statusBarColor">@color/theme_primary_dark</item>
     </style>
-</resources>
\ No newline at end of file
+
+    <!-- BUTTONS -->
+    <style name="Button" parent="@android:style/Widget.Button">
+        <item name="android:background">@drawable/button_grey_background</item>
+        <item name="android:textColor">@color/white</item>
+        <item name="android:textSize">24sp</item>
+        <item name="android:paddingTop">15dp</item>
+        <item name="android:paddingBottom">15dp</item>
+        <item name="android:paddingRight">15dp</item>
+        <item name="android:paddingLeft">15dp</item>
+        <item name="android:gravity">center</item>
+        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:textAllCaps">true</item>
+    </style>
+
+    <style name="ButtonWhiteBlue" parent="@style/Button">
+        <item name="android:background">@drawable/button_blue_background</item>
+        <item name="android:textColor">@color/white</item>
+    </style>
+
+    <style name="ButtonWhitePink" parent="@style/Button">
+        <item name="android:background">@drawable/button_pink_transparent_background</item>
+        <item name="android:textColor">@color/white</item>
+    </style>
+
+    <!-- TEXT REGULAR -->
+    <style name="TextRegular">
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
+    <style name="TextRegular14" parent="@style/TextRegular">
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="TextRegular16" parent="@style/TextRegular">
+        <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="TextRegular14BlueLight" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/blue_light</item>
+    </style>
+
+    <style name="TextRegular14BlueDark" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/blue_dark</item>
+    </style>
+
+    <style name="TextRegular14GreenLight" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/green_light</item>
+    </style>
+
+    <style name="TextRegular14GreenDark" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/green_dark</item>
+    </style>
+
+    <style name="TextRegular14GreyLight" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/grey_light</item>
+    </style>
+
+    <style name="TextRegular14GreyDark" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/grey_dark</item>
+    </style>
+
+    <style name="TextRegular14PinkLight" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/pink_light</item>
+    </style>
+
+    <style name="TextRegular14PinkDark" parent="@style/TextRegular14">
+        <item name="android:textColor">@color/pink_dark</item>
+    </style>
+
+    <style name="TextRegular16BlueLight" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/blue_light</item>
+    </style>
+
+    <style name="TextRegular16BlueDark" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/blue_dark</item>
+    </style>
+
+    <style name="TextRegular16GreenLight" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/green_light</item>
+    </style>
+
+    <style name="TextRegular16GreenDark" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/green_dark</item>
+    </style>
+
+    <style name="TextRegular16GreyLight" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/grey_light</item>
+    </style>
+
+    <style name="TextRegular16GreyDark" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/grey_dark</item>
+    </style>
+
+    <style name="TextRegular16PinkLight" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/pink_light</item>
+    </style>
+
+    <style name="TextRegular16PinkDark" parent="@style/TextRegular16">
+        <item name="android:textColor">@color/pink_dark</item>
+    </style>
+
+    <!-- TEXT LIGHT -->
+    <style name="TextLight">
+        <item name="android:fontFamily">sans-serif-light</item>
+    </style>
+
+    <!-- TEXT BOLD -->
+    <style name="TextBold">
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextBold14" parent="@style/TextBold">
+        <item name="android:textSize">14sp</item>
+    </style>
+
+    <style name="TextBold16" parent="@style/TextBold">
+        <item name="android:textSize">16sp</item>
+    </style>
+
+    <style name="TextBold14BlueLight" parent="@style/TextBold14">
+        <item name="android:textColor">@color/blue_light</item>
+    </style>
+
+    <style name="TextBold14BlueDark" parent="@style/TextBold14">
+        <item name="android:textColor">@color/blue_dark</item>
+    </style>
+
+    <style name="TextBold14GreenLight" parent="@style/TextBold14">
+        <item name="android:textColor">@color/green_light</item>
+    </style>
+
+    <style name="TextBold14GreenDark" parent="@style/TextBold14">
+        <item name="android:textColor">@color/green_dark</item>
+    </style>
+
+    <style name="TextBold14GreyLight" parent="@style/TextBold14">
+        <item name="android:textColor">@color/grey_light</item>
+    </style>
+
+    <style name="TextBold14GreyDark" parent="@style/TextBold14">
+        <item name="android:textColor">@color/grey_dark</item>
+    </style>
+
+    <style name="TextBold14PinkLight" parent="@style/TextBold14">
+        <item name="android:textColor">@color/pink_light</item>
+    </style>
+
+    <style name="TextBold14PinkDark" parent="@style/TextBold14">
+        <item name="android:textColor">@color/pink_dark</item>
+    </style>
+
+    <style name="TextBold16BlueLight" parent="@style/TextBold16">
+        <item name="android:textColor">@color/blue_light</item>
+    </style>
+
+    <style name="TextBold16BlueDark" parent="@style/TextBold16">
+        <item name="android:textColor">@color/blue_dark</item>
+    </style>
+
+    <style name="TextBold16GreenLight" parent="@style/TextBold16">
+        <item name="android:textColor">@color/green_light</item>
+    </style>
+
+    <style name="TextBold16GreenDark" parent="@style/TextBold16">
+        <item name="android:textColor">@color/green_dark</item>
+    </style>
+
+    <style name="TextBold16GreyLight" parent="@style/TextBold16">
+        <item name="android:textColor">@color/grey_light</item>
+    </style>
+
+    <style name="TextBold16GreyDark" parent="@style/TextBold16">
+        <item name="android:textColor">@color/grey_dark</item>
+    </style>
+
+    <style name="TextBold16PinkLight" parent="@style/TextBold16">
+        <item name="android:textColor">@color/pink_light</item>
+    </style>
+
+    <style name="TextBold16PinkDark" parent="@style/TextBold16">
+        <item name="android:textColor">@color/pink_dark</item>
+    </style>
+
+    <!-- TITLE (HEADER) -->
+    <style name="TitleLight" parent="TextLight">
+        <item name="android:textSize">@dimen/header_big_text_size</item>
+    </style>
+
+    <style name="TitleLightPrimary" parent="TitleLight">
+        <item name="android:textColor">@color/theme_primary</item>
+    </style>
+
+</resources>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 0c7fed7..96fc73c 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -18,10 +18,6 @@
         <!--<item name="wifi_signal">@drawable/wifi_signal_teal</item>-->
         <!--<item name="side_margin">@dimen/settings_side_margin</item>-->
 
-        <!-- Redefine the ActionBar style for contentInsetStart -->
-        <item name="android:actionBarStyle">@style/Theme.ActionBar</item>
-        <item name="@*android:actionBarSize">@dimen/actionbar_size</item>
-
         <!--<item name="switchBarTheme">@style/Theme.SwitchBar.Settings</item>-->
 
         <item name="preferenceBackgroundColor">@drawable/preference_background</item>
@@ -38,11 +34,6 @@
         <!--<item name="@android:timePickerStyle">@style/Widget.TimePicker</item>-->
     </style>
 
-
-    <style name="Theme.ActionBar" parent="@android:style/Widget.Material.Light.ActionBar.Solid">
-        <item name="android:contentInsetStart">@dimen/actionbar_contentInsetStart</item>
-    </style>
-
     <style name="Theme.DialogWhenLarge" parent="@android:style/Theme.Material.Light.DialogWhenLarge">
         <!-- Explicitly override the background color. -->
         <item name="android:colorBackground">@android:color/white</item>
@@ -54,9 +45,6 @@
         <!-- Used by controls, e.g. CheckBox, ProgressBar, etc. -->
         <item name="android:colorAccent">@color/theme_accent</item>
 
-        <!-- Redefine the ActionBar style for contentInsetStart -->
-        <item name="android:actionBarStyle">@style/Theme.ActionBar</item>
-
         <item name="preferenceBackgroundColor">@drawable/preference_background</item>
     </style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/build.gradle b/build.gradle
index a4012cf..aff4f41 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.2.0-alpha4'
+        classpath 'com.android.tools.build:gradle:2.1.2'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files