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;
+ }
+}