Merge "Update TARE UI." into tm-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9d868cb..917c666 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -13731,18 +13731,26 @@
     translate "maximum balance when device is fully charged" instead. Balance is the same meaning as
     having money in a bank account. Balance in our feature is the amount of Android Resource Credits
     an app can have. Android Resource Credits are a form of payment used by apps to be able to
-    perform tasks. [CHAR LIMIT=40]-->
+    perform tasks. [CHAR LIMIT=80]-->
     <string name="tare_max_satiated_balance">Maximum Satiated Balance</string>
-    <!-- Title for the TARE policy factor that determines the maximum credits in circulation between
-    all the apps [CHAR LIMIT=40]-->
-    <string name="tare_max_circulation">Maximum Circulation</string>
-    <!-- Title for the TARE policy factor that determines the minimum credits an app can have in one
-    battery life cycle. Satiated means battery is fully charged; If this is not easily translatable,
-    translate "minimum balance when device is fully charged" instead. Balance is the same meaning as
-    having money in a bank account. Balance in our feature is the amount of Android Resource Credits
-    an app can have. Android Resource Credits are a form of payment used by apps to be able to
-    perform tasks. [CHAR LIMIT=40]-->
-    <string name="tare_min_satiated_balance">Minimum Satiated Balance</string>
+    <!-- Title for the TARE policy factors that affect how many credits an app may have. Balance
+    in this context is the same as "bank balance" or "account balance" (ie. how much "money" may be
+    in a bank account). [CHAR LIMIT=55]-->
+    <string name="tare_balances">Balances</string>
+    <!-- Title for the TARE section to modify consumption limits. "Consumption" refers to the idea
+     using resources that are not replenished. [CHAR LIMIT=55]-->
+    <string name="tare_consumption_limits">Consumption Limits</string>
+    <!-- Title for the TARE policy factor that determines the initial maximum amount of credits that
+    can be consumed by all the apps [CHAR LIMIT=80]-->
+    <string name="tare_initial_consumption_limit">Initial Consumption Limit</string>
+    <!-- Title for the TARE policy factor that determines the maximum consumption limit the system
+     can have [CHAR LIMIT=80]-->
+    <string name="tare_hard_consumption_limit">Maximum Consumption Limit</string>
+    <!-- Titles for the consumption limits factors. [CHAR LIMIT=40]-->
+    <string-array name="tare_consumption_limit_subfactors" translatable="false">
+        <item>@string/tare_initial_consumption_limit</item>
+        <item>@string/tare_hard_consumption_limit</item>
+    </string-array>
     <!-- Title for the various modifiers that alter the cost of TARE tasks based on battery status
     (charging, power save mode, etc.) [CHAR LIMIT=40]-->
     <string name="tare_modifiers">Modifiers</string>
@@ -13778,14 +13786,6 @@
     <string name="tare_nonwakeup_inexact" translatable="false">Inexact NonWakeup Alarm</string>
     <!-- Title for the AlarmClock alarm set via AlarmManager.setAlarmClock() [CHAR LIMIT=50]-->
     <string name="tare_alarm_clock" translatable="false">AlarmClock</string>
-    <!-- Exempted apps are those apps exempted from most power saving features. [CHAR LIMIT=50]-->
-    <string name="tare_exempted">Exempted</string>
-    <!-- A headless system app is a preinstalled app that does not have any activities/UI that the
-    user can interact with. [CHAR LIMIT=50]-->
-    <string name="tare_headless_app">Headless System App</string>
-    <!-- Other apps are those apps interacted with by users that are not exempted or headless
-    system apps. [CHAR LIMIT=50]-->
-    <string name="tare_other_app">Other App</string>
     <!-- Top activity means an app is in the TOP android process state and is thus visible to the
     user[CHAR LIMIT=50]-->
     <string name="tare_top_activity">Top Activity</string>
@@ -13842,12 +13842,29 @@
     <!-- Title for the penalty an app receives for letting a job use the maximum execution time and
      time out [CHAR LIMIT=50]-->
     <string name="tare_job_timeout_penalty">Job Timeout Penalty</string>
+    <!-- Title for the TARE factor that determines the minimum credits an app should have when the
+    device is fully charged. "Satiated" in this context means that the battery is fully charged.
+    Balance is the same meaning as having money in a bank account.
+    Exempted apps are those apps exempted from most power saving features. [CHAR LIMIT=80]-->
+    <string name="tare_min_balance_exempted">Minimum Satiated Balance (Exempted)</string>
+    <!-- Title for the TARE factor that determines the minimum credits an app should have when the
+    device is fully charged. "Satiated" in this context means that the battery is fully charged.
+    Balance is the same meaning as having money in a bank account.
+    A headless system app is a preinstalled app that does not have any activities/UI that the
+    user can interact with. [CHAR LIMIT=80]-->
+    <string name="tare_min_balance_headless_app">Minimum Satiated Balance (Headless System App)</string>
+    <!-- Title for the TARE factor that determines the minimum credits an app should have when the
+    device is fully charged. "Satiated" in this context means that the battery is fully charged.
+    Balance is the same meaning as having money in a bank account.
+    Remaining apps are those apps that don't fit into predefined categories. [CHAR LIMIT=80]-->
+    <string name="tare_min_balance_other_app">Minimum Satiated Balance (Remaining Apps)</string>
     <!-- Titles for the minimum satiated credit balances for different types of apps
     (per battery cycle). Satiated means battery is fully charged. [CHAR LIMIT=40]-->
-    <string-array name="tare_min_satiated_balance_subfactors" translatable="false">
-        <item>@string/tare_exempted</item>
-        <item>@string/tare_headless_app</item>
-        <item>@string/tare_other_app</item>
+    <string-array name="tare_app_balance_subfactors" translatable="false">
+        <item>@string/tare_max_satiated_balance</item>
+        <item>@string/tare_min_balance_exempted</item>
+        <item>@string/tare_min_balance_headless_app</item>
+        <item>@string/tare_min_balance_other_app</item>
     </string-array>
     <!-- Various modifier subfactors that alter the cost of TARE tasks depending on what battery
     state the device is in [CHAR LIMIT=50]-->
diff --git a/src/com/android/settings/development/tare/AlarmManagerFragment.java b/src/com/android/settings/development/tare/AlarmManagerFragment.java
index dbc4e58..fe76b12 100644
--- a/src/com/android/settings/development/tare/AlarmManagerFragment.java
+++ b/src/com/android/settings/development/tare/AlarmManagerFragment.java
@@ -15,17 +15,16 @@
  */
 package com.android.settings.development.tare;
 
+import android.annotation.Nullable;
 import android.app.Fragment;
+import android.app.tare.EconomyManager;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView.OnChildClickListener;
-import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.settings.R;
 
@@ -33,130 +32,96 @@
  * Creates the AlarmManager fragment to display all the AlarmManager factors
  * when the AlarmManager policy is chosen in the dropdown TARE menu.
  */
-public class AlarmManagerFragment extends Fragment {
+public class AlarmManagerFragment extends Fragment implements
+        TareFactorController.DataChangeListener {
+
+    private TareFactorController mFactorController;
+
+    private TareFactorExpandableListAdapter mExpandableListAdapter;
+
+    private String[] mGroups;
+    private String[][] mChildren;
+    private String[][] mKeys;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mFactorController = TareFactorController.getInstance(getContext());
+        populateArrays();
+    }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         View v = inflater.inflate(R.layout.tare_policy_fragment, null);
         ExpandableListView elv = (ExpandableListView) v.findViewById(R.id.factor_list);
-        final SavedTabsListAdapter expListAdapter = new SavedTabsListAdapter();
+        mExpandableListAdapter = new TareFactorExpandableListAdapter(
+                mFactorController, LayoutInflater.from(getActivity()), mGroups, mChildren, mKeys);
         elv.setGroupIndicator(null);
-        elv.setAdapter(expListAdapter);
+        elv.setAdapter(mExpandableListAdapter);
         elv.setOnChildClickListener(new OnChildClickListener() {
             public boolean onChildClick(ExpandableListView parent, View v,
                     int groupPosition, int childPosition, long id) {
-                final String selected =
-                        (String) expListAdapter.getChild(groupPosition, childPosition);
-                Toast.makeText(getActivity(), selected, Toast.LENGTH_SHORT).show();
+                final String key = mExpandableListAdapter.getKey(groupPosition, childPosition);
+                mFactorController.createDialog(key).show(getFragmentManager(), key);
                 return true;
             }
         });
         return v;
     }
 
-     /**
-     * Creates the expandable list containing all AlarmManager factors within the
-     * AlarmManager fragment.
-     */
-    public class SavedTabsListAdapter extends BaseExpandableListAdapter {
+    @Override
+    public void onStart() {
+        super.onStart();
+        mFactorController.registerListener(this);
+    }
 
-        private final LayoutInflater mInflater;
-        private Resources mResources = getActivity().getResources();
+    @Override
+    public void onStop() {
+        mFactorController.unregisterListener(this);
+        super.onStop();
+    }
 
-        private String[] mGroups = {
-                mResources.getString(R.string.tare_max_circulation),
-                mResources.getString(R.string.tare_max_satiated_balance),
-                mResources.getString(R.string.tare_min_satiated_balance),
-                mResources.getString(R.string.tare_modifiers),
-                mResources.getString(R.string.tare_actions),
-                mResources.getString(R.string.tare_rewards)
+    @Override
+    public void onDataChanged() {
+        mExpandableListAdapter.notifyDataSetChanged();
+    }
+
+    private void populateArrays() {
+        final Resources resources = getResources();
+
+        mGroups = new String[]{
+                resources.getString(R.string.tare_consumption_limits),
+                resources.getString(R.string.tare_balances),
+                // resources.getString(R.string.tare_modifiers),
+                // resources.getString(R.string.tare_actions),
+                // resources.getString(R.string.tare_rewards)
         };
 
-        /*
-         * First two are empty arrays because the first two factors have no subfactors (no
-         * children).
-         */
-        private String[][] mChildren = {
-                {},
-                {},
-                mResources.getStringArray(R.array.tare_min_satiated_balance_subfactors),
-                mResources.getStringArray(R.array.tare_modifiers_subfactors),
-                mResources.getStringArray(R.array.tare_alarm_manager_actions),
-                mResources.getStringArray(R.array.tare_rewards_subfactors)
+        mChildren = new String[][]{
+                resources.getStringArray(R.array.tare_consumption_limit_subfactors),
+                resources.getStringArray(R.array.tare_app_balance_subfactors),
+                // TODO: support
+                // resources.getStringArray(R.array.tare_modifiers_subfactors),
+                // resources.getStringArray(R.array.tare_alarm_manager_actions),
+                // resources.getStringArray(R.array.tare_rewards_subfactors)
         };
 
-        public SavedTabsListAdapter() {
-            mInflater = LayoutInflater.from(getActivity());
-        }
-
-        @Override
-        public int getGroupCount() {
-            return mGroups.length;
-        }
-
-        @Override
-        public int getChildrenCount(int groupPosition) {
-            return mChildren[groupPosition].length;
-        }
-
-        @Override
-        public Object getGroup(int groupPosition) {
-            return mGroups[groupPosition];
-        }
-
-        @Override
-        public Object getChild(int groupPosition, int childPosition) {
-            return mChildren[groupPosition][childPosition];
-        }
-
-        @Override
-        public long getGroupId(int groupPosition) {
-            return groupPosition;
-        }
-
-        @Override
-        public long getChildId(int groupPosition, int childPosition) {
-            return childPosition;
-        }
-
-        @Override
-        public boolean hasStableIds() {
-            return true;
-        }
-
-        @Override
-        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
-                ViewGroup parent) {
-            if (convertView == null) {
-                convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
-            }
-            TextView factor = (TextView) convertView.findViewById(android.R.id.text1);
-            factor.setText(getGroup(groupPosition).toString());
-            return convertView;
-        }
-
-        @Override
-        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
-                View convertView, ViewGroup parent) {
-            // Here a custom child item is used instead of android.R.simple_list_item_2 because it
-            // is more customizable for this specific UI
-            if (convertView == null) {
-                convertView = mInflater.inflate(R.layout.tare_child_item, null);
-            }
-            TextView factor = (TextView) convertView.findViewById(R.id.factor);
-            TextView value = (TextView) convertView.findViewById(R.id.factor_number);
-
-            // TODO: Replace these hardcoded values with either default or user inputted TARE values
-            factor.setText(getChild(groupPosition, childPosition).toString());
-            value.setText("500");
-
-            return convertView;
-        }
-
-        @Override
-        public boolean isChildSelectable(int groupPosition, int childPosition) {
-            return true;
-        }
+        mKeys = new String[][]{
+                {
+                        EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT,
+                        EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT
+                },
+                {
+                        EconomyManager.KEY_AM_MAX_SATIATED_BALANCE,
+                        EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
+                        EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+                        EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP
+                },
+                // {},
+                // {},
+                // {},
+        };
     }
 }
diff --git a/src/com/android/settings/development/tare/DropdownActivity.java b/src/com/android/settings/development/tare/DropdownActivity.java
index 55f1fec..66b57dd 100644
--- a/src/com/android/settings/development/tare/DropdownActivity.java
+++ b/src/com/android/settings/development/tare/DropdownActivity.java
@@ -43,7 +43,6 @@
     static final int POLICY_JOB_SCHEDULER = 1;
     private static final int DEFAULT_POLICY = POLICY_ALARM_MANAGER;
 
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/src/com/android/settings/development/tare/JobSchedulerFragment.java b/src/com/android/settings/development/tare/JobSchedulerFragment.java
index 5a7f4a9..1c6598c 100644
--- a/src/com/android/settings/development/tare/JobSchedulerFragment.java
+++ b/src/com/android/settings/development/tare/JobSchedulerFragment.java
@@ -15,17 +15,16 @@
  */
 package com.android.settings.development.tare;
 
+import android.annotation.Nullable;
 import android.app.Fragment;
+import android.app.tare.EconomyManager;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView.OnChildClickListener;
-import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.settings.R;
 
@@ -33,131 +32,97 @@
  * Creates the JobScheduler fragment to display all the JobScheduler factors
  * when the JobScheduler policy is chosen in the dropdown TARE menu.
  */
-public class JobSchedulerFragment extends Fragment {
+public class JobSchedulerFragment extends Fragment implements
+        TareFactorController.DataChangeListener {
+
+    private TareFactorController mFactorController;
+
+    private TareFactorExpandableListAdapter mExpandableListAdapter;
+
+    private String[] mGroups;
+    private String[][] mChildren;
+    private String[][] mKeys;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mFactorController = TareFactorController.getInstance(getContext());
+        populateArrays();
+    }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         View v = inflater.inflate(R.layout.tare_policy_fragment, null);
         ExpandableListView elv = (ExpandableListView) v.findViewById(R.id.factor_list);
-        final SavedTabsListAdapter expListAdapter = new SavedTabsListAdapter();
+        mExpandableListAdapter = new TareFactorExpandableListAdapter(
+                mFactorController, LayoutInflater.from(getActivity()), mGroups, mChildren, mKeys);
         elv.setGroupIndicator(null);
-        elv.setAdapter(expListAdapter);
+        elv.setAdapter(mExpandableListAdapter);
         elv.setOnChildClickListener(new OnChildClickListener() {
             public boolean onChildClick(ExpandableListView parent, View v,
                     int groupPosition, int childPosition, long id) {
-                final String selected =
-                        (String) expListAdapter.getChild(groupPosition, childPosition);
-                Toast.makeText(getActivity(), selected, Toast.LENGTH_SHORT)
-                        .show();
+                final String key = mExpandableListAdapter.getKey(groupPosition, childPosition);
+                mFactorController.createDialog(key).show(getFragmentManager(), key);
                 return true;
             }
         });
+
         return v;
     }
 
-    /**
-     * Creates the expandable list containing all JobScheduler factors within the
-     * JobScheduler fragment.
-     */
-    public class SavedTabsListAdapter extends BaseExpandableListAdapter {
+    @Override
+    public void onStart() {
+        super.onStart();
+        mFactorController.registerListener(this);
+    }
 
-        private final LayoutInflater mInflater;
-        private Resources mResources = getActivity().getResources();
+    @Override
+    public void onStop() {
+        mFactorController.unregisterListener(this);
+        super.onStop();
+    }
 
-        private String[] mGroups = {
-                mResources.getString(R.string.tare_max_circulation),
-                mResources.getString(R.string.tare_max_satiated_balance),
-                mResources.getString(R.string.tare_min_satiated_balance),
-                mResources.getString(R.string.tare_modifiers),
-                mResources.getString(R.string.tare_actions),
-                mResources.getString(R.string.tare_rewards)
+    @Override
+    public void onDataChanged() {
+        mExpandableListAdapter.notifyDataSetChanged();
+    }
+
+    private void populateArrays() {
+        final Resources resources = getResources();
+
+        mGroups = new String[]{
+                resources.getString(R.string.tare_consumption_limits),
+                resources.getString(R.string.tare_balances),
+                // mResources.getString(R.string.tare_modifiers),
+                // mResources.getString(R.string.tare_actions),
+                // mResources.getString(R.string.tare_rewards)
         };
 
-        /*
-         * First two are empty arrays because the first two factors have no subfactors (no
-         * children).
-         */
-        private String[][] mChildren = {
-                {},
-                {},
-                mResources.getStringArray(R.array.tare_min_satiated_balance_subfactors),
-                mResources.getStringArray(R.array.tare_modifiers_subfactors),
-                mResources.getStringArray(R.array.tare_job_scheduler_actions),
-                mResources.getStringArray(R.array.tare_rewards_subfactors)
+        mChildren = new String[][]{
+                resources.getStringArray(R.array.tare_consumption_limit_subfactors),
+                resources.getStringArray(R.array.tare_app_balance_subfactors),
+                // TODO: support
+                // mResources.getStringArray(R.array.tare_modifiers_subfactors),
+                // mResources.getStringArray(R.array.tare_job_scheduler_actions),
+                // mResources.getStringArray(R.array.tare_rewards_subfactors)
         };
 
-        public SavedTabsListAdapter() {
-            mInflater = LayoutInflater.from(getActivity());
-        }
-
-        @Override
-        public int getGroupCount() {
-            return mGroups.length;
-        }
-
-        @Override
-        public int getChildrenCount(int groupPosition) {
-            return mChildren[groupPosition].length;
-        }
-
-        @Override
-        public Object getGroup(int groupPosition) {
-            return mGroups[groupPosition];
-        }
-
-        @Override
-        public Object getChild(int groupPosition, int childPosition) {
-            return mChildren[groupPosition][childPosition];
-        }
-
-        @Override
-        public long getGroupId(int groupPosition) {
-            return groupPosition;
-        }
-
-        @Override
-        public long getChildId(int groupPosition, int childPosition) {
-            return childPosition;
-        }
-
-        @Override
-        public boolean hasStableIds() {
-            return true;
-        }
-
-        @Override
-        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
-                ViewGroup parent) {
-            if (convertView == null) {
-                convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
-            }
-            TextView factor = (TextView) convertView.findViewById(android.R.id.text1);
-            factor.setText(getGroup(groupPosition).toString());
-            return convertView;
-        }
-
-        @Override
-        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
-                View convertView, ViewGroup parent) {
-            // Here a custom child item is used instead of android.R.simple_list_item_2 because it
-            // is more customizable for this specific UI
-            if (convertView == null) {
-                convertView = mInflater.inflate(R.layout.tare_child_item, null);
-            }
-            TextView factor = (TextView) convertView.findViewById(R.id.factor);
-            TextView value = (TextView) convertView.findViewById(R.id.factor_number);
-
-            // TODO: Replace these hardcoded values with either default or user inputted TARE values
-            factor.setText(getChild(groupPosition, childPosition).toString());
-            value.setText("500");
-
-            return convertView;
-        }
-
-        @Override
-        public boolean isChildSelectable(int groupPosition, int childPosition) {
-            return true;
-        }
+        mKeys = new String[][]{
+                {
+                        EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT,
+                        EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT
+                },
+                {
+                        EconomyManager.KEY_JS_MAX_SATIATED_BALANCE,
+                        EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
+                        EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
+                        EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP
+                },
+                // {},
+                // {},
+                // {},
+        };
     }
 }
diff --git a/src/com/android/settings/development/tare/TareFactorController.java b/src/com/android/settings/development/tare/TareFactorController.java
index 3cabd8d..b9f813d 100644
--- a/src/com/android/settings/development/tare/TareFactorController.java
+++ b/src/com/android/settings/development/tare/TareFactorController.java
@@ -24,8 +24,13 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
@@ -38,6 +43,8 @@
 public class TareFactorController {
     private static final String TAG = "TareFactorController";
 
+    private static TareFactorController sInstance;
+
     private static final int POLICY_ALARM_MANAGER = 0;
     private static final int POLICY_JOB_SCHEDULER = 1;
 
@@ -49,10 +56,15 @@
     private String mAlarmManagerConstants;
     private String mJobSchedulerConstants;
 
-    public TareFactorController(Context context) {
+    private final ArraySet<DataChangeListener> mDataChangeListeners = new ArraySet<>();
+
+    private TareFactorController(Context context) {
         mContentResolver = context.getContentResolver();
         mResources = context.getResources();
 
+        ConfigObserver configObserver = new ConfigObserver(new Handler(Looper.getMainLooper()));
+        configObserver.start();
+
         mAlarmManagerConstants =
                 Settings.Global.getString(mContentResolver, TARE_ALARM_MANAGER_CONSTANTS);
         mJobSchedulerConstants =
@@ -65,21 +77,30 @@
         parseJobSchedulerGlobalSettings();
     }
 
+    static TareFactorController getInstance(Context context) {
+        synchronized (TareFactorController.class) {
+            if (sInstance == null) {
+                sInstance = new TareFactorController(context.getApplicationContext());
+            }
+        }
+        return sInstance;
+    }
+
     /**
      * Initialization for AlarmManager Map that sets a AM factor key to a title, default value, and
      * policy type in a data object.
      */
     private void initAlarmManagerMap() {
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED,
-                new TareFactorData(mResources.getString(R.string.tare_min_satiated_balance),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_exempted),
                         EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
-                new TareFactorData(mResources.getString(R.string.tare_headless_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_headless_app),
                         EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP,
-                new TareFactorData(mResources.getString(R.string.tare_other_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_other_app),
                         EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE,
@@ -87,9 +108,13 @@
                         EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE,
                         POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT,
-                new TareFactorData(mResources.getString(R.string.tare_max_circulation),
+                new TareFactorData(mResources.getString(R.string.tare_initial_consumption_limit),
                         EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT,
                         POLICY_ALARM_MANAGER));
+        mAlarmManagerMap.put(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT,
+                new TareFactorData(mResources.getString(R.string.tare_hard_consumption_limit),
+                        EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT,
+                        POLICY_ALARM_MANAGER));
         mAlarmManagerMap.put(EconomyManager.KEY_AM_REWARD_TOP_ACTIVITY_INSTANT,
                 new TareFactorData(mResources.getString(R.string.tare_top_activity),
                         EconomyManager.DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT,
@@ -253,15 +278,15 @@
      */
     private void initJobSchedulerMap() {
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED,
-                new TareFactorData(mResources.getString(R.string.tare_min_satiated_balance),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_exempted),
                         EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
-                new TareFactorData(mResources.getString(R.string.tare_headless_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_headless_app),
                         EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP,
-                new TareFactorData(mResources.getString(R.string.tare_other_app),
+                new TareFactorData(mResources.getString(R.string.tare_min_balance_other_app),
                         EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE,
@@ -269,9 +294,13 @@
                         EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE,
                         POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT,
-                new TareFactorData(mResources.getString(R.string.tare_max_circulation),
+                new TareFactorData(mResources.getString(R.string.tare_initial_consumption_limit),
                         EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT,
                         POLICY_JOB_SCHEDULER));
+        mJobSchedulerMap.put(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT,
+                new TareFactorData(mResources.getString(R.string.tare_hard_consumption_limit),
+                        EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT,
+                        POLICY_JOB_SCHEDULER));
         mJobSchedulerMap.put(EconomyManager.KEY_JS_REWARD_TOP_ACTIVITY_INSTANT,
                 new TareFactorData(mResources.getString(R.string.tare_top_activity),
                         EconomyManager.DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT,
@@ -474,17 +503,7 @@
      * @param factorPolicy the policy you want the title of
      */
     private String getTitle(String key, int factorPolicy) {
-        ArrayMap<String, TareFactorData> currentMap;
-        switch (factorPolicy) {
-            case POLICY_ALARM_MANAGER:
-                currentMap = mAlarmManagerMap;
-                break;
-            case POLICY_JOB_SCHEDULER:
-                currentMap = mJobSchedulerMap;
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid factor policy given");
-        }
+        final ArrayMap<String, TareFactorData> currentMap = getMap(factorPolicy);
         return currentMap.get(key).title;
     }
 
@@ -516,6 +535,11 @@
         return currentMap.get(key).factorPolicy;
     }
 
+    int getValue(String key) {
+        final int policy = getFactorType(key);
+        return getCurrentValue(key, policy);
+    }
+
     /**
      * Takes a key,edited value, and factor policy as input and assigns the new edited value to
      * be the new current value for that factors key.
@@ -545,17 +569,9 @@
         switch (factorPolicy) {
             case POLICY_ALARM_MANAGER:
                 writeConstantsToSettings(mAlarmManagerMap, TARE_ALARM_MANAGER_CONSTANTS);
-
-                mAlarmManagerConstants = Settings.Global
-                        .getString(mContentResolver, Settings.Global
-                                .TARE_ALARM_MANAGER_CONSTANTS);
                 break;
             case POLICY_JOB_SCHEDULER:
                 writeConstantsToSettings(mJobSchedulerMap, TARE_JOB_SCHEDULER_CONSTANTS);
-
-                mJobSchedulerConstants = Settings.Global
-                        .getString(mContentResolver, Settings.Global
-                                .TARE_JOB_SCHEDULER_CONSTANTS);
                 break;
         }
     }
@@ -610,4 +626,51 @@
             this.currentValue = defaultValue;
         }
     }
-}
\ No newline at end of file
+
+    interface DataChangeListener {
+        void onDataChanged();
+    }
+
+    void registerListener(DataChangeListener listener) {
+        mDataChangeListeners.add(listener);
+    }
+
+    void unregisterListener(DataChangeListener listener) {
+        mDataChangeListeners.remove(listener);
+    }
+
+    void notifyListeners() {
+        for (int i = mDataChangeListeners.size() - 1; i >= 0; --i) {
+            mDataChangeListeners.valueAt(i).onDataChanged();
+        }
+    }
+
+    private class ConfigObserver extends ContentObserver {
+
+        ConfigObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void start() {
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS), false, this);
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (uri.equals(Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS))) {
+                mAlarmManagerConstants =
+                        Settings.Global.getString(mContentResolver, TARE_ALARM_MANAGER_CONSTANTS);
+                parseAlarmManagerGlobalSettings();
+                notifyListeners();
+            } else if (uri.equals(Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS))) {
+                mJobSchedulerConstants =
+                        Settings.Global.getString(mContentResolver, TARE_JOB_SCHEDULER_CONSTANTS);
+                parseJobSchedulerGlobalSettings();
+                notifyListeners();
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/development/tare/TareFactorExpandableListAdapter.java b/src/com/android/settings/development/tare/TareFactorExpandableListAdapter.java
new file mode 100644
index 0000000..8fe4c05
--- /dev/null
+++ b/src/com/android/settings/development/tare/TareFactorExpandableListAdapter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.settings.development.tare;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+/**
+ * Creates the expandable list that will allow modifying individual factors.
+ */
+public class TareFactorExpandableListAdapter extends BaseExpandableListAdapter {
+
+    private final LayoutInflater mLayoutInflater;
+    private final TareFactorController mFactorController;
+
+    private final String[] mGroups;
+    private final String[][] mChildren;
+    private final String[][] mKeys;
+
+    TareFactorExpandableListAdapter(TareFactorController factorController,
+            LayoutInflater layoutInflater, String[] groups, String[][] children, String[][] keys) {
+        mLayoutInflater = layoutInflater;
+        mFactorController = factorController;
+
+        mGroups = groups;
+        mChildren = children;
+        mKeys = keys;
+
+        validateMappings();
+    }
+
+    private void validateMappings() {
+        if (mGroups.length != mChildren.length) {
+            throw new IllegalStateException("groups and children don't have the same length");
+        }
+        if (mChildren.length != mKeys.length) {
+            throw new IllegalStateException("children and keys don't have the same length");
+        }
+        for (int i = 0; i < mChildren.length; ++i) {
+            if (mChildren[i].length != mKeys[i].length) {
+                throw new IllegalStateException(
+                        "children and keys don't have the same length in row " + i);
+            }
+        }
+    }
+
+    @Override
+    public int getGroupCount() {
+        return mGroups.length;
+    }
+
+    @Override
+    public int getChildrenCount(int groupPosition) {
+        return mChildren[groupPosition].length;
+    }
+
+    @Override
+    public Object getGroup(int groupPosition) {
+        return mGroups[groupPosition];
+    }
+
+    @Override
+    public Object getChild(int groupPosition, int childPosition) {
+        return mChildren[groupPosition][childPosition];
+    }
+
+    @Override
+    public long getGroupId(int groupPosition) {
+        return groupPosition;
+    }
+
+    @Override
+    public long getChildId(int groupPosition, int childPosition) {
+        return childPosition;
+    }
+
+    @NonNull
+    String getKey(int groupPosition, int childPosition) {
+        return mKeys[groupPosition][childPosition];
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    @Override
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+            ViewGroup parent) {
+        if (convertView == null) {
+            convertView = mLayoutInflater.inflate(android.R.layout.simple_list_item_1, parent,
+                    false);
+        }
+        TextView factor = convertView.findViewById(android.R.id.text1);
+        factor.setText(getGroup(groupPosition).toString());
+        return convertView;
+    }
+
+    @Override
+    @SuppressLint("InflateParams") // AdapterView doesn't support addView
+    public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+            View convertView, ViewGroup parent) {
+        // Here a custom child item is used instead of android.R.simple_list_item_2 because it
+        // is more customizable for this specific UI
+        if (convertView == null) {
+            convertView = mLayoutInflater.inflate(R.layout.tare_child_item, null);
+        }
+        TextView factor = convertView.findViewById(R.id.factor);
+        TextView value = convertView.findViewById(R.id.factor_number);
+
+        factor.setText(getChild(groupPosition, childPosition).toString());
+        value.setText(String.valueOf(
+                mFactorController.getValue(getKey(groupPosition, childPosition))));
+
+        return convertView;
+    }
+
+    @Override
+    public boolean isChildSelectable(int groupPosition, int childPosition) {
+        return true;
+    }
+}