shamu: vibrator tuning settings

 - values taken from CyanogenMod

Change-Id: Ia5c7d8b563a9d34c1f30780222cdd0d0b616d5fe
diff --git a/BoardConfigEV.mk b/BoardConfigEV.mk
index 0da0d52..9155b39 100644
--- a/BoardConfigEV.mk
+++ b/BoardConfigEV.mk
@@ -1,3 +1,6 @@
+#Device Settings
+BOARD_DEVICE_SETTINGS := device/moto/shamu/DeviceSettings
+
 # Disable Modules
 TARGET_KERNEL_NO_MODULES := true
 
diff --git a/DeviceSettings/res/layout/preference_dialog_vibrator_tuning.xml b/DeviceSettings/res/layout/preference_dialog_vibrator_tuning.xml
new file mode 100644
index 0000000..baa255f
--- /dev/null
+++ b/DeviceSettings/res/layout/preference_dialog_vibrator_tuning.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Evervolv Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center_horizontal"
+            android:paddingBottom="20dip">
+
+        <TextView android:id="@+id/vibrator_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/vibrator_tuning_title"
+                android:paddingTop="10dip"
+                android:paddingLeft="20dip"
+                android:paddingRight="20dip" />
+        <TextView android:id="@+id/vibrator_value"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:paddingTop="10dip"
+                android:paddingLeft="20dip"
+                android:paddingRight="20dip" />
+        <SeekBar android:id="@+id/vibrator_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/vibrator_text"
+                android:paddingTop="2dip"
+                android:paddingLeft="20dip"
+                android:paddingRight="20dip" />
+
+        <TextView
+                android:id="@+id/textWarn"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/vibrator_seekbar"
+                android:paddingTop="8dip"
+                android:gravity="center"
+                android:text="@string/vibrator_warning"
+                android:textSize="15dp" />
+
+    </RelativeLayout>
+</ScrollView>
diff --git a/DeviceSettings/res/values/strings.xml b/DeviceSettings/res/values/strings.xml
new file mode 100644
index 0000000..038ced0
--- /dev/null
+++ b/DeviceSettings/res/values/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2015 The Evervolv Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="vibrator_title">Vibrator</string>
+    <string name="vibrator_tuning_title">Vibration strength</string>
+    <string name="vibrator_tuning_summary_head">Adjust the strength of the vibration feedback</string>
+    <string name="vibrator_warning">Values higher than %1$d are not recommended</string>
+    <string name="defaults_button">Reset to default</string>
+</resources>
diff --git a/DeviceSettings/res/xml/device_haptic.xml b/DeviceSettings/res/xml/device_haptic.xml
new file mode 100644
index 0000000..6d6dbf0
--- /dev/null
+++ b/DeviceSettings/res/xml/device_haptic.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2015 The Evervolv Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+            android:title="@string/vibrator_title">
+        <com.evervolv.toolbox.custom.VibratorTuningPreference
+            android:key="vibrator_tuning"
+            android:title="@string/vibrator_tuning_title"
+            android:dialogTitle="@string/vibrator_tuning_title" />
+    </PreferenceCategory>
+</PreferenceScreen>
diff --git a/DeviceSettings/src/com/evervolv/toolbox/custom/VibratorTuningPreference.java b/DeviceSettings/src/com/evervolv/toolbox/custom/VibratorTuningPreference.java
new file mode 100644
index 0000000..300908e
--- /dev/null
+++ b/DeviceSettings/src/com/evervolv/toolbox/custom/VibratorTuningPreference.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2011-2013 The CyanogenMod Project
+ * Poritions Copyright (C) 2015 The Evervolv Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.evervolv.toolbox.custom;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.graphics.Color;
+import android.graphics.LightingColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Bundle;
+import android.os.Vibrator;
+import android.preference.DialogPreference;
+import android.preference.PreferenceManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Button;
+
+import java.lang.Math;
+import java.text.DecimalFormat;
+
+import com.evervolv.toolbox.R;
+import com.evervolv.toolbox.misc.FileUtil;
+
+/**
+ * Special preference type that allows configuration of vibrator intensity
+ */
+public class VibratorTuningPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
+    private static final String TAG = "VibratorTuningPreference";
+
+    public static final String FILE_PATH         = "/sys/vibrator/pwmvalue";
+    public static final int MAX_VALUE            = 127;
+    public static final int WARNING_THRESHOLD    = -1;
+    public static final int DEFAULT_VALUE        = 100;
+    public static final int MIN_VALUE            = 0;
+
+    private SeekBar mSeekBar;
+    private TextView mValue;
+    private TextView mWarning;
+
+    private String mOriginalValue;
+    private int mOriginalPercent;
+
+    private Drawable mProgressDrawable;
+    private Drawable mProgressThumb;
+    private LightingColorFilter mRedFilter;
+
+    public VibratorTuningPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setDialogLayoutResource(R.layout.preference_dialog_vibrator_tuning);
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+        builder.setNeutralButton(R.string.defaults_button, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+            }
+        });
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        mSeekBar = (SeekBar) view.findViewById(R.id.vibrator_seekbar);
+        mValue = (TextView) view.findViewById(R.id.vibrator_value);
+        mWarning = (TextView) view.findViewById(R.id.textWarn);
+
+        if (WARNING_THRESHOLD > 0) {
+            String strWarnMsg = getContext().getResources().getString(
+                    R.string.vibrator_warning, strengthToPercent(WARNING_THRESHOLD));
+            mWarning.setText(strWarnMsg);
+        } else if (mWarning != null) {
+            mWarning.setVisibility(View.GONE);
+        }
+
+        Drawable progressDrawable = mSeekBar.getProgressDrawable();
+        if (progressDrawable instanceof LayerDrawable) {
+            LayerDrawable ld = (LayerDrawable) progressDrawable;
+            mProgressDrawable = ld.findDrawableByLayerId(android.R.id.progress);
+        }
+        mProgressThumb = mSeekBar.getThumb();
+        mRedFilter = new LightingColorFilter(Color.BLACK,
+                getContext().getResources().getColor(android.R.color.holo_red_light));
+
+        mOriginalValue = FileUtil.fileReadOneLine(FILE_PATH);
+        mOriginalPercent = strengthToPercent(Integer.parseInt(mOriginalValue));
+
+        mSeekBar.setOnSeekBarChangeListener(this);
+        mSeekBar.setProgress(Integer.valueOf(mOriginalPercent));
+    }
+
+    @Override
+    protected void showDialog(Bundle state) {
+        super.showDialog(state);
+
+        // can't use onPrepareDialogBuilder for this as we want the dialog
+        // to be kept open on click
+        AlertDialog d = (AlertDialog) getDialog();
+        Button defaultsButton = d.getButton(DialogInterface.BUTTON_NEUTRAL);
+        defaultsButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mSeekBar.setProgress(strengthToPercent(DEFAULT_VALUE));
+            }
+        });
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        if (positiveResult) {
+            Editor editor = getEditor();
+
+            int strength = percentToStrength(mSeekBar.getProgress());
+            editor.putString(FILE_PATH, String.valueOf(strength));
+            editor.commit();
+        } else {
+            FileUtil.fileWriteOneLine(FILE_PATH, String.valueOf(mOriginalPercent));
+        }
+    }
+
+    public static void restore(Context context) {
+        if (!isSupported(FILE_PATH)) {
+            return;
+        }
+
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        final String value = prefs.getString(FILE_PATH, null);
+
+        if (value != null) {
+            Log.d(TAG, "Restoring vibration setting: " + value);
+            FileUtil.fileWriteOneLine(FILE_PATH, value);
+        }
+    }
+
+    public static boolean isSupported(String filePath) {
+        return FileUtil.fileExists(filePath);
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        boolean shouldWarn = WARNING_THRESHOLD > 0 && progress >= strengthToPercent(WARNING_THRESHOLD);
+        if (mProgressDrawable != null) {
+            mProgressDrawable.setColorFilter(shouldWarn ? mRedFilter : null);
+        }
+        if (mProgressThumb != null) {
+            mProgressThumb.setColorFilter(shouldWarn ? mRedFilter : null);
+        }
+        FileUtil.fileWriteOneLine(FILE_PATH, String.valueOf(percentToStrength(progress)));
+        mValue.setText(String.format("%d%%", progress));
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {
+        // Do nothing
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {
+        Vibrator vib = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
+        vib.vibrate(200);
+    }
+
+    /**
+    * Convert vibrator strength to percent
+    */
+    public static int strengthToPercent(int strength) {
+        double maxValue = MAX_VALUE;
+        double minValue = MIN_VALUE;
+
+        double percent = (strength - minValue) * (100 / (maxValue - minValue));
+
+        if (percent > 100)
+            percent = 100;
+        else if (percent < 0)
+            percent = 0;
+
+        return (int) percent;
+    }
+
+    /**
+    * Convert percent to vibrator strength
+    */
+    public static int percentToStrength(int percent) {
+        int strength = Math.round((((MAX_VALUE - MIN_VALUE) * percent) / 100) + MIN_VALUE);
+
+        if (strength > MAX_VALUE)
+            strength = MAX_VALUE;
+        else if (strength < MIN_VALUE)
+            strength = MIN_VALUE;
+
+        return strength;
+    }
+
+}
diff --git a/DeviceSettings/src/com/evervolv/toolbox/fragments/DeviceHaptic.java b/DeviceSettings/src/com/evervolv/toolbox/fragments/DeviceHaptic.java
new file mode 100644
index 0000000..fc2b668
--- /dev/null
+++ b/DeviceSettings/src/com/evervolv/toolbox/fragments/DeviceHaptic.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012-2013 The CyanogenMod Project
+ * Poritions Copyright (C) 2015 The Evervolv Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.evervolv.toolbox.fragments;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+
+import com.evervolv.toolbox.R;
+import com.evervolv.toolbox.Toolbox;
+import com.evervolv.toolbox.custom.VibratorTuningPreference;
+import com.evervolv.toolbox.misc.FileUtil;
+
+public class DeviceHaptic extends DeviceSettingFragment {
+
+    private static final String TAG = "DeviceSettings_Haptic";
+    public static final String KEY_VIBRATOR_TUNING = "vibrator_tuning";
+
+    private VibratorTuningPreference mVibratorTuning;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Resources res = getResources();
+
+        addPreferencesFromResource(R.xml.device_haptic);
+
+        mVibratorTuning = (VibratorTuningPreference) findPreference(
+                KEY_VIBRATOR_TUNING);
+        mVibratorTuning.setEnabled(Toolbox.isEnabled(getActivity()));
+    }
+
+    @Override
+    public void restore(Context context, boolean toolboxEnabled) {
+        if (mVibratorTuning != null) {
+            mVibratorTuning.setEnabled(toolboxEnabled);
+        }
+        if (toolboxEnabled) {
+            VibratorTuningPreference.restore(context);
+        } else {
+            FileUtil.fileWriteOneLine(VibratorTuningPreference.FILE_PATH,
+                    Integer.toString(VibratorTuningPreference.DEFAULT_VALUE));
+        }
+    }
+
+}
diff --git a/init.shamu.rc b/init.shamu.rc
index 62074d8..c36a5f9 100644
--- a/init.shamu.rc
+++ b/init.shamu.rc
@@ -341,6 +341,11 @@
     mkdir /data/adspd 0770 media media
     mkdir /data/adspd/1 0770 media media
 
+    # Vibrator permissions
+    chown root system /sys/vibrator/pwmvalue
+    chmod 0664 /sys/vibrator/pwmvalue
+    restorecon /sys/vibrator/pwmvalue
+
 on property:init.svc.wpa_supplicant=stopped
     stop dhcpcd
 
diff --git a/sepolicy/file.te b/sepolicy/file.te
index 0afbe76..00c4d32 100644
--- a/sepolicy/file.te
+++ b/sepolicy/file.te
@@ -43,3 +43,6 @@
 # Define cnd socket and data file type
 type cnd_socket, file_type;
 type cnd_data_file, file_type;
+
+# for Settings' access to VibratorHW
+type sysfs_vibrator, fs_type, sysfs_type;
diff --git a/sepolicy/file_contexts b/sepolicy/file_contexts
index 81fb4b4..9f4b840 100644
--- a/sepolicy/file_contexts
+++ b/sepolicy/file_contexts
@@ -160,3 +160,6 @@
 # CNE
 /dev/socket/cnd                                 u:object_r:cnd_socket:s0
 /system/bin/cnd                                 u:object_r:cnd_exec:s0
+
+# for Settings' access to VibratorHW
+/sys/vibrator/pwmvalue                                   u:object_r:sysfs_vibrator:s0
diff --git a/sepolicy/system_app.te b/sepolicy/system_app.te
index a6bef97..d85bf81 100644
--- a/sepolicy/system_app.te
+++ b/sepolicy/system_app.te
@@ -8,3 +8,6 @@
 #CNE
 allow system_app cne_service:service_manager add;
 unix_socket_connect(system_app, cnd, cnd);
+
+# for Settings' access to VibratorHW
+allow system_app sysfs_vibrator:file rw_file_perms;