OmniGears: add back TimeInState and DozeSettings

Change-Id: I6ff91dfd471c70d679e81075c900cccb70449aa3
diff --git a/res/drawable/ic_info.xml b/res/drawable/ic_info.xml
index 4fdc347..df5a8b1 100644
--- a/res/drawable/ic_info.xml
+++ b/res/drawable/ic_info.xml
@@ -2,8 +2,8 @@
         android:width="24dp"
         android:height="24dp"
         android:viewportHeight="24.0"
-        android:viewportWidth="24.0">
-    <path
-        android:fillColor="?android:attr/colorAccent"
+        android:viewportWidth="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path android:fillColor="@android:color/white"
         android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
 </vector>
diff --git a/res/drawable/ic_menu_filter.xml b/res/drawable/ic_menu_filter.xml
index 1b346d8..67bea43 100644
--- a/res/drawable/ic_menu_filter.xml
+++ b/res/drawable/ic_menu_filter.xml
@@ -2,6 +2,8 @@
     android:height="24dp"
     android:width="24dp"
     android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path android:fillColor="#fff" android:pathData="M6,13H18V11H6M3,6V8H21V6M10,18H14V16H10V18Z" />
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorControlNormal" >
+    <path android:fillColor="@android:color/white"
+          android:pathData="M6,13H18V11H6M3,6V8H21V6M10,18H14V16H10V18Z" />
 </vector>
diff --git a/res/drawable/ic_menu_save.xml b/res/drawable/ic_menu_save.xml
index 8bc0624..308fba7 100644
--- a/res/drawable/ic_menu_save.xml
+++ b/res/drawable/ic_menu_save.xml
@@ -2,8 +2,8 @@
         android:width="24dp"
         android:height="24dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#fff"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path android:fillColor="@android:color/white"
         android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
 </vector>
diff --git a/res/drawable/ic_settings_leds.xml b/res/drawable/ic_settings_leds.xml
index 8e564d6..73c1364 100644
--- a/res/drawable/ic_settings_leds.xml
+++ b/res/drawable/ic_settings_leds.xml
@@ -18,8 +18,8 @@
         android:height="24.0dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0"
-        android:tint="?android:attr/colorAccent">
+        android:tint="?android:attr/colorControlNormal">
     <path
-        android:fillColor="#FFFFFFFF"
+        android:fillColor="@android:color/white"
         android:pathData="M11,0V4H13V0H11M18.3,2.29L15.24,5.29L16.64,6.71L19.7,3.71L18.3,2.29M5.71,2.29L4.29,3.71L7.29,6.71L8.71,5.29L5.71,2.29M12,6A4,4 0 0,0 8,10V16H6V18H9V23H11V18H13V23H15V18H18V16H16V10A4,4 0 0,0 12,6M2,9V11H6V9H2M18,9V11H22V9H18Z" />
 </vector>
diff --git a/res/drawable/ic_settings_power.xml b/res/drawable/ic_settings_power.xml
index babd1be..da79090 100644
--- a/res/drawable/ic_settings_power.xml
+++ b/res/drawable/ic_settings_power.xml
@@ -18,8 +18,8 @@
         android:height="24.0dp"
         android:viewportHeight="24.0"
         android:viewportWidth="24.0"
-        android:tint="?attr/colorControlNormal">
+        android:tint="?android:attr/colorControlNormal">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="@android:color/white"
         android:pathData="M13.0,3.0l-2.0,0.0l0.0,10.0l2.0,0.0L13.0,3.0zm4.83,2.17l-1.42,1.42C17.99,7.86 19.0,9.81 19.0,12.0c0.0,3.87 -3.13,7.0 -7.0,7.0s-7.0,-3.13 -7.0,-7.0c0.0,-2.19 1.01,-4.14 2.58,-5.42L6.17,5.17C4.23,6.82 3.0,9.26 3.0,12.0c0.0,4.97 4.03,9.0 9.0,9.0s9.0,-4.03 9.0,-9.0c0.0,-2.74 -1.23,-5.18 -3.17,-6.83z"/>
 </vector>
diff --git a/res/values/custom_colors.xml b/res/values/custom_colors.xml
index 0de1b37..7a7cd92 100644
--- a/res/values/custom_colors.xml
+++ b/res/values/custom_colors.xml
@@ -16,5 +16,5 @@
  -->
 
 <resources>
-    <color name="system_activity_header_text">#ffffff</color>
+    <color name="system_activity_header_text">#000000</color>
 </resources>
diff --git a/res/xml/more_settings.xml b/res/xml/more_settings.xml
index 805db6e..51cfcfe 100644
--- a/res/xml/more_settings.xml
+++ b/res/xml/more_settings.xml
@@ -23,6 +23,18 @@
             android:key="category_system"
             android:title="@string/system_category">
 
+            <PreferenceScreen
+                android:key="time_in_state"
+                android:title="@string/time_in_state_title"
+                android:summary="@string/time_in_state_summary"
+                android:fragment="org.omnirom.omnigears.system.TimeInState" />
+
+            <PreferenceScreen
+                android:key="doze_settings"
+                android:title="@string/doze_settings_title"
+                android:summary="@string/doze_settings_summary"
+                android:fragment="org.omnirom.omnigears.system.DozeSettings" />
+
             <org.omnirom.omnigears.preference.GlobalSettingSwitchPreference
                 android:key="show_cpu_overlay"
                 android:title="@string/show_cpu_title"
diff --git a/src/org/omnirom/omnigears/system/CPUStateMonitor.java b/src/org/omnirom/omnigears/system/CPUStateMonitor.java
new file mode 100644
index 0000000..5722e3b
--- /dev/null
+++ b/src/org/omnirom/omnigears/system/CPUStateMonitor.java
@@ -0,0 +1,294 @@
+/*
+ * Performance Control - An Android CPU Control application Copyright (C)
+ * Brandon Valosek, 2011 <bvalosek@gmail.com> Copyright (C) Modified by 2012
+ * James Roberts
+ * Copyright (C) 2017 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.omnirom.omnigears.system;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.*;
+import java.util.*;
+
+public class CPUStateMonitor {
+    public static final String TIME_IN_STATE_PATH = "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state";
+    public static final String PREF_OFFSETS = "pref_offsets";
+    public static final String TIME_IN_STATE_OVERALL_PATH = "/sys/devices/system/cpu/cpufreq/overall_stats/overall_time_in_state";
+
+    private Map<Integer, ArrayList<CpuState>> mStates;
+    private Map<Integer, Map<Integer, Long>> mOffsets;
+    private boolean mOverallStats;
+    private int mCpuNum;
+    private List<Integer> mFrequencies;
+    private List<Integer> mShowCpus;
+
+    public CPUStateMonitor(List<Integer> showCpus) {
+        mShowCpus = showCpus;
+        mCpuNum = Helpers.getNumOfCpus();
+        mFrequencies = new ArrayList<Integer>();
+        mStates = new HashMap<Integer, ArrayList<CpuState>>();
+        mOffsets = new HashMap<Integer, Map<Integer, Long>>();
+        for (int i = 0; i < mCpuNum; i++) {
+            ArrayList<CpuState> cpuStates = new ArrayList<CpuState>();
+            mStates.put(i, cpuStates);
+
+            Map<Integer, Long> cpuOffsets = new HashMap<Integer, Long>();
+            mOffsets.put(i, cpuOffsets);
+        }
+        mOverallStats = Helpers.hasOverallStats();
+    }
+
+    public boolean hasOverallStats() {
+        return mOverallStats;
+    }
+
+    @SuppressWarnings("serial")
+    public class CPUStateMonitorException extends Exception {
+        public CPUStateMonitorException(String s) {
+            super(s);
+        }
+    }
+
+    // @SuppressLint({"UseValueOf", "UseValueOf"})
+    public class CpuState implements Comparable<CpuState> {
+        public CpuState(int cpu, int a, long b) {
+            mCpu = cpu;
+            freq = a;
+            duration = b;
+        }
+
+        public int freq = 0;
+        public long duration = 0;
+        public int mCpu = 0;
+
+        @Override
+        public String toString() {
+            return mCpu + ":" + freq + ":" + duration;
+        }
+
+        public int compareTo(CpuState state) {
+            Integer a = freq;
+            Integer b = state.freq;
+            return a.compareTo(b);
+        }
+
+        public long getDuration() {
+            Map<Integer, Long> offsets = getOffsets(mCpu);
+            Long offset = offsets.get(freq);
+            if (offset != null) {
+                return duration - offset;
+            }
+            return duration;
+        }
+    }
+
+    public List<CpuState> getStates(int cpu) {
+        return mStates.get(cpu);
+    }
+
+    public List<Integer> getFrequencies() {
+        return mFrequencies;
+    }
+
+    public CpuState getFreqState(int cpu, int freq) {
+        List<CpuState> cpuStates = mStates.get(cpu);
+        for (CpuState state : cpuStates) {
+            if (state.freq == freq) {
+                return state;
+            }
+        }
+        return null;
+    }
+
+    public CpuState getDeepSleepState() {
+        List<CpuState> cpuStates = mStates.get(0);
+        for (CpuState state : cpuStates) {
+            if (state.freq == 0) {
+                return state;
+            }
+        }
+        return null;
+    }
+
+    public long getTotalStateTime(int cpu, boolean withOffset) {
+        long sum = 0;
+        long offset = 0;
+
+        List<CpuState> cpuStates = mStates.get(cpu);
+        for (CpuState state : cpuStates) {
+            if (withOffset) {
+                sum += state.getDuration();
+            } else {
+                sum += state.duration;
+            }
+        }
+        return sum;
+    }
+
+    public Map<Integer, Long> getOffsets(int cpu) {
+        Map<Integer, Long> cpuOffsets = mOffsets.get(cpu);
+        return cpuOffsets;
+    }
+
+    public void setOffsets(int cpu, Map<Integer, Long> offsets) {
+        mOffsets.put(cpu, offsets);
+    }
+
+    public void setOffsets() throws CPUStateMonitorException {
+        updateStates();
+        for (int i = 0; i < mCpuNum; i++) {
+            if (mShowCpus != null) {
+                if (!mShowCpus.contains(i)) {
+                    continue;
+                }
+            }
+            setOffsets(i);
+        }
+    }
+
+    private void setOffsets(int cpu) throws CPUStateMonitorException {
+        Map<Integer, Long> cpuOffsets = mOffsets.get(cpu);
+        cpuOffsets.clear();
+
+        List<CpuState> cpuStates = mStates.get(cpu);
+        for (CpuState state : cpuStates) {
+            cpuOffsets.put(state.freq, state.duration);
+        }
+    }
+
+    public void removeOffsets() {
+        for (int i = 0; i < mCpuNum; i++) {
+            removeOffsets(i);
+        }
+    }
+
+    private void removeOffsets(int cpu) {
+        Map<Integer, Long> cpuOffsets = mOffsets.get(cpu);
+        cpuOffsets.clear();
+    }
+
+    public void clear() {
+        for (int i = 0; i < mCpuNum; i++) {
+            List<CpuState> cpuStates = mStates.get(i);
+            cpuStates.clear();
+        }
+    }
+
+    public void updateStates() throws CPUStateMonitorException {
+        mFrequencies.clear();
+        if (mOverallStats) {
+            try {
+                InputStream is = new FileInputStream(TIME_IN_STATE_OVERALL_PATH);
+                InputStreamReader ir = new InputStreamReader(is);
+                BufferedReader br = new BufferedReader(ir);
+                clear();
+                readInOverallStates(br);
+                is.close();
+            } catch (IOException e) {
+                throw new CPUStateMonitorException(
+                        "Problem opening time-in-states file");
+            }
+        } else {
+            try {
+                clear();
+                for (int i = 0; i < mCpuNum; i++) {
+                    if (mShowCpus != null) {
+                        if (!mShowCpus.contains(i)) {
+                            continue;
+                        }
+                    }
+                    List<CpuState> cpuStates = mStates.get(i);
+                    InputStream is = new FileInputStream(getCpuFreqPathFor(i));
+                    InputStreamReader ir = new InputStreamReader(is);
+                    BufferedReader br = new BufferedReader(ir);
+                    readInStates(br, i, cpuStates);
+                    is.close();
+                }
+            } catch (IOException e) {
+                throw new CPUStateMonitorException(
+                        "Problem opening time-in-states file");
+            }
+        }
+
+        List<CpuState> cpuStates = mStates.get(0);
+        long sleepTime = Math.max((SystemClock.elapsedRealtime() - SystemClock
+                .uptimeMillis()) / 10, 0);
+        cpuStates.add(new CpuState(0, 0, sleepTime));
+        Collections.sort(mFrequencies);
+    }
+
+    private void readInStates(BufferedReader br, int cpu,
+            List<CpuState> cpuStates) throws CPUStateMonitorException {
+        try {
+            String line;
+            while ((line = br.readLine()) != null) {
+                String[] nums = line.split(" ");
+                int freq = Integer.parseInt(nums[0]);
+                cpuStates.add(new CpuState(cpu, freq, Long
+                        .parseLong(nums[1])));
+                if (!mFrequencies.contains(freq)) {
+                    mFrequencies.add(freq);
+                }
+            }
+            Collections.sort(cpuStates, Collections.reverseOrder());
+        } catch (IOException e) {
+            throw new CPUStateMonitorException(
+                    "Problem processing time-in-states file");
+        }
+    }
+
+    private void readInOverallStates(BufferedReader br)
+            throws CPUStateMonitorException {
+        int cpu = 0;
+        List<CpuState> cpuStates = null;
+        ;
+        int firstFreq = 0;
+        try {
+            String line;
+            while ((line = br.readLine()) != null) {
+                String[] nums = line.split(" ");
+                int freq = Integer.parseInt(nums[0]);
+                if (firstFreq == 0) {
+                    firstFreq = freq;
+                } else if (freq == firstFreq) {
+                    cpu++;
+                    if (cpuStates != null) {
+                        Collections.sort(cpuStates, Collections.reverseOrder());
+                    }
+                }
+                cpuStates = mStates.get(cpu);
+                cpuStates.add(new CpuState(cpu, freq, Long.parseLong(nums[1])));
+                if (!mFrequencies.contains(freq)) {
+                    mFrequencies.add(freq);
+                }
+            }
+        } catch (IOException e) {
+            throw new CPUStateMonitorException(
+                    "Problem processing time-in-states file");
+        }
+    }
+
+    public void dump() {
+        Log.d("PC", "states = " + mStates + "\noffsets = " + mOffsets);
+    }
+
+    private String getCpuFreqPathFor(int cpu) {
+        return TIME_IN_STATE_PATH.replace("cpu0", "cpu" + cpu);
+    }
+}
diff --git a/src/org/omnirom/omnigears/system/DozeSettings.java b/src/org/omnirom/omnigears/system/DozeSettings.java
new file mode 100644
index 0000000..be4ee48
--- /dev/null
+++ b/src/org/omnirom/omnigears/system/DozeSettings.java
@@ -0,0 +1,439 @@
+/*
+ *  Copyright (C) 2017 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+*/
+package org.omnirom.omnigears.system;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import org.omnirom.omnigears.OmniDashboardFragment;
+
+public class DozeSettings extends SettingsPreferenceFragment {
+    private static final String TAG = "DozeSettings";
+
+    private static final String SHARED_PREFERENCES_NAME = "doze_settings";
+    private static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
+    private static final String KEY_LIGHT_IDLE_FACTOR = "light_idle_factor";
+    private static final String KEY_LOCATION_ACCURACY = "location_accuracy";
+    private static final String KEY_IDLE_PENDING_FACTOR = "idle_pending_factor";
+    private static final String KEY_IDLE_FACTOR = "idle_factor";
+    private static final String KEY_SHOW_ADVANCED = "show_advanced";
+
+    final long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = 5 * 60 * 1000L;
+    final long LIGHT_PRE_IDLE_TIMEOUT = 10 * 60 * 1000L;
+    final long LIGHT_IDLE_TIMEOUT = 5 * 60 * 1000L;
+    final float LIGHT_IDLE_FACTOR = 2f;
+    final long LIGHT_MAX_IDLE_TIMEOUT = 15 * 60 * 1000L;
+    final long LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = 1 * 60 * 1000L;
+    final long LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = 5 * 60 * 1000L;
+    final long MIN_LIGHT_MAINTENANCE_TIME = 5 * 1000L;
+    final long MIN_DEEP_MAINTENANCE_TIME = 30 * 1000L;
+    final long INACTIVE_TIMEOUT = 30 * 60 * 1000L;
+    final long SENSING_TIMEOUT = 4 * 60 * 1000L;
+    final long LOCATING_TIMEOUT = 30 * 1000L;
+    final float LOCATION_ACCURACY = 20;
+    final long MOTION_INACTIVE_TIMEOUT = 10 * 60 * 1000L;
+    final long IDLE_AFTER_INACTIVE_TIMEOUT = 30 * 60 * 1000L;
+    final long IDLE_PENDING_TIMEOUT = 5 * 60 * 1000L;
+    final long MAX_IDLE_PENDING_TIMEOUT = 10 * 60 * 1000L;
+    final float IDLE_PENDING_FACTOR = 2;
+    final long IDLE_TIMEOUT = 60 * 60 * 1000L;
+    final long MAX_IDLE_TIMEOUT = 6 * 60 * 60 * 1000L;
+    final float IDLE_FACTOR = 2;
+    final long MIN_TIME_TO_ALARM = 60 * 60 * 1000L;
+    final long MAX_TEMP_APP_WHITELIST_DURATION = 5 * 60 * 1000L;
+    final long MMS_TEMP_APP_WHITELIST_DURATION = 60 * 1000L;
+    final long SMS_TEMP_APP_WHITELIST_DURATION = 20 * 1000L;
+    final long NOTIFICATION_WHITELIST_DURATION = 30 * 1000L;
+
+    private int millisecondsInOneSecond = 1000;
+    private LinearLayout mContainer;
+    private List<String> mIdleConfigKeys = new ArrayList<String>();
+    private List<String> mIdleConfigDesc = new ArrayList<String>();
+    private List<String> mIdleConfigShort = new ArrayList<String>();
+    private List<Object> mIdleConfigValues = new ArrayList<Object>();
+    private List<String> mIdleConfigKeysBasic = new ArrayList<String>();
+    private List<EditText> mIdleConfigEdit = new ArrayList<EditText>();
+    private List<View> mIdleConfigViews = new ArrayList<View>();
+
+    private Context mContext;
+    private boolean mShowAdvanced;
+
+    private SharedPreferences getPrefs() {
+        return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mContext = getActivity();
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return OmniDashboardFragment.ACTION_SETTINGS_OMNI;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup root,
+            Bundle savedInstanceState) {
+        super.onCreateView(inflater, root, savedInstanceState);
+
+        mShowAdvanced = getPrefs().getBoolean(KEY_SHOW_ADVANCED, false);
+        View view = inflater.inflate(R.layout.doze_settings, root, false);
+
+        Profiles.initProfiles();
+        Profiles.loadUserProfiles(getPrefs());
+
+        mIdleConfigKeys.addAll(Arrays.asList(getResources().getStringArray(R.array.idle_config_keys)));
+        mIdleConfigDesc.addAll(Arrays.asList(getResources().getStringArray(R.array.idle_config_desc)));
+        mIdleConfigShort.addAll(Arrays.asList(getResources().getStringArray(R.array.idle_config_short)));
+        mIdleConfigKeysBasic.addAll(Arrays.asList(getResources().getStringArray(R.array.idle_config_keys_basic)));
+
+        mIdleConfigValues.add(LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
+        mIdleConfigValues.add(LIGHT_PRE_IDLE_TIMEOUT);
+        mIdleConfigValues.add(LIGHT_IDLE_TIMEOUT);
+        mIdleConfigValues.add(LIGHT_IDLE_FACTOR);
+        mIdleConfigValues.add(LIGHT_MAX_IDLE_TIMEOUT);
+        mIdleConfigValues.add(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
+        mIdleConfigValues.add(LIGHT_IDLE_MAINTENANCE_MAX_BUDGET);
+        mIdleConfigValues.add(MIN_LIGHT_MAINTENANCE_TIME);
+        mIdleConfigValues.add(MIN_DEEP_MAINTENANCE_TIME);
+        mIdleConfigValues.add(INACTIVE_TIMEOUT);
+        mIdleConfigValues.add(SENSING_TIMEOUT);
+        mIdleConfigValues.add(LOCATING_TIMEOUT);
+        mIdleConfigValues.add(LOCATION_ACCURACY);
+        mIdleConfigValues.add(MOTION_INACTIVE_TIMEOUT);
+        mIdleConfigValues.add(IDLE_AFTER_INACTIVE_TIMEOUT);
+        mIdleConfigValues.add(IDLE_PENDING_TIMEOUT);
+        mIdleConfigValues.add(MAX_IDLE_PENDING_TIMEOUT);
+        mIdleConfigValues.add(IDLE_PENDING_FACTOR);
+        mIdleConfigValues.add(IDLE_TIMEOUT);
+        mIdleConfigValues.add(MAX_IDLE_TIMEOUT);
+        mIdleConfigValues.add(IDLE_FACTOR);
+        mIdleConfigValues.add(MIN_TIME_TO_ALARM);
+        mIdleConfigValues.add(MAX_TEMP_APP_WHITELIST_DURATION);
+        mIdleConfigValues.add(MMS_TEMP_APP_WHITELIST_DURATION);
+        mIdleConfigValues.add(SMS_TEMP_APP_WHITELIST_DURATION);
+        mIdleConfigValues.add(NOTIFICATION_WHITELIST_DURATION);
+
+        mContainer = (LinearLayout) view.findViewById(R.id.idle_config_container);
+        createOptionsList(inflater);
+
+        getSettings();
+        return view;
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        inflater.inflate(R.menu.doze_settings_menu, menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        int id = item.getItemId();
+
+        switch (id) {
+            case R.id.action_profile:
+                displayProfiles();
+                break;
+            case R.id.action_save:
+                save();
+                break;
+            case R.id.action_save_as_profile:
+                saveAsProfile();
+                break;
+            case R.id.action_restoredefault:
+                restoreDefaults();
+                break;
+            case R.id.action_filter:
+                mShowAdvanced = !mShowAdvanced;
+                getPrefs().edit().putBoolean(KEY_SHOW_ADVANCED, mShowAdvanced).commit();
+                int i = 0;
+                for (View idleConfig : mIdleConfigViews) {
+                    final String configKey = mIdleConfigKeys.get(i);
+                    if (mShowAdvanced) {
+                        idleConfig.setVisibility(View.VISIBLE);
+                    } else {
+                        idleConfig.setVisibility(!mIdleConfigKeysBasic.contains(configKey) ? View.GONE : View.VISIBLE);
+                    }
+                    i++;
+                }
+                break;
+            }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    private String getMessage(String desc, String value) {
+        return desc +
+                "\n\n" + getResources().getString(R.string.string_default) +
+                ": " +
+                value +
+                " " +
+                getResources().getString(R.string.string_seconds);
+    }
+
+
+    private void getSettings() {
+        String line = Settings.Global.getString(getContentResolver(), DEVICE_IDLE_CONSTANTS);
+        Log.d(TAG, "getSettings = " + line);
+
+        KeyValueListParser parser = new KeyValueListParser(',');
+        if ("null".equals(line)) {
+            parser.setString(line + "=0");
+        } else {
+            parser.setString(line);
+        }
+        int divideBy = getDisplayValueFix();
+        for (int i = 0; i < mIdleConfigKeys.size(); i++) {
+            EditText editText = mIdleConfigEdit.get(i);
+            String key = mIdleConfigKeys.get(i);
+            Object defaultValue = mIdleConfigValues.get(i);
+
+            if (!key.equals(KEY_IDLE_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_LOCATION_ACCURACY) && !key.equals(KEY_LIGHT_IDLE_FACTOR)) {
+                Long value = parser.getLong(key, (Long) defaultValue);
+                value = value / divideBy;
+                editText.setText(String.valueOf(value));
+            } else {
+                Float value = parser.getFloat(key, (Float) defaultValue);
+                editText.setText(String.valueOf(value));
+            }
+        }
+    }
+
+
+    private int getDisplayValueFix() {
+        return millisecondsInOneSecond;
+    }
+
+    private void save() {
+        int multiplyBy = getDisplayValueFix();
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < mIdleConfigKeys.size(); i++) {
+            EditText editText = mIdleConfigEdit.get(i);
+            String key = mIdleConfigKeys.get(i);
+
+            if (!key.equals(KEY_IDLE_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_LOCATION_ACCURACY) && !key.equals(KEY_LIGHT_IDLE_FACTOR)) {
+                Long value = Long.valueOf(editText.getText().toString()) * multiplyBy;
+                sb.append(key + "=" + value + ",");
+            } else {
+                Float value = Float.valueOf(editText.getText().toString());
+                sb.append(key + "=" + value + ",");
+            }
+        }
+        if (sb.length() != 0) {
+            sb.deleteCharAt(sb.length() - 1);
+        }
+        Log.d(TAG, "save = " + sb.toString());
+        Settings.Global.putString(getContentResolver(), DEVICE_IDLE_CONSTANTS, sb.toString());
+        showApplyToast();
+    }
+
+    private void restoreDefaults() {
+        Log.d(TAG, "restoreDefaults");
+        Settings.Global.putString(getContentResolver(), DEVICE_IDLE_CONSTANTS, null);
+        getSettings();
+        showApplyToast();
+    }
+
+    private void applyProfile(String settings) {
+        Log.d(TAG, "apply = " + settings);
+        Settings.Global.putString(getContentResolver(), DEVICE_IDLE_CONSTANTS, settings);
+        getSettings();
+        showApplyToast();
+    }
+
+    private void displayProfiles() {
+        final ArrayList<Profiles.Profile> combinedProfileList = new ArrayList<>();
+        combinedProfileList.addAll(Profiles.getSystemProfileList());
+        combinedProfileList.addAll(Profiles.getUserProfileList());
+        Collections.sort(combinedProfileList);
+        int profileCount = 0;
+        String[] names = new String[combinedProfileList.size()];
+        for (Profiles.Profile profile : combinedProfileList) {
+            if (Profiles.getSystemProfileList().contains(profile)) {
+                names[profileCount] = profile.getName() + " *";
+            } else {
+                names[profileCount] = profile.getName();
+            }
+            profileCount++;
+        }
+        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+        builder.setTitle(getResources().getString(R.string.action_profile));
+        builder.setItems(names, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int item) {
+                applyProfile(combinedProfileList.get(item).getSettings());
+            }
+        });
+        final AlertDialog alert = builder.create();
+        alert.setOnShowListener(new DialogInterface.OnShowListener() {
+            @Override
+            public void onShow(DialogInterface dialog) {
+                ListView lv = alert.getListView();
+                lv.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+                    @Override
+                    public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
+                        Profiles.Profile p = combinedProfileList.get(position);
+                        if (Profiles.getUserProfileList().contains(p)) {
+                            AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
+                            dialog.setTitle(getResources().getString(R.string.delete_profile));
+                            dialog.setMessage(p.getName());
+                            dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                                @Override
+                                public void onClick(DialogInterface dialog, int which) {
+                                    Profiles.removeProfile(p);
+                                    combinedProfileList.remove(p);
+                                    Profiles.saveUserProfiles(getPrefs());
+                                    alert.hide();
+                                }
+                            });
+                            dialog.setNegativeButton(android.R.string.cancel, null);
+                            dialog.create().show();
+                        }
+                        return true;
+                    }
+                });
+            }
+        });
+        alert.show();
+    }
+
+    private void saveAsProfile() {
+        AlertDialog.Builder alert = new AlertDialog.Builder(mContext);
+        final EditText edittext = new EditText(mContext);
+        alert.setTitle(getResources().getString(R.string.profile_name));
+
+        alert.setView(edittext);
+
+        alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                String name = edittext.getText().toString();
+                int multiplyBy = getDisplayValueFix();
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < mIdleConfigKeys.size(); i++) {
+                    EditText editText = mIdleConfigEdit.get(i);
+                    String key = mIdleConfigKeys.get(i);
+
+                    if (!key.equals(KEY_IDLE_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_LOCATION_ACCURACY) && !key.equals(KEY_LIGHT_IDLE_FACTOR)) {
+                        Long value = Long.valueOf(editText.getText().toString()) * multiplyBy;
+                        sb.append(key + "=" + value + ",");
+                    } else {
+                        Float value = Float.valueOf(editText.getText().toString());
+                        sb.append(key + "=" + value + ",");
+                    }
+                }
+                if (sb.length() != 0) {
+                    sb.deleteCharAt(sb.length() - 1);
+                }
+                Profiles.Profile profile = new Profiles.Profile(name, sb.toString());
+                Profiles.addProfile(profile);
+                Profiles.saveUserProfiles(getPrefs());
+            }
+        });
+
+        alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int whichButton) {
+                // what ever you want to do with No option.
+            }
+        });
+
+        alert.show();
+    }
+
+    private void createOptionsList(LayoutInflater inflater) {
+        mContainer.removeAllViews();
+        for (int i = 0; i < mIdleConfigKeys.size(); i++) {
+            final String configKey = mIdleConfigKeys.get(i);
+            final int idx = i;
+            View idleConfig = inflater.inflate(R.layout.doze_item, null, false);
+            TextView idleConfigText = (TextView) idleConfig.findViewById(R.id.idle_config_text);
+            idleConfigText.setText(mIdleConfigShort.get(i));
+            EditText idleConfigValue = (EditText) idleConfig.findViewById(R.id.idle_config_edit);
+            idleConfigValue.setSaveEnabled(false);
+            mIdleConfigEdit.add(idleConfigValue);
+            ImageView idleConfigImage = (ImageView) idleConfig.findViewById(R.id.idle_config_image);
+            idleConfig.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    String key = mIdleConfigKeys.get(idx);
+                    int divideBy = getDisplayValueFix();
+                    AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+                    builder.setTitle(key);
+                    Object value = mIdleConfigValues.get(idx);
+                    if (!key.equals(KEY_IDLE_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_IDLE_PENDING_FACTOR) && !key.equals(KEY_LOCATION_ACCURACY) && !key.equals(KEY_LIGHT_IDLE_FACTOR)) {
+                        if (value instanceof Long) {
+                            value = ((Long) value) / divideBy;
+                        }
+                        builder.setMessage(getMessage(mIdleConfigDesc.get(idx), String.valueOf(value)));
+                    } else {
+                        builder.setMessage(mIdleConfigDesc.get(idx) +
+                                "\n\n" +
+                                getResources().getString(R.string.string_default) +
+                                ": " +
+                                String.valueOf(value));
+                    }
+                    builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int id) {
+                        }
+                    });
+                    AlertDialog dialog = builder.create();
+                    dialog.show();
+                }
+            });
+            mContainer.addView(idleConfig);
+            mIdleConfigViews.add(idleConfig);
+            if (mShowAdvanced) {
+                idleConfig.setVisibility(View.VISIBLE);
+            } else {
+                idleConfig.setVisibility(!mIdleConfigKeysBasic.contains(configKey) ? View.GONE : View.VISIBLE);
+            }
+        }
+    }
+
+    private void showApplyToast() {
+        Toast.makeText(mContext, getResources().getString(R.string.doze_settings_applied), Toast.LENGTH_SHORT).show();
+    }
+}
diff --git a/src/org/omnirom/omnigears/system/Helpers.java b/src/org/omnirom/omnigears/system/Helpers.java
new file mode 100644
index 0000000..6f747ad
--- /dev/null
+++ b/src/org/omnirom/omnigears/system/Helpers.java
@@ -0,0 +1,93 @@
+/*
+ * Performance Control - An Android CPU Control application Copyright (C) 2012
+ * Jared Rummler Copyright (C) 2012 James Roberts
+ * 
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.omnirom.omnigears.system;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class Helpers {
+    public static final String TIME_IN_STATE_PATH = "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state";
+    public static final String NUM_OF_CPUS_PATH = "/sys/devices/system/cpu/present";
+    public static final String TIME_IN_STATE_OVERALL_PATH = "/sys/devices/system/cpu/cpufreq/overall_stats/overall_time_in_state";
+
+    /**
+     * Read one line from file
+     *
+     * @param fname
+     * @return line
+     */
+    public static String readOneLine(String fname) {
+        String line = null;
+        if (new File(fname).exists()) {
+            BufferedReader br;
+            try {
+                br = new BufferedReader(new FileReader(fname), 512);
+                try {
+                    line = br.readLine();
+                } finally {
+                    br.close();
+                }
+            } catch (Exception e) {
+            }
+        }
+        return line;
+    }
+
+    /**
+     * Get total number of cpus
+     *
+     * @return total number of cpus
+     */
+    public static int getNumOfCpus() {
+        int numOfCpu = 1;
+        String numOfCpus = Helpers.readOneLine(NUM_OF_CPUS_PATH);
+        if (numOfCpus == null) {
+            return numOfCpu;
+        }
+        String[] cpuCount = numOfCpus.split("-");
+        if (cpuCount.length > 1) {
+            try {
+                int cpuStart = Integer.parseInt(cpuCount[0]);
+                int cpuEnd = Integer.parseInt(cpuCount[1]);
+
+                numOfCpu = cpuEnd - cpuStart + 1;
+
+                if (numOfCpu < 0)
+                    numOfCpu = 1;
+            } catch (NumberFormatException ex) {
+                numOfCpu = 1;
+            }
+        }
+        return numOfCpu;
+    }
+
+    public static boolean hasOverallStats() {
+        return new File(TIME_IN_STATE_OVERALL_PATH).exists();
+    }
+}
diff --git a/src/org/omnirom/omnigears/system/KeyValueListParser.java b/src/org/omnirom/omnigears/system/KeyValueListParser.java
new file mode 100644
index 0000000..3669d78
--- /dev/null
+++ b/src/org/omnirom/omnigears/system/KeyValueListParser.java
@@ -0,0 +1,114 @@
+package org.omnirom.omnigears.system;
+/*
+ * Copyright (C) 2015 The Android Open Source 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.
+ */
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+/**
+ * Parses a list of key=value pairs, separated by some delimiter, and puts the results in
+ * an internal Map. Values can be then queried by key, or if not found, a default value
+ * can be used.
+ * @hide
+ */
+public class KeyValueListParser {
+    private final ArrayMap<String, String> mValues = new ArrayMap<>();
+    private final TextUtils.StringSplitter mSplitter;
+
+    /**
+     * Constructs a new KeyValueListParser. This can be reused for different strings
+     * by calling {@link #setString(String)}.
+     * @param delim The delimiter that separates key=value pairs.
+     */
+    public KeyValueListParser(char delim) {
+        mSplitter = new TextUtils.SimpleStringSplitter(delim);
+    }
+
+    /**
+     * Resets the parser with a new string to parse. The string is expected to be in the following
+     * format:
+     * <pre>key1=value,key2=value,key3=value</pre>
+     *
+     * where the delimiter is a comma.
+     *
+     * @param str the string to parse.
+     * @throws IllegalArgumentException if the string is malformed.
+     */
+    public void setString(String str) throws IllegalArgumentException {
+        mValues.clear();
+        if (str != null) {
+            mSplitter.setString(str);
+            for (String pair : mSplitter) {
+                int sep = pair.indexOf('=');
+                if (sep < 0) {
+                    mValues.clear();
+                    throw new IllegalArgumentException(
+                            "'" + pair + "' in '" + str + "' is not a valid key-value pair");
+                }
+                mValues.put(pair.substring(0, sep).trim(), pair.substring(sep + 1).trim());
+            }
+        }
+    }
+
+    /**
+     * Get the value for key as a long.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found, or the value was not a long.
+     * @return the long value associated with the key.
+     */
+    public long getLong(String key, long def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                return Long.parseLong(value);
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /**
+     * Get the value for key as a float.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found, or the value was not a float.
+     * @return the float value associated with the key.
+     */
+    public float getFloat(String key, float def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                return Float.parseFloat(value);
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
+
+    /**
+     * Get the value for key as a string.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found.
+     * @return the string value associated with the key.
+     */
+    public String getString(String key, String def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            return value;
+        }
+        return def;
+    }
+}
diff --git a/src/org/omnirom/omnigears/system/Profiles.java b/src/org/omnirom/omnigears/system/Profiles.java
new file mode 100644
index 0000000..e2e28a6
--- /dev/null
+++ b/src/org/omnirom/omnigears/system/Profiles.java
@@ -0,0 +1,113 @@
+/*
+ *  Copyright (C) 2017 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+*/
+package org.omnirom.omnigears.system;
+
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Profiles {
+
+    public static class Profile implements Comparable<Profile> {
+
+        private String mName;
+        private String mSettings;
+
+        public Profile(String name, String settings){
+            mName = name;
+            mSettings = settings;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public String getSettings() {
+            return mSettings;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || ! (o instanceof Profile)) {
+                return false;
+            }
+            return mName.equals(((Profile)o).getName());
+        }
+
+        @Override
+        public int compareTo(Profile profile) {
+            return mName.compareTo(profile.getName());
+        }
+    }
+
+    private static final String PROFILE_PREFS = "profiles";
+
+    private static List<Profile> mUserProfileList = new ArrayList<>();
+    private static List<Profile> mSysteProfileList = new ArrayList<>();
+
+    public static void initProfiles() {
+        mSysteProfileList.clear();
+        mSysteProfileList.add(new Profile("aggressive", "light_after_inactive_to=30000,light_pre_idle_to=30000,light_idle_to=30000,light_idle_factor=2.0,light_max_idle_to=60000,light_idle_maintenance_min_budget=30000,light_idle_maintenance_max_budget=60000,min_light_maintenance_time=5000,min_deep_maintenance_time=10000,inactive_to=60000,sensing_to=0,locating_to=10000,location_accuracy=20.0,motion_inactive_to=60000,idle_after_inactive_to=0,idle_pending_to=30000,max_idle_pending_to=60000,idle_pending_factor=2.0,idle_to=3600000,max_idle_to=21600000,idle_factor=2.0,min_time_to_alarm=3600000,max_temp_app_whitelist_duration=20000,mms_temp_app_whitelist_duration=20000,sms_temp_app_whitelist_duration=20000,notification_whitelist_duration=20000"));
+    }
+
+    public static List<Profile> getUserProfileList() {
+        return mUserProfileList;
+    }
+
+    public static List<Profile> getSystemProfileList() {
+        return mSysteProfileList;
+    }
+
+    public static void addProfile(Profile profile) {
+        if (mUserProfileList.contains(profile)) {
+            mUserProfileList.remove(profile);
+        }
+        mUserProfileList.add(profile);
+    }
+
+    public static void removeProfile(Profile profile) {
+        mUserProfileList.remove(profile);
+    }
+
+    public static void loadUserProfiles(SharedPreferences sharedPref) {
+        String userProfile = sharedPref.getString(PROFILE_PREFS , null);
+        mUserProfileList.clear();
+        if (!TextUtils.isEmpty(userProfile)) {
+            String[] profiles = userProfile.split(";");
+            for (String profile : profiles) {
+                String[] profileParts = profile.split(":");
+                Profile p = new Profile(profileParts[0], profileParts[1]);
+                mUserProfileList.add(p);
+            }
+        }
+    }
+
+    public static void saveUserProfiles(SharedPreferences sharedPref){
+        if (mUserProfileList.size() != 0) {
+            StringBuffer sb = new StringBuffer();
+            for (Profile p : mUserProfileList) {
+                sb.append(p.getName() + ":" + p.getSettings() + ";");
+            }
+            sharedPref.edit().putString(PROFILE_PREFS, sb.toString()).commit();
+        } else {
+            sharedPref.edit().remove(PROFILE_PREFS).commit();
+        }
+    }
+}
diff --git a/src/org/omnirom/omnigears/system/TimeInState.java b/src/org/omnirom/omnigears/system/TimeInState.java
new file mode 100644
index 0000000..c52d54b
--- /dev/null
+++ b/src/org/omnirom/omnigears/system/TimeInState.java
@@ -0,0 +1,516 @@
+/*
+ *  Copyright (C) 2017 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+*/
+package org.omnirom.omnigears.system;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.*;
+import android.widget.*;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import org.omnirom.omnigears.OmniDashboardFragment;
+import org.omnirom.omnigears.system.CPUStateMonitor.CpuState;
+import org.omnirom.omnigears.system.CPUStateMonitor.CPUStateMonitorException;
+
+public class TimeInState extends SettingsPreferenceFragment {
+    private static final String TAG = "TimeInState";
+    public static final String PREF_OFFSETS = "pref_offsets";
+    public static final String PREF_STATE_MODE = "pref_state_mode";
+    public static final String PREF_CORE_MODE = "pref_core_mode";
+
+    private LinearLayout mStatesView;
+    private TextView mTotalStateTime;
+    private TextView mStatesWarning;
+    private CheckBox mStateMode;
+    private boolean mUpdatingData = false;
+    private CPUStateMonitor monitor;
+    private Context mContext;
+    private int mCpuNum;
+    private boolean mActiveStateMode;
+    private boolean mActiveCoreMode;
+    private Spinner mPeriodTypeSelect;
+    private LinearLayout mProgress;
+    private CheckBox mCoreMode;
+    private int mPeriodType = 1;
+    private boolean sHasRefData;
+    private Intent mShareIntent;
+    private List<Integer> mShowCpus;
+    private static boolean sResetStats;
+
+    private static final int MENU_REFRESH = Menu.FIRST;
+    private static final int MENU_SHARE = MENU_REFRESH + 1;
+    private static final String SHARED_PREFERENCES_NAME = "time_in_state";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mContext = getActivity();
+        mShowCpus = new ArrayList<Integer>();
+        String showCpus = getResources().getString(R.string.config_cpufreq_show_cpus);
+        if (!TextUtils.isEmpty(showCpus)) {
+            String[] parts = showCpus.split(",");
+            for (String cpu : parts) {
+                mShowCpus.add(Integer.valueOf(cpu));
+            }
+        }
+        monitor = new CPUStateMonitor(mShowCpus);
+        mActiveCoreMode = mShowCpus.size() > 1;
+
+        mCpuNum = Helpers.getNumOfCpus();
+        mPeriodType = getPrefs().getInt("which", 1);
+        if (savedInstanceState != null) {
+            mUpdatingData = savedInstanceState.getBoolean("updatingData");
+            mPeriodType = savedInstanceState.getInt("which");
+        }
+
+        if (sResetStats) {
+            sResetStats = false;
+            clearOffsets();
+        }
+        loadOffsets();
+
+        setHasOptionsMenu(true);
+    }
+
+    private SharedPreferences getPrefs() {
+        return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return OmniDashboardFragment.ACTION_SETTINGS_OMNI;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup root,
+            Bundle savedInstanceState) {
+        super.onCreateView(inflater, root, savedInstanceState);
+
+        View view = inflater.inflate(R.layout.time_in_state, root, false);
+
+        mStatesView = (LinearLayout) view.findViewById(R.id.ui_states_view);
+        mStatesWarning = (TextView) view.findViewById(R.id.ui_states_warning);
+        mTotalStateTime = (TextView) view
+                .findViewById(R.id.ui_total_state_time);
+
+        mStateMode = (CheckBox) view.findViewById(R.id.ui_mode_switch);
+        mActiveStateMode = getPrefs().getBoolean(PREF_STATE_MODE, false);
+        mStateMode.setChecked(mActiveStateMode);
+        mStateMode.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView,
+                    boolean isChecked) {
+                mActiveStateMode = isChecked;
+                SharedPreferences.Editor editor = getPrefs().edit();
+                editor.putBoolean(PREF_STATE_MODE, mActiveStateMode).commit();
+                updateView();
+            }
+        });
+
+        mCoreMode = (CheckBox) view.findViewById(R.id.ui_core_switch);
+        mCoreMode.setVisibility(View.GONE);
+
+        mPeriodTypeSelect = (Spinner) view
+                .findViewById(R.id.period_type_select);
+        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
+                mContext, R.array.period_type_entries, R.layout.spinner_item);
+        mPeriodTypeSelect.setAdapter(adapter);
+        mPeriodTypeSelect
+                .setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {
+                    @Override
+                    public void onItemSelected(AdapterView<?> parent,
+                            View view, int position, long id) {
+                        mPeriodType = position;
+                        if (position == 0) {
+                            loadOffsets();
+                        } else if (position == 1) {
+                            monitor.removeOffsets();
+                        }
+                        refreshData();
+                    }
+
+                    @Override
+                    public void onNothingSelected(AdapterView<?> arg0) {
+                    }
+                });
+        mPeriodTypeSelect.setSelection(mPeriodType);
+        mProgress = (LinearLayout) view.findViewById(R.id.ui_progress);
+        return view;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean("updatingData", mUpdatingData);
+        outState.putInt("which", mPeriodType);
+    }
+
+    @Override
+    public void onResume() {
+        refreshData();
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        getPrefs().edit().putInt("which", mPeriodType).commit();
+        super.onPause();
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        inflater.inflate(R.menu.time_in_state_menu, menu);
+
+        menu.add(0, MENU_REFRESH, 0, R.string.mt_refresh)
+                .setIcon(R.drawable.ic_menu_refresh_new)
+                .setAlphabeticShortcut('r')
+                .setShowAsAction(
+                        MenuItem.SHOW_AS_ACTION_IF_ROOM
+                                | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+
+        menu.add(1, MENU_SHARE, 0, R.string.mt_share)
+                .setIcon(R.drawable.ic_menu_share_material)
+                .setAlphabeticShortcut('s')
+                .setShowAsAction(
+                        MenuItem.SHOW_AS_ACTION_IF_ROOM
+                                | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case MENU_REFRESH:
+            refreshData();
+            break;
+        case R.id.reset:
+            createResetPoint();
+            break;
+        case MENU_SHARE:
+            if (mShareIntent != null) {
+                Intent intent = Intent.createChooser(mShareIntent, null);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivity(intent);
+            }
+            break;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void createResetPoint() {
+        try {
+            monitor.setOffsets();
+        } catch (Exception e) {
+            // not good
+        }
+        saveOffsets();
+        if (mPeriodType == 1) {
+            monitor.removeOffsets();
+        }
+        refreshData();
+    }
+
+    public void updateView() {
+        Log.d(TAG, "updateView " + mUpdatingData);
+        if (mUpdatingData) {
+            return;
+        }
+
+        StringBuffer data = new StringBuffer();
+        mStatesView.removeAllViews();
+
+        if (monitor.getStates(0).size() == 0) {
+            mStatesWarning.setVisibility(View.VISIBLE);
+            mTotalStateTime.setText(getResources().getString(R.string.total_time)
+                    + " " + toString(0));
+            mStatesView.setVisibility(View.GONE);
+        } else {
+            if (mPeriodType == 0 && !sHasRefData) {
+                mStatesWarning.setVisibility(View.VISIBLE);
+                mStatesWarning.setText(getResources().getString(R.string.no_stat_because_reset_state));
+                mTotalStateTime.setText(getResources().getString(R.string.total_time)
+                        + " " + toString(0));
+                mStatesView.setVisibility(View.VISIBLE);
+            } else {
+                mStatesWarning.setVisibility(View.GONE);
+                mStatesView.setVisibility(View.VISIBLE);
+                long totTime = getStateTime(mActiveStateMode);
+                data.append(totTime + "\n");
+                totTime = totTime / 100;
+                if (!mActiveStateMode) {
+                    CpuState deepSleepState = monitor.getDeepSleepState();
+                    if (deepSleepState != null) {
+                        generateStateRowHeader(deepSleepState, mStatesView);
+                        generateStateRow(deepSleepState, mStatesView);
+                        data.append(deepSleepState.freq + " "
+                                + deepSleepState.getDuration() + "\n");
+                    }
+                }
+                if (mActiveCoreMode) {
+                    int cpu = 0;
+                    for (int freq : monitor.getFrequencies()) {
+                        boolean headerCreated = false;
+                        for (cpu = 0; cpu < mCpuNum; cpu++) {
+                            if (mShowCpus != null) {
+                                if (!mShowCpus.contains(cpu)) {
+                                    continue;
+                                }
+                            }
+                            CpuState state = monitor.getFreqState(cpu, freq);
+                            if (state == null) {
+                                continue;
+                            }
+                            if (!headerCreated) {
+                                generateStateRowHeader(state, mStatesView);
+                                headerCreated = true;
+                            }
+                            generateStateRow(state, mStatesView);
+                            data.append(state.mCpu + " " + state.freq + " "
+                                    + state.getDuration() + "\n");
+                        }
+                    }
+                } else {
+                    for (CpuState state : monitor.getStates(0)) {
+                        if (state.freq == 0) {
+                            continue;
+                        }
+                        generateStateRowHeader(state, mStatesView);
+                        generateStateRow(state, mStatesView);
+                        data.append(state.freq + " " + state.getDuration() + "\n");
+                    }
+                }
+
+                mTotalStateTime.setText(getResources().getString(R.string.total_time)
+                        + " " + toString(totTime));
+            }
+        }
+        updateShareIntent(data.toString());
+    }
+
+    public void refreshData() {
+        if (!mUpdatingData) {
+            new RefreshStateDataTask().execute((Void) null);
+        }
+    }
+
+    private static String toString(long tSec) {
+        long h = (long) Math.max(0, Math.floor(tSec / (60 * 60)));
+        long m = (long) Math.max(0, Math.floor((tSec - h * 60 * 60) / 60));
+        long s = Math.max(0, tSec % 60);
+        String sDur;
+        sDur = h + ":";
+        if (m < 10)
+            sDur += "0";
+        sDur += m + ":";
+        if (s < 10)
+            sDur += "0";
+        sDur += s;
+
+        return sDur;
+    }
+
+    private View generateStateRow(CpuState state, ViewGroup parent) {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        LinearLayout view = (LinearLayout) inflater.inflate(
+                R.layout.state_row_line, parent, false);
+
+        float per = 0f;
+        String sPer = "";
+        String sDur = "";
+        String sCpu = " ";
+        long tSec = 0;
+
+        if (state != null) {
+            long duration = state.getDuration();
+            if (duration != 0) {
+                per = (float) duration * 100 / getStateTime(mActiveStateMode);
+                if (per > 100f) {
+                    per = 0f;
+                }
+                if (per < 0f) {
+                    per = 0f;
+                }
+                tSec = duration / 100;
+            }
+            sPer = String.format("%3d", (int) per) + "%";
+            sDur = toString(tSec);
+            if (state.freq != 0 && mActiveCoreMode) {
+                sCpu = String.valueOf(state.mCpu);
+            }
+        }
+
+        TextView cpuText = (TextView) view.findViewById(R.id.ui_cpu_text);
+        TextView durText = (TextView) view.findViewById(R.id.ui_duration_text);
+        TextView perText = (TextView) view
+                .findViewById(R.id.ui_percentage_text);
+        ProgressBar bar = (ProgressBar) view.findViewById(R.id.ui_bar);
+
+        cpuText.setText(sCpu);
+        perText.setText(sPer);
+        durText.setText(sDur);
+        bar.setProgress((int) per);
+
+        parent.addView(view);
+        return view;
+    }
+
+    private View generateStateRowHeader(CpuState state, ViewGroup parent) {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        LinearLayout view = (LinearLayout) inflater.inflate(
+                R.layout.state_row_header, parent, false);
+
+        String sFreq;
+        if (state.freq == 0) {
+            sFreq = getString(R.string.deep_sleep);
+        } else {
+            sFreq = state.freq / 1000 + " MHz";
+        }
+
+        TextView freqText = (TextView) view.findViewById(R.id.ui_freq_text);
+        freqText.setText(sFreq);
+
+        parent.addView(view);
+        return view;
+    }
+
+    protected class RefreshStateDataTask extends AsyncTask<Void, Void, Void> {
+        @Override
+        protected Void doInBackground(Void... v) {
+            try {
+                monitor.updateStates();
+            } catch (CPUStateMonitorException e) {
+            }
+            return null;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            mProgress.setVisibility(View.VISIBLE);
+            mUpdatingData = true;
+        }
+
+        @Override
+        protected void onPostExecute(Void v) {
+            try {
+                mProgress.setVisibility(View.GONE);
+                mUpdatingData = false;
+                updateView();
+            } catch(Exception e) {
+            }
+        }
+    }
+
+    private void loadOffsets() {
+        String prefs = getPrefs().getString(PREF_OFFSETS, "");
+        if (TextUtils.isEmpty(prefs)) {
+            return;
+        }
+        String[] cpus = prefs.split(":");
+        if (cpus.length != mCpuNum) {
+            return;
+        }
+        for (int cpu = 0; cpu < mCpuNum; cpu++) {
+            if (mShowCpus != null) {
+                if (!mShowCpus.contains(cpu)) {
+                    continue;
+                }
+            }
+            String cpuData = cpus[cpu];
+            Map<Integer, Long> offsets = new HashMap<Integer, Long>();
+            String[] sOffsets = cpuData.split(",");
+            for (String offset : sOffsets) {
+                String[] parts = offset.split(";");
+                offsets.put(Integer.parseInt(parts[0]),
+                        Long.parseLong(parts[1]));
+            }
+            monitor.setOffsets(cpu, offsets);
+        }
+        sHasRefData = true;
+    }
+
+    private void saveOffsets() {
+        StringBuffer str = new StringBuffer();
+        for (int cpu = 0; cpu < mCpuNum; cpu++) {
+            boolean saveCpu = true;
+            if (mShowCpus != null) {
+                if (!mShowCpus.contains(cpu)) {
+                    // just placeholder
+                    str.append("cpu");
+                    saveCpu = false;
+                }
+            }
+            if (saveCpu) {
+                int size = monitor.getOffsets(cpu).entrySet().size();
+                int i = 0;
+                for (Map.Entry<Integer, Long> entry : monitor.getOffsets(cpu)
+                        .entrySet()) {
+                    str.append(entry.getKey() + ";" + entry.getValue());
+                    if (i < size - 1) {
+                        str.append(",");
+                    }
+                    i++;
+                }
+            }
+            if (cpu < mCpuNum - 1) {
+                str.append(":");
+            }
+        }
+        getPrefs().edit().putString(PREF_OFFSETS, str.toString()).commit();
+        sHasRefData = true;
+    }
+
+    private void clearOffsets() {
+        getPrefs().edit().putString(PREF_OFFSETS, "").commit();
+        sHasRefData = false;
+    }
+
+    private long getStateTime(boolean activeMode) {
+        long total = monitor.getTotalStateTime(0, true);
+        if (activeMode) {
+            CpuState deepSleepState = monitor.getDeepSleepState();
+            return total - deepSleepState.getDuration();
+        }
+        return total;
+    }
+
+    private void updateShareIntent(String data) {
+        mShareIntent = new Intent();
+        mShareIntent.setAction(Intent.ACTION_SEND);
+        mShareIntent.setType("text/plain");
+        mShareIntent.putExtra(Intent.EXTRA_TEXT, data);
+    }
+
+    public static void triggerResetStats() {
+        // on reboot
+        sResetStats = true;
+    }
+}