├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── apk ├── AdvDataPlan.apk └── AdvDataPlan_debug.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── xposed_init │ │ ├── java │ │ └── net │ │ │ └── m0m4x │ │ │ └── android │ │ │ └── xposed │ │ │ └── advdataplan │ │ │ └── HookMain.java │ │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-it │ │ └── strings.xml │ │ └── values │ │ └── strings.xml └── trash.txt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot ├── 1.png ├── 2.png └── 3.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 2 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 3 | 4 | *.iml 5 | .gradle 6 | /local.properties 7 | /.idea/workspace.xml 8 | /.idea/libraries 9 | .DS_Store 10 | /build 11 | /captures 12 | .externalNativeBuild 13 | 14 | # User-specific stuff: 15 | .idea/**/workspace.xml 16 | .idea/**/tasks.xml 17 | .idea/dictionaries 18 | 19 | # Sensitive or high-churn files: 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.xml 23 | .idea/**/dataSources.local.xml 24 | .idea/**/sqlDataSources.xml 25 | .idea/**/dynamic.xml 26 | .idea/**/uiDesigner.xml 27 | 28 | # Gradle: 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # CMake 33 | cmake-build-debug/ 34 | 35 | # Mongo Explorer plugin: 36 | .idea/**/mongoSettings.xml 37 | 38 | ## File-based project format: 39 | *.iws 40 | 41 | ## Plugin-specific files: 42 | 43 | # IntelliJ 44 | /out/ 45 | 46 | # mpeltonen/sbt-idea plugin 47 | .idea_modules/ 48 | 49 | # JIRA plugin 50 | atlassian-ide-plugin.xml 51 | 52 | # Cursive Clojure plugin 53 | .idea/replstate.xml 54 | 55 | # Crashlytics plugin (for Android Studio and IntelliJ) 56 | com_crashlytics_export_strings.xml 57 | crashlytics.properties 58 | crashlytics-build.properties 59 | fabric.properties -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Data Plan 2 | Xposed Framework module
3 | Change Data Usage Cycle date range 4 | 5 | Features 6 | ======== 7 | 8 | * Calibrate Android stock Data Usage Summary with your mobile plan 9 | * Check your data usage without 3-party Apps 10 | * Set Daily or Weekly mobile data plan 11 | * Set Data Plan specifying a custom duration in days 12 | 13 | Requirements 14 | ============ 15 | Tested with API23, but maybe will work with early versions 16 | 17 | Screenshot 18 | ========== 19 |

20 | pict1 21 | pict2 22 | pict3 23 |

24 | 25 | Support 26 | ======= 27 | Please visit [XDA:DevDB Project Thread](https://forum.xda-developers.com/xposed/modules/xposed-advanced-data-plan-t3634138). 28 | 29 | Tested with 30 | =========== 31 | * Cm or Aosp based roms 32 | * TouchWiz roms 33 | 34 | Know issues 35 | =========== 36 | * None :\ 37 | -------------------------------------------------------------------------------- /apk/AdvDataPlan.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/apk/AdvDataPlan.apk -------------------------------------------------------------------------------- /apk/AdvDataPlan_debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/apk/AdvDataPlan_debug.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "net.m0m4x.android.xposed.advdataplan" 8 | minSdkVersion 23 9 | targetSdkVersion 25 10 | versionCode 6 11 | versionName "1.0.6" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | testCompile 'junit:junit:4.12' 23 | provided 'de.robv.android.xposed:api:82' 24 | provided 'de.robv.android.xposed:api:82:sources' 25 | } 26 | 27 | -------------------------------------------------------------------------------- /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 C:\Users\max\AppData\Local\Android\Sdk/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 | 3 | 4 | 10 | 13 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | net.m0m4x.android.xposed.advdataplan.HookMain -------------------------------------------------------------------------------- /app/src/main/java/net/m0m4x/android/xposed/advdataplan/HookMain.java: -------------------------------------------------------------------------------- 1 | package net.m0m4x.android.xposed.advdataplan; 2 | /** 3 | * Created by max on 09/04/2017. 4 | */ 5 | 6 | import android.annotation.SuppressLint; 7 | import android.app.AlertDialog; 8 | import android.app.DatePickerDialog; 9 | import android.app.DialogFragment; 10 | import android.content.Context; 11 | import android.content.DialogInterface; 12 | import android.content.res.XModuleResources; 13 | import android.os.Build; 14 | import android.text.format.DateUtils; 15 | import android.text.format.Time; 16 | import android.util.Log; 17 | import android.util.TypedValue; 18 | import android.view.Gravity; 19 | import android.view.LayoutInflater; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.widget.DatePicker; 23 | import android.widget.LinearLayout; 24 | import android.widget.NumberPicker; 25 | import android.widget.TextView; 26 | import android.widget.Toast; 27 | 28 | import java.lang.reflect.Field; 29 | import java.lang.reflect.Method; 30 | import java.lang.reflect.Modifier; 31 | import java.text.Format; 32 | import java.text.SimpleDateFormat; 33 | import java.util.Calendar; 34 | import java.util.Date; 35 | 36 | import de.robv.android.xposed.IXposedHookInitPackageResources; 37 | import de.robv.android.xposed.IXposedHookLoadPackage; 38 | import de.robv.android.xposed.IXposedHookZygoteInit; 39 | import de.robv.android.xposed.XC_MethodHook; 40 | import de.robv.android.xposed.XC_MethodReplacement; 41 | import de.robv.android.xposed.XposedBridge; 42 | import de.robv.android.xposed.XposedHelpers; 43 | import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam; 44 | import de.robv.android.xposed.callbacks.XC_LayoutInflated; 45 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; 46 | 47 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; 48 | 49 | 50 | public class HookMain implements IXposedHookZygoteInit, IXposedHookInitPackageResources, IXposedHookLoadPackage { 51 | 52 | private static final boolean DEBUG = true; 53 | 54 | /**************************** 55 | RESOURCES Hooking 56 | 57 | */ 58 | 59 | private static String MODULE_PATH = null; 60 | 61 | 62 | static int R_layout_data_usage_cycle_editor; 63 | static int R25_xml_data_usage_cellular; 64 | static int R_id_datepicker; 65 | static int R_id_cycle_days; 66 | static int R_id_cycle_day; 67 | 68 | static int modR_strings_dataplan_days; 69 | static int modR_strings_dataplan_day; 70 | static int modR_strings_nr1_daily; 71 | static int modR_strings_nr7_weekly; 72 | static int modR_strings_nr30_fixedmonth; 73 | static int modR_strings_nr31_monthly; 74 | static int modR_strings_summary_days; 75 | static int modR_strings_summary_starting; 76 | static int modR_strings_cycle_days; 77 | static int modR_strings_cycle_detail; 78 | 79 | @Override 80 | public void initZygote(StartupParam startupParam) throws Throwable { 81 | MODULE_PATH = startupParam.modulePath; 82 | if(DEBUG) XposedBridge.log("HOOK init AdvDataPlan - modulePath:" + startupParam.modulePath + " sdk: "+Build.VERSION.SDK_INT+""); 83 | 84 | } 85 | 86 | @Override 87 | public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable { 88 | if(DEBUG) XposedBridge.log("HOOK RES init - " + resparam.packageName + " ! "); 89 | if(!resparam.packageName.equals("com.android.settings")) { 90 | return; 91 | } 92 | 93 | /* 94 | Get ID of module resources 95 | 96 | Marshmallow: native 97 | Nougat: Ok! 98 | */ 99 | XModuleResources modRes = XModuleResources.createInstance(MODULE_PATH, resparam.res); 100 | modR_strings_dataplan_days = resparam.res.addResource(modRes, R.string.dataplan_days); 101 | modR_strings_dataplan_day = resparam.res.addResource(modRes, R.string.dataplan_day); 102 | modR_strings_nr1_daily = resparam.res.addResource(modRes, R.string.nr1_daily); 103 | modR_strings_nr7_weekly = resparam.res.addResource(modRes, R.string.nr7_weekly); 104 | modR_strings_nr30_fixedmonth = resparam.res.addResource(modRes, R.string.nr30_fixedmonth); 105 | modR_strings_nr31_monthly = resparam.res.addResource(modRes, R.string.nr31_monthly); 106 | modR_strings_summary_days = resparam.res.addResource(modRes, R.string.summary_days); 107 | modR_strings_summary_starting = resparam.res.addResource(modRes, R.string.summary_starting); 108 | modR_strings_cycle_days = resparam.res.addResource(modRes, R.string.cycle_days); 109 | modR_strings_cycle_detail = resparam.res.addResource(modRes, R.string.cycle_detail); 110 | 111 | /* 112 | Get ID of native resources 113 | 114 | Marshmallow: native 115 | Nougat: Ok! 116 | */ 117 | R_layout_data_usage_cycle_editor = resparam.res.getIdentifier("data_usage_cycle_editor", "layout", "com.android.settings"); 118 | if(DEBUG) XposedBridge.log("HOOK RES ...found R.layout.data_usage_cycle_editor : " + R_layout_data_usage_cycle_editor + " !"); 119 | 120 | R25_xml_data_usage_cellular = resparam.res.getIdentifier("data_usage_cellular", "xml", "com.android.settings"); 121 | if(DEBUG) XposedBridge.log("HOOK RES ...found R.layout.data_usage_cellular : " + R25_xml_data_usage_cellular + " !"); 122 | 123 | /* 124 | Hook Layout 'data_usage_cycle_editor' 125 | 126 | Marshmallow: native 127 | Nougat: Ok! 128 | */ 129 | resparam.res.hookLayout("com.android.settings", "layout", "data_usage_cycle_editor", new XC_LayoutInflated() { 130 | @SuppressLint("NewApi") 131 | @Override 132 | public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { 133 | if(DEBUG) XposedBridge.log("HOOK RES handleLayoutInflated(): Layout is inflating... - data_usage_cycle_editor!"); 134 | 135 | Context context = liparam.view.getContext(); 136 | 137 | /* 138 | layout 0 : [ 139 | txtview <= original (visibility GONE) 140 | numpicker <= original (visibility GONE) 141 | + layout 1 [ datepicker ] 142 | + layout 2 [txtview numpicker] 143 | ] 144 | */ 145 | int i150dip = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150, context.getResources().getDisplayMetrics()); 146 | int i100dip = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, context.getResources().getDisplayMetrics()); 147 | int i48dip = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources().getDisplayMetrics()); 148 | int i16dip = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, context.getResources().getDisplayMetrics()); 149 | 150 | try { 151 | 152 | //numberPicker 153 | R_id_cycle_day = liparam.res.getIdentifier("cycle_day", "id", "com.android.settings"); 154 | Object l0_num = liparam.view.findViewById(R_id_cycle_day); 155 | XposedHelpers.callMethod(l0_num, "setVisibility", View.GONE); 156 | 157 | //debug 158 | //if (DEBUG) view_dump(l0_num); 159 | 160 | //layout 0 (root - ViewGroup) 161 | ViewGroup res_layout0 = (ViewGroup) XposedHelpers.callMethod(l0_num, "getParent"); 162 | if(DEBUG) XposedBridge.log("HOOK RES handleLayoutInflated(): Parent layout is " + res_layout0.getClass().getName()); 163 | //case: LinearLayout - set Vertical 164 | if (res_layout0 instanceof LinearLayout) { 165 | if(DEBUG) XposedBridge.log("HOOK RES handleLayoutInflated(): LinearLayout instance detected."); 166 | LinearLayout res_lin_layout0 = (LinearLayout) res_layout0; 167 | res_lin_layout0.setOrientation(LinearLayout.VERTICAL); 168 | } 169 | 170 | //hide originals layouts 171 | try { 172 | for (int i = 0; i < res_layout0.getChildCount(); i++) { 173 | res_layout0.getChildAt(i).setVisibility(View.GONE); 174 | } 175 | } catch(Exception ex) { 176 | XposedBridge.log("HOOK WARNING handleLayoutInflated(): Cannot empty existent layout!"); 177 | StackTraceElement[] elements = ex.getStackTrace(); 178 | XposedBridge.log("HOOK WARNING handleLayoutInflated(): " + ex.getMessage()); 179 | XposedBridge.log("HOOK WARNING handleLayoutInflated(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 180 | } 181 | 182 | //layout 1 183 | LinearLayout res_layout1 = new LinearLayout(context); 184 | res_layout1.setOrientation(LinearLayout.HORIZONTAL); 185 | TextView l1_txt = new TextView(context); 186 | LinearLayout.LayoutParams l1_txt_lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f); 187 | l1_txt_lp.gravity = Gravity.CENTER_VERTICAL; 188 | l1_txt_lp.setMarginStart(i16dip); 189 | l1_txt.setLayoutParams(l1_txt_lp); 190 | l1_txt.setGravity(Gravity.CENTER_VERTICAL); 191 | //l2_txt.setTextAppearance(context, android.R.style.TextAppearance_DeviceDefault_Medium); 192 | l1_txt.setText(modR_strings_dataplan_day); 193 | DatePickerDialog l1_dat = new DatePickerDialog(context, android.R.style.Theme_Holo_Light_Dialog, null, 2017,4,16); 194 | l1_dat.getDatePicker().findViewById(context.getResources().getIdentifier("year","id","android")).setVisibility(View.GONE); 195 | l1_dat.getDatePicker().setId(View.generateViewId()); 196 | R_id_datepicker = l1_dat.getDatePicker().getId(); 197 | l1_dat.getDatePicker().setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f)); 198 | l1_dat.getDatePicker().setCalendarViewShown(false); 199 | l1_dat.getDatePicker().setSpinnersShown(true); 200 | //l1_dat.getDatePicker().setVisibility(View.GONE); 201 | res_layout1.addView(l1_txt); 202 | res_layout1.addView(l1_dat.getDatePicker()); 203 | 204 | //layout 2 205 | LinearLayout res_layout2 = new LinearLayout(context); 206 | res_layout2.setOrientation(LinearLayout.HORIZONTAL); 207 | TextView l2_txt = new TextView(context); 208 | LinearLayout.LayoutParams l2_txt_lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f); 209 | l2_txt_lp.gravity = Gravity.CENTER_VERTICAL; 210 | l2_txt_lp.setMarginStart(i16dip); 211 | l2_txt.setLayoutParams(l2_txt_lp); 212 | l2_txt.setGravity(Gravity.CENTER_VERTICAL); 213 | //l2_txt.setTextAppearance(context, android.R.style.TextAppearance_DeviceDefault_Medium); 214 | l2_txt.setText(modR_strings_dataplan_days); 215 | NumberPicker l2_num = new NumberPicker(context); 216 | l2_num.setId(View.generateViewId()); 217 | R_id_cycle_days = l2_num.getId(); 218 | LinearLayout.LayoutParams l2_num_lp = new LinearLayout.LayoutParams( i150dip, i100dip); 219 | l2_num_lp.setMarginEnd(i16dip); 220 | l2_num_lp.setMarginStart(i16dip); 221 | l2_num.setLayoutParams(l2_num_lp); 222 | l2_num.setGravity(Gravity.CENTER_VERTICAL); 223 | res_layout2.addView(l2_txt); 224 | res_layout2.addView(l2_num); 225 | 226 | //Adding Layouts 227 | (res_layout0).addView(res_layout1); 228 | (res_layout0).addView(res_layout2); 229 | 230 | } catch (Exception ex) { 231 | XposedBridge.log("HOOK ERROR handleLayoutInflated(): " + ex.getMessage()); 232 | StackTraceElement[] elements = ex.getStackTrace(); 233 | XposedBridge.log("HOOK ERROR handleLayoutInflated(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 234 | throw ex; 235 | } 236 | 237 | if(DEBUG) XposedBridge.log("HOOK RES handleLayoutInflated(): Layout is inflated!"); 238 | } 239 | }); 240 | 241 | } 242 | 243 | 244 | 245 | 246 | 247 | /**************************** 248 | METHODS Hooking 249 | 250 | */ 251 | 252 | public static final int CYCLE_NONE = -1; 253 | private static final String EXTRA_TEMPLATE = "template"; 254 | 255 | public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { 256 | 257 | if(DEBUG) XposedBridge.log("HOOK handleLoadPackage - " + lpparam.packageName + "! " ); 258 | 259 | /* 260 | NetworkPolicy Classes - Compute cycle Boundaries 261 | 262 | */ 263 | if( lpparam.packageName.equals("android") 264 | || lpparam.packageName.equals("com.android.systemui") 265 | || lpparam.packageName.equals("com.android.settings") 266 | || lpparam.packageName.equals("com.android.providers.settings") 267 | ){ 268 | if(DEBUG) XposedBridge.log("HOOK NetworkPolicyManager methods! (pkg:"+lpparam.packageName+")"); 269 | 270 | final Class NetworkPolicyManager = XposedHelpers.findClass( 271 | "android.net.NetworkPolicyManager", 272 | lpparam.classLoader); 273 | final Class NetworkPolicy = XposedHelpers.findClass( 274 | "android.net.NetworkPolicy", 275 | lpparam.classLoader); 276 | 277 | findAndHookMethod(NetworkPolicyManager, "computeNextCycleBoundary", long.class , NetworkPolicy , new XC_MethodReplacement() { 278 | 279 | @Override 280 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 281 | if(DEBUG) XposedBridge.log("HOOK REQ computeNextCycleBoundary(): (pkg:"+lpparam.packageName+")"); 282 | 283 | // Get Params 284 | long currentTime = (long) param.args[0]; // long currentTime 285 | Object policy = param.args[1]; // NetworkPolicy policy 286 | int cycle_day = (int) XposedHelpers.getObjectField(policy, "cycleDay"); 287 | 288 | // Check 289 | if (cycle_day == CYCLE_NONE) { 290 | throw new IllegalArgumentException("Unable to compute boundary without cycleDay"); 291 | } 292 | 293 | return mComputeNextCycleBoundary(currentTime, cycle_day); 294 | 295 | } 296 | 297 | }); 298 | 299 | findAndHookMethod(NetworkPolicyManager, "computeLastCycleBoundary", long.class , NetworkPolicy , new XC_MethodReplacement() { 300 | 301 | @Override 302 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 303 | if(DEBUG) XposedBridge.log("HOOK REQ computeLastCycleBoundary(): (pkg:"+lpparam.packageName+")"); 304 | 305 | // Get Params 306 | long currentTime = (long) param.args[0]; // long currentTime 307 | Object policy = param.args[1]; // NetworkPolicy policy 308 | int cycle_day = (int) XposedHelpers.getObjectField(policy, "cycleDay"); 309 | 310 | // Need to Check policy template mobile? (otherwise do default) 311 | 312 | // Check 313 | if (cycle_day == CYCLE_NONE) { 314 | throw new IllegalArgumentException("Unable to compute boundary without cycleDay"); 315 | } 316 | return mComputeLastCycleBoundary(currentTime, cycle_day); 317 | 318 | } 319 | 320 | }); 321 | 322 | } 323 | 324 | /* 325 | DataUsage Classes - Editor Dialog Fragment 326 | 327 | */ 328 | if( lpparam.packageName.equals("com.android.settings") ) { 329 | if(DEBUG) XposedBridge.log("HOOK DataUsageSummary/BillingCycleSettings methods! (pkg:"+lpparam.packageName+")!"); 330 | 331 | //SDK23 332 | if (Build.VERSION.SDK_INT == 23) { 333 | final Class CycleEditorFragment = XposedHelpers.findClass( 334 | "com.android.settings.DataUsageSummary.CycleEditorFragment", 335 | lpparam.classLoader); 336 | findAndHookMethod(CycleEditorFragment, "onCreateDialog", "android.os.Bundle", new XC_MethodReplacement() { 337 | @Override 338 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 339 | if (DEBUG) XposedBridge.log("HOOK onCreateDialog(): Called now!"); 340 | 341 | try { 342 | return createAdvDialog(param); 343 | } catch (Exception ex) { 344 | XposedBridge.log("HOOK ERROR createAdvDialog(): " + ex.getMessage()); 345 | StackTraceElement[] elements = ex.getStackTrace(); 346 | XposedBridge.log("HOOK ERROR createAdvDialog(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 347 | String dump=obj_dump(param.thisObject); 348 | XposedBridge.log("HOOK ERROR createAdvDialog() objDUMP: Dumping DataUsageSummary.CycleEditorFragment ..."); 349 | for (String item : dump.split(System.getProperty("line.separator"))) { 350 | XposedBridge.log("HOOK ERROR createAdvDialog() objDUMP: " + item); 351 | } 352 | throw ex; 353 | } 354 | 355 | } 356 | 357 | }); 358 | } 359 | 360 | //SDK24-25 361 | if (Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) { 362 | final Class CycleEditorFragment = XposedHelpers.findClass( 363 | "com.android.settings.datausage.BillingCycleSettings.CycleEditorFragment", 364 | lpparam.classLoader); 365 | findAndHookMethod(CycleEditorFragment, "onCreateDialog", "android.os.Bundle", new XC_MethodReplacement() { 366 | @Override 367 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 368 | if (DEBUG) XposedBridge.log("HOOK onCreateDialog(): Starting here! " + lpparam.packageName); 369 | 370 | try { 371 | return createAdvDialog(param); 372 | 373 | } catch (Exception ex) { 374 | XposedBridge.log("HOOK ERROR createAdvDialog(): " + ex.getMessage()); 375 | StackTraceElement[] elements = ex.getStackTrace(); 376 | XposedBridge.log("HOOK ERROR createAdvDialog(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 377 | String dump=obj_dump(param.thisObject); 378 | XposedBridge.log("HOOK ERROR createAdvDialog() objDUMP: Dumping BillingCycleSettings.CycleEditorFragment ..."); 379 | for (String item : dump.split(System.getProperty("line.separator"))) { 380 | XposedBridge.log("HOOK ERROR createAdvDialog() objDUMP: " + item); 381 | } 382 | throw ex; 383 | } 384 | 385 | } 386 | }); 387 | findAndHookMethod(CycleEditorFragment, "onClick", DialogInterface.class, int.class , new XC_MethodReplacement() { 388 | @Override 389 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 390 | if (DEBUG) XposedBridge.log("HOOK onClick START!" + lpparam.packageName); 391 | 392 | try { 393 | final Object args = XposedHelpers.callMethod(param.thisObject, "getArguments"); 394 | final Object template = XposedHelpers.callMethod(args, "getParcelable", EXTRA_TEMPLATE ); //type NetworkTemplate 395 | final Object target = XposedHelpers.callMethod(param.thisObject, "getTargetFragment"); //type sdk24:BillingCycleSettings sdk25:DataUsageEditController 396 | 397 | final Object editor; //type NetworkPolicyEditor 398 | if (Build.VERSION.SDK_INT == 24) { 399 | Object services = XposedHelpers.getObjectField(target,"services"); 400 | editor = XposedHelpers.getObjectField(services,"mPolicyEditor"); 401 | } else if (Build.VERSION.SDK_INT == 25) { 402 | editor = XposedHelpers.callMethod(target, "getNetworkPolicyEditor"); 403 | } else { 404 | XposedBridge.log("HOOK AdvDialog.onClick(): SDK "+Build.VERSION.SDK_INT+" not supported!"); 405 | return null; 406 | } 407 | 408 | final NumberPicker cycleDayPicker = (NumberPicker) XposedHelpers.getObjectField(param.thisObject, "mCycleDayPicker"); 409 | final NumberPicker cycleDaysPicker = (NumberPicker) XposedHelpers.getAdditionalStaticField(param.thisObject, "mCycleDaysPicker"); 410 | final DatePicker cycleDatePicker = (DatePicker) XposedHelpers.getAdditionalStaticField(param.thisObject, "mCycleDatePicker"); 411 | 412 | // clear focus to finish pending text edits 413 | cycleDayPicker.clearFocus(); 414 | cycleDaysPicker.clearFocus(); 415 | cycleDatePicker.clearFocus(); 416 | 417 | // Encode Day of Month, Month and Duration into one int 418 | // via BitShift method. 419 | int bs = encodeBitShiftedInt(cycleDatePicker, cycleDaysPicker); 420 | 421 | //Save in policy CycleDay 422 | final String cycleTimezone = new Time().timezone; 423 | XposedHelpers.callMethod(editor, "setPolicyCycleDay", template, bs, cycleTimezone); 424 | 425 | if (Build.VERSION.SDK_INT == 24) { 426 | XposedHelpers.callMethod(target, "updatePrefs"); 427 | } else if (Build.VERSION.SDK_INT == 25) { 428 | XposedHelpers.callMethod(target, "updateDataUsage"); 429 | } 430 | 431 | return null; 432 | 433 | } catch (Exception ex) { 434 | XposedBridge.log("HOOK ERROR AdvDialog.onClick(): " + ex.getMessage()); 435 | StackTraceElement[] elements = ex.getStackTrace(); 436 | XposedBridge.log("HOOK ERROR AdvDialog.onClick(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 437 | String dump=obj_dump(param.thisObject); 438 | XposedBridge.log("HOOK ERROR AdvDialog.onClick() objDUMP: Dumping BillingCycleSettings.CycleEditorFragment ..."); 439 | for (String item : dump.split(System.getProperty("line.separator"))) { 440 | XposedBridge.log("HOOK ERROR AdvDialog.onClick() objDUMP: " + item); 441 | } 442 | throw ex; 443 | } 444 | 445 | } 446 | }); 447 | 448 | /* 449 | findAndHookMethod(CycleEditorFragment, "show", BillingCycleSettings, new XC_MethodReplacement() { 450 | @Override 451 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 452 | if (DEBUG) XposedBridge.log("HOOK show START!" + lpparam.packageName); 453 | 454 | return null; 455 | } 456 | }); 457 | */ 458 | 459 | } 460 | 461 | } 462 | 463 | /* 464 | System UI - getDataUsageInfo 465 | 466 | */ 467 | 468 | if( lpparam.packageName.equals("com.android.systemui") 469 | || lpparam.packageName.equals("com.android.settings") 470 | ){ 471 | 472 | if(DEBUG) XposedBridge.log("HOOK SystemUI methods! (pkg:"+lpparam.packageName+")!"); 473 | 474 | //SDK22 475 | // Lollipop not supported 476 | // CLASS_MOBILE_DATA_CONTROLLER_23 = "com.android.systemui.statusbar.policy.MobileDataControllerImpl"; 477 | // CLASS_MOBILE_DATA_CONTROLLER_22 = "com.android.systemui.statusbar.policy.MobileDataController"; 478 | // Build.VERSION.SDK_INT >= 22 ? CLASS_MOBILE_DATA_CONTROLLER_23 : CLASS_MOBILE_DATA_CONTROLLER_22; 479 | 480 | //SDK23 481 | if (Build.VERSION.SDK_INT == 23) { 482 | final Class MobileDataController = XposedHelpers.findClass( 483 | "com.android.systemui.statusbar.policy.MobileDataControllerImpl", 484 | lpparam.classLoader); 485 | final Class NetworkTemplate = XposedHelpers.findClass( 486 | "android.net.NetworkTemplate", 487 | lpparam.classLoader); 488 | final Class NetworkStatsHistory = XposedHelpers.findClass( 489 | "android.net.NetworkStatsHistory", 490 | lpparam.classLoader); 491 | final Class DataUsageInfo = XposedHelpers.findClass( 492 | "com.android.systemui.statusbar.policy.NetworkController$MobileDataController$DataUsageInfo", 493 | lpparam.classLoader); 494 | findAndHookMethod(MobileDataController, "getDataUsageInfo", new XC_MethodReplacement() { 495 | @Override 496 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 497 | if (DEBUG) 498 | XposedBridge.log("HOOK UI MobileDataController.getDataUsageInfo(): (pkg:" + lpparam.packageName + ")"); 499 | 500 | return getAdvDataUsageInfo(param, NetworkTemplate, NetworkStatsHistory, DataUsageInfo); 501 | } 502 | }); 503 | } 504 | 505 | 506 | //SDK24-25 507 | if (Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) { 508 | final Class DataUsageController = XposedHelpers.findClass( 509 | "com.android.settingslib.net.DataUsageController", 510 | lpparam.classLoader); 511 | final Class NetworkTemplate = XposedHelpers.findClass( 512 | "android.net.NetworkTemplate", 513 | lpparam.classLoader); 514 | final Class NetworkStatsHistory = XposedHelpers.findClass( 515 | "android.net.NetworkStatsHistory", 516 | lpparam.classLoader); 517 | final Class DataUsageInfo = XposedHelpers.findClass( 518 | "com.android.settingslib.net.DataUsageController.DataUsageInfo", 519 | lpparam.classLoader); 520 | findAndHookMethod(DataUsageController, "getDataUsageInfo", NetworkTemplate, new XC_MethodReplacement() { 521 | @Override 522 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { 523 | if (DEBUG) 524 | XposedBridge.log("HOOK UI DataUsageController.getDataUsageInfo(): (pkg:" + lpparam.packageName + ")"); 525 | 526 | return getAdvDataUsageInfo(param, NetworkTemplate, NetworkStatsHistory, DataUsageInfo); 527 | } 528 | }); 529 | 530 | } 531 | 532 | } 533 | 534 | 535 | 536 | /* 537 | System UI - Settings App - Billing Cycle preview 538 | only in SDK 24-25 539 | */ 540 | 541 | if (Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) { 542 | if( false|| lpparam.packageName.equals("com.android.settings") 543 | || lpparam.packageName.equals("com.android.settings.datausage") 544 | ) { 545 | 546 | //BillingCyclePreference.java 547 | // com.android.settings.datausage.BillingCyclePreference 548 | // extend android.support.v7.preference.Preference 549 | final Class Preferencev7 = XposedHelpers.findClass( 550 | "android.support.v7.preference.Preference", 551 | lpparam.classLoader); 552 | findAndHookMethod(Preferencev7, "setSummary", CharSequence.class, new XC_MethodHook() { 553 | @Override 554 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 555 | if (DEBUG)XposedBridge.log("HOOK UI Preference.setSummary(): class "+param.thisObject.getClass().getName().toString()+" (pkg:" + lpparam.packageName + ")"); 556 | 557 | //com.android.settings.datausage.BillingCyclePreference 558 | if (param.thisObject.getClass().getName().equals("com.android.settings.datausage.BillingCyclePreference")){ 559 | 560 | try{ 561 | //Get CycleDay 562 | Object policy = XposedHelpers.getObjectField(param.thisObject, "mPolicy"); 563 | int cycleDay = 1; 564 | if(policy != null) cycleDay = (int) XposedHelpers.getObjectField(policy, "cycleDay"); 565 | 566 | //Decode CycleDay 567 | Object[] decodedArr = decodeBitShiftedInt(cycleDay); 568 | Calendar pref_cycleDate = (Calendar) decodedArr[0]; 569 | int pref_cycleDays = (int) decodedArr[1]; 570 | 571 | //Build phrase 572 | final Context context = (Context) XposedHelpers.callMethod(param.thisObject, "getContext"); 573 | String strDays; 574 | switch (pref_cycleDays) { 575 | case 1: strDays = (String) XposedHelpers.callMethod(context, "getString", modR_strings_nr1_daily); 576 | break; 577 | case 7: strDays = (String) XposedHelpers.callMethod(context, "getString", modR_strings_nr7_weekly); 578 | break; 579 | case 31: strDays = (String) XposedHelpers.callMethod(context, "getString", modR_strings_nr31_monthly); 580 | break; 581 | default: strDays = String.format((String) XposedHelpers.callMethod(context, "getString", modR_strings_summary_days), pref_cycleDays); 582 | break; 583 | } 584 | 585 | Format format = new SimpleDateFormat("dd MMM yyyy"); 586 | param.args[0] = strDays + " " + String.format( (String) XposedHelpers.callMethod(context, "getString", modR_strings_summary_starting), format.format(new Date(pref_cycleDate.getTimeInMillis()))); 587 | 588 | } catch (Exception ex) { 589 | XposedBridge.log("HOOK ERROR Preference.setSummary(): " + ex.getMessage()); 590 | StackTraceElement[] elements = ex.getStackTrace(); 591 | XposedBridge.log("HOOK ERROR Preference.setSummary(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 592 | String dump=obj_dump(param.thisObject); 593 | XposedBridge.log("HOOK ERROR Preference.setSummary() objDUMP: Dumping Preferencev7 ..."); 594 | for (String item : dump.split(System.getProperty("line.separator"))) { 595 | XposedBridge.log("HOOK ERROR Preference.setSummary() objDUMP: " + item); 596 | } 597 | throw ex; 598 | } 599 | } 600 | 601 | } 602 | }); 603 | 604 | //BillingCycleSettings.java 605 | // com.android.settings.datausage.BillingCycleSettings 606 | // calls android.support.v7.preference.Preference setSummary() 607 | final Class BillingCycleSettings = XposedHelpers.findClass( 608 | "com.android.settings.datausage.BillingCycleSettings", 609 | lpparam.classLoader); 610 | findAndHookMethod(BillingCycleSettings, "updatePrefs", new XC_MethodHook() { 611 | @Override 612 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 613 | if (DEBUG)XposedBridge.log("HOOK UI BillingCycleSettings.updatePrefs(): (pkg:" + lpparam.packageName + ")"); 614 | 615 | try { 616 | Object template = XposedHelpers.getObjectField(param.thisObject,"mNetworkTemplate"); 617 | Object services = XposedHelpers.getObjectField(param.thisObject,"services"); 618 | Object editor = XposedHelpers.getObjectField(services,"mPolicyEditor"); 619 | Object policy = XposedHelpers.callMethod(editor,"getPolicy",template); 620 | 621 | //Get CycleDay 622 | int cycleDay = 1; 623 | if(policy != null) cycleDay = (int) XposedHelpers.getObjectField(policy, "cycleDay"); 624 | 625 | //Decode CycleDay 626 | Object[] decodedArr = decodeBitShiftedInt(cycleDay); 627 | Calendar pref_cycleDate = (Calendar) decodedArr[0]; 628 | Calendar pref_cycleDateEnd = Calendar.getInstance(); 629 | pref_cycleDateEnd.setTimeInMillis(mComputeNextCycleBoundary(pref_cycleDate.getTimeInMillis(), cycleDay)); 630 | int pref_cycleDays = (int) decodedArr[1]; 631 | 632 | //Build phrase 633 | final Context context = (Context) XposedHelpers.callMethod(param.thisObject, "getContext"); 634 | String strDays; 635 | switch (pref_cycleDays) { 636 | case 1: strDays = (String) XposedHelpers.callMethod(context, "getString", modR_strings_nr1_daily); 637 | break; 638 | case 7: strDays = (String) XposedHelpers.callMethod(context, "getString", modR_strings_nr7_weekly); 639 | break; 640 | case 31: strDays = (String) XposedHelpers.callMethod(context, "getString", modR_strings_nr31_monthly); 641 | break; 642 | default: strDays = String.format((String) XposedHelpers.callMethod(context, "getString", modR_strings_cycle_days), Integer.toString(pref_cycleDays)); 643 | break; 644 | } 645 | 646 | Format format = new SimpleDateFormat("dd MMM"); 647 | Object pref = XposedHelpers.getObjectField(param.thisObject, "mBillingCycle"); 648 | XposedHelpers.callMethod(pref, "setSummary", String.format((String) XposedHelpers.callMethod(context, "getString", modR_strings_cycle_detail), format.format(new Date(pref_cycleDate.getTimeInMillis())), format.format(new Date(pref_cycleDateEnd.getTimeInMillis())), strDays.toLowerCase() )); 649 | 650 | } catch (Exception ex) { 651 | XposedBridge.log("HOOK ERROR BillingCycleSettings.updatePrefs(): " + ex.getMessage()); 652 | StackTraceElement[] elements = ex.getStackTrace(); 653 | XposedBridge.log("HOOK ERROR BillingCycleSettings.updatePrefs(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 654 | String dump=obj_dump(param.thisObject); 655 | XposedBridge.log("HOOK ERROR BillingCycleSettings.updatePrefs() objDUMP: Dumping BillingCycleSettings ..."); 656 | for (String item : dump.split(System.getProperty("line.separator"))) { 657 | XposedBridge.log("HOOK ERROR BillingCycleSettings.updatePrefs() objDUMP: " + item); 658 | } 659 | throw ex; 660 | } 661 | 662 | } 663 | }); 664 | 665 | } 666 | } 667 | 668 | } 669 | 670 | /**************************** 671 | Main Methods 672 | 673 | */ 674 | 675 | //Compute methods 676 | private static long mComputeLastCycleBoundary(long currentTime, int cycleDay) { 677 | //Decode CycleDay 678 | Object[] decodedArr = decodeBitShiftedInt(cycleDay); 679 | Calendar pref_cycleDate = (Calendar) decodedArr[0]; 680 | int pref_cycleDays = (int) decodedArr[1]; 681 | 682 | //Debug - Wait... What is the request? 683 | if(DEBUG) { Format format = new SimpleDateFormat("dd/MM/yyyy"); 684 | XposedBridge.log("HOOK REQ LAST Cycle with prefTime:"+format.format(new Date(pref_cycleDate.getTimeInMillis()))+ 685 | "*"+pref_cycleDays+" for currentTime:"+format.format(new Date(currentTime))); } 686 | 687 | // Approach to date currentTime, when i am close choose Last <- or Next -> 688 | Calendar cycleDate = (Calendar) pref_cycleDate.clone(); 689 | int m; 690 | if (cycleDate.getTimeInMillis()>=currentTime) m = -1; else m = 1; 691 | while ( daysBetween(cycleDate.getTimeInMillis(), currentTime) >= pref_cycleDays ) { 692 | cycleDate.add(Calendar.DAY_OF_YEAR, pref_cycleDays * m); 693 | } 694 | // Set Last Cycle 695 | if(cycleDate.getTimeInMillis()>=currentTime) cycleDate.add(Calendar.DAY_OF_YEAR, -pref_cycleDays); 696 | 697 | //Debug - Ok... That's my result. 698 | if(DEBUG) { Format format = new SimpleDateFormat("dd/MM/yyyy"); 699 | XposedBridge.log("HOOK from prefTime:"+format.format(new Date(pref_cycleDate.getTimeInMillis()))+ 700 | " LAST to currentTime:"+format.format(new Date(currentTime))+ 701 | " is "+format.format(new Date(cycleDate.getTimeInMillis())) ); } 702 | 703 | //Return Last 704 | return cycleDate.getTimeInMillis(); 705 | } 706 | 707 | private static long mComputeNextCycleBoundary(long currentTime, int cycleDay) { 708 | //Decode CycleDay 709 | Object[] decodedArr = decodeBitShiftedInt(cycleDay); 710 | Calendar pref_cycleDate = (Calendar) decodedArr[0]; 711 | int pref_cycleDays = (int) decodedArr[1]; 712 | 713 | //Debug - Wait... What is the request? 714 | if(DEBUG) { Format format = new SimpleDateFormat("dd/MM/yyyy"); 715 | XposedBridge.log("HOOK REQ NEXT Cycle with prefTime:"+format.format(new Date(pref_cycleDate.getTimeInMillis()))+ 716 | "*"+pref_cycleDays+" for currentTime:"+format.format(new Date(currentTime))); } 717 | 718 | // Approach to date currentTime, when i am close choose Last <- or Next -> 719 | Calendar cycleDate = (Calendar) pref_cycleDate.clone(); 720 | int m; 721 | if (cycleDate.getTimeInMillis()>currentTime) m = -1; else m = 1; 722 | while ( daysBetween(cycleDate.getTimeInMillis(), currentTime) >= pref_cycleDays ) { 723 | if(DEBUG) XposedBridge.log("HOOK " + m + " " + pref_cycleDays ); 724 | cycleDate.add(Calendar.DAY_OF_YEAR, pref_cycleDays * m); 725 | } 726 | // Set Next Cycle 727 | if(cycleDate.getTimeInMillis()<=currentTime) cycleDate.add(Calendar.DAY_OF_YEAR, pref_cycleDays); 728 | 729 | //Debug - Ok... That's my result. 730 | if(DEBUG) { Format format = new SimpleDateFormat("dd/MM/yyyy"); 731 | XposedBridge.log("HOOK from prefTime:"+format.format(new Date(pref_cycleDate.getTimeInMillis()))+ 732 | " NEXT to currentTime:"+format.format(new Date(currentTime))+ 733 | " is "+format.format(new Date(cycleDate.getTimeInMillis())) ); } 734 | 735 | //Return Last 736 | return cycleDate.getTimeInMillis(); 737 | } 738 | 739 | //Dialog methods 740 | private static Object createAdvDialog(XC_MethodHook.MethodHookParam param) { 741 | 742 | XposedBridge.log("HOOK createAdvDialog(): start!"); 743 | 744 | DialogFragment mCycleEditorFragment = (DialogFragment) param.thisObject; //CycleEditorFragment 745 | final Context context = (Context) XposedHelpers.callMethod(param.thisObject, "getActivity"); 746 | 747 | final Object target = XposedHelpers.callMethod(param.thisObject, "getTargetFragment"); //type sdk:23 DataUsageSummary sdk:24 BillingCycleSettings sdk:25 DataUsageEditController 748 | final Object editor; //type NetworkPolicyEditor 749 | if (Build.VERSION.SDK_INT == 23) { 750 | editor = XposedHelpers.getObjectField(target, "mPolicyEditor"); 751 | } else if (Build.VERSION.SDK_INT == 24) { 752 | Object services = XposedHelpers.getObjectField(target,"services"); 753 | editor = XposedHelpers.getObjectField(services,"mPolicyEditor"); 754 | } else if (Build.VERSION.SDK_INT == 25) { 755 | editor = XposedHelpers.callMethod(target, "getNetworkPolicyEditor"); 756 | } else { 757 | XposedBridge.log("HOOK createAdvDialog(): SDK "+Build.VERSION.SDK_INT+" not supported!"); 758 | return null; 759 | } 760 | 761 | final AlertDialog.Builder builder = new AlertDialog.Builder(context); 762 | final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 763 | final View view = (View) XposedHelpers.callMethod(dialogInflater, "inflate", R_layout_data_usage_cycle_editor, null, false); 764 | 765 | if(DEBUG) XposedBridge.log("HOOK createAdvDialog(): R_id_cycle_day="+R_id_cycle_day); 766 | if(DEBUG) XposedBridge.log("HOOK createAdvDialog(): R_id_cycle_days="+R_id_cycle_days); 767 | if(DEBUG) XposedBridge.log("HOOK createAdvDialog(): R_id_datepicker="+R_id_datepicker); 768 | //view_dump(view); 769 | 770 | //get original cycleDayPicker (useless) 771 | final Object cycleDayPicker; 772 | if (Build.VERSION.SDK_INT == 23) { 773 | cycleDayPicker = view.findViewById(R_id_cycle_day); 774 | } else if (Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) { 775 | XposedHelpers.setObjectField(param.thisObject, "mCycleDayPicker", view.findViewById(R_id_cycle_day)); 776 | cycleDayPicker = XposedHelpers.getObjectField(param.thisObject, "mCycleDayPicker"); 777 | } else { 778 | Toast.makeText(context, "SDK "+Build.VERSION.SDK_INT+" not supported!", Toast.LENGTH_LONG).show(); 779 | return null; 780 | } 781 | 782 | //Get cycleDay 783 | final Object args = XposedHelpers.callMethod(param.thisObject, "getArguments"); 784 | final Object template = XposedHelpers.callMethod(args, "getParcelable", EXTRA_TEMPLATE ); //type NetworkTemplate 785 | final int cycleDay = (int) XposedHelpers.callMethod(editor, "getPolicyCycleDay", template); 786 | 787 | //Decode cycleDay 788 | Object[] decodedArr = decodeBitShiftedInt(cycleDay); 789 | Calendar pref_cycle_date = (Calendar) decodedArr[0]; 790 | int pref_cycle_days = (int) decodedArr[1]; 791 | 792 | //Update pref_cycle_date to Last Cycle 793 | while (pref_cycle_date.getTimeInMillis() < System.currentTimeMillis()) { // Cycle until cycle_date > currentTime 794 | pref_cycle_date.add(Calendar.DAY_OF_MONTH, pref_cycle_days); 795 | if (DEBUG) { 796 | Format ft = new SimpleDateFormat("dd MMM yyyy"); 797 | XposedBridge.log("HOOK createAdvDialog(): pref_cycle_date updated to " + ft.format(new Date(pref_cycle_date.getTimeInMillis())) + ""); 798 | } 799 | } 800 | pref_cycle_date.add(Calendar.DAY_OF_MONTH, -pref_cycle_days); //Set Last Cycle 801 | 802 | //Set layout cycleDayPicker (useless) 803 | try { 804 | if (DEBUG) XposedBridge.log("HOOK createAdvDialog(): cycleDayPicker id = " + XposedHelpers.callMethod(cycleDayPicker, "getId") + " " + cycleDayPicker.getClass().toString()); 805 | XposedHelpers.callMethod(cycleDayPicker, "setMinValue", 1); 806 | XposedHelpers.callMethod(cycleDayPicker, "setMaxValue", 365); 807 | XposedHelpers.callMethod(cycleDayPicker, "setValue", cycleDay); 808 | XposedHelpers.callMethod(cycleDayPicker, "setWrapSelectorWheel", true); 809 | } catch (Exception ex) { 810 | XposedBridge.log("HOOK ERROR createAdvDialog(): Updating cycleDayPicker - StackTrace: " + ex.getMessage()); 811 | StackTraceElement[] elements = ex.getStackTrace(); 812 | XposedBridge.log("HOOK ERROR createAdvDialog(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 813 | String dump=obj_dump(cycleDayPicker); 814 | XposedBridge.log("HOOK ERROR createAdvDialog(): objDUMP: Dumping cycleDayPicker ..."); 815 | for (String item : dump.split(System.getProperty("line.separator"))) { 816 | XposedBridge.log("HOOK ERROR createAdvDialog(): objDUMP: " + item); 817 | } 818 | } 819 | 820 | //Set layout cycleDaysPicker 821 | final NumberPicker cycleDaysPicker = (NumberPicker) view.findViewById(R_id_cycle_days); 822 | String[] values = new String[100]; 823 | for (int i = 0; i < 100; ++i) { 824 | values[i] = "" + (i + 1); 825 | } 826 | values[0] = "1 - " + context.getString(modR_strings_nr1_daily); 827 | values[6] = "7 - " + context.getString(modR_strings_nr7_weekly); 828 | values[29] = "30 - " + context.getString(modR_strings_nr30_fixedmonth); 829 | values[30] = "31 - " + context.getString(modR_strings_nr31_monthly); 830 | cycleDaysPicker.setDisplayedValues(values); 831 | cycleDaysPicker.setMinValue(1); 832 | cycleDaysPicker.setMaxValue(100); 833 | cycleDaysPicker.setValue(pref_cycle_days); 834 | cycleDaysPicker.setWrapSelectorWheel(true); 835 | cycleDaysPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); 836 | 837 | //Set layout cycleDatePicker 838 | final DatePicker cycleDatePicker = (DatePicker) view.findViewById(R_id_datepicker); 839 | int year = pref_cycle_date.get(Calendar.YEAR); 840 | int month = pref_cycle_date.get(Calendar.MONTH); 841 | int day = pref_cycle_date.get(Calendar.DAY_OF_MONTH); 842 | cycleDatePicker.updateDate(year, month, day); 843 | cycleDatePicker.setMaxDate(System.currentTimeMillis()); 844 | cycleDatePicker.setDescendantFocusability(DatePicker.FOCUS_BLOCK_DESCENDANTS); 845 | 846 | // Set builder 847 | builder.setTitle("Advanced Cycle Editor"); //R.string.data_usage_cycle_editor_title 848 | builder.setView(view); 849 | if (Build.VERSION.SDK_INT == 23) { 850 | builder.setPositiveButton("OK", //R.string.data_usage_cycle_editor_positive 851 | new DialogInterface.OnClickListener() { 852 | @Override 853 | public void onClick(DialogInterface dialog, int which) { 854 | // clear focus to finish pending text edits 855 | XposedHelpers.callMethod(cycleDayPicker, "clearFocus"); 856 | cycleDaysPicker.clearFocus(); 857 | cycleDatePicker.clearFocus(); 858 | int bs = encodeBitShiftedInt(cycleDatePicker, cycleDaysPicker); 859 | 860 | //Save in policy CycleDay 861 | final String cycleTimezone = new Time().timezone; 862 | XposedHelpers.callMethod(editor, "setPolicyCycleDay", template, bs, cycleTimezone); 863 | XposedHelpers.callMethod(target, "updatePolicy", true); 864 | 865 | } 866 | }); 867 | } else if (Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) { 868 | XposedHelpers.setAdditionalStaticField(param.thisObject,"mCycleDatePicker",cycleDatePicker); 869 | XposedHelpers.setAdditionalStaticField(param.thisObject,"mCycleDaysPicker",cycleDaysPicker); 870 | builder.setPositiveButton("OK", (DialogInterface.OnClickListener) param.thisObject); 871 | } 872 | 873 | //Create Dialog & return 874 | if (DEBUG) XposedBridge.log("HOOK onCreateDialog(): Completed!"); 875 | return builder.create(); 876 | } 877 | 878 | //Encoding methods 879 | private static int encodeBitShiftedInt(DatePicker cycleDatePicker, NumberPicker cycleDaysPicker) { 880 | // Encode Day of Month, Month and Duration into one int 881 | // via BitShift method. 882 | int bs1 = cycleDatePicker.getDayOfMonth(); 883 | int bs2 = cycleDatePicker.getMonth(); 884 | int bs3 = cycleDaysPicker.getValue(); 885 | int bs = (bs1 & 0xFF) | ((bs2 & 0xFF) << 8) | ((bs3 & 0xFF) << 16); 886 | if (DEBUG) XposedBridge.log("HOOK BITSH pref SAVED " + bs + " (" + bs1 + "." + bs2 + "." + bs3 + ")"); 887 | return bs; 888 | } 889 | 890 | private static Object[] decodeBitShiftedInt(int cycleDay){ 891 | 892 | // Get Preferences 893 | Calendar pref_cycleDate = Calendar.getInstance(); 894 | 895 | //Set no hour 896 | pref_cycleDate.set(Calendar.HOUR_OF_DAY, 0); 897 | pref_cycleDate.set(Calendar.MINUTE, 0); 898 | pref_cycleDate.set(Calendar.SECOND, 0); 899 | pref_cycleDate.set(Calendar.MILLISECOND, 0); 900 | 901 | int pref_cycleDays = 31; 902 | if (DEBUG) XposedBridge.log("HOOK BITSH pref LOAD " + cycleDay + ""); 903 | if(cycleDay <= 31){ 904 | // Not Bitshifted 905 | pref_cycleDate.set(Calendar.DAY_OF_MONTH, cycleDay); 906 | } else { 907 | // Decode Day of Month, Month and Duration from one int 908 | // via BitShift method. 909 | try{ 910 | int bs1 = cycleDay & 0xFF; // Day of Month 911 | int bs2 = (cycleDay >> 8) & 0xFF; // Month 912 | int bs3 = (cycleDay >> 16) & 0xFF; // num Days 913 | if(DEBUG) XposedBridge.log("HOOK BITSH loaded bitshited Ints "+bs1+"."+bs2+"."+bs3+""); 914 | pref_cycleDate.set(Calendar.DAY_OF_MONTH, bs1); 915 | pref_cycleDate.set(Calendar.MONTH, bs2); 916 | if(pref_cycleDate.getTimeInMillis() > System.currentTimeMillis()) { 917 | pref_cycleDate.set(Calendar.YEAR, pref_cycleDate.get(Calendar.YEAR) - 1); 918 | if(DEBUG) XposedBridge.log("HOOK BITSH preference year set to "+pref_cycleDate.get(Calendar.YEAR)+""); 919 | } 920 | if(bs3 != 0) pref_cycleDays = bs3; else { pref_cycleDays = 31; XposedBridge.log("HOOK ERR pref_cycleDays=0 - forced 31!"); } 921 | } catch (Exception ex){ 922 | XposedBridge.log("HOOK ERROR decodeBitShiftedInt(): " + ex.getMessage()); 923 | StackTraceElement[] elements = ex.getStackTrace(); 924 | XposedBridge.log("HOOK ERROR decodeBitShiftedInt(): at " + elements[0].getClassName() + "." + elements[0].getMethodName() + "() line: " + elements[0].getLineNumber()); 925 | } 926 | } 927 | 928 | return new Object[] {pref_cycleDate, pref_cycleDays}; 929 | } 930 | 931 | 932 | //Status Bar methods 933 | private static Object getAdvDataUsageInfo(XC_MethodHook.MethodHookParam param, Class networkTemplate, Class networkStatsHistory, Class dataUsageInfo) { 934 | 935 | //Get Session 936 | Object session = XposedHelpers.callMethod(param.thisObject, "getSession"); //INetworkStatsSession 937 | if (session == null) { 938 | return XposedHelpers.callMethod(param.thisObject, "warn", "no stats session"); 939 | } 940 | 941 | //Get Template 942 | Object template = null; 943 | if(Build.VERSION.SDK_INT == 23) { 944 | final Context context = (Context) XposedHelpers.getObjectField(param.thisObject, "mContext"); 945 | final String subscriberId = (String) XposedHelpers.callMethod(param.thisObject, "getActiveSubscriberId", context); //String 946 | if (subscriberId == null) { 947 | XposedHelpers.callMethod(param.thisObject, "warn", "no subscriber id"); 948 | } 949 | if (DEBUG) XposedBridge.log("HOOK UI getAdvDataUsageInfo(): subscriberId :" + subscriberId + ""); 950 | Object mTelephonyManager = XposedHelpers.getObjectField(param.thisObject, "mTelephonyManager"); 951 | template = XposedHelpers.callStaticMethod(networkTemplate, "buildTemplateMobileAll", subscriberId); 952 | template = XposedHelpers.callStaticMethod(networkTemplate, "normalize", template, XposedHelpers.callMethod(mTelephonyManager, "getMergedSubscriberIds")); 953 | } 954 | else if(Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) { 955 | template = param.args[0]; 956 | } 957 | 958 | final Object policy = XposedHelpers.callMethod(param.thisObject, "findNetworkPolicy", template); 959 | try { 960 | if(Build.VERSION.SDK_INT == 23) session = XposedHelpers.getObjectField(param.thisObject, "mSession"); 961 | 962 | //Initialize vars 963 | int FIELD_RX_BYTES = XposedHelpers.getStaticIntField(networkStatsHistory, "FIELD_RX_BYTES"); 964 | int FIELD_TX_BYTES = XposedHelpers.getStaticIntField(networkStatsHistory, "FIELD_TX_BYTES"); 965 | int FIELDS = FIELD_RX_BYTES | FIELD_TX_BYTES; 966 | final Object history = XposedHelpers.callMethod(session, "getHistoryForNetwork", template, FIELDS); //type NetworkStatsHistory 967 | final long now = System.currentTimeMillis(); 968 | final long start, end; 969 | 970 | //Compute Cycle boundaries 971 | int cycleDay = 0; 972 | if (policy != null) { cycleDay = (int) XposedHelpers.getObjectField(policy, "cycleDay"); } 973 | if (policy != null && cycleDay > 31) { 974 | start = mComputeLastCycleBoundary(now, cycleDay); 975 | end = mComputeNextCycleBoundary(now, cycleDay); 976 | } else { 977 | // period = last 4 wks 978 | end = now; 979 | start = now - DateUtils.WEEK_IN_MILLIS * 4; 980 | } 981 | 982 | //Formatting and finalize values 983 | final long callStart = System.currentTimeMillis(); 984 | final Object entry = XposedHelpers.callMethod(history, "getValues", start, end, now, null); //type NetworkStatsHistory.Entry 985 | final long callEnd = System.currentTimeMillis(); 986 | if (DEBUG) XposedBridge.log("HOOK UI getAdvDataUsageInfo(): History call from " + 987 | new Date(start) + " to " + new Date(end) + " now=" + new Date(now) + " took " + (callEnd - callStart) + ": " + 988 | (String) XposedHelpers.callMethod(param.thisObject, "historyEntryToString", entry)); 989 | if (entry == null) { 990 | if (DEBUG) XposedBridge.log("HOOK UI getAdvDataUsageInfo(): entry is null"); 991 | return XposedHelpers.callMethod(param.thisObject, "warn", "no entry data"); 992 | } 993 | final long totalBytes = XposedHelpers.getLongField(entry, "rxBytes") + XposedHelpers.getLongField(entry, "txBytes"); 994 | 995 | //Create dataUsageInfo and return 996 | final Object usage = XposedHelpers.newInstance(dataUsageInfo); 997 | if(Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) { XposedHelpers.setLongField(usage, "startDate", (long) start); } 998 | XposedHelpers.setLongField(usage, "usageLevel", (long) totalBytes); 999 | XposedHelpers.setObjectField(usage, "period", (String) XposedHelpers.callMethod(param.thisObject, "formatDateRange", start, end)); 1000 | if (policy != null) { 1001 | XposedHelpers.setLongField(usage, "limitLevel", XposedHelpers.getLongField(policy, "limitBytes") > 0 ? XposedHelpers.getLongField(policy, "limitBytes") : 0); 1002 | XposedHelpers.setLongField(usage, "warningLevel", XposedHelpers.getLongField(policy, "warningBytes") > 0 ? XposedHelpers.getLongField(policy, "warningBytes") : 0); 1003 | } else { 1004 | XposedHelpers.setLongField(usage, "warningLevel", 2L * 1024 * 1024 * 1024); 1005 | } 1006 | Object mNetworkController = XposedHelpers.getObjectField(param.thisObject, "mNetworkController"); 1007 | if (usage != null && mNetworkController != null) { 1008 | XposedHelpers.setObjectField(usage, "carrier", (String) XposedHelpers.callMethod(mNetworkController, "getMobileDataNetworkName")); 1009 | } 1010 | if (DEBUG && usage != null)XposedBridge.log("HOOK UI getAdvDataUsageInfo(): usageLevel=" + XposedHelpers.getObjectField(usage, "usageLevel") + " period=" + XposedHelpers.getObjectField(usage, "period") + ""); 1011 | 1012 | return usage; 1013 | } catch (Exception e) { 1014 | if (DEBUG) XposedBridge.log("HOOK UI getAdvDataUsageInfo(): remote call failed StackTrace:" + Log.getStackTraceString(e)); 1015 | return XposedHelpers.callMethod(param.thisObject, "warn", "remote call failed"); 1016 | } 1017 | 1018 | //Return Null 1019 | //if (DEBUG) XposedBridge.log("HOOK UI DataUsage: returning null!"); 1020 | //return null; 1021 | } 1022 | 1023 | 1024 | /**************************** 1025 | Utility Methods 1026 | */ 1027 | 1028 | public static void view_dump(View view){ 1029 | if(DEBUG) XposedBridge.log( "HOOK DUMP view " + view.toString()); 1030 | try { 1031 | ViewGroup rootView = (ViewGroup) view.getRootView(); 1032 | int childViewCount = rootView.getChildCount(); 1033 | for (int i=0; i dayOne.get(Calendar.YEAR)) { 1065 | //swap them 1066 | Calendar temp = dayOne; 1067 | dayOne = dayTwo; 1068 | dayTwo = temp; 1069 | } 1070 | int extraDays = 0; 1071 | 1072 | int dayOneOriginalYearDays = dayOne.get(Calendar.DAY_OF_YEAR); 1073 | 1074 | while (dayOne.get(Calendar.YEAR) > dayTwo.get(Calendar.YEAR)) { 1075 | dayOne.add(Calendar.YEAR, -1); 1076 | // getActualMaximum() important for leap years 1077 | extraDays += dayOne.getActualMaximum(Calendar.DAY_OF_YEAR); 1078 | } 1079 | 1080 | return extraDays - dayTwo.get(Calendar.DAY_OF_YEAR) + dayOneOriginalYearDays ; 1081 | } 1082 | } 1083 | 1084 | public static String obj_dump(Object obj) { 1085 | StringBuilder result = new StringBuilder(); 1086 | String newLine = System.getProperty("line.separator"); 1087 | 1088 | result.append( obj.getClass().getName() ); 1089 | result.append( " Object {" ); 1090 | result.append(newLine); 1091 | 1092 | try { 1093 | //Fields 1094 | Field[] fields = obj.getClass().getDeclaredFields(); 1095 | for ( Field field : fields ) { 1096 | result.append(" "); 1097 | try { 1098 | result.append( " (" + field.getType().getName() + ") " + field.getName() ); 1099 | try { 1100 | //requires access to private field: 1101 | result.append( " = " + field.get(obj) ); 1102 | } catch ( Throwable ex ) { 1103 | } 1104 | } catch ( Throwable ex ) { 1105 | result.append("Error dumping fields "+obj.getClass().getName()+":" + ex + ""); 1106 | } 1107 | result.append(newLine); 1108 | } 1109 | //Methods 1110 | /*Method[] methods = obj.getClass().getDeclaredMethods(); 1111 | for ( Method method : methods ) { 1112 | try { 1113 | result.append( method.getName() + " "); 1114 | Class retType = method.getReturnType(); 1115 | Class[] paramTypes = method.getParameterTypes(); 1116 | String name = method.getName(); 1117 | result.append(" " + Modifier.toString(method.getModifiers()) + " " + retType.getName() + " " + name + "("); 1118 | for (int j = 0; j < paramTypes.length; j++) { 1119 | if (j > 0) 1120 | result.append(", "); 1121 | result.append(paramTypes[j].getName()); 1122 | } 1123 | result.append(");"); 1124 | } catch ( Throwable ex ) { 1125 | XposedBridge.log("HOOK error dumping methods "+obj.getClass().getName()+":" + ex + ""); 1126 | } 1127 | result.append(newLine); 1128 | }*/ 1129 | //Inner Methods 1130 | for (Class c = obj.getClass(); c != null; c = c.getSuperclass()) { 1131 | for (Method method : c.getDeclaredMethods()) { 1132 | try { 1133 | if (c!=obj.getClass()) result.append( c.getName() + "$"); 1134 | Class retType = method.getReturnType(); 1135 | Class[] paramTypes = method.getParameterTypes(); 1136 | String name = method.getName(); 1137 | result.append(" " + Modifier.toString(method.getModifiers()) + " " + retType.getName() + " " + name + "("); 1138 | for (int j = 0; j < paramTypes.length; j++) { 1139 | if (j > 0) 1140 | result.append(", "); 1141 | result.append(paramTypes[j].getName()); 1142 | } 1143 | result.append(");"); 1144 | } catch ( Throwable ex ) { 1145 | result.append("Error dumping inner methods "+obj.getClass().getName()+":" + ex + ""); 1146 | } 1147 | result.append(newLine); 1148 | } 1149 | } 1150 | } catch (Throwable e) { 1151 | result.append("Error dumping obj "+obj.getClass().getName()+":" + e + ""); 1152 | } 1153 | 1154 | result.append("}"); 1155 | 1156 | return result.toString(); 1157 | } 1158 | 1159 | } 1160 | 1161 | 1162 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Durata in giorni del piano: 4 | Giornaliero 5 | Settimanale 6 | Mensile corto 7 | Mensile intero 8 | Giorno di inizio del piano: 9 | Ogni %1$s giorni 10 | a partire dal %1$s 11 | di %1$s giorni 12 | Il ciclo è iniziato in data %1$s e finirà in data %2$s alle ore 00:00. La durata del ciclo è %3$s. 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Advanced Data Plan 3 | Data Plan days: 4 | Day to start data usage cycle: 5 | Daily 6 | Weekly 7 | Fixed Monthly 8 | Monthly 9 | Every %1$s days 10 | starting %1$s 11 | %1$s days 12 | The cycle started on %1$s and will ends on %2$s at 00:00. The cycle duration is %3$s. 13 | 14 | -------------------------------------------------------------------------------- /app/trash.txt: -------------------------------------------------------------------------------- 1 | /* 2 | findAndHookMethod("com.android.settings.DataUsageSummary.CycleEditorFragment", lpparam.classLoader, "onCreateDialog", "android.os.Bundle" , new XC_MethodHook() { 3 | 4 | @Override 5 | protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { 6 | // this will be called before the clock was updated by the original method 7 | XposedBridge.log("HOOK before onCreateDialog CycleEditorFragment !"); 8 | 9 | 10 | } 11 | @RequiresApi(api = Build.VERSION_CODES.M) 12 | @Override 13 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 14 | // this will be called after the clock was updated by the original method 15 | XposedBridge.log("HOOK after onCreateDialog CycleEditorFragment !"); 16 | DialogFragment mCycleEditorFragment = (DialogFragment) param.thisObject; //CycleEditorFragment 17 | 18 | View viewDialog = (View) XposedHelpers.callMethod(mCycleEditorFragment,"getView"); //getView(); 19 | final Context context = mCycleEditorFragment.getContext(); //SubSettings 20 | 21 | Fragment target = mCycleEditorFragment.getTargetFragment(); 22 | Object editor = XposedHelpers.getObjectField(target, "mPolicyEditor"); //type NetworkPolicyEditor 23 | Bundle bundle = (Bundle) (XposedHelpers.callMethod(mCycleEditorFragment, "getArguments")); 24 | Object template = bundle.getParcelable("template"); //type NetworkTemplate 25 | int cycleDay = (int) XposedHelpers.callMethod(editor, "getPolicyCycleDay", template); 26 | 27 | XposedBridge.log("HOOK cycleDay - " + cycleDay + " !"); 28 | 29 | //Salva valori 30 | //XposedHelpers.setAdditionalStaticField(mCycleEditorFragment,"",""); 31 | 32 | final AlertDialog.Builder builder = new AlertDialog.Builder(context); 33 | final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); 34 | final View view = dialogInflater.inflate(R.layout.testlayout, null, false); 35 | builder.setView(view); 36 | builder.create(); 37 | 38 | final DatePicker mDatePicker1 = (DatePicker) XposedHelpers.callMethod( view, "findViewById" , R.id.datePicker ); 39 | mDatePicker1.updateDate(2017,6,21); 40 | 41 | 42 | 43 | 44 | 45 | /*DatePicker mDatePicker = (DatePicker) XposedHelpers.getObjectField( 46 | viewSubSettings, "datePicker"); 47 | NumberPicker mNumberPicker = (NumberPicker) XposedHelpers.getObjectField( 48 | viewSubSettings, "cycle_days"); 49 | NumberPicker defNumberPicker = (NumberPicker) XposedHelpers.getObjectField( 50 | viewSubSettings, "cycle_day");*/ 51 | /*int monthDay = defNumberPicker.getValue(); 52 | Calendar today = Calendar.getInstance(); 53 | today.set(Calendar.DAY_OF_MONTH, monthDay); 54 | int year=today.get(Calendar.YEAR); 55 | int month=today.get(Calendar.MONTH); 56 | int day=today.get(Calendar.DAY_OF_MONTH); 57 | mDatePicker.updateDate(year, month, day);*/ 58 | 59 | //final Class DataUsageSuammary = XposedHelpers.findClass("com.android.settings.DataUsageSummary", lpparam.classLoader); 60 | /* 61 | mNumberPicker.setMinValue(1); 62 | mNumberPicker.setMaxValue(31); 63 | mNumberPicker.setValue(1); 64 | mNumberPicker.setWrapSelectorWheel(true);* / 65 | } 66 | }); 67 | */ -------------------------------------------------------------------------------- /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 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Apr 09 12:52:47 CEST 2017 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-3.3-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 | -------------------------------------------------------------------------------- /screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/screenshot/1.png -------------------------------------------------------------------------------- /screenshot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/screenshot/2.png -------------------------------------------------------------------------------- /screenshot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m0m4x/AdvDataPlan/9d5f81d34666f5c86fb550420a5677a1bc137d4c/screenshot/3.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------