├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── trianguloy
│ │ └── continuousDataUsage
│ │ ├── activities
│ │ ├── HistoryActivity.java
│ │ ├── ListAdapter.java
│ │ ├── Main.java
│ │ └── SettingsActivity.java
│ │ ├── common
│ │ ├── Accumulated.java
│ │ ├── DataUsage.java
│ │ ├── NonTouchableView.java
│ │ ├── NumericEditText.java
│ │ ├── PeriodCalendar.java
│ │ ├── Preferences.java
│ │ ├── Tweaks.java
│ │ └── Utils.java
│ │ └── widgets
│ │ ├── AppWidgetBase.java
│ │ ├── AppWidgetProgress.java
│ │ └── AppWidgetRate.java
│ ├── play
│ ├── contact-email.txt
│ ├── contact-website.txt
│ ├── listings
│ │ └── en-US
│ │ │ ├── full-description.txt
│ │ │ ├── graphics
│ │ │ ├── feature-graphic
│ │ │ │ ├── featured.png
│ │ │ │ └── featured.xcf
│ │ │ ├── icon
│ │ │ │ └── ic_launcher-web.png
│ │ │ └── phone-screenshots
│ │ │ │ ├── Screenshot_1601111957.png
│ │ │ │ ├── Screenshot_1601111968.png
│ │ │ │ ├── Screenshot_1601112040.png
│ │ │ │ └── Screenshot_1601112106.png
│ │ │ ├── short-description.txt
│ │ │ └── title.txt
│ └── release-notes
│ │ └── en-US
│ │ └── default.txt
│ └── res
│ ├── drawable-nodpi
│ ├── preview_progress.png
│ └── preview_rate.png
│ ├── drawable
│ ├── background_progress.xml
│ ├── background_progress_white.xml
│ ├── background_rate.xml
│ ├── background_rate_white.xml
│ ├── ic_data_usage_black_24dp.xml
│ ├── ic_history.xml
│ └── ic_history_black.xml
│ ├── layout
│ ├── activity_history.xml
│ ├── activity_main.xml
│ ├── activity_settings.xml
│ ├── lv_item.xml
│ ├── widget_progress.xml
│ ├── widget_progress_short.xml
│ └── widget_rate.xml
│ ├── menu
│ └── menu_history.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-ru
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── integers.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── widgetprovider_progress.xml
│ └── widgetprovider_rate.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 | mapping.txt
5 | output.json
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | /app/release/release/
18 |
19 | # Gradle files
20 | .gradle/
21 | build/
22 |
23 | # Local configuration file (sdk path, etc)
24 | local.properties
25 |
26 | # Proguard folder generated by Eclipse
27 | proguard/
28 |
29 | # Log Files
30 | *.log
31 |
32 | # Android Studio Navigation editor temp files
33 | .navigation/
34 |
35 | # Android Studio captures folder
36 | captures/
37 |
38 | # Intellij
39 | *.iml
40 | .idea/
41 |
42 | # Keystore files
43 | *.jks
44 |
45 | # External native build folder generated in Android Studio 2.2 and later
46 | .externalNativeBuild
47 |
48 | # Google Services (e.g. APIs or Firebase)
49 | google-services.json
50 |
51 | # Freeline
52 | freeline.py
53 | freeline/
54 | freeline_project_description.json
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 TrianguloY
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Average-data-usage-widget
2 |
3 | Android app made by TrianguloY
4 |
5 | Get it on Google Play:\
6 | https://play.google.com/store/apps/details?id=com.trianguloy.continuousDataUsage \
7 | [](https://play.google.com/store/apps/details?id=com.trianguloy.continuousDataUsage)
10 |
11 | Get it on F-Droid:\
12 | https://f-droid.org/packages/com.trianguloy.continuousDataUsage/ \
13 | [](https://f-droid.org/packages/com.trianguloy.continuousDataUsage/)
16 |
17 | Check more details on the app page: \
18 | http://triangularapps.blogspot.com/p/average-data-usage-widget.html
19 |
20 | You can use parts of this project in your own ones, create pull request, or upload modified versions of it AS LONG AS you credit me.
21 |
22 | How to credit:
23 | - You must add my nick (TrianguloY) in an 'about' or 'acknowledgments' section visible to the user.
24 | - You must add a link to this GitHub main page (https://github.com/TrianguloY/Average-data-usage-widget/) or subpage (if you used a part of the code or an asset) in an 'about' or 'acknowledgments' section visible to the user.
25 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'com.github.triplet.play' version '3.7.0'
4 | }
5 |
6 | android {
7 | namespace 'com.trianguloy.continuousDataUsage'
8 | compileSdk 34
9 | defaultConfig {
10 | applicationId "com.trianguloy.continuousDataUsage"
11 | minSdkVersion 29
12 | targetSdkVersion 34
13 | versionCode 28
14 | versionName "4.1"
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled true
19 | shrinkResources true
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_17
26 | targetCompatibility JavaVersion.VERSION_17
27 | }
28 | buildFeatures {
29 | buildConfig true
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation fileTree(dir: 'libs', include: ['*.jar'])
35 | }
36 |
37 |
38 | play {
39 | track.set("beta")
40 | releaseName.set(android.defaultConfig.versionName + " (" + android.defaultConfig.versionCode + ")")
41 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
37 |
38 |
42 |
43 |
44 |
45 |
46 |
49 |
50 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/activities/HistoryActivity.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.activities;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.app.TaskStackBuilder;
6 | import android.content.ActivityNotFoundException;
7 | import android.content.ComponentName;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.os.Bundle;
11 | import android.view.Menu;
12 | import android.view.MenuItem;
13 | import android.view.View;
14 | import android.view.Window;
15 | import android.widget.Button;
16 | import android.widget.ListView;
17 | import android.widget.ProgressBar;
18 | import android.widget.TextView;
19 | import android.widget.Toast;
20 |
21 | import com.trianguloy.continuousDataUsage.R;
22 | import com.trianguloy.continuousDataUsage.common.Accumulated;
23 | import com.trianguloy.continuousDataUsage.common.DataUsage;
24 | import com.trianguloy.continuousDataUsage.common.PeriodCalendar;
25 | import com.trianguloy.continuousDataUsage.common.Preferences;
26 |
27 | import java.text.DateFormat;
28 | import java.text.SimpleDateFormat;
29 | import java.util.Calendar;
30 | import java.util.Locale;
31 |
32 |
33 | /**
34 | * Activity to show the usage each day from the period.
35 | */
36 | public class HistoryActivity extends Activity {
37 |
38 | // objects
39 | private ListAdapter adapter;
40 | private PeriodCalendar periodCalendar;
41 | private DataUsage dataUsage;
42 | private Preferences pref;
43 |
44 | // variables
45 | private int period;
46 | private TextView view_title;
47 | private ProgressBar view_loading;
48 | private Button view_right;
49 | private final DateFormat dateFormat = SimpleDateFormat.getDateInstance();
50 | private Thread thread_setPeriod = null;
51 |
52 | @Override
53 | protected void onCreate(Bundle savedInstanceState) {
54 | super.onCreate(savedInstanceState);
55 | requestWindowFeature(Window.FEATURE_ACTION_BAR);
56 | setContentView(R.layout.activity_history);
57 |
58 | // initialize objects
59 | pref = new Preferences(this);
60 | periodCalendar = new PeriodCalendar(pref);
61 | dataUsage = new DataUsage(this, pref);
62 | adapter = new ListAdapter(this);
63 |
64 | try {
65 | // check for permission explicitly
66 | dataUsage.init();
67 | } catch (DataUsage.Error e) {
68 | // can't open, probably no permission
69 | Toast.makeText(this, e.errorId, Toast.LENGTH_LONG).show();
70 | finish();
71 | }
72 |
73 | // get views
74 | ListView view_list = findViewById(R.id.h_lv_list);
75 | view_title = findViewById(R.id.h_tv_title);
76 | view_loading = findViewById(R.id.h_pb_loading);
77 | view_right = findViewById(R.id.h_btn_right);
78 |
79 | // initialize elements
80 | view_list.setAdapter(adapter);
81 | adapter.setDummyView(findViewById(R.id.h_item_dummy));
82 |
83 | // update
84 | new Accumulated(pref, dataUsage, periodCalendar).updatePeriod();
85 |
86 | period = 0;
87 | }
88 |
89 | @Override
90 | protected void onResume() {
91 | super.onResume();
92 | // When resumed, reload period
93 | setPeriod();
94 | }
95 |
96 | @Override
97 | public boolean onCreateOptionsMenu(Menu menu) {
98 | getMenuInflater().inflate(R.menu.menu_history, menu);
99 | return true;
100 | }
101 |
102 | public void onClick(View view) {
103 | switch (view.getId()) {
104 | case R.id.h_btn_left:
105 | // decrement period
106 | period--;
107 | setPeriod();
108 | break;
109 | case R.id.h_btn_right:
110 | // increment period (if possible)
111 | if (period < 0) {
112 | period++;
113 | setPeriod();
114 | }
115 | break;
116 | }
117 | }
118 |
119 | @Override
120 | public boolean onOptionsItemSelected(MenuItem item) {
121 | switch (item.getItemId()) {
122 | case R.id.h_action_info:
123 | //show info
124 | new AlertDialog.Builder(this).setMessage(R.string.h_toast_info).setCancelable(true).show();
125 | return true;
126 | case R.id.h_action_settings:
127 | //show settings
128 | startActivity(new Intent(this, SettingsActivity.class));
129 | return true;
130 | case R.id.h_action_android:
131 | //open android usage screen
132 | openAndroidUsageScreen(this);
133 | return true;
134 | case android.R.id.home:
135 | //explicitly go back to main activity
136 | final Intent upIntent = getParentActivityIntent();
137 | if (shouldUpRecreateTask(upIntent) || isTaskRoot()) {
138 | TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities();
139 | } else {
140 | navigateUpTo(upIntent);
141 | }
142 | return true;
143 | }
144 | return super.onOptionsItemSelected(item);
145 | }
146 |
147 | public static void openAndroidUsageScreen(Context cntx) {
148 | Intent settings = new Intent(Intent.ACTION_MAIN);
149 | settings.setComponent(new ComponentName("com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity"));
150 | settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
151 | try {
152 | cntx.startActivity(settings);
153 | } catch (ActivityNotFoundException e) {
154 | Toast.makeText(cntx, R.string.toast_activityNotFound, Toast.LENGTH_LONG).show();
155 | }
156 | }
157 |
158 | // reload period
159 | public void setPeriod() {
160 |
161 | //init variables
162 | final Calendar to = periodCalendar.getStartOfPeriod(period);
163 | SimpleDateFormat title_format = new SimpleDateFormat("MMM yyyy", Locale.getDefault());
164 |
165 | //get from
166 | String monthFrom = title_format.format(to.getTime());
167 | final long from = to.getTimeInMillis();
168 |
169 |
170 | //get to
171 | to.add(pref.getPeriodType(), pref.getPeriodLength());
172 | to.add(Calendar.DAY_OF_MONTH, -1);
173 | String monthTo = title_format.format(to.getTime());
174 |
175 | //set title
176 | final String month = monthTo.equals(monthFrom) ? monthFrom : monthFrom + " - " + monthTo;
177 | view_title.setText(month);
178 | to.add(Calendar.DAY_OF_MONTH, 1);
179 |
180 |
181 | //background update
182 | if (thread_setPeriod != null) {
183 | thread_setPeriod.interrupt();
184 | try {
185 | thread_setPeriod.join();
186 | } catch (InterruptedException e) {
187 | e.printStackTrace();
188 | }
189 | }
190 |
191 | //set as loading
192 | view_loading.setVisibility(View.VISIBLE);
193 | adapter.clearItems();
194 | adapter.notifyDataSetChanged();
195 | view_right.setEnabled(period < 0);
196 |
197 |
198 | thread_setPeriod = new Thread(() -> {
199 |
200 | // set period&average
201 | adapter.setTotalData(pref.getTotalData());
202 | adapter.setDataPerDay(
203 | pref.getTotalData() / Math.round((to.getTimeInMillis() - from) / 1000d / 60d / 60d / 24d)
204 | );
205 |
206 | var periodData = 0D;
207 | var days = 0;
208 |
209 | try {
210 | // each day
211 | while (from < to.getTimeInMillis()) {
212 | if (Thread.currentThread().isInterrupted()) return;
213 |
214 | long end = to.getTimeInMillis();
215 | to.add(Calendar.DAY_OF_MONTH, -1);
216 | if (to.getTimeInMillis() <= System.currentTimeMillis()) {
217 | var dayData = dataUsage.getDataFromPeriod(to.getTimeInMillis(), end);
218 | adapter.addAverageItem(dayData, dateFormat.format(to.getTime()));
219 | days++;
220 | periodData += dayData;
221 | }
222 | }
223 |
224 | adapter.addSeparator();
225 |
226 | // average & total
227 | adapter.addAverageItem(periodData / days, getString(R.string.txt_average));
228 | adapter.addTotalItem(periodData, getString(R.string.txt_total));
229 |
230 | } catch (final DataUsage.Error e) {
231 | if (Thread.currentThread().isInterrupted()) return;
232 | adapter.clearItems();
233 | runOnUiThread(() -> Toast.makeText(HistoryActivity.this, e.errorId, Toast.LENGTH_LONG).show());
234 | }
235 |
236 | if (Thread.currentThread().isInterrupted()) return;
237 |
238 | //notify
239 | runOnUiThread(() -> {
240 | adapter.notifyDataSetChanged();
241 | view_loading.setVisibility(View.GONE);
242 | });
243 | });
244 | thread_setPeriod.start();
245 |
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/activities/ListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.activities;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.BaseAdapter;
8 | import android.widget.ProgressBar;
9 | import android.widget.TextView;
10 |
11 | import com.trianguloy.continuousDataUsage.R;
12 | import com.trianguloy.continuousDataUsage.common.Preferences;
13 | import com.trianguloy.continuousDataUsage.common.Tweaks;
14 | import com.trianguloy.continuousDataUsage.common.Utils;
15 |
16 | import java.util.ArrayList;
17 |
18 | /**
19 | * ListAdapter for the {@link HistoryActivity}
20 | */
21 | public class ListAdapter extends BaseAdapter {
22 |
23 | /**
24 | * total data of the full period
25 | */
26 | private float totalData;
27 |
28 | /**
29 | * Context used
30 | */
31 | private final Context cntx;
32 |
33 | /**
34 | * Preferences
35 | */
36 | private final Preferences pref;
37 |
38 | /**
39 | * Items in the list
40 | */
41 | private ArrayList items = new ArrayList<>();
42 | private ArrayList itemsTemp = new ArrayList<>();
43 |
44 | /**
45 | * Data per day
46 | */
47 | private float dataPerDay;
48 |
49 | /**
50 | * Adds an average item, doesn't refresh
51 | */
52 | void addAverageItem(double usage, String date) {
53 | final Item item = new Item(usage, date, Item.Type.AVERAGE);
54 | itemsTemp.add(0, item);
55 |
56 | // update max width
57 | String text = item.usageString();
58 | if (text.length() > dummy_usage_text.length()) {
59 | dummy_usage_text = text;
60 | }
61 | }
62 |
63 | /**
64 | * Dummy view to extract sizes
65 | */
66 | private View dummy;
67 | private TextView dummy_usage;
68 | private String dummy_usage_text;
69 |
70 | //---------- Public -----------
71 |
72 | /**
73 | * Default constructor
74 | *
75 | * @param cntx context
76 | */
77 | ListAdapter(Context cntx) {
78 | this.cntx = cntx;
79 | this.pref = new Preferences(cntx);
80 | }
81 |
82 | /**
83 | * Adds a total item, doesn't refresh
84 | */
85 | void addTotalItem(double usage, String label) {
86 | var item = new Item(usage, label, Item.Type.TOTAL);
87 | itemsTemp.add(0, item);
88 |
89 |
90 | // update max width
91 | String text = item.usageString();
92 | if (text.length() > dummy_usage_text.length()) {
93 | dummy_usage_text = text;
94 | }
95 | }
96 |
97 | /**
98 | * Adds a separator, doesn't refresh
99 | */
100 | void addSeparator() {
101 | itemsTemp.add(0, new Item(0, "", Item.Type.SEPARATOR));
102 | }
103 |
104 | /**
105 | * sets the total data
106 | */
107 | void setTotalData(float totalData) {
108 | this.totalData = totalData;
109 | }
110 |
111 | /**
112 | * Removes all the items, doesn't refresh
113 | */
114 | void clearItems() {
115 | itemsTemp.clear();
116 | dummy_usage_text = "";
117 | }
118 |
119 | /**
120 | * sets the data per day
121 | */
122 | void setDataPerDay(float dataPerDay) {
123 | this.dataPerDay = dataPerDay;
124 | }
125 |
126 | /**
127 | * Sets the dummy view
128 | */
129 | public void setDummyView(View dummy) {
130 | this.dummy = dummy;
131 | dummy_usage = dummy.findViewById(R.id.lv_txt_usage);
132 | }
133 |
134 | //------------ Adapter overrides ---------------
135 |
136 |
137 | @Override
138 | public void notifyDataSetChanged() {
139 | items = itemsTemp;
140 | itemsTemp = new ArrayList<>();
141 | dummy_usage.setText(dummy_usage_text);
142 | super.notifyDataSetChanged();
143 | }
144 |
145 | @Override
146 | public int getCount() {
147 | return items.size();
148 | }
149 |
150 | @Override
151 | public long getItemId(int i) {
152 | return items.get(i).label.hashCode();
153 | }
154 |
155 | @Override
156 | public Object getItem(int i) {
157 | return items.get(i);
158 | }
159 |
160 | @Override
161 | public View getView(int position, View convertView, ViewGroup parent) {
162 |
163 | // inflate the layout for each list row
164 | if (convertView == null) {
165 | convertView = LayoutInflater.from(cntx).
166 | inflate(R.layout.lv_item, parent, false);
167 |
168 | for (int id : new int[]{R.id.lv_txt_date, R.id.lv_txt_usage, R.id.lv_pgb_negative, R.id.lv_pgb_positive}) {
169 | View view = convertView.findViewById(id);
170 | ViewGroup.LayoutParams lp = view.getLayoutParams();
171 | lp.width = dummy.findViewById(id).getMeasuredWidth();
172 | view.setLayoutParams(lp);
173 | }
174 | }
175 |
176 | // get current item to be displayed
177 | Item currentItem = (Item) getItem(position);
178 |
179 | // get the Views
180 | TextView txt_date = convertView.findViewById(R.id.lv_txt_date);
181 | TextView txt_usage = convertView.findViewById(R.id.lv_txt_usage);
182 | ProgressBar pgb_positive = convertView.findViewById(R.id.lv_pgb_positive);
183 | ProgressBar pgb_negative = convertView.findViewById(R.id.lv_pgb_negative);
184 |
185 | if (currentItem.type == Item.Type.SEPARATOR) {
186 | // separator, just hide and return
187 | txt_date.setVisibility(View.GONE);
188 | txt_usage.setVisibility(View.GONE);
189 | pgb_positive.setVisibility(View.GONE);
190 | pgb_negative.setVisibility(View.GONE);
191 | return convertView;
192 | } else {
193 | // non-separator, unhide
194 | txt_date.setVisibility(View.VISIBLE);
195 | txt_usage.setVisibility(View.VISIBLE);
196 | pgb_positive.setVisibility(View.VISIBLE);
197 | pgb_negative.setVisibility(View.VISIBLE);
198 | }
199 |
200 | // sets the properties
201 | txt_date.setText(currentItem.label);
202 | txt_usage.setText(currentItem.usageString());
203 |
204 | double rate = currentItem.usage / (currentItem.type == Item.Type.TOTAL ? totalData : dataPerDay);
205 | if (rate > 1) {
206 | // more than average/total
207 | pgb_negative.setProgress(0);
208 | pgb_positive.setProgress(Utils.dbl2int((rate % 1) * pgb_positive.getMax()));
209 | pgb_positive.setSecondaryProgress(rate > 2 ? pgb_positive.getMax() : 0);
210 | } else {
211 | // less than average
212 | pgb_negative.setProgress(Utils.dbl2int((1 - rate) * pgb_negative.getMax()));
213 | pgb_positive.setProgress(0);
214 | pgb_positive.setSecondaryProgress(0);
215 | }
216 |
217 | // tweaks
218 | if (pref.getTweak(Tweaks.Tweak.capNoWarp) && pgb_positive.getSecondaryProgress() > 0) {
219 | // cap
220 | pgb_positive.setProgress(pgb_positive.getMax());
221 | pgb_positive.setSecondaryProgress(0);
222 | }
223 |
224 | // returns the view for the current row
225 | return convertView;
226 | }
227 |
228 | /**
229 | * Each element in the list
230 | */
231 | class Item {
232 | /**
233 | * The day
234 | */
235 | String label;
236 |
237 | /**
238 | * The usage
239 | */
240 | double usage;
241 | /**
242 | * Type of the row
243 | */
244 | Type type;
245 | Item(double usage, String label, Type type) {
246 | this.usage = usage;
247 | this.label = label;
248 | this.type = type;
249 | }
250 |
251 | /**
252 | * @return the usage string
253 | */
254 | String usageString() {
255 | return Utils.formatData(pref, "{0} / {M}", usage, (double) (type == Type.TOTAL ? totalData : dataPerDay));
256 | }
257 |
258 | /**
259 | * Type of the row
260 | */
261 | enum Type {
262 | /**
263 | * An average value
264 | */
265 | AVERAGE,
266 | /**
267 | * A total value
268 | */
269 | TOTAL,
270 | /**
271 | * A separator
272 | */
273 | SEPARATOR
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/activities/Main.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.activities;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.text.method.LinkMovementMethod;
7 | import android.view.View;
8 | import android.widget.FrameLayout;
9 | import android.widget.RemoteViews;
10 | import android.widget.TextView;
11 | import android.widget.Toast;
12 |
13 | import com.trianguloy.continuousDataUsage.R;
14 | import com.trianguloy.continuousDataUsage.common.Preferences;
15 | import com.trianguloy.continuousDataUsage.common.Tweaks;
16 | import com.trianguloy.continuousDataUsage.widgets.AppWidgetProgress;
17 |
18 | /**
19 | * Main activity. Info
20 | */
21 | public class Main extends Activity {
22 |
23 | private FrameLayout preview;
24 |
25 | /**
26 | * That function
27 | *
28 | * @param savedInstanceState no idea what this is
29 | */
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_main);
34 |
35 | //clickable links
36 | ((TextView) findViewById(R.id.stt_txt_info)).setMovementMethod(LinkMovementMethod.getInstance());
37 |
38 | preview = findViewById(R.id.m_f_widget);
39 |
40 | }
41 |
42 | /**
43 | * When the activity is resumed, update the widget
44 | */
45 | @Override
46 | protected void onResume() {
47 |
48 | updatePreview();
49 |
50 | super.onResume();
51 | }
52 |
53 | /**
54 | * A button is clicked
55 | *
56 | * @param view which one
57 | */
58 | public void onClick(View view) {
59 | switch (view.getId()) {
60 | case R.id.m_btn_history:
61 | //open history
62 | startActivity(new Intent(this, HistoryActivity.class));
63 | break;
64 | case R.id.m_btn_settings:
65 | //open settings
66 | startActivity(new Intent(this, SettingsActivity.class));
67 | break;
68 | case R.id.m_f_widget:
69 | //show info
70 | Toast.makeText(this, R.string.toast_preview, Toast.LENGTH_LONG).show();
71 | break;
72 | }
73 |
74 | }
75 |
76 | /**
77 | * Updates the preview
78 | */
79 | private void updatePreview() {
80 | // Construct the RemoteViews object
81 | RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.widget_progress);
82 | AppWidgetProgress.updateViews(this, remoteViews);
83 |
84 | try {
85 |
86 | View views = remoteViews.apply(this, preview);
87 | preview.removeAllViews();
88 | preview.addView(views);
89 |
90 | } catch (Throwable ignore) {
91 |
92 | // if exception, disable
93 | Toast.makeText(this, "Exception detected, disabling tweaks", Toast.LENGTH_LONG).show();
94 | Preferences prefs = new Preferences(this);
95 | for (Tweaks.Tweak item : Tweaks.Tweak.values()) {
96 | prefs.setTweak(item, false);
97 | }
98 | updatePreview();
99 |
100 | }
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/activities/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.activities;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.app.AlertDialog;
6 | import android.app.AppOpsManager;
7 | import android.app.DatePickerDialog;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.pm.PackageManager;
11 | import android.graphics.Color;
12 | import android.os.Build;
13 | import android.os.Bundle;
14 | import android.os.Process;
15 | import android.provider.Settings;
16 | import android.text.InputType;
17 | import android.text.method.LinkMovementMethod;
18 | import android.view.View;
19 | import android.view.inputmethod.EditorInfo;
20 | import android.widget.AdapterView;
21 | import android.widget.ArrayAdapter;
22 | import android.widget.CheckBox;
23 | import android.widget.EditText;
24 | import android.widget.LinearLayout;
25 | import android.widget.SeekBar;
26 | import android.widget.Spinner;
27 | import android.widget.TextView;
28 | import android.widget.Toast;
29 |
30 | import com.trianguloy.continuousDataUsage.R;
31 | import com.trianguloy.continuousDataUsage.common.Accumulated;
32 | import com.trianguloy.continuousDataUsage.common.DataUsage;
33 | import com.trianguloy.continuousDataUsage.common.NumericEditText;
34 | import com.trianguloy.continuousDataUsage.common.PeriodCalendar;
35 | import com.trianguloy.continuousDataUsage.common.Preferences;
36 | import com.trianguloy.continuousDataUsage.common.Tweaks;
37 | import com.trianguloy.continuousDataUsage.common.Utils;
38 |
39 | import java.text.SimpleDateFormat;
40 | import java.util.Arrays;
41 | import java.util.Calendar;
42 | import java.util.List;
43 |
44 | /**
45 | * Main activity: settings
46 | */
47 | public class SettingsActivity extends Activity {
48 |
49 | // used classes
50 | private Preferences pref = null;
51 | private Accumulated accumulated;
52 |
53 | // views
54 | private NumericEditText view_accumulated;
55 | private TextView view_txt_decimals;
56 | private EditText txt_periodStart;
57 | private TextView txt_notif;
58 | private LinearLayout ll_notif;
59 | private TextView txt_usageStats;
60 |
61 |
62 | /**
63 | * When the activity is created, like a constructor
64 | *
65 | * @param savedInstanceState previous saved state, used on super only
66 | */
67 | @Override
68 | protected void onCreate(Bundle savedInstanceState) {
69 | super.onCreate(savedInstanceState);
70 | setContentView(R.layout.activity_settings);
71 |
72 | // get objects
73 | pref = new Preferences(this);
74 | accumulated = new Accumulated(pref, new DataUsage(this, pref), new PeriodCalendar(pref));
75 |
76 | // get views
77 | txt_notif = findViewById(R.id.stt_txt_notif);
78 | ll_notif = findViewById(R.id.ll_notif);
79 | txt_usageStats = findViewById(R.id.stt_txt_usageStats);
80 |
81 | //initializes
82 | initialize();
83 |
84 | // updates
85 | accumulated.updatePeriod();
86 | updateViews();
87 |
88 | }
89 |
90 |
91 | /**
92 | * Initialize all the views
93 | */
94 | private void initialize() {
95 |
96 | // totaldata
97 | final NumericEditText view_totalData = findViewById(R.id.stt_edTxt_totalData);
98 | view_totalData.initFloat(false, false, pref.getTotalData(), number -> pref.setTotalData(number));
99 |
100 | // period length, amount
101 | final NumericEditText txt_periodLength = findViewById(R.id.stt_edTxt_periodLength);
102 | txt_periodLength.initInt(false, false, pref.getPeriodLength(), number -> pref.setPeriodLength(number));
103 |
104 | // period length, type
105 | final Spinner spn_periodType = findViewById(R.id.stt_spn_periodType);
106 | final List periodTypes = Arrays.asList(Calendar.DAY_OF_MONTH, Calendar.MONTH);
107 | spn_periodType.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{getString(R.string.days), getString(R.string.months)}));
108 | spn_periodType.setSelection(periodTypes.indexOf(pref.getPeriodType()));
109 | spn_periodType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
110 | @Override
111 | public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
112 | pref.setPeriodType(periodTypes.get(i));
113 | }
114 |
115 | @Override
116 | public void onNothingSelected(AdapterView> adapterView) {
117 | pref.setPeriodType(periodTypes.get(0));
118 | }
119 | });
120 |
121 | // periodStart
122 | txt_periodStart = findViewById(R.id.stt_edTxt_periodStart);
123 |
124 | // decimals
125 | view_txt_decimals = findViewById(R.id.stt_txt_decimals);
126 | SeekBar view_sb_decimals = findViewById(R.id.stt_sb_decimals);
127 | view_sb_decimals.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
128 | @Override
129 | public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
130 | view_txt_decimals.setText(Integer.toString(i));
131 | pref.setDecimals(i);
132 | }
133 |
134 | @Override
135 | public void onStartTrackingTouch(SeekBar seekBar) {
136 | }
137 |
138 | @Override
139 | public void onStopTrackingTouch(SeekBar seekBar) {
140 | }
141 | });
142 | view_sb_decimals.setProgress(pref.getDecimals(), false); // setProgress may not update if the value is the same
143 | view_txt_decimals.setText(Integer.toString(pref.getDecimals()));
144 |
145 | // GB
146 | CheckBox view_gb = findViewById(R.id.stt_chk_gb);
147 | view_gb.setChecked(pref.getGB());
148 | view_gb.setOnCheckedChangeListener((compoundButton, b) -> pref.setGB(b));
149 |
150 | // inv
151 | var view_inv = this.findViewById(R.id.stt_chk_inv);
152 | view_inv.setChecked(pref.getInv());
153 | view_inv.setOnCheckedChangeListener((compoundButton, b) -> pref.setInv(b));
154 |
155 | // alternate conversion
156 | final CheckBox view_alternateConversion = findViewById(R.id.stt_chkBx_alternateConversion);
157 | view_alternateConversion.setChecked(pref.getAltConversion());
158 | view_alternateConversion.setOnClickListener(view -> pref.setAltConversion(view_alternateConversion.isChecked()));
159 |
160 | // accumulated periods
161 | final NumericEditText view_sb_savedPeriods = findViewById(R.id.stt_edTxt_savedPeriods);
162 | view_sb_savedPeriods.initInt(true, false, pref.getSavedPeriods(), number -> pref.setSavedPeriods(number));
163 |
164 | // accumulated megas
165 | view_accumulated = findViewById(R.id.stt_edTxt_accum);
166 | view_accumulated.initFloat(true, true, pref.getAccumulated(), number -> pref.setAccumulated(number));
167 |
168 | // clickable links
169 | ((TextView) findViewById(R.id.stt_txt_perm_us)).setMovementMethod(LinkMovementMethod.getInstance());
170 |
171 | }
172 |
173 | private void updateViews() {
174 | // period start
175 | final Calendar periodStart = pref.getPeriodStart();
176 | txt_periodStart.setText(SimpleDateFormat.getDateInstance().format(periodStart.getTime()));
177 |
178 | // accumulated megas
179 | view_accumulated.setValue(pref.getAccumulated());
180 |
181 | // getUsageStats permission
182 | int mode = AppOpsManager.MODE_DEFAULT;
183 | AppOpsManager appOps = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
184 | if (appOps != null) {
185 | //check permission
186 | mode = appOps.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, Process.myUid(), getPackageName());
187 | }
188 | setPermissionState(txt_usageStats, mode == AppOpsManager.MODE_ALLOWED, true);
189 |
190 | // notifications permission
191 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
192 | setPermissionState(txt_notif, checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED, false);
193 | ll_notif.setVisibility(View.VISIBLE);
194 | } else {
195 | ll_notif.setVisibility(View.GONE);
196 | }
197 | }
198 |
199 |
200 | /**
201 | * Updates the specified id with the specified state.
202 | * state=true -> green and 'permission granted'
203 | * state=false && !required -> orange and 'permission not granted'
204 | * state=false && required -> red and 'permission needed'
205 | */
206 | private void setPermissionState(TextView txt, boolean granted, boolean required) {
207 | txt.setText(granted ? getString(R.string.txt_permissionGranted)
208 | : required ? getString(R.string.txt_permissionNeeded)
209 | : getString(R.string.txt_permissionNotGranted));
210 | txt.setBackgroundColor(granted ? Color.argb(128, 0, 255, 0) // green
211 | : required ? Color.argb(128, 255, 0, 0) // orange
212 | : Color.argb(128, 255, 127, 0)); // red
213 | }
214 |
215 |
216 | /**
217 | * Button clicked
218 | *
219 | * @param view which button
220 | */
221 | public void onClick(View view) {
222 | switch (view.getId()) {
223 | //open usage settings to give permission
224 | case R.id.stt_btn_usageStats -> startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
225 | //request 'notifications permission' button, request permission
226 | case R.id.stt_btn_notif -> {
227 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
228 | requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
229 | }
230 | // start date picker
231 | case R.id.stt_edTxt_periodStart -> pickPeriodStart();
232 | case R.id.stt_btn_accum -> {
233 | //auto-calculate accumulated
234 | calculate();
235 | }
236 | // show tweaks
237 | case R.id.stt_btn_tweaks -> new Tweaks(pref, this).showDialog();
238 | }
239 | }
240 |
241 | /**
242 | * Allows the user to choose how to calculate the accumulated data
243 | */
244 | private void calculate() {
245 | new AlertDialog.Builder(this)
246 | .setTitle(R.string.btn_settings_accum)
247 | .setItems(R.array.itms_settings_accum, (dialog, which) -> {
248 | try {
249 | switch (which) {
250 | case 0 -> {
251 | view_accumulated.setValue((float) accumulated.autoCalculateAccumulated());
252 |
253 | // update if necessary
254 | txt_periodStart.setText(SimpleDateFormat.getDateInstance().format(pref.getPeriodStart().getTime()));
255 | }
256 | case 1 -> setVisibleData();
257 | }
258 | } catch (DataUsage.Error e) {
259 | Toast.makeText(this, getString(e.errorId), Toast.LENGTH_LONG).show();
260 | }
261 | })
262 | .show();
263 |
264 |
265 | }
266 |
267 | /**
268 | * Calculates the accumulated data based on the visible amount
269 | */
270 | private void setVisibleData() throws DataUsage.Error {
271 |
272 | double data = accumulated.getUsedDataFromCurrentPeriod();
273 |
274 | // invert data
275 | if (pref.getInv()) {
276 | data = pref.getTotalData() - data;
277 | }
278 |
279 | final float[] input = {(float) data};
280 | NumericEditText txt = new NumericEditText(this);
281 | txt.setInputType(EditorInfo.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED);
282 | txt.initFloat(true, true, input[0], number -> input[0] = number);
283 | new AlertDialog.Builder(this)
284 | .setTitle(R.string.txt_visible_title)
285 | .setMessage(R.string.txt_visible_message)
286 | .setView(txt)
287 | .setPositiveButton(android.R.string.ok, (dialog, which) -> {
288 |
289 | // re-invert data
290 | if (pref.getInv()) {
291 | input[0] = pref.getTotalData() - input[0];
292 | }
293 |
294 | try {
295 | view_accumulated.setValue((float) accumulated.setUsedDataFromCurrentPeriod(input[0]));
296 | } catch (DataUsage.Error e) {
297 | Toast.makeText(this, getString(e.errorId), Toast.LENGTH_LONG).show();
298 | }
299 |
300 | // update if necessary
301 | txt_periodStart.setText(SimpleDateFormat.getDateInstance().format(pref.getPeriodStart().getTime()));
302 | })
303 | .setNegativeButton(android.R.string.cancel, null)
304 | .show();
305 | }
306 |
307 | /**
308 | * Shows a date dialog to change the start date
309 | */
310 | private void pickPeriodStart() {
311 | final DatePickerDialog dialog = new DatePickerDialog(this);
312 | dialog.setOnDateSetListener((datePicker, year, month, day) -> {
313 | // new date
314 | final Calendar cal = PeriodCalendar.from(year, month, day);
315 | txt_periodStart.setText(SimpleDateFormat.getDateInstance().format(cal.getTime()));
316 | pref.setPeriodStart(cal);
317 | });
318 | // set
319 | final Calendar cal = pref.getPeriodStart();
320 | dialog.updateDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH));
321 | // show
322 | dialog.show();
323 | }
324 |
325 |
326 | /**
327 | * Activity resumed, probably after opening usage settings, updates views accordingly.
328 | */
329 | @Override
330 | protected void onResume() {
331 | super.onResume();
332 | accumulated.updatePeriod();
333 | updateViews();
334 | }
335 |
336 | @Override
337 | protected void onPause() {
338 | super.onPause();
339 | Utils.updateAllWidgets(this);
340 | }
341 |
342 | /**
343 | * After requesting permissions, updates views accordingly.
344 | */
345 | @Override
346 | public void onRequestPermissionsResult(int i, String[] s, int[] j) {
347 | updateViews();
348 | }
349 |
350 | }
351 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/Accumulated.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.util.Log;
4 |
5 | import com.trianguloy.continuousDataUsage.BuildConfig;
6 |
7 | /**
8 | * Functions related to the accumulated data.
9 | */
10 | public class Accumulated {
11 |
12 | // objects
13 | private final Preferences pref;
14 | private final DataUsage dataUsage;
15 | private final PeriodCalendar periodCalendar;
16 |
17 | public Accumulated(Preferences pref, DataUsage dataUsage, PeriodCalendar periodCalendar) {
18 | this.pref = pref;
19 | this.dataUsage = dataUsage;
20 | this.periodCalendar = periodCalendar;
21 | }
22 |
23 | /**
24 | * Returns the used data from the current period (used - accumulated)
25 | */
26 | public double getUsedDataFromCurrentPeriod() throws DataUsage.Error {
27 | double usedData = dataUsage.getDataFromPeriod(periodCalendar.getLimitsOfPeriod(0).first, Long.MAX_VALUE);
28 |
29 | // subtract accumulated/offset from previous period
30 | usedData -= pref.getAccumulated();
31 |
32 | return usedData;
33 | }
34 |
35 | /**
36 | * Sets the used data from the current period (by changing the accumulated value, which then returns)
37 | */
38 | public double setUsedDataFromCurrentPeriod(double usedData) throws DataUsage.Error {
39 | double accumulated = dataUsage.getDataFromPeriod(periodCalendar.getLimitsOfPeriod(0).first, Long.MAX_VALUE) - usedData;
40 | pref.setAccumulated((float) accumulated);
41 | return accumulated;
42 | }
43 |
44 | /**
45 | * Checks if the currently saved period and accumulated data needs update, and does so.
46 | */
47 | public void updatePeriod() {
48 | int current = periodCalendar.getCurrentPeriod();
49 |
50 | if (current != 0) {
51 | // new period
52 |
53 | // update start of period
54 | pref.setPeriodStart(periodCalendar.getStartOfPeriod(current));
55 | if (BuildConfig.DEBUG && periodCalendar.getCurrentPeriod() != 0) {
56 | throw new AssertionError("Current period is " + periodCalendar.getCurrentPeriod() + ", not 0!");
57 | }
58 |
59 | // calculate new accumulated data
60 | try {
61 | pref.setAccumulated((float) calculateAccumulated(pref.getAccumulated(), -current, false, periodCalendar));
62 | } catch (DataUsage.Error ignore) {
63 | // can't get, just ignore
64 | }
65 | Log.d("UPDATE", "Updated");
66 | }
67 | }
68 |
69 | // -----------------------------------
70 |
71 | /**
72 | * Recursive function to get the accumulated data starting from any previous period (ignores empty)
73 | *
74 | * @param accum accumulated data in latest period
75 | * @param period which latest period
76 | * @param skipEmpty if true, unspent months will be skipped
77 | * @param perCal PeriodCalendar object
78 | * @return accumulated data in previous current period
79 | * @throws DataUsage.Error if can't calculate it
80 | */
81 | public double calculateAccumulated(double accum, int period, boolean skipEmpty, PeriodCalendar perCal) throws DataUsage.Error {
82 | if (period >= 0) {
83 | //end of recursion, final period
84 | return accum;
85 | }
86 |
87 | // onto next period
88 | double dataInPeriod = dataUsage.getDataFromPeriod(perCal.getLimitsOfPeriod(period));
89 |
90 | // skip if required and nothing was spent (device not configured yet)
91 | if (!(skipEmpty && dataInPeriod == 0)) {
92 |
93 | // calculate accumulated
94 | accum += pref.getTotalData() - dataInPeriod;
95 | if (accum < 0) accum = 0; // if used more, accumulated=0 (used all, nothing saved)
96 | float periodsData = pref.getTotalData() * pref.getSavedPeriods();
97 | if (accum > periodsData) accum = periodsData; // if accumulated more than possible, cut
98 | skipEmpty = false;
99 |
100 | }
101 |
102 | return calculateAccumulated(accum, period + 1, skipEmpty, perCal);
103 | }
104 |
105 | /**
106 | * Tries to calculate the accumulated data of the previous period from the latest 100 periods (ignoring empty)
107 | *
108 | * @return calculated accumulated data
109 | * @throws DataUsage.Error if can't get data
110 | */
111 | public double autoCalculateAccumulated() throws DataUsage.Error {
112 | return calculateAccumulated(0, -100, true, periodCalendar);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/DataUsage.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.Manifest;
4 | import android.app.usage.NetworkStats;
5 | import android.app.usage.NetworkStatsManager;
6 | import android.content.Context;
7 | import android.content.pm.PackageManager;
8 | import android.net.ConnectivityManager;
9 | import android.os.RemoteException;
10 | import android.telephony.TelephonyManager;
11 | import android.util.Log;
12 | import android.util.Pair;
13 |
14 | import com.trianguloy.continuousDataUsage.R;
15 |
16 | /**
17 | * Data-related functions.
18 | */
19 | public class DataUsage {
20 |
21 | /**
22 | * Returned when calling a function
23 | */
24 | public static class Error extends Exception {
25 | /**
26 | * The id of the string resource with the error
27 | */
28 | public int errorId;
29 |
30 | Error(int errorId) {
31 | this.errorId = errorId;
32 | }
33 | }
34 |
35 | /**
36 | * Preferences
37 | */
38 | private final Preferences pref;
39 |
40 | /**
41 | * Context
42 | */
43 | private final Context cntx;
44 |
45 |
46 | //------------ Public --------------
47 |
48 | /**
49 | * Constructor to avoid duplicated Preferences
50 | *
51 | * @param cntx context
52 | * @param pref preferences class
53 | */
54 | public DataUsage(Context cntx, Preferences pref) {
55 | this.pref = pref;
56 | this.cntx = cntx;
57 | }
58 |
59 |
60 | /**
61 | * Initializes internal data
62 | */
63 | public void init() throws Error {
64 | //get service
65 | nsm = cntx.getSystemService(NetworkStatsManager.class);
66 | if (nsm == null) {
67 | //can't get NetworkStatsManager
68 | Log.d("widget", "error on NetworkStatsManager");
69 | throw new Error(R.string.txt_widget_errorService);
70 | }
71 | }
72 |
73 | /**
74 | * Returns the data usage on the given period
75 | *
76 | * @param from_to pair start&end of period
77 | * @return data usage in period
78 | * @throws Error if can't get the data
79 | */
80 | public double getDataFromPeriod(Pair from_to) throws Error {
81 | return getDataFromPeriod(from_to.first, from_to.second);
82 | }
83 |
84 | /**
85 | * Returns the data usage on the given period
86 | *
87 | * @param from start of period
88 | * @param to end of period
89 | * @return data usage in period
90 | * @throws Error if can't get the data
91 | */
92 | public double getDataFromPeriod(long from, long to) throws Error {
93 |
94 | //get data
95 | NetworkStats.Bucket bucket;
96 | try {
97 | bucket = getNsm().querySummaryForDevice(ConnectivityManager.TYPE_MOBILE, null, from, to);
98 | } catch (RemoteException e) {
99 | Log.d("widget", "error on querySummaryForDevice-RemoteException");
100 | throw new Error(R.string.txt_widget_errorQuering);
101 | } catch (SecurityException se) {
102 | Log.d("widget", "error on querySummaryForDevice-SecurityException");
103 | throw new Error(R.string.txt_widget_noPermission);
104 | }
105 |
106 | double bytesConversion = pref.getAltConversion() ?
107 | 1f / 1000f / 1000f
108 | :
109 | 1f / 1024f / 1024f;
110 | return (bucket.getRxBytes() + bucket.getTxBytes()) * bytesConversion;
111 | }
112 |
113 | // ------------------- internal data -------------------
114 |
115 |
116 | /**
117 | * Class for the calculations
118 | */
119 | private NetworkStatsManager nsm = null;
120 |
121 | private NetworkStatsManager getNsm() throws Error {
122 | if (nsm == null)
123 | init();
124 | return nsm;
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/NonTouchableView.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 | import android.widget.FrameLayout;
7 |
8 | /**
9 | * A view that removes all touches
10 | */
11 | public class NonTouchableView extends FrameLayout {
12 |
13 | public NonTouchableView(Context context) {
14 | super(context);
15 | }
16 |
17 | public NonTouchableView(Context context, AttributeSet attrs) {
18 | super(context, attrs);
19 | }
20 |
21 | public NonTouchableView(Context context, AttributeSet attrs, int defStyleAttr) {
22 | super(context, attrs, defStyleAttr);
23 | }
24 |
25 | public NonTouchableView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
26 | super(context, attrs, defStyleAttr, defStyleRes);
27 | }
28 |
29 | @Override
30 | public boolean onInterceptTouchEvent (MotionEvent ev){
31 | return true;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/NumericEditText.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.content.Context;
4 | import android.text.Editable;
5 | import android.text.TextWatcher;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 | import android.widget.EditText;
9 |
10 | import java.text.NumberFormat;
11 | import java.text.ParseException;
12 | import java.util.Locale;
13 |
14 | public class NumericEditText extends EditText {
15 | private boolean mAllowZero = false;
16 | private boolean mAllowNegative = false;
17 |
18 | public NumericEditText(Context context) {
19 | super(context);
20 | }
21 |
22 | public NumericEditText(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | }
25 |
26 | // ------------------- Integer -------------------
27 | private OnNewIntListener mIntListener = null;
28 |
29 | public void initInt(boolean allowZero, boolean allowNegative, int value, OnNewIntListener listener) {
30 | mAllowZero = allowZero;
31 | mAllowNegative = allowNegative;
32 | addTextChangedListener(new TextWatcher() {
33 | @Override
34 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
35 | }
36 |
37 | @Override
38 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
39 | }
40 |
41 | @Override
42 | public void afterTextChanged(Editable editable) {
43 | // empty = nothing
44 | if (editable.toString().isEmpty()) return;
45 |
46 | try {
47 | int value = NumberFormat.getInstance(Locale.US).parse(editable.toString()).intValue();
48 | if ((value > 0) || (mAllowZero && value == 0)) {
49 | // valid number
50 | setHint(String.format(Locale.US, "%s", value));
51 | if (mIntListener != null) mIntListener.newNumber(value);
52 | } else {
53 | // invalid number
54 | setText("");
55 | }
56 | } catch (ParseException | NullPointerException e) {
57 | Log.d("settings", "numberformatexception");
58 | e.printStackTrace();
59 | }
60 | }
61 | });
62 | setValue(value);
63 | mIntListener = listener;
64 | }
65 |
66 | public void setValue(int value) {
67 | setText(String.format(Locale.US, "%s", value));
68 | }
69 |
70 | public interface OnNewIntListener {
71 | void newNumber(int number);
72 | }
73 |
74 | // ------------------- Float -------------------
75 |
76 | private OnNewFloatListener mFloatListener = null;
77 |
78 | public void initFloat(boolean allowZero, boolean allowNegative, float initial, OnNewFloatListener listener) {
79 | mAllowZero = allowZero;
80 | mAllowNegative = allowNegative;
81 | addTextChangedListener(new TextWatcher() {
82 | @Override
83 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
84 | }
85 |
86 | @Override
87 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
88 | }
89 |
90 | @Override
91 | public void afterTextChanged(Editable editable) {
92 | // empty = nothing
93 | if (editable.toString().isEmpty()) return;
94 |
95 | try {
96 | float value = NumberFormat.getInstance(Locale.US).parse(editable.toString()).floatValue();
97 | if ((mAllowNegative && value < 0) || value > 0 || (mAllowZero && value == 0)) {
98 | // valid number
99 | setHint(String.format(Locale.US, "%s", value));
100 | if (mFloatListener != null) mFloatListener.newNumber(value);
101 | } else {
102 | // invalid number
103 | setText("");
104 | }
105 | } catch (ParseException | NullPointerException e) {
106 | Log.d("settings", "numberformatexception");
107 | e.printStackTrace();
108 | }
109 | }
110 | });
111 | setValue(initial);
112 | mFloatListener = listener;
113 | }
114 |
115 | public void setValue(float value) {
116 | setText(String.format(Locale.US, "%s", value));
117 | }
118 |
119 | public interface OnNewFloatListener {
120 | void newNumber(float number);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/PeriodCalendar.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.util.Pair;
4 |
5 | import java.util.Calendar;
6 |
7 | /**
8 | * Calendar-related functions, manages a period
9 | */
10 | public class PeriodCalendar {
11 |
12 | private final Preferences pref;
13 |
14 | /**
15 | * Constructor
16 | *
17 | * @param pref preferences to get info about saved period
18 | */
19 | public PeriodCalendar(Preferences pref) {
20 | this.pref = pref;
21 | }
22 |
23 | /**
24 | * Returns the start and end millis of the specified period
25 | *
26 | * @param period 0 for current, -1 for previous, -2 for two previous...
27 | * @return start&end millis pair
28 | */
29 | public Pair getLimitsOfPeriod(int period) {
30 |
31 | Calendar cal = getStartOfPeriod(period);
32 |
33 | // get start of the period
34 | long startOfPeriod = cal.getTimeInMillis();
35 |
36 | // get end of period
37 | cal.add(pref.getPeriodType(), pref.getPeriodLength());
38 | long endOfPeriod = cal.getTimeInMillis();
39 |
40 | return new Pair<>(startOfPeriod, endOfPeriod);
41 | }
42 |
43 | /**
44 | * @return the current period based on the saved prefs.
45 | * Almost always 0
46 | * 1 (or more) when period changes
47 | * -1 or less when time travel
48 | */
49 | public int getCurrentPeriod() {
50 | int period = 0;
51 |
52 | long now = System.currentTimeMillis();
53 |
54 | while (true) {
55 | final Pair limits = getLimitsOfPeriod(period);
56 | if (limits.first <= now && now < limits.second) return period;
57 | if (now >= limits.second) period++;
58 | if (now < limits.first) period--;
59 | }
60 | }
61 |
62 |
63 | // ----------------
64 |
65 | /**
66 | * @param period 0 for current, -1 for previous...
67 | * @return a calendar at the start of the specified period
68 | */
69 | public Calendar getStartOfPeriod(int period) {
70 | Calendar cal = pref.getPeriodStart();
71 |
72 | // goto period wanted
73 | while (period > 0) {
74 | // add length for future period
75 | cal.add(pref.getPeriodType(), pref.getPeriodLength());
76 | period--;
77 | }
78 | while (period < 0) {
79 | // substract length for past (or current) period
80 | cal.add(pref.getPeriodType(), -pref.getPeriodLength());
81 | period++;
82 | }
83 |
84 | return cal;
85 | }
86 |
87 | // ------------------- static -------------------
88 |
89 | /**
90 | * @return today as a calendar
91 | */
92 | public static Calendar today() {
93 | Calendar cal = Calendar.getInstance();
94 | cal.set(Calendar.HOUR_OF_DAY, 0);
95 | cal.set(Calendar.MINUTE, 0);
96 | cal.set(Calendar.SECOND, 0);
97 | cal.set(Calendar.MILLISECOND, 0);
98 | return cal;
99 | }
100 |
101 | /**
102 | * @return A calendar from a @param year, @param month and @param day
103 | */
104 | public static Calendar from(int year, int month, int day) {
105 | Calendar cal = today();
106 | cal.set(Calendar.YEAR, year);
107 | cal.set(Calendar.MONTH, month);
108 | cal.set(Calendar.DAY_OF_MONTH, day);
109 | return cal;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/Preferences.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import java.util.Calendar;
7 | import java.util.Collections;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | /**
12 | * Utility class to manage the shared preferences
13 | */
14 |
15 | public class Preferences {
16 |
17 | /**
18 | * SharedPrefs
19 | */
20 | private static final String PREF_NAME = "pref";
21 | private final SharedPreferences sharedPreferences;
22 |
23 | private static final String KEY_INV = "inverse";
24 |
25 |
26 | /**
27 | * Total data (float > 0)
28 | */
29 | private static final String KEY_TOTALDATA = "totalData";
30 | private static final float DEFAULT_TOTALDATA = 1024;
31 |
32 | public float getTotalData() {
33 | return sharedPreferences.getFloat(KEY_TOTALDATA, DEFAULT_TOTALDATA);
34 | }
35 |
36 | public void setTotalData(float totalData) {
37 | sharedPreferences.edit().putFloat(KEY_TOTALDATA, totalData).apply();
38 | }
39 |
40 |
41 | /**
42 | * Alternative conversion (boolean)
43 | */
44 | private static final String KEY_ALTCONVERSION = "altConversion";
45 | private static final boolean DEFAULT_ALTCONVERSION = false;
46 |
47 | public boolean getAltConversion() {
48 | return sharedPreferences.getBoolean(KEY_ALTCONVERSION, DEFAULT_ALTCONVERSION);
49 | }
50 |
51 | public void setAltConversion(boolean altConversion) {
52 | sharedPreferences.edit().putBoolean(KEY_ALTCONVERSION, altConversion).apply();
53 | }
54 |
55 |
56 | /**
57 | * Start date for current period (inclusive)
58 | */
59 | private static final String KEY_PERIODSTART = "periodStart";
60 |
61 | public Calendar getPeriodStart() {
62 | Calendar cal;
63 | final long millis = sharedPreferences.getLong(KEY_PERIODSTART, -1);
64 | if (millis != -1) {
65 | cal = Calendar.getInstance();
66 | cal.setTimeInMillis(millis);
67 | } else {
68 | // DEFAULT: first day of current month
69 | cal = PeriodCalendar.today();
70 | cal.set(Calendar.DAY_OF_MONTH, 1);
71 | setPeriodStart(cal); // save, otherwise it will never be updated
72 | }
73 | return cal;
74 | }
75 |
76 | public void setPeriodStart(Calendar periodStart) {
77 | sharedPreferences.edit().putLong(KEY_PERIODSTART, periodStart.getTimeInMillis()).apply();
78 | }
79 |
80 |
81 | /**
82 | * Length of current period (int)
83 | * See Period
84 | */
85 | private static final String KEY_PERIODLENGTH = "periodLength";
86 | private static final int DEFAULT_PERIODLENGTH = 1;
87 |
88 | public int getPeriodLength() {
89 | return sharedPreferences.getInt(KEY_PERIODLENGTH, DEFAULT_PERIODLENGTH);
90 | }
91 |
92 | public void setPeriodLength(int periodLength) {
93 | sharedPreferences.edit().putInt(KEY_PERIODLENGTH, periodLength).apply();
94 | }
95 |
96 | /**
97 | * Type of current period (month or days)
98 | * See Length
99 | */
100 | private static final String KEY_PERIODTYPE = "periodType";
101 | private static final int DEFAULT_PERIODTYPE = Calendar.MONTH;
102 |
103 | public int getPeriodType() {
104 | return sharedPreferences.getInt(KEY_PERIODTYPE, DEFAULT_PERIODTYPE);
105 | }
106 |
107 | public void setPeriodType(int periodType) {
108 | sharedPreferences.edit().putInt(KEY_PERIODTYPE, periodType).apply();
109 | }
110 |
111 | /**
112 | * Information requested flag. Cleared when read.
113 | */
114 | private static final String KEY_INFOREQUESTED = "infoRequested";
115 |
116 | public void infoRequested() {
117 | sharedPreferences.edit().putBoolean(KEY_INFOREQUESTED, true).apply();
118 | }
119 |
120 | public boolean isInfoRequested() {
121 | boolean b = sharedPreferences.getBoolean(KEY_INFOREQUESTED, false);
122 | sharedPreferences.edit().remove(KEY_INFOREQUESTED).apply();
123 | return b;
124 | }
125 |
126 |
127 | /**
128 | * Saved Periods (int)
129 | */
130 | private static final String KEY_SAVEDPERIODS = "savedPeriods";
131 | private static final int DEFAULT_SAVEDPERIODS = 0;
132 |
133 | public int getSavedPeriods() {
134 | return sharedPreferences.getInt(KEY_SAVEDPERIODS, DEFAULT_SAVEDPERIODS);
135 | }
136 |
137 | public void setSavedPeriods(int savedPeriods) {
138 | sharedPreferences.edit().putInt(KEY_SAVEDPERIODS, savedPeriods).apply();
139 | }
140 |
141 | /**
142 | * Accumulated data of current period (float)
143 | */
144 | private static final String KEY_ACCUMULATED = "accumulated";
145 | private static final float DEFAULT_ACCUMULATED = 0;
146 |
147 | public Float getAccumulated() {
148 | return sharedPreferences.getFloat(KEY_ACCUMULATED, DEFAULT_ACCUMULATED);
149 | }
150 |
151 | public void setAccumulated(float accumulated) {
152 | sharedPreferences.edit().putFloat(KEY_ACCUMULATED, accumulated).apply();
153 | }
154 |
155 | /**
156 | * Decimals (int)
157 | */
158 | private static final String KEY_DECIMALS = "decimals";
159 | private static final int DEFAULT_DECIMALS = 2;
160 |
161 | public int getDecimals() {
162 | return sharedPreferences.getInt(KEY_DECIMALS, DEFAULT_DECIMALS);
163 | }
164 |
165 | public void setDecimals(int decimals) {
166 | sharedPreferences.edit().putInt(KEY_DECIMALS, decimals).apply();
167 | }
168 |
169 | /**
170 | * Gigabytes (boolean)
171 | */
172 | private static final String KEY_GB = "gb";
173 | private static final boolean DEFAULT_GB = false;
174 |
175 | public boolean getGB() {
176 | return sharedPreferences.getBoolean(KEY_GB, DEFAULT_GB);
177 | }
178 |
179 | public void setGB(boolean gb) {
180 | sharedPreferences.edit().putBoolean(KEY_GB, gb).apply();
181 | }
182 | private static final boolean DEFAULT_INV = false;
183 | public Preferences(Context context) {
184 | this.sharedPreferences = context.getSharedPreferences(Preferences.PREF_NAME, Context.MODE_PRIVATE);
185 |
186 | // updates
187 | final String KEY_ACCUMULATE = "accumulate";
188 | if (sharedPreferences.contains(KEY_ACCUMULATE)) {
189 | // accumulate=true/false -> savedPeriods=1/0
190 | setSavedPeriods(sharedPreferences.getBoolean(KEY_ACCUMULATE, false) ? 1 : 0);
191 | sharedPreferences.edit().remove(KEY_ACCUMULATE).apply();
192 | }
193 |
194 | final String KEY_FIRSTDAY = "firstDay";
195 | if (sharedPreferences.contains(KEY_FIRSTDAY)) {
196 | // firstDay=n -> periodStart=today(day=n)
197 | Calendar cal = PeriodCalendar.today();
198 | cal.set(Calendar.DAY_OF_MONTH, sharedPreferences.getInt(KEY_FIRSTDAY, 0));
199 | if (System.currentTimeMillis() < cal.getTimeInMillis()) {
200 | cal.add(Calendar.MONTH, -1);
201 | }
202 | setPeriodStart(cal);
203 | sharedPreferences.edit().remove(KEY_FIRSTDAY).apply();
204 | }
205 |
206 | final String KEY_ACCUMULATEDM = "accumulatedm";
207 | if (sharedPreferences.contains(KEY_ACCUMULATEDM)) {
208 | // accumulated month -> shift current month
209 | int diff = (getPeriodStart().get(Calendar.MONTH) - sharedPreferences.getInt(KEY_ACCUMULATEDM, 0) + 12) % 12;
210 | if (diff != 0) {
211 | Calendar cal = getPeriodStart();
212 | cal.add(Calendar.MONTH, -diff);
213 | setPeriodStart(cal);
214 | }
215 | sharedPreferences.edit().remove(KEY_ACCUMULATEDM).apply();
216 | }
217 |
218 | var KEY_REMAINING = "showRemaining";
219 | if (sharedPreferences.contains(KEY_REMAINING)) {
220 | // show remaining tweak -> show inv
221 | setInv(sharedPreferences.getBoolean(KEY_REMAINING, false));
222 | sharedPreferences.edit().remove(KEY_REMAINING).apply();
223 | }
224 |
225 | // sharedPreferences.edit().clear().apply();
226 | }
227 |
228 | public boolean getInv() {
229 | return sharedPreferences.getBoolean(KEY_INV, DEFAULT_INV);
230 | }
231 |
232 | public void setInv(boolean inv) {
233 | sharedPreferences.edit().putBoolean(KEY_INV, inv).apply();
234 | }
235 |
236 | // ------------------- tweaks -------------------
237 |
238 | /**
239 | * Tweaks
240 | */
241 | private static final String KEY_TWEAKS = "tweaks";
242 | private final Set DEFAULT_TWEAKS = Collections.emptySet();
243 |
244 | public boolean getTweak(Tweaks.Tweak tweak) {
245 | return sharedPreferences.getBoolean(tweak.name(), false);
246 | }
247 |
248 | public void setTweak(Tweaks.Tweak tweak, boolean enabled) {
249 | sharedPreferences.edit().putBoolean(tweak.name(), enabled).apply();
250 | }
251 |
252 | public void cleanupTweaks() {
253 | SharedPreferences.Editor edit = sharedPreferences.edit();
254 |
255 | // remove old tweaks:
256 | for (String name : sharedPreferences.getStringSet(KEY_TWEAKS, DEFAULT_TWEAKS)) {
257 | // check every saved tweak
258 | try {
259 | // check if still exists
260 | Tweaks.Tweak.valueOf(name);
261 | } catch (IllegalArgumentException e) {
262 | // if not, remove
263 | edit.remove(name);
264 | }
265 | }
266 |
267 | // save current tweaks list:
268 | Tweaks.Tweak[] items = Tweaks.Tweak.values();
269 | Set names = new HashSet<>(items.length);
270 | for (Tweaks.Tweak tweak : items) {
271 | // add current tweak name to list
272 | names.add(tweak.name());
273 | }
274 | edit.putStringSet(KEY_TWEAKS, names);
275 |
276 | // save
277 | edit.apply();
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/Tweaks.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.content.res.Resources;
7 |
8 | import com.trianguloy.continuousDataUsage.BuildConfig;
9 | import com.trianguloy.continuousDataUsage.R;
10 |
11 | /**
12 | * Manager of the tweaks
13 | */
14 | public class Tweaks implements DialogInterface.OnMultiChoiceClickListener {
15 |
16 | /**
17 | * Prefix for the tweaks
18 | */
19 | private static final String ID_PREFIX = "tweak_";
20 |
21 | private final Preferences prefs;
22 | private final Context cntx;
23 |
24 | /**
25 | * Tweak items.
26 | * Note: names are used as keys in preferences and as ids in strings (with prefix)
27 | */
28 | public enum Tweak {
29 | hideDate,
30 | hideData,
31 | hideBars,
32 | hideTexts,
33 | whiteWidgets,
34 | showConsumed,
35 | showAverage,
36 | advancedSecondary,
37 | capNoWarp,
38 | androidUsageButton,
39 | }
40 |
41 | // ------------------- Tweaks -------------------
42 |
43 | /**
44 | * Main constructor
45 | * @param prefs base prefs
46 | * @param cntx base context
47 | */
48 | public Tweaks(Preferences prefs, Context cntx) {
49 | this.prefs = prefs;
50 | this.cntx = cntx;
51 | }
52 |
53 | /**
54 | * Displays the dialog with the list of tweaks to toggle
55 | */
56 | public void showDialog() {
57 |
58 | // get tweaks state
59 | Tweak[] tweaks = Tweak.values();
60 |
61 | CharSequence[] items = new CharSequence[tweaks.length];
62 | boolean[] checkItems = new boolean[tweaks.length];
63 |
64 | for (int i = 0; i < tweaks.length; i++) {
65 | Tweak tweak = tweaks[i];
66 | // initialize item and append
67 | items[i] = getItemDescr(tweak);
68 | checkItems[i] = prefs.getTweak(tweak);
69 | }
70 |
71 | // do cleanup
72 | prefs.cleanupTweaks();
73 |
74 | // show
75 | new AlertDialog.Builder(cntx)
76 | .setMultiChoiceItems(items, checkItems, this)
77 | .setTitle(R.string.btn_tweaks)
78 | .setPositiveButton(cntx.getString(R.string.btn_close), null)
79 | .show();
80 |
81 | }
82 |
83 | /**
84 | * Returns the description of a tweak
85 | *
86 | * @param item which tweak
87 | * @return the description as string
88 | */
89 | private String getItemDescr(Tweak item) {
90 | try {
91 | return cntx.getString(cntx.getResources().getIdentifier(ID_PREFIX + item.name(), "string", cntx.getPackageName()));
92 | } catch (Resources.NotFoundException e) {
93 | if (BuildConfig.DEBUG) {
94 | throw e; // rethrow in debug mode
95 | } else {
96 | return "---" + item.name() + "---"; // placeholder in release mode
97 | }
98 | }
99 | }
100 |
101 | // ------------------- DialogInterface.OnMultiChoiceClickListener -------------------
102 |
103 | @Override
104 | public void onClick(DialogInterface dialogInterface, int i, boolean b) {
105 | prefs.setTweak(Tweak.values()[i], b);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/common/Utils.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.common;
2 |
3 | import android.appwidget.AppWidgetManager;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.Intent;
7 |
8 | import com.trianguloy.continuousDataUsage.widgets.AppWidgetProgress;
9 | import com.trianguloy.continuousDataUsage.widgets.AppWidgetRate;
10 |
11 | import java.text.NumberFormat;
12 | import java.util.Locale;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 |
16 | public class Utils {
17 |
18 | /**
19 | * Rounds double to int
20 | *
21 | * @param d double value
22 | * @return round(d) as int
23 | */
24 | public static int dbl2int(double d) {
25 | return Math.round(Math.round(d));
26 | }
27 |
28 | // ------------------- Format -------------------
29 |
30 | /**
31 | * for {@link #formatData(Preferences, String, Double...)}
32 | */
33 | private static final Pattern pattern = Pattern.compile("\\{(.)\\}");
34 |
35 | /**
36 | * Custom formatter based on the preferences.
37 | *
38 | * Uses getDecimals to set the number of decimals for data numbers.
39 | * Uses getGB to set the suffix and convert the argument.
40 | * Uses getAltConversion for the GB conversion.
41 | *
42 | *
'{0}' data without suffix -> '1.123'
43 | *
'{M}' data with suffix -> '1.123 MB'
44 | *
'{%}' percentage -> '1.23%'
45 | *
'{/}' ratio -> '1.23'
46 | *
47 | * @param pref preferences
48 | * @param format format string, see above for formatting options
49 | * @param args values
50 | * @return formatted string
51 | */
52 | public static String formatData(Preferences pref, String format, Double... args) {
53 | // init elements
54 | NumberFormat formatter = NumberFormat.getNumberInstance(Locale.US);
55 | StringBuffer formatted = new StringBuffer(format.length());
56 | final Matcher matcher = pattern.matcher(format);
57 | int i = 0;
58 |
59 | // replace each formatting element
60 | while (matcher.find()) {
61 |
62 | // set replacing properties
63 | String suffix = "";
64 | switch (matcher.group(1)) {
65 | case "M":
66 | // data with suffix
67 | suffix = (pref.getGB() ? " GB" : " MB");
68 | case "0":
69 | // data without suffix
70 | formatter.setMinimumFractionDigits(pref.getDecimals());
71 | formatter.setMaximumFractionDigits(pref.getDecimals());
72 | if (pref.getGB()) args[i] /= pref.getAltConversion() ? 1000 : 1024;
73 | break;
74 | case "%":
75 | // percentage
76 | suffix = "%";
77 | case "/":
78 | // ratio
79 | formatter.setMinimumFractionDigits(2);
80 | formatter.setMaximumFractionDigits(2);
81 | break;
82 | default:
83 | throw new IllegalArgumentException(matcher.group());
84 | }
85 |
86 | // replace
87 | matcher.appendReplacement(formatted,
88 | Matcher.quoteReplacement(formatter.format(args[i]) + suffix)
89 | );
90 | i++;
91 | }
92 | matcher.appendTail(formatted);
93 |
94 | // return
95 | return formatted.toString();
96 | }
97 |
98 | /**
99 | * Update all widgets now
100 | */
101 | public static void updateAllWidgets(Context context) {
102 | AppWidgetManager man = AppWidgetManager.getInstance(context);
103 | for (Class> cls : new Class[]{AppWidgetProgress.class, AppWidgetRate.class}) {
104 | Intent updateIntent = new Intent(context, cls);
105 | updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
106 | updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, man.getAppWidgetIds(new ComponentName(context, cls)));
107 | context.sendBroadcast(updateIntent);
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/widgets/AppWidgetBase.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.widgets;
2 |
3 | import android.app.PendingIntent;
4 | import android.appwidget.AppWidgetManager;
5 | import android.appwidget.AppWidgetProvider;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.util.Log;
9 | import android.util.Pair;
10 | import android.widget.RemoteViews;
11 | import android.widget.Toast;
12 |
13 | import com.trianguloy.continuousDataUsage.R;
14 | import com.trianguloy.continuousDataUsage.activities.HistoryActivity;
15 | import com.trianguloy.continuousDataUsage.common.Accumulated;
16 | import com.trianguloy.continuousDataUsage.common.DataUsage;
17 | import com.trianguloy.continuousDataUsage.common.PeriodCalendar;
18 | import com.trianguloy.continuousDataUsage.common.Preferences;
19 | import com.trianguloy.continuousDataUsage.common.Tweaks;
20 |
21 | import java.text.SimpleDateFormat;
22 | import java.util.Calendar;
23 |
24 | /**
25 | * Implementation of App Widget functionality. Base class.
26 | */
27 | abstract class AppWidgetBase extends AppWidgetProvider {
28 |
29 | //abstract
30 | abstract void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId);
31 |
32 | private static final String EXTRA_ACTION = "com.trianguloy.continuousDataUsage.action";
33 | static final int ACTION_REFRESH = 0;
34 | static final int ACTION_USAGE = 1;
35 | static final int ACTION_INFO = 2;
36 |
37 |
38 | /**
39 | * When a widget is created for the first time. Nothing done.
40 | *
41 | * @param context base context
42 | */
43 | @Override
44 | public void onEnabled(Context context) {
45 | }
46 |
47 |
48 | /**
49 | * When the last widget is removed. Nothing done
50 | *
51 | * @param context base context
52 | */
53 | @Override
54 | public void onDisabled(Context context) {
55 | }
56 |
57 |
58 | /**
59 | * Called when one or more widgets needs to be updated. Updates all of them
60 | *
61 | * @param context the base context
62 | * @param appWidgetManager the widget manager
63 | * @param appWidgetIds the ids of the widgets that needs to be updated
64 | */
65 | @Override
66 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
67 | for (int appWidgetId : appWidgetIds) {
68 | updateAppWidget(context, appWidgetManager, appWidgetId);
69 | }
70 | }
71 |
72 |
73 | /**
74 | * Intent received, act accordingly.
75 | *
76 | * @param context that magical class
77 | * @param intent the intent
78 | */
79 | @Override
80 | public void onReceive(Context context, Intent intent) {
81 | var prefs = new Preferences(context);
82 |
83 | switch (intent.getIntExtra(EXTRA_ACTION, -1)) {
84 | case ACTION_USAGE:
85 | if (prefs.getTweak(Tweaks.Tweak.androidUsageButton)) {
86 | HistoryActivity.openAndroidUsageScreen(context);
87 | } else {
88 | //open the history activity
89 | Intent usage = new Intent(context, HistoryActivity.class);
90 | usage.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
91 | context.startActivity(usage);
92 | }
93 | break;
94 | case ACTION_INFO:
95 | //info requested, set flag
96 | prefs.infoRequested();
97 | default:
98 | super.onReceive(context, intent);
99 | break;
100 | }
101 | }
102 |
103 | /**
104 | * To return a bundle with multiple values in the #getCommonInfo function
105 | */
106 | static class ReturnedInfo {
107 | double percentDate = 0;
108 | double totalData = 0;
109 |
110 | int error = -1;
111 |
112 | double percentData = 0;
113 | double megabytes = 0;
114 | }
115 |
116 | /**
117 | * The core of the widgets, calculates the necessary data
118 | *
119 | * @param context not to be confused with contest
120 | * @return calculated data in a packed class
121 | */
122 | static ReturnedInfo getCommonInfo(Context context) {
123 |
124 | ReturnedInfo returnedInfo = new ReturnedInfo();
125 |
126 | // get objects
127 | Preferences pref = new Preferences(context);
128 | PeriodCalendar periodCalendar = new PeriodCalendar(pref);
129 | DataUsage dataUsage = new DataUsage(context, pref);
130 | Accumulated accumulated = new Accumulated(pref, dataUsage, periodCalendar);
131 |
132 | // update
133 | accumulated.updatePeriod();
134 |
135 | boolean infoRequested = pref.isInfoRequested();
136 |
137 | //current time
138 | long currentMillis = System.currentTimeMillis();
139 |
140 | //current period
141 | Pair val = periodCalendar.getLimitsOfPeriod(0);
142 | long startOfPeriod = val.first;
143 | long endOfPeriod = val.second;
144 |
145 |
146 | //upper bar
147 | returnedInfo.percentDate = (currentMillis - startOfPeriod) / (double) (endOfPeriod - startOfPeriod);
148 | double totalData = pref.getTotalData();
149 | returnedInfo.totalData = totalData;
150 |
151 | //bottom bar
152 | double percentData;
153 | double megabytes;
154 |
155 | try {
156 | megabytes = accumulated.getUsedDataFromCurrentPeriod();
157 |
158 | } catch (DataUsage.Error e) {
159 | returnedInfo.error = e.errorId;
160 | return returnedInfo;
161 | }
162 |
163 | returnedInfo.megabytes = megabytes;
164 | percentData = megabytes / totalData;
165 | returnedInfo.percentData = percentData;
166 |
167 |
168 | //current usage as date
169 | if (infoRequested) {
170 | double millisEquivalent = (endOfPeriod - startOfPeriod) * percentData + startOfPeriod;
171 | Calendar cal = Calendar.getInstance();
172 | cal.clear();
173 | cal.setTimeInMillis(Math.round(millisEquivalent));
174 | Toast.makeText(context, context.getString(R.string.toast_currentUsage,
175 | SimpleDateFormat.getDateTimeInstance().format(cal.getTime()),
176 | millisToInterval(cal.getTimeInMillis() - currentMillis)), Toast.LENGTH_LONG).show();
177 | }
178 |
179 | // invert data
180 | if (pref.getInv()) {
181 | returnedInfo.percentData = 1 - returnedInfo.percentData;
182 | returnedInfo.megabytes = returnedInfo.totalData - returnedInfo.megabytes;
183 | returnedInfo.percentDate = 1 - returnedInfo.percentDate;
184 | }
185 |
186 | Log.d("Widget", "updated");
187 |
188 | return returnedInfo;
189 | }
190 |
191 |
192 | //--------------utils-------------------
193 |
194 |
195 | /**
196 | * Puts the updateWidget click event on the specific views
197 | *
198 | * @param context base context
199 | * @param appWidgetIds ids to update
200 | * @param remoteViews where to find the views
201 | * @param views views to set
202 | * @param action intent action
203 | * @param classForIntent class for the intent
204 | */
205 | static void setOnClick(Context context, int[] appWidgetIds, RemoteViews remoteViews, int[] views, int action, Class> classForIntent) {
206 | Intent intent = new Intent(context, classForIntent);
207 | intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
208 | intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
209 | intent.putExtra(EXTRA_ACTION, action);
210 | PendingIntent pendingIntent = PendingIntent.getBroadcast(context, action, intent, PendingIntent.FLAG_IMMUTABLE);
211 | for (int view : views) {
212 | remoteViews.setOnClickPendingIntent(view, pendingIntent);
213 | }
214 | }
215 |
216 | /**
217 | * Converts millis to a string display
218 | *
219 | * @param millis mililiseconds of a specific time
220 | * @return the time as string
221 | */
222 | static String millisToInterval(long millis) {
223 | StringBuilder sb = new StringBuilder();
224 | String prefix = millis >= 0 ? "+ " : "- ";
225 | millis = millis > 0 ? millis : -millis;
226 |
227 | millis /= 1000;
228 |
229 | sb.insert(0, " seconds");
230 | sb.insert(0, millis % 60);
231 | millis /= 60;
232 |
233 | if (millis > 0) {
234 | sb.insert(0, " minutes, ");
235 | sb.insert(0, millis % 60);
236 | millis /= 60;
237 | }
238 |
239 | if (millis > 0) {
240 | sb.insert(0, " hours, ");
241 | sb.insert(0, millis % 24);
242 | millis /= 24;
243 | }
244 |
245 | if (millis > 0) {
246 | sb.insert(0, " days, ");
247 | sb.insert(0, millis);
248 | }
249 |
250 | sb.insert(0, prefix);
251 |
252 | return sb.toString();
253 | }
254 |
255 |
256 | }
257 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/widgets/AppWidgetProgress.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.widgets;
2 |
3 | import android.appwidget.AppWidgetManager;
4 | import android.content.Context;
5 | import android.graphics.Color;
6 | import android.graphics.Point;
7 | import android.os.Bundle;
8 | import android.view.View;
9 | import android.view.WindowManager;
10 | import android.widget.RemoteViews;
11 |
12 | import com.trianguloy.continuousDataUsage.R;
13 | import com.trianguloy.continuousDataUsage.common.Preferences;
14 | import com.trianguloy.continuousDataUsage.common.Tweaks;
15 | import com.trianguloy.continuousDataUsage.common.Utils;
16 |
17 | /**
18 | * Implementation of the Progress Widget functionality.
19 | * Displays the values and two progress bar corresponding to the 'average' and 'current' usage.
20 | */
21 | public class AppWidgetProgress extends AppWidgetBase {
22 |
23 | /**
24 | * When the size changes, update the widget
25 | *
26 | * @param context base context
27 | * @param appWidgetManager widget manager, base class
28 | * @param appWidgetId id of the widget to update
29 | * @param newOptions unused
30 | */
31 | @Override
32 | public void onAppWidgetOptionsChanged(Context context,
33 | AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
34 |
35 | updateAppWidget(context, appWidgetManager, appWidgetId);
36 | super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
37 | }
38 |
39 | /**
40 | * Updates a widget adding its views and configuring them.
41 | *
42 | * @param context base context
43 | * @param appWidgetManager widget manager, base class
44 | * @param appWidgetId id of the widget to update
45 | */
46 | void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
47 |
48 | boolean small = appWidgetManager.getAppWidgetOptions(appWidgetId).getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) < 180;
49 |
50 | // Construct the RemoteViews object
51 | RemoteViews views = new RemoteViews(context.getPackageName(), small ? R.layout.widget_progress_short : R.layout.widget_progress);
52 | updateViews(context, views);
53 |
54 | //update when clicking
55 | setOnClick(context, new int[]{appWidgetId}, views, new int[]{R.id.wdg_prgBar_data, R.id.wdg_prgBar_date}, ACTION_REFRESH, AppWidgetProgress.class);
56 | setOnClick(context, new int[]{appWidgetId}, views, new int[]{R.id.btn_showData}, ACTION_USAGE, AppWidgetProgress.class);
57 | setOnClick(context, new int[]{appWidgetId}, views, new int[]{R.id.wdg_txt_data, R.id.wdg_txt_date}, ACTION_INFO, AppWidgetProgress.class);
58 |
59 |
60 | // Instruct the widget manager to update the widget
61 | appWidgetManager.updateAppWidget(appWidgetId, views);
62 | }
63 |
64 | /**
65 | * Updates the views of a widget
66 | *
67 | * @param context base context
68 | * @param views views to update
69 | */
70 | public static void updateViews(Context context, RemoteViews views) {
71 | ReturnedInfo commonInfo = getCommonInfo(context);
72 | Preferences pref = new Preferences(context);
73 |
74 | //variables
75 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
76 | Point p = new Point(context.getResources().getInteger(R.integer.DEFAULT_PROGRESS_PRECISION), 0);
77 | if (wm != null) {
78 | wm.getDefaultDisplay().getSize(p);
79 | }
80 | int progressPrecision = p.x;
81 |
82 | //top bar
83 | views.setProgressBar(R.id.wdg_prgBar_date, progressPrecision, Utils.dbl2int(commonInfo.percentDate * progressPrecision), false);
84 | views.setTextViewText(R.id.wdg_txt_date,
85 | Utils.formatData(pref, "{M} ({%})", commonInfo.percentDate * commonInfo.totalData, commonInfo.percentDate * 100)
86 | );
87 |
88 | //error
89 | if (commonInfo.error != -1) {
90 | views.setTextViewText(R.id.wdg_txt_data, context.getString(commonInfo.error));
91 | return;
92 | }
93 |
94 | // bottom bar
95 | views.setProgressBar(R.id.wdg_prgBar_data, progressPrecision, Utils.dbl2int((commonInfo.percentData % 1) * progressPrecision), false);
96 | views.setInt(R.id.wdg_prgBar_data, "setSecondaryProgress",
97 | commonInfo.percentData > 1 ? progressPrecision
98 | : commonInfo.percentData < 0 ? Utils.dbl2int((1 + (commonInfo.percentData % 1)) * progressPrecision)
99 | : 0);
100 | views.setTextViewText(R.id.wdg_txt_data,
101 | Utils.formatData(pref, "{M} ({%})", commonInfo.megabytes, commonInfo.percentData * 100)
102 | );
103 |
104 | // tweaks
105 | if (pref.getTweak(Tweaks.Tweak.hideDate)) {
106 | views.setViewVisibility(R.id.wdg_txt_date, View.GONE);
107 | views.setViewVisibility(R.id.wdg_prgBar_date, View.GONE);
108 | }
109 | if (pref.getTweak(Tweaks.Tweak.hideData)) {
110 | views.setViewVisibility(R.id.wdg_txt_data, View.GONE);
111 | views.setViewVisibility(R.id.wdg_prgBar_data, View.GONE);
112 | }
113 | if (pref.getTweak(Tweaks.Tweak.hideBars)) {
114 | views.setViewVisibility(R.id.wdg_prgBar_data, View.GONE);
115 | views.setViewVisibility(R.id.wdg_prgBar_date, View.GONE);
116 | }
117 | if (pref.getTweak(Tweaks.Tweak.hideTexts)) {
118 | views.setViewVisibility(R.id.wdg_txt_data, View.GONE);
119 | views.setViewVisibility(R.id.wdg_txt_date, View.GONE);
120 | }
121 | if (pref.getTweak(Tweaks.Tweak.advancedSecondary)) {
122 | final int sp = pref.getSavedPeriods();
123 | double percent; // this is easier with Kotlin :(
124 | if (commonInfo.percentData >= 1)
125 | // [1,oo) = more than the current usage -> show full
126 | percent = 1;
127 | else if (commonInfo.percentData >= 0)
128 | // [0,1) = normal usage -> don't show
129 | percent = 0;
130 | else if (commonInfo.percentData >= -sp + 1)
131 | // [-sp+1,0) = saved data -> % in range
132 | percent = (commonInfo.percentData + sp - 1) / (sp - 1);
133 | else if (commonInfo.percentData >= -sp)
134 | // [-sp,-sp+1) = will be lost -> % in range
135 | percent = commonInfo.percentData + sp;
136 | else
137 | // (-oo,-sp) = more than one period will be lost -> show nothing
138 | percent = 0;
139 | views.setInt(R.id.wdg_prgBar_data, "setSecondaryProgress", Utils.dbl2int(percent * progressPrecision));
140 | }
141 | if (pref.getTweak(Tweaks.Tweak.capNoWarp)) {
142 | views.setProgressBar(R.id.wdg_prgBar_data, progressPrecision, Utils.dbl2int((commonInfo.percentData) * progressPrecision), false);
143 | views.setInt(R.id.wdg_prgBar_data, "setSecondaryProgress", 0);
144 | }
145 | if (pref.getTweak(Tweaks.Tweak.whiteWidgets)) {
146 | views.setInt(R.id.wdg_parent, "setBackgroundResource", R.drawable.background_progress_white);
147 | views.setTextColor(R.id.wdg_txt_date, Color.BLACK);
148 | views.setTextColor(R.id.wdg_txt_data, Color.BLACK);
149 | views.setInt(R.id.btn_showData, "setImageResource", R.drawable.ic_history_black);
150 | }
151 |
152 | }
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/app/src/main/java/com/trianguloy/continuousDataUsage/widgets/AppWidgetRate.java:
--------------------------------------------------------------------------------
1 | package com.trianguloy.continuousDataUsage.widgets;
2 |
3 | import android.appwidget.AppWidgetManager;
4 | import android.content.Context;
5 | import android.graphics.Color;
6 | import android.widget.RemoteViews;
7 |
8 | import com.trianguloy.continuousDataUsage.R;
9 | import com.trianguloy.continuousDataUsage.common.Preferences;
10 | import com.trianguloy.continuousDataUsage.common.Tweaks;
11 | import com.trianguloy.continuousDataUsage.common.Utils;
12 |
13 | /**
14 | * Implementation of the Rate widget functionality.
15 | * Displays a number with the rate between used_data / average_data
16 | */
17 | public class AppWidgetRate extends AppWidgetBase {
18 |
19 |
20 | /**
21 | * Updates a widget adding its views and configuring them.
22 | *
23 | * @param context base context
24 | * @param appWidgetManager widget manager, base class
25 | * @param appWidgetId id of the widget to update
26 | */
27 | void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
28 |
29 | // Construct the RemoteViews object
30 | RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_rate);
31 | updateViews(context, views);
32 |
33 | //update when clicking
34 | setOnClick(context, new int[]{appWidgetId}, views, new int[]{R.id.wdg_prgBar_data, R.id.wdg_txt_rate}, ACTION_INFO, AppWidgetRate.class);
35 |
36 |
37 | // Instruct the widget manager to update the widget
38 | appWidgetManager.updateAppWidget(appWidgetId, views);
39 | }
40 |
41 | /**
42 | * Updates the views of a widget
43 | *
44 | * @param context base context
45 | * @param views views to update
46 | */
47 | void updateViews(Context context, RemoteViews views) {
48 | ReturnedInfo commonInfo = getCommonInfo(context);
49 | Preferences pref = new Preferences(context);
50 |
51 | if (commonInfo.error != -1) {
52 | views.setTextViewText(R.id.wdg_txt_rate, context.getString(commonInfo.error));
53 | return;
54 | }
55 |
56 | //number
57 | double rate = commonInfo.percentData / commonInfo.percentDate;
58 | views.setTextViewText(R.id.wdg_txt_rate, Utils.formatData(pref, "{/}", rate));
59 |
60 | // tweaks
61 | if (pref.getTweak(Tweaks.Tweak.showConsumed)) {
62 | rate = commonInfo.megabytes;
63 | views.setTextViewText(R.id.wdg_txt_rate, Utils.formatData(pref, "{0}", rate));
64 | }
65 | if (pref.getTweak(Tweaks.Tweak.showAverage)) {
66 | rate = commonInfo.totalData;
67 | views.setTextViewText(R.id.wdg_txt_rate, Utils.formatData(pref, "{0}", rate));
68 | }
69 | if (pref.getTweak(Tweaks.Tweak.whiteWidgets)) {
70 | views.setInt(R.id.wdg_parent, "setBackgroundResource", R.drawable.background_rate_white);
71 | views.setTextColor(R.id.wdg_txt_rate, Color.BLACK);
72 | }
73 |
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/play/contact-email.txt:
--------------------------------------------------------------------------------
1 | correo--correo+appCDUW@hotmail.com
--------------------------------------------------------------------------------
/app/src/main/play/contact-website.txt:
--------------------------------------------------------------------------------
1 | https://triangularapps.blogspot.com/
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/full-description.txt:
--------------------------------------------------------------------------------
1 | New: You can now configure the start and duration of the period, for example one week, 28 days, or even one year.
2 |
3 |
4 |
5 | Do you have an almost unlimited data plan and you never consume all your data? Lucky you! Unfortunately this app will be useless in this situation.
6 |
7 | On the other hand: do you have a limited data plan and it has happened to you:
8 | a) You always spend too much data on the first days of the period, and you have few left at the end?
9 | or
10 | b) You try not to spend too much data at the beginning of the period, and then you end with unused data?
11 | or
12 | c) You always wanted to know 'Did I spent too much already?' 'Am I above an average usage?'.
13 |
14 | Then this app will (I hope) help you!
15 | It shows your data usage (bottom bar, how much you already used) with an ideal 'average data usage' (top bar, how much you would have used by downloading the same amount of bytes every second in the period). This way with just one look you can check if you are above or below the 'average data usage'.
16 | - If the top bar is longer than the bottom: Good! You can download a bit more and still have at the end of the period.
17 | - If the top bar is shorter than the bottom: Not good! You need to stop using too much data, otherwise you'll end with no more left.
18 |
19 | Isn't this useful? I think it is, and that's why I (TrianguloY) published it. It doesn't contains ads, and it is absurdly lightweight, so give it a try.
20 | If you have any suggestion or comment leave one or send an email.
21 |
22 | DISCLAIMER!!!!
23 | Please note that the current consumption is measured by your device and may differ with your company measurement. I can't take responsibility if the displayed data usage is wrong.
24 |
25 |
26 | Permissions:
27 | - PACKAGE_USAGE_STATS - Permission needed to get the current usage from the usage service. No other data is retrieved nor used.
28 | More info here: https://developer.android.com/reference/android/app/usage/NetworkStatsManager.html#querySummaryForDevice(int,%20java.lang.String,%20long,%20long)
29 | - POST_NOTIFICATIONS - Permission needed to show a small message at the bottom of the screen (toast) when pressing the widget. The app does not send any standard notification.
30 |
31 | NOTE: there is no internet permission, there are no ads so it is not necessary.
32 |
33 | ---------------------------------
34 | The source code is available here: https://github.com/TrianguloY/Average-data-usage-widget
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/feature-graphic/featured.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/play/listings/en-US/graphics/feature-graphic/featured.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/feature-graphic/featured.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/play/listings/en-US/graphics/feature-graphic/featured.xcf
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/icon/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/play/listings/en-US/graphics/icon/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601111957.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601111957.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601111968.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601111968.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601112040.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601112040.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601112106.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/play/listings/en-US/graphics/phone-screenshots/Screenshot_1601112106.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/short-description.txt:
--------------------------------------------------------------------------------
1 | Check your data usage and compare it with an indicator of the average usage.
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Average data usage widget
--------------------------------------------------------------------------------
/app/src/main/play/release-notes/en-US/default.txt:
--------------------------------------------------------------------------------
1 | V 4.1
2 | - Added Russian translation. Thank you kojjii!
3 |
4 | V 4.0
5 | - Updated to Android 10+
6 | - New: Average and total data on the history screen
7 | - Tweak: Remaining tweak promoted to full setting (Pending/Used)
8 | - New: Option to calculate accumulated data by setting the desired visible amount
9 | - Improve: Accumulated data can be negative
10 | - Improve: Accumulated data can be set while accumulated period is 0 (as offset data)
11 | - New: Tweak: open android settings when clicking the widget button
12 |
13 | V 3.0
14 | - Configurable period length (any number of days or months).
15 | - New tweak: advanced secondary bars
16 | - Improved settings screen
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/preview_progress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/res/drawable-nodpi/preview_progress.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/preview_rate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/res/drawable-nodpi/preview_rate.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_progress.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_progress_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_rate.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_rate_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_data_usage_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_history.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_history_black.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
21 |
22 |
30 |
31 |
38 |
39 |
40 |
47 |
48 |
55 |
56 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
21 |
22 |
32 |
33 |
42 |
43 |
44 |
47 |
48 |
53 |
54 |
60 |
61 |
68 |
69 |
70 |
71 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
22 |
23 |
28 |
29 |
34 |
35 |
41 |
42 |
49 |
50 |
55 |
56 |
61 |
62 |
66 |
67 |
73 |
74 |
81 |
82 |
83 |
89 |
90 |
95 |
96 |
103 |
104 |
109 |
110 |
118 |
119 |
125 |
126 |
127 |
128 |
133 |
134 |
138 |
139 |
143 |
144 |
152 |
153 |
161 |
162 |
163 |
167 |
168 |
179 |
180 |
185 |
186 |
190 |
191 |
195 |
196 |
200 |
201 |
207 |
208 |
209 |
214 |
215 |
219 |
220 |
225 |
226 |
231 |
232 |
237 |
238 |
243 |
244 |
250 |
251 |
255 |
256 |
261 |
262 |
267 |
268 |
272 |
273 |
281 |
282 |
286 |
287 |
292 |
293 |
301 |
302 |
308 |
309 |
316 |
317 |
318 |
323 |
324 |
328 |
329 |
336 |
337 |
340 |
341 |
342 |
343 |
344 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/lv_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
22 |
23 |
32 |
33 |
34 |
42 |
43 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/widget_progress.xml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
20 |
21 |
29 |
30 |
39 |
40 |
48 |
49 |
58 |
59 |
60 |
61 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/widget_progress_short.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
21 |
22 |
31 |
32 |
42 |
43 |
52 |
53 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/widget_rate.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Спроси разрешения
3 | Настройки статистики использования
4 | Вычислять
5 | Хитрости
6 | Отображайте данные в гигабайтах, а не в МБ.
7 | Отображать отложенные данные (столбцы уменьшатся со 100%dо 0%) вместо данных, используемых по умолчанию (столбцы увеличатся с 0%dо 100%).
8 | Используйте альтернативное преобразование МБ
9 | Дни
10 | Настройки:\nСписок экспериментальных настроек для настройки некоторых свойств виджетов (при необходимости могут быть добавлены/ удалены в будущих версиях). Некоторые комбинации могут не работать вместе или давать нежелательные результаты (например, \"скрыть дату\" + \"скрыть данные\", при которых будет отображаться пустой виджет), но их можно безопасно включать / отключать, так что получайте удовольствие, настраивая их по своему усмотрению.\n\nВажный! Возможно, потребуется щелкнуть по существующим виджетам или удалить / добавить их для обновления.
11 | Просмотр истории
12 | На этом экране отображается показатель использования за каждый день периода, соответствующий среднему показателю.\n\nПолоса справа означает, что использование было больше среднего, полоса слева - наоборот.\n\nВы также можете увидеть среднее и общее использование за весь период.\n\nИспользуйте кнопки, чтобы изменить период.
13 | Использование Android
14 | Инфо
15 | Месяцы
16 | Настройки
17 | мегабайт
18 | Активность Android не обнаружена.
19 | Это всего лишь предварительный просмотр. Добавьте настоящий виджет на свой домашний экран!
20 | Вторичный прогресс динамично адаптируется.
21 | Откройте экран использования Android, нажав на кнопку виджета.
22 | Удерживайте планку на 100%без перекоса (на случай, если на вашем устройстве не отображается дополнительная планка).
23 | Скрытие строк данных и дат
24 | Скрыть панель данных и текст (нижнюю)
25 | Скрыть строку даты и текст (верхний)
26 | Скрыть текст данных и дат
27 | Показывать средние данные в виджете 1x1 (вместо показателя)
28 | Показывать потребленные данные в виджете 1x1 (вместо тарифа)
29 | Показывать оставшиеся данные вместо использованных (столбцы изменятся со 100%на 0%вместо 0%на 100%).
30 | Показывать виджеты световой тематики (белый фон с черным текстом)
31 | средний
32 | Это небольшая утилита-виджет для отображения использования тарифного плана с \'постоянной\' полосой использования. Полезно для отслеживания, превышаете ли вы или нет \'постоянное\' использование данных (если вы потребляете одинаковое количество данных каждую секунду).
33 | Приложение создано TrianguloY.
34 | Исходный код доступен на GitHub.
35 | уведомления:\nЧтобы при нажатии на виджет в нижней части экрана отображалось небольшое сообщение (всплывающее окно), Android теперь должен разрешить уведомления. Приложение не будет отправлять никаких стандартных уведомлений.
36 | "PACKAGE_USAGE_STATS:
37 | Требуется разрешение для получения информации о текущем использовании из "службы использования. Никакие другие данные не извлекаются и не используются. При необходимости нажмите кнопку ниже, выберите это приложение и включите разрешение.
38 | Альтернативное преобразование:\nПроверьте это, если показанное использование данных всегда меньше, чем реальное, отображаемое приложением вашей компании. [Технические подробности: для преобразования байтов в мегабайты используется значение 1000 вместо 1024]
39 | Продолжительность периода:\nВыберите продолжительность периода данных, например, 1 месяц, 7 дней (неделя), 28 дней, 1 день…
40 | Начало периода:\nВыберите первый день (включительно) текущего периода, начиная с сегодняшнего дня. Например, если у вас ежемесячный период, который меняется каждый месяц, выберите 1-й день текущего месяца.\nЭто значение будет автоматически обновляться при каждом изменении текущего периода. Примечание: если вы настроите его неправильно (например, выберете следующий день), он также будет автоматически обновляться. Вы можете закрыть и снова открыть экран настроек, чтобы выполнить принудительное обновление.
41 | Общее количество данных:\nколичество использованных данных отображается в верхней строке при полном заполнении. Обычно это лимит вашего тарифного плана. В мегабайтах (МБ)
42 | Описание виджета: \n- Верхняя строка показывает оптимальное постоянное использование: 0 в начале периода, половина в середине периода, полная в конце. \n- Нижняя строка показывает текущее использование. Если нижняя строка меньше верхней, то при такой скорости у вас будут неиспользованные данные. Если она больше, то вы, вероятно, превысите ее. \n- Нажмите правую кнопку виджета, чтобы открыть список истории, нажмите на текст, чтобы показать соответствующий день текущего использования, нажмите в любом другом месте, чтобы обновить его вручную (он обновляется автоматически).
43 | Ошибка при получении состояния разрешения
44 | Разрешение получено
45 | Требуется разрешение
46 | Разрешение не предоставлено
47 | Накопленные периоды:\nКоличество периодов, в течение которых неиспользуемые данные сохраняются для будущих периодов. Если равно нулю, неиспользуемые данные теряются при смене периода. Если нет, неиспользованные мегабайты из предыдущих n периодов будут израсходованы раньше текущих (до тех пор будет отображаться \"отрицательное\" использование). Пример: если ваш поставщик данных \"накапливает мегабайты за последние три месяца\" (с указанием месячного периода), введите 3.
48 | Накопленные значения в мегабитах/смещение данных:\nЗначения в мегабитах, накопленные за предыдущий период (общие данные о смещении). Это значение будет добавлено к доступным данным за текущий период. Вы можете использовать его для смещения отображаемого значения (введите отрицательное значение, чтобы вычесть доступные данные).\nЭто значение будет автоматически обновляться при каждом изменении текущего периода, но если вы включаете это в первый раз, вам может потребоваться вручную ввести накопленные значения за предыдущий период (уточните у своего поставщика данных). В качестве альтернативы вы можете нажать кнопку для настройки с использованием других помощников. Вы можете отредактировать его в любое время.
49 | Десятичные:\nКоличество знаков после запятой, которые будут отображаться в числах данных.
50 | МБ или ГБ:
51 | Ожидающий/использованный:
52 | Информация:
53 | Разрешения:
54 | Настройки:
55 | весь
56 | Укажите здесь, что должен отображать виджет, и данные будут настроены соответствующим образом
57 | Текущее значение
58 | Ошибка при запросе данных
59 | Услуга недоступна
60 | Отсутствует разрешение
61 | -загрузка-
62 | Индикаторы выполнения (4x1)
63 | Скорость (1x1)
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #01579b
4 | #3949ab
5 | #FFE082
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | 0dp
9 | 16dp
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 512
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Average data usage widget
5 | Information:
6 | This is a small widget utility to show the data plan usage with a \'constant\' usage bar. Useful to know if you are above or below the \'constant\' data usage (if you consume the same data every second).\nApp created by TrianguloY.\nThe source code is available on GitHub.
7 | Widget description: \n- The upper bar is the optimal continuous usage: 0 at the start of the period, half at the middle of the period, full at the end. \n- The bottom bar is your current usage. If the bottom bar is less than the upper bar then at that rate you will have data unused. If it is more then you will probably surpass it. \n- Click the right button of the widget to open the history list, click the text to show the corresponding day of the current usage, click anywhere else to manually update it (it updates automatically).
8 | This is just a preview. Add the real widget to your home screen!
9 |
10 |
11 |
12 | History Viewer
13 | This screen shows the usage on each day of the period related to the average one.\n\nA bar to the right means the usage was greater than the average, a bar to the left means the opposite.\n\nYou can also see the average and total usage of the full period.\n\nUse the buttons to change the period.
14 | Info
15 | Android Usage
16 | average
17 | total
18 |
19 |
20 |
21 | Permissions:
22 | NOTIFICATIONS:\nIn order to display a small message at the bottom of the screen (toast) when clicking the widget, Android requires now to allow notifications. The app will not send any standard notification.
23 | Ask permission
24 | PACKAGE_USAGE_STATS:\nPermission needed to get the current usage from the usage service. No other data is retrieved nor used. If required click the button below, select this app and enable the permission.
25 | Usage Stats settings
26 | Permission granted
27 | Permission not granted
28 | Permission needed
29 | Error while getting permission state
30 |
31 |
32 |
33 | Settings
34 | Settings:
35 | Total data:\nusage to show in the upper bar when totally filled. Normally your data plan limit. In megabytes (MB)
36 | Period length:\nChoose the length of the data period, for example 1 month, 7 days (a week), 28 days, 1 day…
37 | Period start:\nChoose the first day (included) of the current period, as of today. For example if you have a monthly period that changes each month, choose day 1 of today\'s month.\nThis value will auto-update each time the current period changes. Note: if you set it inconsistently (for example you choose a future day) it will also autoupdate. You can close and reopen the settings screen to force updating.
38 | Decimals:\nNumber of decimals that data numbers will display.
39 | MB or GB:
40 | Display data in GB instead of MB.
41 | Pending/Used:
42 | Show pending data (bars will decrease from 100% to 0%) instead of the default used data (bars increase from 0% to 100%).
43 | Alternate conversion:\nCheck this if data usage shown is always less than real usage returned by your company app. [Technical details: uses 1000 instead of 1024 to convert from bytes to megabytes]
44 | Use alternate MB conversion
45 | MB
46 | Accumulated periods:\nNumber of periods that unused data is saved to future periods. If zero, unused data is lost when changing period. If not, the unused megas from the previous n periods will be spent before the current ones (will show a \'negative\' usage until then). Example: if your data provider \'accumulates megas from the past three months\' (with a monthly period) enter 3.
47 | Acumulated megas/Data offset:\nMegas accumulated from the previous period, (general offset data). This value will be added to the available data in your current period. You can use it to offset the displayed value (enter negative to subtract available data).\nThis value will auto-update each time the current period changes, but if you are enabling this for the first time you may want to manually enter the accumulated megas from the previous period (check your data provider). Alternatively you can press the button to configure using different helpers. You can edit it at any time.
48 | Calculate
49 | Months
50 | Days
51 |
52 | Set automatically finding month without usage (better check it afterwards)
53 | Set visible value
54 |
55 | Current value
56 | Set here what the widget should display, and the data will be set accordingly
57 |
58 |
59 |
60 | -loading-
61 | Permission missing
62 | Error querying data
63 | Service unavailable
64 | %s (%s)
65 | Android activity not found.
66 | Progress bars (4x1)
67 | Rate (1x1)
68 |
69 | ]]>
70 |
71 |
72 | Tweaks
73 | Tweaks:\nList of experimental settings to tweak some properties of widgets (may be added/removed in future versions if necessary). Some combinations may not work together or show unwanted results (like \'hide date\'+\'hide data\' which will show an empty widget) but they are safe to enable/disable so have fun tweaking as you want.\n\nImportant! Existing widgets may need to be clicked or removed/added to update.
74 | close
75 | Hide date bar and text (the top one)
76 | Hide data bar and text (the bottom one)
77 | Hide data and date bars
78 | Hide data and date texts
79 | Show light-theme widgets (white background with black text)
80 | Show the consumed data in the 1x1 widget (instead of the rate)
81 | Show the average data in the 1x1 widget (instead of the rate)
82 | Show remaining data instead of consumed data (bars will move from 100% to 0% instead of 0% to 100%)
83 | "Keep bar at 100% instead of warping (in case your device doesn't show the secondary bar)"
84 | The secondary progress adapts dynamically.
85 | Open android usage screen when pressing the widget button.
86 |
87 |
88 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/widgetprovider_progress.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/widgetprovider_rate.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:8.7.1'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | google()
19 | mavenCentral()
20 | }
21 | }
22 |
23 | tasks.register('clean', Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | android.nonFinalResIds=false
13 | android.nonTransitiveRClass=false
14 | org.gradle.jvmargs=-Xmx1536m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrianguloY/Average-data-usage-widget/2d3a7657887687cc13ed43dfd7c42e3a7eb55670/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jun 28 17:38:33 CEST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------