From 13547d590f65c688fecaca317a1c9b6d1a281552 Mon Sep 17 00:00:00 2001 From: rahul kumar Date: Sun, 22 Mar 2015 22:48:04 +0530 Subject: [PATCH] changed to gradle --- .gitignore | 32 +- LICENSE | 17 - README.md | 39 - androidCommon/build.gradle | 24 + androidCommon/libs/RootTools-2.6.jar | Bin 0 -> 65775 bytes androidCommon/lint.xml | 7 + androidCommon/src/main/AndroidManifest.xml | 16 + .../asksven/andoid/common/contrib/Debug.java | 40 + .../asksven/andoid/common/contrib/Shell.java | 266 +++ .../contrib/ShellOnMainThreadException.java | 15 + .../andoid/common/contrib/StreamGobbler.java | 78 + .../asksven/andoid/common/contrib/Util.java | 162 ++ .../com/asksven/android/common/AppRater.java | 136 ++ .../android/common/CommonLogSettings.java | 28 + .../android/common/ReadmeActivity.java | 120 + .../com/asksven/android/common/RootShell.java | 90 + .../common/kernelutils/AlarmDumpsysTests.java | 64 + .../common/kernelutils/AlarmsDumpsys.java | 448 ++++ .../android/common/kernelutils/CpuStates.java | 94 + .../kernelutils/NativeKernelWakelock.java | 393 ++++ .../android/common/kernelutils/Netstats.java | 167 ++ .../common/kernelutils/OtherStatsDumpsys.java | 217 ++ .../kernelutils/PartialWakelocksDumpsys.java | 273 +++ .../kernelutils/ProcessStatsDumpsys.java | 276 +++ .../android/common/kernelutils/State.java | 187 ++ .../android/common/kernelutils/Wakelocks.java | 255 +++ .../common/kernelutils/WakeupSources.java | 186 ++ .../android/common/location/GeoUtils.java | 97 + .../android/common/nameutils/UidInfo.java | 110 + .../common/nameutils/UidNameResolver.java | 189 ++ .../common/networkutils/DataNetwork.java | 63 + .../common/privateapiproxies/Alarm.java | 421 ++++ .../BatteryInfoUnavailableException.java | 27 + .../privateapiproxies/BatteryStatsProxy.java | 1977 +++++++++++++++++ .../privateapiproxies/BatteryStatsTypes.java | 167 ++ .../common/privateapiproxies/HistoryItem.java | 435 ++++ .../privateapiproxies/HistoryItemIcs.java | 167 ++ .../privateapiproxies/KernelWakelock.java | 197 ++ .../common/privateapiproxies/Misc.java | 205 ++ .../privateapiproxies/NetworkQueryProxy.java | 161 ++ .../privateapiproxies/NetworkUsage.java | 334 +++ .../privateapiproxies/PackageElement.java | 372 ++++ .../common/privateapiproxies/Process.java | 260 +++ .../common/privateapiproxies/StatElement.java | 269 +++ .../SystemPropertiesProxy.java | 295 +++ .../common/privateapiproxies/Wakelock.java | 268 +++ .../android/common/settings/GpsSettings.java | 60 + .../android/common/shellutils/Exec.java | 223 ++ .../android/common/shellutils/ExecResult.java | 54 + .../android/common/utils/ChargerUtil.java | 42 + .../android/common/utils/DataStorage.java | 169 ++ .../android/common/utils/DateUtils.java | 363 +++ .../android/common/utils/GenericLogger.java | 61 + .../android/common/utils/MathUtils.java | 54 + .../android/common/utils/StringUtils.java | 223 ++ .../android/common/utils/SysUtils.java | 59 + .../common/utils/SystemAppInstaller.java | 374 ++++ .../android/common/wifi/WifiManagerProxy.java | 87 + .../android/system/AndroidVersion.java | 59 + .../com/asksven/android/system/Devices.java | 33 + .../asksven/android/system/Installation.java | 70 + .../src/main/res/drawable-hdpi/icon.png | Bin 0 -> 4147 bytes .../src/main/res/drawable-ldpi/icon.png | Bin 0 -> 1723 bytes .../src/main/res/drawable-mdpi/icon.png | Bin 0 -> 2574 bytes androidCommon/src/main/res/layout/main.xml | 12 + .../src/main/res/layout/readmewebview.xml | 37 + androidCommon/src/main/res/values/strings.xml | 17 + app/build.gradle | 25 + {libs => app/libs}/bugsense-3.6.jar | Bin .../src/main/AndroidManifest.xml | 26 +- .../app/adapters/AlarmTriggerAdapter.java | 80 + .../adapters/CpuControlActionBarSpinner.java | 64 + .../app/adapters/CpuWakelocksAdapter.java | 88 + .../app/adapters/KernelWakelockAdapter.java | 80 + .../adapters/NavigationDrawerListAdapter.java | 53 + .../app/adapters/TimeInStatesListAdapter.java | 146 ++ .../WakelockActionBarSpinnerAdapter.java | 55 + .../app/dialogs/AboutDialogBox.java | 28 + ...CpuFrequencyScalingNotSupportedDialog.java | 36 + .../app/dialogs/RootNotFoundAlertDialog.java | 36 + .../app/receivers/BootReceiver.java | 12 +- .../app/receivers/SaveReferenceReceiver.java | 14 +- .../app/services/BootService.java | 59 + .../SaveSinceUnpluggedReferenceService.java | 18 +- .../app/ui/CpuFrequencyFragment.java | 153 +- .../app/ui/IOControlFragment.java | 86 +- .../cpufrequtils/app/ui/MainActivity.java | 144 ++ .../cpufrequtils/app/ui/SettingsFragment.java | 17 + .../app/ui/TimeInStatesFragment.java | 105 + .../app/ui/WakeLocksDetectorFragment.java | 88 + .../app/utils/BatteryStatsUtils.java | 144 ++ .../cpufrequtils/app/utils/Constants.java | 89 + .../cpufrequtils/app/utils/CpuState.java | 31 + .../cpufrequtils/app/utils/SysUtils.java | 391 ++++ .../app/utils/TimeInStateReader.java | 114 + .../app/utils/WakelockReference.java | 31 + ..._ic_menu_moreoverflow_normal_holo_dark.png | Bin ...ic_menu_moreoverflow_normal_holo_light.png | Bin .../res}/drawable-hdpi/action_refresh.png | Bin ...theme_btn_default_focused_holo_light.9.png | Bin ...ptheme_btn_default_normal_holo_light.9.png | Bin ...theme_btn_default_pressed_holo_light.9.png | Bin .../res}/drawable-hdpi/drawer_shadow.9.png | Bin .../res}/drawable-hdpi/ic_action_discard.png | Bin .../res}/drawable-hdpi/ic_action_undo.png | Bin .../res}/drawable-hdpi/ic_drawer_dark.png | Bin .../res}/drawable-hdpi/ic_drawer_light.png | Bin .../main/res}/drawable-hdpi/ic_launcher.png | Bin ..._ic_menu_moreoverflow_normal_holo_dark.png | Bin ...ic_menu_moreoverflow_normal_holo_light.png | Bin .../res}/drawable-mdpi/action_refresh.png | Bin ..._default_disabled_focused_holo_light.9.png | Bin ...heme_btn_default_disabled_holo_light.9.png | Bin ...theme_btn_default_focused_holo_light.9.png | Bin ...ptheme_btn_default_normal_holo_light.9.png | Bin ...theme_btn_default_pressed_holo_light.9.png | Bin .../res}/drawable-mdpi/drawer_shadow.9.png | Bin .../res}/drawable-mdpi/ic_action_discard.png | Bin .../res}/drawable-mdpi/ic_action_undo.png | Bin .../res}/drawable-mdpi/ic_drawer_dark.png | Bin .../res}/drawable-mdpi/ic_drawer_light.png | Bin .../main/res}/drawable-mdpi/ic_launcher.png | Bin .../src/main/res}/drawable-nodpi/about.png | Bin .../src/main/res}/drawable-nodpi/backup.png | Bin .../main/res}/drawable-nodpi/bar_chart.png | Bin .../main/res}/drawable-nodpi/battery_med.png | Bin .../src/main/res}/drawable-nodpi/logo.png | Bin .../src/main/res}/drawable-nodpi/meter.png | Bin .../main/res}/drawable-nodpi/prefs_widget.png | Bin ..._ic_menu_moreoverflow_normal_holo_dark.png | Bin ...ic_menu_moreoverflow_normal_holo_light.png | Bin .../res}/drawable-xhdpi/action_refresh.png | Bin ..._default_disabled_focused_holo_light.9.png | Bin ...heme_btn_default_disabled_holo_light.9.png | Bin ...theme_btn_default_focused_holo_light.9.png | Bin ...ptheme_btn_default_normal_holo_light.9.png | Bin ...theme_btn_default_pressed_holo_light.9.png | Bin .../res}/drawable-xhdpi/drawer_shadow.9.png | Bin .../res}/drawable-xhdpi/ic_action_discard.png | Bin .../res}/drawable-xhdpi/ic_action_undo.png | Bin .../res}/drawable-xhdpi/ic_drawer_dark.png | Bin .../res}/drawable-xhdpi/ic_drawer_light.png | Bin .../main/res}/drawable-xhdpi/ic_launcher.png | Bin .../res}/drawable-xxhdpi/action_refresh.png | Bin ..._default_disabled_focused_holo_light.9.png | Bin ...heme_btn_default_disabled_holo_light.9.png | Bin ...theme_btn_default_focused_holo_light.9.png | Bin ...ptheme_btn_default_normal_holo_light.9.png | Bin ...theme_btn_default_pressed_holo_light.9.png | Bin .../drawable-xxhdpi/ic_action_discard.png | Bin .../res}/drawable-xxhdpi/ic_action_undo.png | Bin .../main/res}/drawable-xxhdpi/ic_launcher.png | Bin .../apptheme_btn_default_holo_light.xml | 24 +- .../alarm_trigger_custom_list_item1.xml | 7 +- .../main/res}/layout/cpu_control_fragment.xml | 9 +- .../src/main/res}/layout/cpu_wakelock_row.xml | 10 +- .../res}/layout/disk_control_fragment.xml | 9 +- .../src/main/res}/layout/drawer_list_item.xml | 2 +- .../layout/fragment_main_layout_navbar.xml | 8 +- .../main/res}/layout/kernel_wakelock_row.xml | 7 +- .../layout/simple_action_bar_spinner_item.xml | 3 +- .../res}/layout/spinner_wheel_box_layout.xml | 8 +- .../res}/layout/time_in_stat_list_item.xml | 5 +- .../src/main/res}/layout/time_in_states.xml | 5 +- .../main/res}/layout/wakelocksfragment.xml | 5 +- .../src/main/res}/menu/time_in_stat_menu.xml | 11 +- {res => app/src/main/res}/values/colors.xml | 0 {res => app/src/main/res}/values/strings.xml | 0 {res => app/src/main/res}/values/styles.xml | 6 + {res => app/src/main/res}/xml/preference.xml | 13 +- build.gradle | 17 + build.xml | 75 - gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++ gradlew.bat | 90 + ic_launcher-web.png | Bin 112818 -> 0 bytes libraries/AndroidCommon | 1 + project.properties | 18 - .../app/adapters/AlarmTriggerAdapter.java | 80 - .../adapters/CpuControlActionBarSpinner.java | 65 - .../app/adapters/CpuWakelocksAdapter.java | 88 - .../app/adapters/KernelWakelockAdapter.java | 80 - .../adapters/NavigationDrawerListAdapter.java | 53 - .../app/adapters/TimeInStatesListAdapter.java | 146 -- .../WakelockActionBarSpinnerAdapter.java | 55 - .../app/dialogs/AboutDialogBox.java | 28 - ...CpuFrequencyScalingNotSupportedDialog.java | 36 - .../app/dialogs/RootNotFoundAlertDialog.java | 36 - .../app/services/BootService.java | 59 - .../cpufrequtils/app/ui/MainActivity.java | 140 -- .../cpufrequtils/app/ui/SettingsFragment.java | 16 - .../app/ui/TimeInStatesFragment.java | 102 - .../app/ui/WakeLocksDetectorFragment.java | 88 - .../app/utils/BatteryStatsUtils.java | 144 -- .../cpufrequtils/app/utils/Constants.java | 85 - .../cpufrequtils/app/utils/CpuState.java | 31 - .../cpufrequtils/app/utils/SysUtils.java | 391 ---- .../app/utils/TimeInStateReader.java | 114 - .../app/utils/WakelockReference.java | 31 - 200 files changed, 13955 insertions(+), 2270 deletions(-) delete mode 100644 LICENSE delete mode 100644 README.md create mode 100644 androidCommon/build.gradle create mode 100644 androidCommon/libs/RootTools-2.6.jar create mode 100644 androidCommon/lint.xml create mode 100644 androidCommon/src/main/AndroidManifest.xml create mode 100644 androidCommon/src/main/java/com/asksven/andoid/common/contrib/Debug.java create mode 100644 androidCommon/src/main/java/com/asksven/andoid/common/contrib/Shell.java create mode 100644 androidCommon/src/main/java/com/asksven/andoid/common/contrib/ShellOnMainThreadException.java create mode 100644 androidCommon/src/main/java/com/asksven/andoid/common/contrib/StreamGobbler.java create mode 100644 androidCommon/src/main/java/com/asksven/andoid/common/contrib/Util.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/AppRater.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/CommonLogSettings.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/ReadmeActivity.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/RootShell.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmDumpsysTests.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmsDumpsys.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/CpuStates.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/NativeKernelWakelock.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/Netstats.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/OtherStatsDumpsys.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/PartialWakelocksDumpsys.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/ProcessStatsDumpsys.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/State.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/Wakelocks.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/kernelutils/WakeupSources.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/location/GeoUtils.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/nameutils/UidInfo.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/nameutils/UidNameResolver.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/networkutils/DataNetwork.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Alarm.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryInfoUnavailableException.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsProxy.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsTypes.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItem.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItemIcs.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/KernelWakelock.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Misc.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkQueryProxy.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkUsage.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/PackageElement.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Process.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/StatElement.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/SystemPropertiesProxy.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Wakelock.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/settings/GpsSettings.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/shellutils/Exec.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/shellutils/ExecResult.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/ChargerUtil.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/DataStorage.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/DateUtils.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/GenericLogger.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/MathUtils.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/StringUtils.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/SysUtils.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/utils/SystemAppInstaller.java create mode 100644 androidCommon/src/main/java/com/asksven/android/common/wifi/WifiManagerProxy.java create mode 100644 androidCommon/src/main/java/com/asksven/android/system/AndroidVersion.java create mode 100644 androidCommon/src/main/java/com/asksven/android/system/Devices.java create mode 100644 androidCommon/src/main/java/com/asksven/android/system/Installation.java create mode 100644 androidCommon/src/main/res/drawable-hdpi/icon.png create mode 100644 androidCommon/src/main/res/drawable-ldpi/icon.png create mode 100644 androidCommon/src/main/res/drawable-mdpi/icon.png create mode 100644 androidCommon/src/main/res/layout/main.xml create mode 100644 androidCommon/src/main/res/layout/readmewebview.xml create mode 100644 androidCommon/src/main/res/values/strings.xml create mode 100644 app/build.gradle rename {libs => app/libs}/bugsense-3.6.jar (100%) rename AndroidManifest.xml => app/src/main/AndroidManifest.xml (78%) create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/adapters/AlarmTriggerAdapter.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/adapters/CpuControlActionBarSpinner.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/adapters/CpuWakelocksAdapter.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/adapters/KernelWakelockAdapter.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/adapters/NavigationDrawerListAdapter.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/adapters/TimeInStatesListAdapter.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/adapters/WakelockActionBarSpinnerAdapter.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/dialogs/AboutDialogBox.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/dialogs/CpuFrequencyScalingNotSupportedDialog.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/dialogs/RootNotFoundAlertDialog.java rename {src => app/src/main/java}/com/phantomLord/cpufrequtils/app/receivers/BootReceiver.java (54%) rename {src => app/src/main/java}/com/phantomLord/cpufrequtils/app/receivers/SaveReferenceReceiver.java (54%) create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/services/BootService.java rename {src => app/src/main/java}/com/phantomLord/cpufrequtils/app/services/SaveSinceUnpluggedReferenceService.java (51%) rename {src => app/src/main/java}/com/phantomLord/cpufrequtils/app/ui/CpuFrequencyFragment.java (51%) rename {src => app/src/main/java}/com/phantomLord/cpufrequtils/app/ui/IOControlFragment.java (56%) create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/ui/MainActivity.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/ui/SettingsFragment.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/ui/TimeInStatesFragment.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/ui/WakeLocksDetectorFragment.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/utils/BatteryStatsUtils.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/utils/Constants.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/utils/CpuState.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/utils/SysUtils.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/utils/TimeInStateReader.java create mode 100644 app/src/main/java/com/phantomLord/cpufrequtils/app/utils/WakelockReference.java rename {res => app/src/main/res}/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png (100%) rename {res => app/src/main/res}/drawable-hdpi/abs__ic_menu_moreoverflow_normal_holo_light.png (100%) rename {res => app/src/main/res}/drawable-hdpi/action_refresh.png (100%) rename {res => app/src/main/res}/drawable-hdpi/apptheme_btn_default_focused_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-hdpi/apptheme_btn_default_normal_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-hdpi/apptheme_btn_default_pressed_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-hdpi/drawer_shadow.9.png (100%) rename {res => app/src/main/res}/drawable-hdpi/ic_action_discard.png (100%) rename {res => app/src/main/res}/drawable-hdpi/ic_action_undo.png (100%) rename {res => app/src/main/res}/drawable-hdpi/ic_drawer_dark.png (100%) rename {res => app/src/main/res}/drawable-hdpi/ic_drawer_light.png (100%) rename {res => app/src/main/res}/drawable-hdpi/ic_launcher.png (100%) rename {res => app/src/main/res}/drawable-mdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png (100%) rename {res => app/src/main/res}/drawable-mdpi/abs__ic_menu_moreoverflow_normal_holo_light.png (100%) rename {res => app/src/main/res}/drawable-mdpi/action_refresh.png (100%) rename {res => app/src/main/res}/drawable-mdpi/apptheme_btn_default_disabled_focused_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-mdpi/apptheme_btn_default_disabled_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-mdpi/apptheme_btn_default_focused_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-mdpi/apptheme_btn_default_normal_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-mdpi/apptheme_btn_default_pressed_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-mdpi/drawer_shadow.9.png (100%) rename {res => app/src/main/res}/drawable-mdpi/ic_action_discard.png (100%) rename {res => app/src/main/res}/drawable-mdpi/ic_action_undo.png (100%) rename {res => app/src/main/res}/drawable-mdpi/ic_drawer_dark.png (100%) rename {res => app/src/main/res}/drawable-mdpi/ic_drawer_light.png (100%) rename {res => app/src/main/res}/drawable-mdpi/ic_launcher.png (100%) rename {res => app/src/main/res}/drawable-nodpi/about.png (100%) rename {res => app/src/main/res}/drawable-nodpi/backup.png (100%) rename {res => app/src/main/res}/drawable-nodpi/bar_chart.png (100%) rename {res => app/src/main/res}/drawable-nodpi/battery_med.png (100%) rename {res => app/src/main/res}/drawable-nodpi/logo.png (100%) rename {res => app/src/main/res}/drawable-nodpi/meter.png (100%) rename {res => app/src/main/res}/drawable-nodpi/prefs_widget.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_dark.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/abs__ic_menu_moreoverflow_normal_holo_light.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/action_refresh.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/apptheme_btn_default_disabled_focused_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/apptheme_btn_default_disabled_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/apptheme_btn_default_focused_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/apptheme_btn_default_normal_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/apptheme_btn_default_pressed_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/drawer_shadow.9.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/ic_action_discard.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/ic_action_undo.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/ic_drawer_dark.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/ic_drawer_light.png (100%) rename {res => app/src/main/res}/drawable-xhdpi/ic_launcher.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/action_refresh.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/apptheme_btn_default_disabled_focused_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/apptheme_btn_default_disabled_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/apptheme_btn_default_focused_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/apptheme_btn_default_normal_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/apptheme_btn_default_pressed_holo_light.9.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/ic_action_discard.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/ic_action_undo.png (100%) rename {res => app/src/main/res}/drawable-xxhdpi/ic_launcher.png (100%) rename {res => app/src/main/res}/drawable/apptheme_btn_default_holo_light.xml (50%) rename {res => app/src/main/res}/layout/alarm_trigger_custom_list_item1.xml (91%) rename {res => app/src/main/res}/layout/cpu_control_fragment.xml (92%) rename {res => app/src/main/res}/layout/cpu_wakelock_row.xml (89%) rename {res => app/src/main/res}/layout/disk_control_fragment.xml (91%) rename {res => app/src/main/res}/layout/drawer_list_item.xml (95%) rename {res => app/src/main/res}/layout/fragment_main_layout_navbar.xml (92%) rename {res => app/src/main/res}/layout/kernel_wakelock_row.xml (91%) rename {res => app/src/main/res}/layout/simple_action_bar_spinner_item.xml (90%) rename {res => app/src/main/res}/layout/spinner_wheel_box_layout.xml (69%) rename {res => app/src/main/res}/layout/time_in_stat_list_item.xml (93%) rename {res => app/src/main/res}/layout/time_in_states.xml (94%) rename {res => app/src/main/res}/layout/wakelocksfragment.xml (88%) rename {res => app/src/main/res}/menu/time_in_stat_menu.xml (82%) rename {res => app/src/main/res}/values/colors.xml (100%) rename {res => app/src/main/res}/values/strings.xml (100%) rename {res => app/src/main/res}/values/styles.xml (81%) rename {res => app/src/main/res}/xml/preference.xml (81%) create mode 100644 build.gradle delete mode 100644 build.xml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat delete mode 100644 ic_launcher-web.png create mode 160000 libraries/AndroidCommon delete mode 100644 project.properties delete mode 100644 src/com/phantomLord/cpufrequtils/app/adapters/AlarmTriggerAdapter.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/adapters/CpuControlActionBarSpinner.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/adapters/CpuWakelocksAdapter.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/adapters/KernelWakelockAdapter.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/adapters/NavigationDrawerListAdapter.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/adapters/TimeInStatesListAdapter.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/adapters/WakelockActionBarSpinnerAdapter.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/dialogs/AboutDialogBox.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/dialogs/CpuFrequencyScalingNotSupportedDialog.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/dialogs/RootNotFoundAlertDialog.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/services/BootService.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/ui/MainActivity.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/ui/SettingsFragment.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/ui/TimeInStatesFragment.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/ui/WakeLocksDetectorFragment.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/utils/BatteryStatsUtils.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/utils/Constants.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/utils/CpuState.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/utils/SysUtils.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/utils/TimeInStateReader.java delete mode 100644 src/com/phantomLord/cpufrequtils/app/utils/WakelockReference.java diff --git a/.gitignore b/.gitignore index d60ba7d..7c39332 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,27 @@ + # built application files *.apk *.ap_ - # files for the dex VM *.dex -*.txt - # Java class files *.class - # generated files bin/ gen/ -lib/ - +out/ +build/ +.gradle/ +# Project files +*.iml +.idea +# .idea/workspace.xml # Local configuration file (sdk path, etc) local.properties - -# Eclipse project files -.classpath -.project - -# Proguard folder generated by Eclipse -proguard/ - -# Intellij project files -*.iml -*.ipr +keystore.properties +# Windows thumbnail db +.DS_Store +# Idea non-crucial project fileS *.iws -.idea/ +# Sandbox stuff + diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e719e8e..0000000 --- a/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ - -Performance Tuner - -Copyright (c) 2013 Rahul Kumar aka PhantomLord - - -Performance Tuner is free software: you can redistribute it and/or modify it under the -terms of the GNU General Public License as published by the Free Software -Foundation, either version 3 of the License, or (at your option) any later -version. - - Performance Tuner is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along -with Performance Tuner. If not, see . diff --git a/README.md b/README.md deleted file mode 100644 index 627bf38..0000000 --- a/README.md +++ /dev/null @@ -1,39 +0,0 @@ -Performance-Tweaker - -======================= - -(c) 2014 Rahul Kumar - -Simple Android App for - -->overclocking/underclocking the cpu clocks and tweaking various other kernel parameters like I/O , Governor etc. - -->Monitoring the time spent by a cpu state in each freqeuncy as well as current kernel information. - -->Spot rogue applications or sources which are causing battery drain i.e Battery and Wakelock Stats (Work In Progress). - - -To Download the App - -http://forum.xda-developers.com/devdb/project/dl/?id=7519&task=get - - -Third party libraries: - -* Sherlock-navigation-Drawer -* Actionbar Sherlock -* Android-Common (https://github.com/asksven/AndroidCommon) -* Spinner-Wheel -* BugSense (For analytics and stuff) - -There is also a discussion thread on XDA Developers: - -http://forum.xda-developers.com/showthread.php?t=2728587 - - -=== LICENSE === - -See the LICENSE file. - - - diff --git a/androidCommon/build.gradle b/androidCommon/build.gradle new file mode 100644 index 0000000..0f10f46 --- /dev/null +++ b/androidCommon/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 21 + buildToolsVersion "21.0.1" + + defaultConfig { + + minSdkVersion 8 + targetSdkVersion 18 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile 'com.google.code.gson:gson:2.2.2' + compile files('libs/RootTools-2.6.jar') +} diff --git a/androidCommon/libs/RootTools-2.6.jar b/androidCommon/libs/RootTools-2.6.jar new file mode 100644 index 0000000000000000000000000000000000000000..78ffca9d97335baead81e1e1a77d9d50ec87ad40 GIT binary patch literal 65775 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 + + + + + + \ No newline at end of file diff --git a/androidCommon/src/main/AndroidManifest.xml b/androidCommon/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2e96601 --- /dev/null +++ b/androidCommon/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Debug.java b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Debug.java new file mode 100644 index 0000000..a4f1ab1 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Debug.java @@ -0,0 +1,40 @@ +/** + * Contrib by Chainfire (see https://raw.github.com/Chainfire/libsuperuser/master/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java) + */ +package com.asksven.andoid.common.contrib; + +/* + * Copyright (C) 2012 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.asksven.android.common.CommonLogSettings; + +import android.util.Log; + +/** + * Utility class that intentionally does nothing when not in debug mode + */ +public class Debug { + /** + * Log a message if we are in debug mode + * @param message The message to log + */ + public static void log(String message) { + if (CommonLogSettings.DEBUG) + { + Log.d("libsuperuser", "[libsuperuser]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message); + } + } +} diff --git a/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Shell.java b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Shell.java new file mode 100644 index 0000000..276f8d5 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Shell.java @@ -0,0 +1,266 @@ +///** +// * Contrib by Chainfire (see https://raw.github.com/Chainfire/libsuperuser/master/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java) +// */ +// +///* +// * Copyright (C) 2012 Jorrit "Chainfire" Jongma +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +// +//package com.asksven.andoid.common.contrib; +// +//import java.io.DataOutputStream; +//import java.io.IOException; +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.List; +// +//import com.asksven.android.common.privateapiproxies.BuildConfig; +// +//import android.os.Looper; +// +///** +// * Class providing functionality to execute commands in a (root) shell +// */ +//public class Shell { +// /** +// * Runs commands using the supplied shell, and returns the output, or null in +// * case of errors. +// * +// * Note that due to compatibility with older Android versions, +// * wantSTDERR is not implemented using redirectErrorStream, but rather appended +// * to the output. STDOUT and STDERR are thus not guaranteed to be in the correct +// * order in the output. +// * +// * Note as well that this code will intentionally crash when run in debug mode +// * from the main thread of the application. You should always execute shell +// * commands from a background thread. +// * +// * When in debug mode, the code will also excessively log the commands passed to +// * and the output returned from the shell. +// * +// * Though this function uses background threads to gobble STDOUT and STDERR so +// * a deadlock does not occur if the shell produces massive output, the output is +// * still stored in a List, and as such doing something like "ls -lR /" +// * will probably have you run out of memory. +// * +// * @param shell The shell to use for executing the commands +// * @param commands The commands to execute +// * @param wantSTDERR Return STDERR in the output ? +// * @return Output of the commands, or null in case of an error +// */ +// public static List run(String shell, String[] commands, boolean wantSTDERR) { +// String shellUpper = shell.toUpperCase(); +// +//// if (BuildConfig.DEBUG) { +//// // check if we're running in the main thread, and if so, crash if we're in debug mode, +//// // to let the developer know attention is needed here. +//// +//// if (Looper.myLooper() == Looper.getMainLooper()) { +//// Debug.log("Application attempted to run a shell command from the main thread"); +//// throw new ShellOnMainThreadException(); +//// } +//// +//// Debug.log(String.format("[%s%%] START", shellUpper)); +//// } +// +// List res = Collections.synchronizedList(new ArrayList()); +// +// try { +// // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers +// Process process = Runtime.getRuntime().exec(shell); +// DataOutputStream STDIN = new DataOutputStream(process.getOutputStream()); +// StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), res); +// StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), wantSTDERR ? res : null); +// +// // start gobbling and write our commands to the shell +// STDOUT.start(); +// STDERR.start(); +// for (String write : commands) { +// if (BuildConfig.DEBUG) Debug.log(String.format("[%s+] %s", shellUpper, write)); +// STDIN.writeBytes(write + "\n"); +// STDIN.flush(); +// } +// STDIN.writeBytes("exit\n"); +// STDIN.flush(); +// +// // wait for our process to finish, while we gobble away in the background +// process.waitFor(); +// +// // make sure our threads are done gobbling, our streams are closed, and the process is +// // destroyed - while the latter two shouldn't be needed in theory, and may even produce +// // warnings, in "normal" Java they are required for guaranteed cleanup of resources, so +// // lets be safe and do this on Android as well +// try { +// STDIN.close(); +// } catch (IOException e) { +// } +// STDOUT.join(); +// STDERR.join(); +// process.destroy(); +// +// // in case of su, 255 usually indicates access denied +// if (shell.equals("su") && (process.exitValue() == 255)) { +// res = null; +// } +// } catch (IOException e) { +// // shell probably not found +// res = null; +// } catch (InterruptedException e) { +// // this should really be re-thrown +// res = null; +// } +// +// if (BuildConfig.DEBUG) Debug.log(String.format("[%s%%] END", shell.toUpperCase())); +// return res; +// } +// +// /** +// * This class provides utility functions to easily execute commands using SH +// */ +// public static class SH { +// /** +// * Runs command and return output +// * +// * @param command The command to run +// * @return Output of the command, or null in case of an error +// */ +// public static List run(String command) { +// return Shell.run("sh", new String[] { command }, false); +// } +// +// /** +// * Runs commands and return output +// * +// * @param commands The commands to run +// * @return Output of the commands, or null in case of an error +// */ +// public static List run(List commands) { +// return Shell.run("sh", commands.toArray(new String[commands.size()]), false); +// } +// +// /** +// * Runs command and return output +// * +// * @param commands The commands to run +// * @return Output of the commands, or null in case of an error +// */ +// public static List run(String[] commands) { +// return Shell.run("sh", commands, false); +// } +// } +// +// /** +// * This class provides utility functions to easily execute commands using SU +// * (root shell), as well as detecting whether or not root is available, and +// * if so which version. +// */ +// public static class SU { +// /** +// * Runs command as root (if available) and return output +// * +// * @param command The command to run +// * @return Output of the command, or null if root isn't available or in case of an error +// */ +// public static List run(String command) { +// return Shell.run("su", new String[] { command }, false); +// } +// +// /** +// * Runs commands as root (if available) and return output +// * +// * @param command The commands to run +// * @return Output of the commands, or null if root isn't available or in case of an error +// */ +// public static List run(List commands) { +// return Shell.run("su", commands.toArray(new String[commands.size()]), false); +// } +// +// /** +// * Runs commands as root (if available) and return output +// * +// * @param command The commands to run +// * @return Output of the commands, or null if root isn't available or in case of an error +// */ +// public static List run(String[] commands) { +// return Shell.run("su", commands, false); +// } +// +// /** +// * Detects whether or not superuser access is available, by checking the output +// * of the "id" command if available, checking if a shell runs at all otherwise +// * +// * @return True if superuser access available +// */ +// public static boolean available() { +// // this is only one of many ways this can be done +// +// List ret = run(new String[] { +// "id", +// "echo -EOC-" +// }); +// if (ret == null) return false; +// +// for (String line : ret) { +// if (line.contains("uid=")) { +// // id command is working, let's see if we are actually root +// return line.contains("uid=0"); +// } else if (line.contains("-EOC-")) { +// // if we end up here, the id command isn't present, but at +// // least the su commands starts some kind of shell, let's +// // hope it has root priviliges - no way to know without +// // additional native binaries +// return true; +// } +// } +// return false; +// } +// +// /** +// * Detects the version of the su binary installed (if any), if supported by the binary. +// * Most binaries support two different version numbers, the public version that is +// * displayed to users, and an internal version number that is used for version number +// * comparisons. Returns null if su not available or retrieving the version isn't supported. +// * +// * Note that su binary version and GUI (APK) version can be completely different. +// * +// * @param internal Request human-readable version or application internal version +// * @return String containing the su version or null +// */ +// public static String version(boolean internal) { +// // we add an additional exit call, because the command +// // line options are not available in all su versions, +// // thus potentially launching a shell instead +// +// List ret = Shell.run("sh", new String[] { +// internal ? "su -V" : "su -v", +// "exit" +// }, false); +// if (ret == null) return null; +// +// for (String line : ret) { +// if (!internal) { +// if (line.contains(".")) return line; +// } else { +// try { +// if (Integer.parseInt(line) > 0) return line; +// } catch(NumberFormatException e) { +// } +// } +// } +// return null; +// } +// } +//} +// diff --git a/androidCommon/src/main/java/com/asksven/andoid/common/contrib/ShellOnMainThreadException.java b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/ShellOnMainThreadException.java new file mode 100644 index 0000000..21b0fab --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/ShellOnMainThreadException.java @@ -0,0 +1,15 @@ +/** + * Contrib by Chainfire (see https://raw.github.com/Chainfire/libsuperuser/master/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java) + */ +package com.asksven.andoid.common.contrib; + +/** + * Exception class used to crash application when shell commands are executed + * from the main thread, and we are in debug mode. + */ +@SuppressWarnings("serial") +public class ShellOnMainThreadException extends RuntimeException { + public ShellOnMainThreadException() { + super("Application attempted to run a shell command from the main thread"); + } +} diff --git a/androidCommon/src/main/java/com/asksven/andoid/common/contrib/StreamGobbler.java b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/StreamGobbler.java new file mode 100644 index 0000000..4e9e7b3 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/StreamGobbler.java @@ -0,0 +1,78 @@ +/** + * Contrib by Chainfire (see https://raw.github.com/Chainfire/libsuperuser/master/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java) + */ + +/* + * Copyright (C) 2012 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.andoid.common.contrib; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +import com.asksven.android.common.privateapiproxies.BuildConfig; + +/** + * Thread utility class continuously reading from an InputStream + */ +public class StreamGobbler extends Thread { + private String shell = null; + private BufferedReader reader = null; + private List writer = null; + + /** + * StreamGobbler constructor + * + * We use this class because shell STDOUT and STDERR should be read as quickly as + * possible to prevent a deadlock from occurring, or Process.waitFor() never + * returning (as the buffer is full, pausing the native process + * + * @param shell Name of the shell + * @param inputStream InputStream to read from + * @param outputList List to write to, or null + */ + public StreamGobbler(String shell, InputStream inputStream, List outputList) { + this.shell = shell; + reader = new BufferedReader(new InputStreamReader(inputStream)); + writer = outputList; + } + + @Override + public void run() { + // keep reading the InputStream until it ends (or an error occurs) + try { + String line = null; + while ((line = reader.readLine()) != null) { + if (BuildConfig.DEBUG) { + Debug.log(String.format("[%s] %s", shell, line)); + } + if (writer != null) { + writer.add(line); + } + } + } catch (IOException e) { + } + + // make sure our stream is closed and resources will be freed + try { + reader.close(); + } catch (IOException e) { + } + } +} diff --git a/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Util.java b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Util.java new file mode 100644 index 0000000..da2ecbc --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/andoid/common/contrib/Util.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2011 Adam Shanks (ChainsDD) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package com.asksven.andoid.common.contrib; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.preference.PreferenceManager; +import android.text.format.DateFormat; +import android.util.Log; +import android.util.SparseArray; + + +public class Util { + private static final String TAG = "Util"; + + + + + public static ArrayList run(String command) { + return run("/system/bin/sh", command); + } + + public static ArrayList run(String shell, String command) { + return run(shell, new String[] { + command + }); + } + + public static ArrayList run(String shell, ArrayList commands) { + String[] commandsArray = new String[commands.size()]; + commands.toArray(commandsArray); + return run(shell, commandsArray); + } + + public static ArrayList run(String shell, String[] commands) { + ArrayList output = new ArrayList(); + + try { + Process process = Runtime.getRuntime().exec(shell); + + BufferedOutputStream shellInput = + new BufferedOutputStream(process.getOutputStream()); + BufferedReader shellOutput = + new BufferedReader(new InputStreamReader(process.getInputStream())); + + for (String command : commands) { + Log.i(TAG, "command: " + command); + shellInput.write((command + " 2>&1\n").getBytes()); + } + + shellInput.write("exit\n".getBytes()); + shellInput.flush(); + + String line; + while ((line = shellOutput.readLine()) != null) { +// Log.d(TAG, "command output: " + line); + output.add(line); + } + + process.waitFor(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + return output; + } + + public static boolean writeStoreFile(Context context, int uid, int execUid, String cmd, int allow) { + File storedDir = new File(context.getFilesDir().getAbsolutePath() + File.separator + "stored"); + storedDir.mkdirs(); + if (cmd == null) { + Log.d(TAG, "App stored for logging purposes, file not required"); + return false; + } + String fileName = uid + "-" + execUid; + try { + OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream( + new File(storedDir.getAbsolutePath() + File.separator + fileName))); + out.write(cmd); + out.write('\n'); + out.write(String.valueOf(allow)); + out.write('\n'); + out.flush(); + out.close(); + } catch (FileNotFoundException e) { + Log.w(TAG, "Store file not written", e); + return false; + } catch (IOException e) { + Log.w(TAG, "Store file not written", e); + return false; + } + return true; + } + + public static boolean writeDetaultStoreFile(Context context, String action) { + File storedDir = new File(context.getFilesDir().getAbsolutePath() + File.separator + "stored"); + storedDir.mkdirs(); + File defFile = new File(storedDir.getAbsolutePath() + File.separator + "default"); + try { + OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(defFile.getAbsolutePath())); + if (action.equals("allow")) { + out.write("1"); + } else if (action.equals("deny")) { + out.write("0"); + } else { + out.write("-1"); + } + out.flush(); + out.close(); + } catch (FileNotFoundException e) { + Log.w(TAG, "Default file not written", e); + return false; + } catch (IOException e) { + Log.w(TAG, "Default file not written", e); + return false; + } + return true; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/AppRater.java b/androidCommon/src/main/java/com/asksven/android/common/AppRater.java new file mode 100644 index 0000000..b79afc5 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/AppRater.java @@ -0,0 +1,136 @@ +/** + * + */ +package com.asksven.android.common; + +import com.asksven.android.common.privateapiproxies.R; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * @author sven From + * http://www.androidsnippets.com/prompt-engaged-users-to-rate- + * your-app-in-the-android-market-appirater + * Make sure to add following to strings.xml + * AndroidCommon + * com.asksven.androidcommon + * Remind me later + * Rate + * No, thanks + * If you enjoy using %s, please take a moment to rate it. Thanks for your support! + * + * To test it and to tweak the dialog appearence, you can call AppRater.showRateDialog(this, null) + * from your Activity. + * + * Normal use is to invoke AppRater.app_launched(this) each time your activity is invoked + * (eg. from within the onCreate method). If all conditions are met, the dialog appears. + */ +public class AppRater +{ + private final static int DAYS_UNTIL_PROMPT = 3; + private final static int LAUNCHES_UNTIL_PROMPT = 7; + + public static void app_launched(Context ctx) + { + SharedPreferences prefs = ctx.getSharedPreferences("apprater", 0); + if (prefs.getBoolean("dontshowagain", false)) + { + return; + } + + SharedPreferences.Editor editor = prefs.edit(); + + // Increment launch counter + long launch_count = prefs.getLong("launch_count", 0) + 1; + editor.putLong("launch_count", launch_count); + + // Get date of first launch + Long date_firstLaunch = prefs.getLong("date_firstlaunch", 0); + if (date_firstLaunch == 0) + { + date_firstLaunch = System.currentTimeMillis(); + editor.putLong("date_firstlaunch", date_firstLaunch); + } + + // Wait at least n days before opening + if (launch_count >= LAUNCHES_UNTIL_PROMPT) + { + if (System.currentTimeMillis() >= date_firstLaunch + (DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000)) + { + showRateDialog(ctx, editor); + } + } + + editor.commit(); + } + + public static void showRateDialog(final Context ctx, final SharedPreferences.Editor editor) + { + final Dialog dialog = new Dialog(ctx); + dialog.setTitle(ctx.getString(R.string.label_button_rate) + " " + ctx.getString(R.string.app_name)); + + LinearLayout ll = new LinearLayout(ctx); + ll.setOrientation(LinearLayout.VERTICAL); + + TextView tv = new TextView(ctx); + tv.setText(ctx.getString(R.string.text_dialog_rate, ctx.getString(R.string.app_name))); + tv.setWidth(240); + tv.setPadding(4, 0, 4, 10); + ll.addView(tv); + + Button b1 = new Button(ctx); + b1.setText(ctx.getString(R.string.label_button_rate)); + b1.setOnClickListener(new Button.OnClickListener() + { + public void onClick(View v) + { + ctx.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + ctx.getString(R.string.app_pname)))); + if (editor != null) + { + editor.putBoolean("dontshowagain", true); + editor.commit(); + } + dialog.dismiss(); + } + }); + ll.addView(b1); + + Button b2 = new Button(ctx); + b2.setText(R.string.label_button_remind); + b2.setOnClickListener(new Button.OnClickListener() + { + public void onClick(View v) + { + dialog.dismiss(); + } + }); + ll.addView(b2); + + Button b3 = new Button(ctx); + b3.setText(R.string.label_button_no); + b3.setOnClickListener(new Button.OnClickListener() + { + public void onClick(View v) + { + if (editor != null) + { + editor.putBoolean("dontshowagain", true); + editor.commit(); + } + dialog.dismiss(); + } + }); + ll.addView(b3); + + dialog.setContentView(ll); + dialog.show(); + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/CommonLogSettings.java b/androidCommon/src/main/java/com/asksven/android/common/CommonLogSettings.java new file mode 100644 index 0000000..2f227dd --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/CommonLogSettings.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common; + +/** + * @author sven + * + */ +public class CommonLogSettings +{ + public static final String LOGGING_TAG = "AndoidCommon"; + public static boolean DEBUG = false; + public static boolean TRACE = false; + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/ReadmeActivity.java b/androidCommon/src/main/java/com/asksven/android/common/ReadmeActivity.java new file mode 100644 index 0000000..0ac6bab --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/ReadmeActivity.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common; + + +import com.asksven.android.common.privateapiproxies.R; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.Button; + +/** + * + * @author sven + * + * Add following strings to your app + * Other Apps + * Dismiss + * Follow me + * https://twitter.com/#!/asksven + * market://search?q=com.asksven + * + * and the activity to your manifest + * + */ +public class ReadmeActivity extends Activity +{ + /** + * @see android.app.Activity#onCreate(Bundle) + */ + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.readmewebview); + + WebView browser = (WebView)findViewById(R.id.webview); + + WebSettings settings = browser.getSettings(); + settings.setJavaScriptEnabled(true); + + // retrieve any passed data (filename) + String strFilename = getIntent().getStringExtra("filename"); + String strURL = getIntent().getStringExtra("url"); + + // if a URL is passed open it + // if not open a local file + if ( (strURL == null) || (strURL.equals("")) ) + { + if (strFilename.equals("")) + { + browser.loadUrl("file:///android_asset/help.html"); + } + else + { + browser.loadUrl("file:///android_asset/" + strFilename); + } + } + else + { + browser.loadUrl(strURL); + } + + final Button buttonClose = (Button) findViewById(R.id.buttonClose); + buttonClose.setOnClickListener(new View.OnClickListener() + { + public void onClick(View v) + { + finish(); + } + }); + + final Button buttonRate = (Button) findViewById(R.id.buttonMarket); + buttonRate.setOnClickListener(new View.OnClickListener() + { + public void onClick(View v) + { + openURL(ReadmeActivity.this.getString(R.string.market_link)); + finish(); + } + }); + final Button buttonFollow = (Button) findViewById(R.id.buttonTwitter); + buttonFollow.setOnClickListener(new View.OnClickListener() + { + public void onClick(View v) + { + openURL(ReadmeActivity.this.getString(R.string.twitter_link)); + finish(); + } + }); + + } + + public void openURL( String inURL ) + { + Intent browse = new Intent( Intent.ACTION_VIEW , Uri.parse( inURL ) ); + + startActivity( browse ); + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/RootShell.java b/androidCommon/src/main/java/com/asksven/android/common/RootShell.java new file mode 100644 index 0000000..e942dd3 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/RootShell.java @@ -0,0 +1,90 @@ +/** + * + */ +package com.asksven.android.common; + +import java.util.ArrayList; +import java.util.List; + +//import com.asksven.andoid.common.contrib.Shell; +import com.stericson.RootTools.RootTools; +import com.stericson.RootTools.execution.Command; +import com.stericson.RootTools.execution.Shell; + +/** + * @author sven + * Sigleton performing su operations + * + */ +public class RootShell +{ + static RootShell m_instance = null; + static Shell m_shell = null; + private RootShell() + { + } + + public static RootShell getInstance() + { + if (m_instance == null) + { + m_instance = new RootShell(); + try + { + m_shell = RootTools.getShell(true); + } + catch (Exception e) + { + m_shell = null; + } + } + + return m_instance; + } + +// public List run1(String command) +// { +// return Shell.SU.run(command); +// } + + public synchronized List run(String command) + { + final List res = new ArrayList(); + + if (!RootTools.isRootAvailable()) + { + return res; + } + + if (m_shell == null) + { + // reopen if for whatever reason the shell got closed + RootShell.getInstance(); + } + Command shellCommand = new Command(0, command) + { + @Override + public void output(int id, String line) + { + res.add(line); + } + }; + try + { + RootTools.getShell(true).add(shellCommand).waitForFinish(); + } + catch (Exception e) + { + + } + + return res; + + } + + public boolean rooted() + { + return RootTools.isRootAvailable(); + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmDumpsysTests.java b/androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmDumpsysTests.java new file mode 100644 index 0000000..401a06f --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmDumpsysTests.java @@ -0,0 +1,64 @@ +/** + * + */ +package com.asksven.android.common.kernelutils; + +import java.util.ArrayList; +import java.util.List; + +import com.asksven.android.common.privateapiproxies.StatElement; + +import junit.framework.TestCase; + +/** + * @author sven + * + */ +public class AlarmDumpsysTests extends TestCase +{ + + /** + * Test method for {@link com.asksven.android.common.kernelutils.AlarmsDumpsys#getAlarms()}. + */ + public void testGetAlarms() + { + ArrayList test4_3 = AlarmsDumpsys.getAlarmsFrom_4_3(getTestData_4_3()); + assertNotNull(test4_3); + assertTrue(test4_3.size() > 1); + System.out.print(test4_3); + } + + static ArrayList getTestData_4_3() + { + ArrayList myRet = new ArrayList() + {{ + add("Alarm Stats:"); + add(" android +1m35s119ms running, 67 wakeups:"); + add(" +39s818ms 0 wakes 9 alarms: act=com.android.server.action.NETWORK_STATS_POLL"); + add(" +4s693ms 8 wakes 8 alarms: act=android.appwidget.action.APPWIDGET_UPDATE cmp={com.devexpert.weather/com.devexpert.weather.view.WidgetWeather5x2}"); + add(" com.google.android.gsf +5s50ms running, 30 wakeups:"); + add(" +5s50ms 30 wakes 30 alarms: cmp={com.google.android.gsf/com.google.android.gsf.checkin.EventLogService$Receiver}"); + add(" com.android.vending +46ms running, 4 wakeups:"); + add(" +29ms 1 wakes 1 alarms: cmp={com.android.vending/com.google.android.finsky.services.DailyHygiene}"); + add(" +17ms 3 wakes 3 alarms: cmp={com.android.vending/com.google.android.finsky.services.ContentSyncService}"); + }}; + return myRet; + } + static ArrayList getTestData_4_2() + { + ArrayList myRet = new ArrayList() + {{ + add("Alarm Stats:"); + add(" com.google.android.gsf"); + add(" 8417ms running, 204 wakeups"); + add(" 17 alarms: act=com.google.android.intent.action.GTALK_RECONNECT flg=0x4"); + add(" 187 alarms: flg=0x4"); + add(" com.carl.trafficcounter"); + add(" 446486ms running, 5584 wakeups"); + add(" 5584 alarms: act=com.carl.trafficcounter.UPDATE_RUN flg=0x4"); + }}; + + return myRet; + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmsDumpsys.java b/androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmsDumpsys.java new file mode 100644 index 0000000..6fb0611 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/kernelutils/AlarmsDumpsys.java @@ -0,0 +1,448 @@ +/** + * + */ +package com.asksven.android.common.kernelutils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.os.Build; +import android.util.Log; + +//import com.asksven.andoid.common.contrib.Shell; +import com.asksven.andoid.common.contrib.Util; +import com.asksven.android.common.RootShell; +import com.asksven.android.common.privateapiproxies.Alarm; +import com.asksven.android.common.privateapiproxies.StatElement; +import com.asksven.android.common.shellutils.Exec; +import com.asksven.android.common.shellutils.ExecResult; + +/** + * Parses the content of 'dumpsys alarm' + * processes the result of 'dumpsys alarm' as explained in KB article + * https://github.com/asksven/BetterBatteryStats-Knowledge-Base/wiki/AlarmManager + * @author sven + */ +public class AlarmsDumpsys +{ + static final String TAG = "AlarmsDumpsys"; + static final String PERMISSION_DENIED = "su rights required to access alarms are not available / were not granted"; + + public static ArrayList getAlarms() + { + String release = Build.VERSION.RELEASE; + int sdk = Build.VERSION.SDK_INT; + Log.i(TAG, "getAlarms: SDK=" + sdk + ", RELEASE=" + release); + + List res = RootShell.getInstance().run("dumpsys alarm"); + + if (sdk < 17) // Build.VERSION_CODES.JELLY_BEAN_MR1) + { + return getAlarmsPriorTo_4_2_2(res); + } + else if (sdk == Build.VERSION_CODES.JELLY_BEAN_MR1) + { + if (release.equals("4.2.2")) + { + return getAlarmsFrom_4_2_2(res); + } + else + { + return getAlarmsPriorTo_4_2_2(res); + } + } + + else + { + return getAlarmsFrom_4_3(res); + } + } + /** + * Returns a list of alarm value objects + * @return + * @throws Exception + */ + protected static ArrayList getAlarmsPriorTo_4_2_2(List res) + { + ArrayList myAlarms = null; + long nTotalCount = 0; + +// if (res.getSuccess()) + if ((res != null) && (res.size() != 0)) + + { +// String strRes = res.getResultLine(); + if (true) //strRes.contains("Permission Denial")) + { + Pattern begin = Pattern.compile("Alarm Stats"); + boolean bParsing = false; +// ArrayList myRes = res.getResult(); // getTestData(); + + // we are looking for multiline entries in the format + // ' + // '
list = myGeocoder.getFromLocation( + loc.getLatitude(), + loc.getLongitude(), 1); + if (list != null & list.size() > 0) + { + address = list.get(0); + } + } + catch (Exception e) + { + Log.e(TAG, "Failed while retrieving nearest city"); + } + return address; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/nameutils/UidInfo.java b/androidCommon/src/main/java/com/asksven/android/common/nameutils/UidInfo.java new file mode 100644 index 0000000..4101152 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/nameutils/UidInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.nameutils; + +/** + * @author sven + * + */ +public class UidInfo +{ + private int m_uid; + private String m_uidName = ""; + private String m_uidNamePackage = ""; + private boolean m_uidUniqueName = false; + + public UidInfo() + { + + } + + /** + * @return the m_uid + */ + public int getUid() + { + return m_uid; + } + + /** + * @param m_uid the m_uid to set + */ + public void setUid(int m_uid) + { + this.m_uid = m_uid; + } + + /** + * @return the uidName + */ + public String getName() + { + return m_uidName; + } + + /** + * @param uidName the uidName to set + */ + public void setName(String uidName) + { + this.m_uidName = uidName; + } + + /** + * @return the uidNamePackage + */ + public String getNamePackage() + { + return m_uidNamePackage; + } + + /** + * @param uidNamePackage the uidNamePackage to set + */ + public void setNamePackage(String uidNamePackage) + { + this.m_uidNamePackage = uidNamePackage; + } + + /** + * @return the uidUniqueName + */ + public boolean isUniqueName() + { + return m_uidUniqueName; + } + + /** + * @param uidUniqueName the uidUniqueName to set + */ + public void setUniqueName(boolean uidUniqueName) + { + this.m_uidUniqueName = uidUniqueName; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "UidInfo [m_uid=" + m_uid + ", m_uidName=" + m_uidName + + ", m_uidNamePackage=" + m_uidNamePackage + + ", m_uidUniqueName=" + m_uidUniqueName + "]"; + } + + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/nameutils/UidNameResolver.java b/androidCommon/src/main/java/com/asksven/android/common/nameutils/UidNameResolver.java new file mode 100644 index 0000000..7020c9e --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/nameutils/UidNameResolver.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.nameutils; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; + +/** + * @author sven + * + */ +public class UidNameResolver +{ + + protected String[] m_packages; + protected String[] m_packageNames; + + public String getLabel(Context context, String packageName) + { + String ret = packageName; + PackageManager pm = context.getPackageManager(); + try + { + ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); + CharSequence label = ai.loadLabel(pm); + if (label != null) + { + ret = label.toString(); + } + } + catch (NameNotFoundException e) + { + ret = packageName; + } + + return ret; + } + + // Side effects: sets mName and mUniqueName + // Sets mNamePackage, mName and mUniqueName + public UidInfo getNameForUid(Context context, int uid) + { + String uidName = ""; + String uidNamePackage = ""; + boolean uidUniqueName = false; + + UidInfo myInfo = new UidInfo(); + myInfo.setUid(uid); + myInfo.setName(uidName); + myInfo.setNamePackage(uidNamePackage); + myInfo.setUniqueName(uidUniqueName); + + + PackageManager pm = context.getPackageManager(); + m_packages = pm.getPackagesForUid(uid); + + if (m_packages == null) + { + uidName = Integer.toString(uid); + + myInfo.setName(uidName); + return myInfo; + } + + m_packageNames = new String[m_packages.length]; + System.arraycopy(m_packages, 0, m_packageNames, 0, m_packages.length); + + // Convert package names to user-facing labels where possible + for (int i = 0; i < m_packageNames.length; i++) + { + m_packageNames[i] = getLabel(context, m_packageNames[i]); + } + + if (m_packageNames.length == 1) + { + uidNamePackage = m_packages[0]; + uidName = m_packageNames[0]; + uidUniqueName = true; + } + else + { + uidName = "UID"; // Default name + // Look for an official name for this UID. + for (String name : m_packages) + { + try + { + PackageInfo pi = pm.getPackageInfo(name, 0); + if (pi.sharedUserLabel != 0) + { + CharSequence nm = pm.getText(name, + pi.sharedUserLabel, pi.applicationInfo); + if (nm != null) + { + uidName = nm.toString(); + break; + } + } + } + catch (PackageManager.NameNotFoundException e) + { + } + } + } + myInfo.setName(uidName); + myInfo.setNamePackage(uidNamePackage); + myInfo.setUniqueName(uidUniqueName); + + return myInfo; + } + + /** + * Returns the name for UIDs < 2000 + * @param uid + * @return + */ + String getName(int uid) + { + String ret = ""; + switch (uid) + { + case 0: + ret = "root"; + break; + case 1000: + ret = "system"; + break; + case 1001: + ret = "radio"; + break; + case 1002: + ret = "bluetooth"; + break; + case 1003: + ret = "graphics"; + break; + case 1004: + ret = "input"; + break; + case 1005: + ret = "audio"; + break; + case 1006: + ret = "camera"; + break; + case 1007: + ret = "log"; + break; + case 1008: + ret = "compass"; + break; + case 1009: + ret = "mount"; + break; + case 1010: + ret = "wifi"; + break; + case 1011: + ret = "adb"; + break; + case 1013: + ret = "media"; + break; + case 1014: + ret = "dhcp"; + break; + + } + return ret; + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/networkutils/DataNetwork.java b/androidCommon/src/main/java/com/asksven/android/common/networkutils/DataNetwork.java new file mode 100644 index 0000000..21094ee --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/networkutils/DataNetwork.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011-12 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.networkutils; + +import android.content.Context; +import android.net.ConnectivityManager; + +/** + * Helper class for data connectivity + * @author sven + * + */ +public class DataNetwork +{ + public static boolean hasDataConnection(Context ctx) + { + boolean ret = true; + ConnectivityManager myConnectivity = + (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + + // if no network connection is available buffer the update + // @see android.net.NetworkInfo + if ( (myConnectivity == null) + || (myConnectivity.getActiveNetworkInfo() == null) + || (!myConnectivity.getActiveNetworkInfo().isAvailable()) ) + { + + ret = false; + } + + return ret; + } + + public static boolean hasWifiConnection(Context ctx) + { + boolean ret = false; + ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); + if( cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnectedOrConnecting() ) + { + ret = true; + } + else + { + ret = false; + } + + return ret; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Alarm.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Alarm.java new file mode 100644 index 0000000..591c80e --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Alarm.java @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2011-2012 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import com.asksven.android.common.privateapiproxies.StatElement; +import com.google.gson.annotations.SerializedName; + +/** + * @author sven + * Value holder for alarms + */ +public class Alarm extends StatElement implements Comparable, Serializable +{ + /** + * the tag for logging + */ + private static transient final String TAG = "Alarm"; + + /** The name of the app responsible for the alarm */ + @SerializedName("package_name") + String m_strPackageName; + + /** The number od wakeups */ + @SerializedName("wakeups") + long m_nWakeups; + + /** The total count */ + @SerializedName("total_count") + long m_nTotalCount; + + + /** The details */ + @SerializedName("items") + ArrayList m_items; + + + + /** + * The default cctor + * @param strName + */ + public Alarm(String strName) + { + m_strPackageName = strName; + m_items = new ArrayList(); + + } + + public Alarm clone() + { + Alarm clone = new Alarm(m_strPackageName); + clone.setWakeups(getWakeups()); + clone.setTotalCount(m_nTotalCount); + for (int i=0; i < m_items.size(); i++) + { + clone.m_items.add(m_items.get(i).clone()); + } + return clone; + } + /** + * Store the number of wakeups + * @param nCount + */ + public void setWakeups(long nCount) + { + m_nWakeups = nCount; + } + + + /** + * Set the total wakeup count for the sum of all alarms + * @param nCount + */ + public void setTotalCount(long nCount) + { + m_nTotalCount = nCount; + } + + /** + * Return the max of all alarms (wakeups) + */ + public double getMaxValue() + { + return m_nTotalCount; + } + + /* (non-Javadoc) + * @see com.asksven.android.common.privateapiproxies.StatElement#getName() + */ + public String getName() + { + return m_strPackageName; + } + + /** + * Returns the number of wakeups + * @return + */ + public long getWakeups() + { + return m_nWakeups; + } + + /** + * @see getWakeups + * @return + */ + long getCount() + { + return getWakeups(); + } + + /** + * Not supported for alarms + * @return 0 + */ + long getDuration() + { + return 0; + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = getCount(); + return retVal; + } + + /** + * returns a string representation of the data + */ + public String getData() + { + String strRet = ""; + strRet = "Wakeups: " + getCount(); + + return strRet; + } + + /** + * returns a string representation of the detailed data (including children) + */ + public String getDetailedData() + { + String strRet = ""; + strRet = "Wakeups: " + getCount() + "\n"; + + for (int i=0; i < m_items.size(); i++) + { + strRet += " " + m_items.get(i).getData() + "\n"; + } + + return strRet; + } + + /** + * returns the representation of the data for file dump + */ + public String getDumpData(Context context) + { + return this.getName() + " (" + this.getFqn(context) + "): " + this.getDetailedData(); + } + + + /** + * Adds an item + * @param nCount + * @param strIntent + */ + public void addItem(long nCount, String strIntent) + { + m_items.add(new AlarmItem(nCount, strIntent)); + } + + /** + * Retrieve the list of items + * @return + */ + public ArrayList getItems() + { + return m_items; + } + + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + Alarm myRef = (Alarm) myList.get(i); + if (this.getName().equals(myRef.getName())) + { + // process main values + Log.i(TAG, "Substracting " + myRef.toString() + " from " + this.toString()); + this.m_nWakeups -= myRef.getCount(); + this.m_nTotalCount -= myRef.getMaxValue(); + Log.i(TAG, "Result: " + this.toString()); + + // and process items + for (int j=0; j < this.m_items.size(); j++) + { + AlarmItem myItem = this.m_items.get(j); + myItem.substractFromRef(myRef.getItems()); + } + + if (this.getCount() < 0) + { + Log.e(TAG, "substractFromRef generated negative values (" + this.toString() + " - " + myRef.toString() + ")"); + } + if (this.getItems().size() < myRef.getItems().size()) + { + Log.e(TAG, "substractFromRef error processing alarm items: ref can not have less items"); + } + + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "substractFromRef was called with a wrong list type"); + } + + } + } + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return getName() + " [" + + getData() + + "]"; + } + + + /** + * Value holder for alarm items + * @author sven + * + */ + public class AlarmItem implements Serializable + { + @SerializedName("number") + long m_nNumber; + @SerializedName("intent") + String m_strIntent; + + public AlarmItem clone() + { + AlarmItem clone = new AlarmItem(m_nNumber, m_strIntent); + return clone; + } + /** + * Default cctor + * @param nCount + * @param strIntent + */ + public AlarmItem(long nCount, String strIntent) + { + m_nNumber = nCount; + m_strIntent = strIntent; + } + + /** + * Returns the intent name + * @return + */ + public String getIntent() + { + return m_strIntent; + } + + /** + * Returns the count + * @return + */ + public long getCount() + { + return m_nNumber; + } + /** + * Returns the data as a string + * @return + */ + public String getData() + { + return "Alarms: " + m_nNumber + ", Intent: " + m_strIntent; + } + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + AlarmItem myRef = (AlarmItem) myList.get(i); + if (this.getIntent().equals(myRef.getIntent())) + { + // process main values + this.m_nNumber -= myRef.getCount(); + Log.i(TAG, "Result: " + this.toString()); + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "AlarmItem.substractFromRef was called with a wrong list type"); + } + } + } + } + } + + public Drawable getIcon(Context ctx) + { + if (m_icon == null) + { + // retrieve and store the icon for that package + String myPackage = m_strPackageName; + if (!myPackage.equals("")) + { + PackageManager manager = ctx.getPackageManager(); + try + { + m_icon = manager.getApplicationIcon(myPackage); + } + catch (Exception e) + { + // nop: no icon found + m_icon = null; + } + + } + } + return m_icon; + } + + public String getPackageName() + { + return m_strPackageName; + } + + + + /** + * Compare a given Wakelock with this object. + * If the duration of this object is + * greater than the received object, + * then this object is greater than the other. + */ + public int compareTo(Alarm o) + { + // we want to sort in descending order + return ((int)(o.getWakeups() - this.getWakeups())); + } + + public static class CountComparator implements Comparator + { + public int compare(Alarm a, Alarm b) + { + return ((int)(b.getCount() - a.getCount())); + } + } + + public static class TimeComparator implements Comparator + { + public int compare(Alarm a, Alarm b) + { + return ((int)(b.getDuration() - a.getDuration())); + } + } + + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryInfoUnavailableException.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryInfoUnavailableException.java new file mode 100644 index 0000000..9b69711 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryInfoUnavailableException.java @@ -0,0 +1,27 @@ +/** + * + */ +package com.asksven.android.common.privateapiproxies; + +/** + * @author sven + * + */ +public class BatteryInfoUnavailableException extends Exception +{ + + /** + * + */ + private static final long serialVersionUID = 1L; + + public BatteryInfoUnavailableException() + { + + } + + public BatteryInfoUnavailableException(String msg) + { + super(msg); + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsProxy.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsProxy.java new file mode 100644 index 0000000..bb10121 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsProxy.java @@ -0,0 +1,1977 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; + +import com.asksven.android.common.CommonLogSettings; +import com.asksven.android.common.nameutils.UidInfo; +import com.asksven.android.common.nameutils.UidNameResolver; +import com.asksven.android.common.utils.DateUtils; +import com.asksven.android.system.AndroidVersion; + + + +/** + * A proxy to the non-public API BatteryStats + * http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/os/BatteryStats.java/?v=source + * @author sven + * + */ +public class BatteryStatsProxy +{ + /* + * Instance of the BatteryStatsImpl + */ + private Object m_Instance = null; + @SuppressWarnings("rawtypes") + private Class m_ClassDefinition = null; + + private static final String TAG = "BatteryStatsProxy"; + /* + * The UID stats are kept here as their methods / data can not be accessed + * outside of this class due to non-public types (Uid, Proc, etc.) + */ + private SparseArray m_uidStats = null; + + /** + * An instance to the UidNameResolver + */ + private UidNameResolver m_nameResolver; + private static BatteryStatsProxy m_proxy = null; + + synchronized public static BatteryStatsProxy getInstance(Context ctx) + { + if (m_proxy == null) + { + m_proxy = new BatteryStatsProxy(ctx); + } + + return m_proxy; + } + + public void invalidate() + { + m_proxy = null; + } + + /** + * Default cctor + */ + private BatteryStatsProxy(Context context) + { + /* + * As BatteryStats is a service we need to get a binding using the IBatteryStats.Stub.getStatistics() + * method (using reflection). + * If we would be using a public API the code would look like: + * @see com.android.settings.fuelgauge.PowerUsageSummary.java + * protected void onCreate(Bundle icicle) { + * super.onCreate(icicle); + * + * mStats = (BatteryStatsImpl)getLastNonConfigurationInstance(); + * + * addPreferencesFromResource(R.xml.power_usage_summary); + * mBatteryInfo = IBatteryStats.Stub.asInterface( + * ServiceManager.getService("batteryinfo")); + * mAppListGroup = (PreferenceGroup) findPreference("app_list"); + * mPowerProfile = new PowerProfile(this); + * } + * + * followed by + * private void load() { + * try { + * byte[] data = mBatteryInfo.getStatistics(); + * Parcel parcel = Parcel.obtain(); + * parcel.unmarshall(data, 0, data.length); + * parcel.setDataPosition(0); + * mStats = com.android.internal.os.BatteryStatsImpl.CREATOR + * .createFromParcel(parcel); + * mStats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED); + * } catch (RemoteException e) { + * Log.e(TAG, "RemoteException:", e); + * } + * } + */ + + m_nameResolver = new UidNameResolver(); + + try + { + ClassLoader cl = context.getClassLoader(); + + m_ClassDefinition = cl.loadClass("com.android.internal.os.BatteryStatsImpl"); + + // get the IBinder to the "batteryinfo" service + @SuppressWarnings("rawtypes") + Class serviceManagerClass = cl.loadClass("android.os.ServiceManager"); + + // parameter types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetService= new Class[1]; + paramTypesGetService[0]= String.class; + + @SuppressWarnings("unchecked") + Method methodGetService = serviceManagerClass.getMethod("getService", paramTypesGetService); + + String service = ""; + if (Build.VERSION.SDK_INT == 19) + { + // kitkat + service = "batterystats"; + } + else + { + service = "batteryinfo"; + } + // parameters + Object[] paramsGetService= new Object[1]; + paramsGetService[0] = service; + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "invoking android.os.ServiceManager.getService(\"batteryinfo\")"); + } + IBinder serviceBinder = (IBinder) methodGetService.invoke(serviceManagerClass, paramsGetService); + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "android.os.ServiceManager.getService(\"batteryinfo\") returned a service binder"); + } + + // now we have a binder. Let's us that on IBatteryStats.Stub.asInterface + // to get an IBatteryStats + // Note the $-syntax here as Stub is a nested class + @SuppressWarnings("rawtypes") + Class iBatteryStatsStub = cl.loadClass("com.android.internal.app.IBatteryStats$Stub"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesAsInterface= new Class[1]; + paramTypesAsInterface[0]= IBinder.class; + + @SuppressWarnings("unchecked") + Method methodAsInterface = iBatteryStatsStub.getMethod("asInterface", paramTypesAsInterface); + + // Parameters + Object[] paramsAsInterface= new Object[1]; + paramsAsInterface[0] = serviceBinder; + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "invoking com.android.internal.app.IBatteryStats$Stub.asInterface"); + } + Object iBatteryStatsInstance = methodAsInterface.invoke(iBatteryStatsStub, paramsAsInterface); + + // and finally we call getStatistics from that IBatteryStats to obtain a Parcel + @SuppressWarnings("rawtypes") + Class iBatteryStats = cl.loadClass("com.android.internal.app.IBatteryStats"); + + @SuppressWarnings("unchecked") + Method methodGetStatistics = iBatteryStats.getMethod("getStatistics"); + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "invoking getStatistics"); + } + byte[] data = (byte[]) methodGetStatistics.invoke(iBatteryStatsInstance); + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "retrieving parcel"); + } + + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + + @SuppressWarnings("rawtypes") + Class batteryStatsImpl = cl.loadClass("com.android.internal.os.BatteryStatsImpl"); + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "reading CREATOR field"); + } + Field creatorField = batteryStatsImpl.getField("CREATOR"); + + // From here on we don't need reflection anymore + @SuppressWarnings("rawtypes") + Parcelable.Creator batteryStatsImpl_CREATOR = (Parcelable.Creator) creatorField.get(batteryStatsImpl); + + m_Instance = batteryStatsImpl_CREATOR.createFromParcel(parcel); + } + catch( Exception e ) + { + if (e instanceof InvocationTargetException && e.getCause() != null) { + Log.e(TAG, "An exception occured in BatteryStatsProxy(). Message: " + e.getCause().getMessage()); + } else { + Log.e(TAG, "An exception occured in BatteryStatsProxy(). Message: " + e.getMessage()); + } + m_Instance = null; + + } + } + + /** + * Returns true if the proxy could not be initialized properly + * @return true if the proxy wasn't initialized + */ + public boolean initFailed() + { + return m_Instance == null; + } + + /** + * Returns the total, last, or current battery realtime in microseconds. + * + * @param curTime the current elapsed realtime in microseconds. + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long computeBatteryRealtime(long curTime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("computeBatteryRealtime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(curTime); + params[1]= new Integer(iStatsType); + + ret = (Long) method.invoke(m_Instance, params); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current battery realtime in microseconds. + * + * @param curTime the current elapsed realtime in microseconds. + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getBatteryRealtime(long curTime) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[1]; + paramTypes[0]= long.class; + + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getBatteryRealtime", paramTypes); + + //Parameters + Object[] params= new Object[1]; + params[0]= new Long(curTime); + + + ret= (Long) method.invoke(m_Instance, params); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current battery uptime in microseconds. + * + * @param curTime the current elapsed realtime in microseconds. + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long computeBatteryUptime(long curTime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("computeBatteryUptime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(curTime); + params[1]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current screen on time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getScreenOnTime(long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getScreenOnTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns if phone is on battery. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public boolean getIsOnBattery() throws BatteryInfoUnavailableException + { + boolean ret = true; + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getIsOnBattery", paramTypes); + + + ret= (Boolean) method.invoke(m_Instance); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = true; + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current phone on time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getPhoneOnTime(long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getPhoneOnTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current wifi on time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getWifiOnTime(long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getWifiOnTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "getWifiOnTime with params " + params[0] + " and " + params[1] + " returned " + ret); + } + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current wifi on time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getGlobalWifiRunningTime(long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getGlobalWifiRunningTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "getGlobalWifiRunningTime with params " + params[0] + " and " + params[1] + " returned " + ret); + } + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current wifi running time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getWifiRunningTime(Context context, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = iBatteryStatsUid.getMethod("getWifiRunningTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret += (Long) method.invoke(myUid, params); + + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "getWifiRunningTime with params " + params[0] + " and " + params[1] + " returned " + ret); + } + + + } + } + catch( IllegalArgumentException e ) + { + Log.e(TAG, "getWifiRunning threw an IllegalArgumentException: " + e.getMessage()); + throw e; + } + catch( Exception e ) + { + Log.e(TAG, "getWifiRunning threw an Exception: " + e.getMessage()); + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + } + return ret; + } + + /** + * Returns the total, last, or current wifi lock time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getFullWifiLockTime(Context context, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = iBatteryStatsUid.getMethod("getFullWifiLockTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret += (Long) method.invoke(myUid, params); + + } + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + } + return ret; + } + + /** + * Returns the total, last, or current wifi scanning time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getScanWifiLockTime(Context context, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = iBatteryStatsUid.getMethod("getScanWifiLockTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret += (Long) method.invoke(myUid, params); + + } + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + } + return ret; + } + + /** + * Returns the total, last, or current wifi multicast time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getWifiMulticastTime(Context context, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = iBatteryStatsUid.getMethod("getWifiMulticastTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret += (Long) method.invoke(myUid, params); + + } + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + } + return ret; + } + + /** + * Returns the time in microseconds the phone has been running with the given data connection type. + * + * @params dataType the given data connection type (@see http://www.netmite.com/android/mydroid/donut/frameworks/base/core/java/android/os/BatteryStats.java) + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getPhoneDataConnectionTime(int dataType, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[3]; + paramTypes[0]= int.class; + paramTypes[1]= long.class; + paramTypes[2]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getPhoneDataConnectionTime", paramTypes); + + //Parameters + Object[] params= new Object[3]; + params[0]= new Integer(dataType); + params[1]= new Long(batteryRealtime); + params[2]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "getPhoneDataConnectionTime with params " + params[0] + ", " + params[1] + "and " + params[2] + " returned " + ret); + } + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the time in microseconds the phone has been running with the given signal strength. + * + * @params signalStrength the given data connection type (@see http://www.netmite.com/android/mydroid/donut/frameworks/base/core/java/android/os/BatteryStats.java) + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getPhoneSignalStrengthTime(int signalStrength, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[3]; + paramTypes[0]= int.class; + paramTypes[1]= long.class; + paramTypes[2]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getPhoneDataConnectionTime", paramTypes); + + //Parameters + Object[] params= new Object[3]; + params[0]= new Integer(signalStrength); + params[1]= new Long(batteryRealtime); + params[2]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "getPhoneSignalStrengthTime with params " + params[0] + ", " + params[1] + "and " + params[2] + " returned " + ret); + } + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the time in microseconds the screen has been running with the given brightness + */ + public Long getScreenBrightnessTime(int brightness, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[3]; + paramTypes[0]= int.class; + paramTypes[1]= long.class; + paramTypes[2]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getScreenBrightnessTime", paramTypes); + + //Parameters + Object[] params= new Object[3]; + params[0]= new Integer(brightness); + params[1]= new Long(batteryRealtime); + params[2]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + if (CommonLogSettings.DEBUG) + { + Log.i(TAG, "getScreenBrightnessTime with params " + params[0] + ", " + params[1] + "and " + params[2] + " returned " + ret); + } + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Returns the total, last, or current audio on time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getAudioTurnedOnTime(Context context, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = iBatteryStatsUid.getMethod("getAudioTurnedOnTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret += (Long) method.invoke(myUid, params); + + } + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + } + return ret; + } + + /** + * Returns the total, last, or current video on time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getVideoTurnedOnTime(Context context, long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = iBatteryStatsUid.getMethod("getVideoTurnedTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret += (Long) method.invoke(myUid, params); + + } + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + } + return ret; + } + + /** + * Returns the total, last, or current bluetooth on time in microseconds. + * + * @param batteryRealtime the battery realtime in microseconds (@see computeBatteryRealtime). + * @param iStatsType one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. + */ + public Long getBluetoothOnTime(long batteryRealtime, int iStatsType) throws BatteryInfoUnavailableException + { + Long ret = new Long(0); + + try + { + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= long.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getBluetoothOnTime", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new Long(batteryRealtime); + params[1]= new Integer(iStatsType); + + ret= (Long) method.invoke(m_Instance, params); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = new Long(0); + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Return whether we are currently running on battery. + */ + public boolean getIsOnBattery(Context context) throws BatteryInfoUnavailableException + { + boolean ret = false; + + try + { + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getIsOnBattery"); + + Boolean oRet = (Boolean) method.invoke(m_Instance); + ret = oRet.booleanValue(); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = false; + throw new BatteryInfoUnavailableException(); + } + + return ret; + } + + /** + * Returns the current battery percentage level if we are in a discharge cycle, otherwise + * returns the level at the last plug event. + */ + public int getDischargeCurrentLevel() throws BatteryInfoUnavailableException + { + int ret = 0; + + try + { + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("getDischargeCurrentLevel"); + + Integer oRet = (Integer) method.invoke(m_Instance); + ret = oRet.intValue(); + + } + catch( IllegalArgumentException e ) + { + throw e; + } + catch( Exception e ) + { + ret = 0; + throw new BatteryInfoUnavailableException(); + } + + return ret; + } + + /** + * Initalizes the collection of history items + */ + public boolean startIteratingHistoryLocked() throws BatteryInfoUnavailableException + { + Boolean ret = false; + + try + { + @SuppressWarnings("unchecked") + Method method = m_ClassDefinition.getMethod("startIteratingHistoryLocked"); + + ret= (Boolean) method.invoke(m_Instance); + + } + catch( IllegalArgumentException e ) + { + Log.e(TAG, "An exception occured in startIteratingHistoryLocked(). Message: " + e.getMessage() + ", cause: " + e.getCause().getMessage()); + throw e; + } + catch( Exception e ) + { + ret = false; + throw new BatteryInfoUnavailableException(); + } + + return ret; + + + } + + /** + * Collect the UidStats using reflection and store them + */ + @SuppressWarnings("unchecked") + private void collectUidStats() + { + try + { + Method method = m_ClassDefinition.getMethod("getUidStats"); + + m_uidStats = (SparseArray) method.invoke(m_Instance); + + } + catch( IllegalArgumentException e ) + { + Log.e(TAG, "An exception occured in collectUidStats(). Message: " + e.getMessage() + ", cause: " + e.getCause().getMessage()); + throw e; + } + catch( Exception e ) + { + m_uidStats = null; + } + + } + + /** + * Obtain the wakelock stats as a list of Wakelocks (@see com.asksven.android.common.privateapiproxies.Wakelock} + * @param context a Context + * @param iWakeType a type of wakelock @see com.asksven.android.common.privateapiproxies.BatteryStatsTypes + * @param iStatType a type of stat @see com.asksven.android.common.privateapiproxies.BatteryStatsTypes + * @return a List of Wakelock s + * @throws Exception + */ + @SuppressWarnings("unchecked") + public ArrayList getWakelockStats(Context context, int iWakeType, int iStatType, int iWlPctRef) throws Exception + { + // type checks + boolean validTypes = (BatteryStatsTypes.assertValidWakeType(iWakeType) + && BatteryStatsTypes.assertValidStatType(iStatType) + && BatteryStatsTypes.assertValidWakelockPctRef(iWlPctRef)); + if (!validTypes) + { + Log.e(TAG, "Invalid WakeType or StatType"); + throw new Exception("Invalid WakeType of StatType"); + } + + ArrayList myStats = new ArrayList(); + + this.collectUidStats(); + if (m_uidStats != null) + { + long uSecBatteryTime = this.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, iStatType); + long uSecAwakeTime = this.computeBatteryUptime(SystemClock.elapsedRealtime() * 1000, iStatType); + long uSecScreenOnTime =this.getScreenOnTime(uSecBatteryTime, iStatType); + + try + { + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + // Process wake lock usage + Method methodGetWakelockStats = iBatteryStatsUid.getMethod("getWakelockStats"); + + // Map of String, BatteryStats.Uid.Wakelock + Map wakelockStats = (Map) methodGetWakelockStats.invoke(myUid); + + Method methodGetUid = iBatteryStatsUid.getMethod("getUid"); + Integer uid = (Integer) methodGetUid.invoke(myUid); + + long wakelockTime = 0; + int wakelockCount = 0; + + // Map of String, BatteryStats.Uid.Wakelock + for (Map.Entry wakelockEntry : wakelockStats.entrySet()) + { + // BatteryStats.Uid.Wakelock + Object wakelock = wakelockEntry.getValue(); + + @SuppressWarnings("rawtypes") + Class batteryStatsUidWakelock = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid$Wakelock"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetWakeTime= new Class[1]; + paramTypesGetWakeTime[0]= int.class; + + Method methodGetWakeTime = batteryStatsUidWakelock.getMethod("getWakeTime", paramTypesGetWakeTime); + + + //Parameters + Object[] paramsGetWakeTime= new Object[1]; + + // Partial wake locks BatteryStatsTypes.WAKE_TYPE_PARTIAL + // are the ones that should normally be of interest but + // WAKE_TYPE_PARTIAL, WAKE_TYPE_FULL, WAKE_TYPE_WINDOW + // are possible + paramsGetWakeTime[0]= Integer.valueOf(iWakeType); + + // BatteryStats.Timer + Object wakeTimer = methodGetWakeTime.invoke(wakelock, paramsGetWakeTime); + if (wakeTimer != null) + { + @SuppressWarnings("rawtypes") + Class iBatteryStatsTimer = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Timer"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetTotalTimeLocked= new Class[2]; + paramTypesGetTotalTimeLocked[0]= long.class; + paramTypesGetTotalTimeLocked[1]= int.class; + + Method methodGetTotalTimeLocked = iBatteryStatsTimer.getMethod("getTotalTimeLocked", paramTypesGetTotalTimeLocked); + + //Parameters + Object[] paramsGetTotalTimeLocked= new Object[2]; + paramsGetTotalTimeLocked[0]= new Long(uSecBatteryTime); + paramsGetTotalTimeLocked[1]= Integer.valueOf(iStatType); + + Long wake = (Long) methodGetTotalTimeLocked.invoke(wakeTimer, paramsGetTotalTimeLocked); +// Log.d(TAG, "Wakelocks inner: Process = " + wakelockEntry.getKey() + " wakelock [s] " + wake); + wakelockTime += wake; + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetCountLocked= new Class[1]; + paramTypesGetCountLocked[0]= int.class; + + Method methodGetCountLocked = iBatteryStatsTimer.getMethod("getCountLocked", paramTypesGetCountLocked); + + //Parameters + Object[] paramsGetCountLocked= new Object[1]; + paramsGetCountLocked[0]= new Integer(iStatType); + + Integer count = (Integer) methodGetCountLocked.invoke(wakeTimer, paramsGetCountLocked); +// Log.d(TAG, "Wakelocks inner: Process = " + wakelockEntry.getKey() + " count " + count); + wakelockCount += count; + + } +// else +// { +// Log.d(TAG, "Wakelocks: Process = " + wakelockEntry.getKey() + " with no Timer spotted"); +// } + // convert so milliseconds + wakelockTime /= 1000; + + long uSec = 0; + switch (iWlPctRef) + { + case 0: + uSec = uSecBatteryTime; + break; + case 1: + uSec = uSecAwakeTime; + break; + case 2: + uSec = (uSecAwakeTime - uSecScreenOnTime); + break; + } + + Wakelock myWl = new Wakelock(iWakeType, wakelockEntry.getKey(), wakelockTime, uSec / 1000, wakelockCount); + + // opt for lazy loading: do no populate UidInfo, just uid. UidInfo will be fetched on demand + myWl.setUid(uid); + myStats.add(myWl); + +// Log.d(TAG, "Wakelocks: Process = " + wakelockEntry.getKey() + " wakelock [s] " + wakelockTime + ", count " + wakelockCount); + } + } + } + catch( Exception e ) + { + Log.e(TAG, "An exception occured in getWakelockStats(). Message: " + e.getMessage() + ", cause: " + e.getCause().getMessage()); + throw e; + } + } + return myStats; + } + + /** + * Obtain the wakelock stats as a list of Wakelocks (@see com.asksven.android.common.privateapiproxies.Wakelock} + * @param context a Context + * @param iWakeType a type of wakelock @see com.asksven.android.common.privateapiproxies.BatteryStatsTypes + * @param iStatType a type of stat @see com.asksven.android.common.privateapiproxies.BatteryStatsTypes + * @return a List of Wakelock s + * @throws Exception + */ + @SuppressWarnings("unchecked") + public ArrayList getKernelWakelockStats(Context context, int iStatType, int iWlPctRef, boolean bAlternate) throws Exception + { + // type checks + boolean validTypes = (BatteryStatsTypes.assertValidStatType(iStatType) + && BatteryStatsTypes.assertValidWakelockPctRef(iWlPctRef)); + if (!validTypes) + { + Log.e(TAG, "Invalid WakeType or StatType"); + throw new Exception("Invalid WakeType or StatType"); + } + + Log.d(TAG, "getWakelockStats was called with params " + +"[iStatType] = " + iStatType + + "[iWlPctRef] = " + iWlPctRef); + + ArrayList myStats = new ArrayList(); + + long uSecBatteryTime = this.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, iStatType); + long msSinceBoot = SystemClock.elapsedRealtime(); + + try + { + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStats = cl.loadClass("com.android.internal.os.BatteryStatsImpl"); + + // Process wake lock usage + Method methodGetKernelWakelockStats = iBatteryStats.getMethod("getKernelWakelockStats"); + + Class classSamplingTimer = cl.loadClass("com.android.internal.os.BatteryStatsImpl$SamplingTimer"); + + Field currentReportedCount = classSamplingTimer.getDeclaredField("mCurrentReportedCount"); + Field currentReportedTotalTime = classSamplingTimer.getDeclaredField("mCurrentReportedTotalTime"); + Field unpluggedReportedCount = classSamplingTimer.getDeclaredField("mUnpluggedReportedCount"); + Field unpluggedReportedTotalTime = classSamplingTimer.getDeclaredField("mUnpluggedReportedTotalTime"); + Field inDischarge = classSamplingTimer.getDeclaredField("mInDischarge"); + Field trackingReportedValues = classSamplingTimer.getDeclaredField("mTrackingReportedValues"); + + currentReportedCount.setAccessible(true); + currentReportedTotalTime.setAccessible(true); + unpluggedReportedCount.setAccessible(true); + unpluggedReportedTotalTime.setAccessible(true); + inDischarge.setAccessible(true); + trackingReportedValues.setAccessible(true); + + //Parameters + Object[] params= new Object[1]; + + + // Map of String, BatteryStatsImpl.SamplingTimer + Map kernelWakelockStats = (Map) methodGetKernelWakelockStats.invoke(m_Instance); + + + // Map of String, BatteryStats.Uid.Wakelock + for (Map.Entry wakelockEntry : kernelWakelockStats.entrySet()) + { + // BatteryStats.SamplingTimer + String wakelockName = wakelockEntry.getKey(); + Object samplingTimer = wakelockEntry.getValue(); + + params[0]= samplingTimer; + + // read private fields + Integer currentReportedCountVal = (Integer) currentReportedCount.get(params[0]); + Long currentReportedTotalTimeVal = (Long) currentReportedTotalTime.get(params[0]); + + Integer unpluggedReportedCountVal = (Integer) unpluggedReportedCount.get(params[0]); + Long unpluggedReportedTotalTimeVal = (Long) unpluggedReportedTotalTime.get(params[0]); + + Boolean inDischargeVal = (Boolean) inDischarge.get(params[0]); + Boolean trackingReportedValuesVal = (Boolean) trackingReportedValues.get(params[0]); + + if (CommonLogSettings.DEBUG) + { + Log.d(TAG, "Kernel wakelock '" + wakelockEntry.getKey() + "'" + + " : reading fields from SampleTimer: " + + " [currentReportedCountVal] = " + currentReportedCountVal + + " [currentReportedTotalTimeVal] = " + currentReportedTotalTimeVal + + " [unpluggedReportedCountVal] = " + unpluggedReportedCountVal + + " [mUnpluggedReportedTotalTimeVal] = " + unpluggedReportedTotalTimeVal + + " [mInDischarge] = " + inDischargeVal + + " [mTrackingReportedValues] = " + trackingReportedValuesVal); + } + +// +// @SuppressWarnings("rawtypes") +// Class batteryStatsSamplingTimerClass = cl.loadClass("com.android.internal.os.BatteryStatsImpl$SamplingTimer"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetTotalTimeLocked= new Class[2]; + paramTypesGetTotalTimeLocked[0]= long.class; + paramTypesGetTotalTimeLocked[1]= int.class; + + //Parameters + Object[] paramGetTotalTimeLocked= new Object[2]; + paramGetTotalTimeLocked[0]= new Long(uSecBatteryTime); + paramGetTotalTimeLocked[1]= new Integer(iStatType); + + + Method methodGetTotalTimeLocked = classSamplingTimer + .getMethod("getTotalTimeLocked", paramTypesGetTotalTimeLocked); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetCountLocked= new Class[1]; + paramTypesGetCountLocked[0]= int.class; + + //Parameters + Object[] paramGetCountLocked= new Object[1]; + paramGetCountLocked[0]= new Integer(iStatType); + + Method methodGetCountLocked = classSamplingTimer + .getMethod("getCountLocked", paramTypesGetCountLocked); + + + Long wake = (Long) methodGetTotalTimeLocked.invoke(samplingTimer, paramGetTotalTimeLocked); + + Integer count = (Integer) methodGetCountLocked.invoke(samplingTimer, paramGetCountLocked); + + if (CommonLogSettings.DEBUG) + { + Log.d(TAG, "Kernel wakelock: " + wakelockEntry.getKey() + " wakelock [s] " + wake / 1000 + + " count " + count); + } + + // return the data depending on the method + if (!bAlternate) + { + KernelWakelock myWl = new KernelWakelock(wakelockEntry.getKey(), wake / 1000, uSecBatteryTime / 1000, count); + myStats.add(myWl); + } + else + { + KernelWakelock myWl = new KernelWakelock( + wakelockEntry.getKey(), + unpluggedReportedTotalTimeVal / 1000, + msSinceBoot, + unpluggedReportedCountVal); + myStats.add(myWl); + } + } + } + catch( Exception e ) + { + Log.e(TAG, "An exception occured in getKernelWakelockStats(). Message: " + e.getMessage() + ", cause: " + e.getCause().getMessage()); + throw e; + } + + return myStats; + } + + /** + * Obtain the process stats as a list of Processes (@see com.asksven.android.common.privateapiproxies.Process} + * @param context a Context + * @param iStatType a type of stat @see com.asksven.android.common.privateapiproxies.BatteryStatsTypes + * @return a List of Process es + * @throws Exception + */ + @SuppressWarnings("unchecked") + public ArrayList getProcessStats(Context context, int iStatType) throws Exception + { + // type checks + boolean validTypes = BatteryStatsTypes.assertValidStatType(iStatType); + if (!validTypes) + { + Log.e(TAG, "Invalid WakeType or StatType"); + throw new Exception("Invalid StatType"); + } + + ArrayList myStats = new ArrayList(); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + Method methodGetProcessStats = iBatteryStatsUid.getMethod("getProcessStats"); + + Method methodGetUid = iBatteryStatsUid.getMethod("getUid"); + Integer uid = (Integer) methodGetUid.invoke(myUid); + + // Map of String, BatteryStats.Uid.Proc + Map processStats = (Map) methodGetProcessStats.invoke(myUid); + + if (processStats.size() > 0) + { + for (Map.Entry ent : processStats.entrySet()) + { + if (CommonLogSettings.TRACE) + { + Log.d(TAG, "Process name = " + ent.getKey()); + } + // Object is a BatteryStatsTypes.Uid.Proc + Object ps = ent.getValue(); + @SuppressWarnings("rawtypes") + Class batteryStatsUidProc = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid$Proc"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetXxxTime= new Class[1]; + paramTypesGetXxxTime[0]= int.class; + + Method methodGetUserTime = batteryStatsUidProc.getMethod("getUserTime", paramTypesGetXxxTime); + Method methodGetSystemTime = batteryStatsUidProc.getMethod("getSystemTime", paramTypesGetXxxTime); + Method methodGetStarts = batteryStatsUidProc.getMethod("getStarts", paramTypesGetXxxTime); + + //Parameters + Object[] paramsGetXxxTime= new Object[1]; + paramsGetXxxTime[0]= new Integer(iStatType); + + Long userTime = (Long) methodGetUserTime.invoke(ps, paramsGetXxxTime); + Long systemTime = (Long) methodGetSystemTime.invoke(ps, paramsGetXxxTime); + Integer starts = (Integer) methodGetStarts.invoke(ps, paramsGetXxxTime); + + // starting in kitkat usertime and system time are expressed in 1/100s + // @see https://android.googlesource.com/platform/frameworks/base/+/android-4.4_r1.1/core/java/android/os/BatteryStats.java + // line 343ff + if (Build.VERSION.SDK_INT >= 19) + { + userTime *= 10; + systemTime *= 10; + } + + + if (CommonLogSettings.TRACE) + { + Log.d(TAG, "UserTime = " + userTime); + Log.d(TAG, "SystemTime = " + systemTime); + Log.d(TAG, "Starts = " + starts); + } + + boolean ignore = false; + + // take only the processes with CPU time + if ((userTime + systemTime) > 100) + { + Process myPs = new Process(ent.getKey(), userTime, systemTime, starts); + // opt for lazy loading: do no populate UidInfo, just uid. UidInfo will be fetched on demand + myPs.setUid(uid); + // try resolving names +// String myName = m_nameResolver.getLabel(context, ent.getKey()); + + myStats.add(myPs); + } + else + { + if (CommonLogSettings.TRACE) + { + Log.d(TAG, "Process " + ent.getKey() + " was discarded (CPU time =0)"); + } + } + } + } + } + } + catch( Exception e ) + { + Log.e(TAG, "An exception occured in getProcessStats(). Message: " + e.getMessage() + ", cause: " + e.getCause().getMessage()); + throw e; + } + } + return myStats; + } + + /** + * Obtain the network usage stats as a list of NetworkUsages (@see com.asksven.android.common.privateapiproxies.NetworkUsage} + * @param context a Context + * @param iStatType a type of stat @see com.asksven.android.common.privateapiproxies.BatteryStatsTypes + * @return a List of NetworkUsage s + * @throws Exception + */ + @SuppressWarnings("unchecked") + public ArrayList getNetworkUsageStats(Context context, int iStatType) throws Exception + { + // type checks + boolean validTypes = BatteryStatsTypes.assertValidStatType(iStatType); + if (!validTypes) + { + Log.e(TAG, "Invalid WakeType or StatType"); + throw new Exception("Invalid StatType"); + } + + ArrayList myStats = new ArrayList(); + + this.collectUidStats(); + if (m_uidStats != null) + { + try + { + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class iBatteryStatsUid = cl.loadClass("com.android.internal.os.BatteryStatsImpl$Uid"); + int NU = m_uidStats.size(); + for (int iu = 0; iu < NU; iu++) + { + // Object is an instance of BatteryStats.Uid + Object myUid = m_uidStats.valueAt(iu); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetTcpBytesXxx= new Class[1]; + paramTypesGetTcpBytesXxx[0]= int.class; + + Method methodGetTcpBytesReceived = iBatteryStatsUid.getMethod("getTcpBytesReceived", paramTypesGetTcpBytesXxx); + Method methodGetTcpBytesSent = iBatteryStatsUid.getMethod("getTcpBytesSent", paramTypesGetTcpBytesXxx); + + //Parameters + Object[] paramGetTcpBytesXxx = new Object[1]; + paramGetTcpBytesXxx[0]= new Integer(iStatType); + + Long tcpBytesReceived = (Long) methodGetTcpBytesReceived.invoke(myUid, paramGetTcpBytesXxx); + Long tcpBytesSent = (Long) methodGetTcpBytesSent.invoke(myUid, paramGetTcpBytesXxx); + + Method methodGetUid = iBatteryStatsUid.getMethod("getUid"); + Integer uid = (Integer) methodGetUid.invoke(myUid); + + if (CommonLogSettings.DEBUG) + { + Log.d(TAG, "Uid = " + uid + ": received:" + tcpBytesReceived + ", sent: " + tcpBytesSent); + } + + NetworkUsage myData = new NetworkUsage(uid, tcpBytesReceived, tcpBytesSent); + // try resolving names + UidInfo myInfo = m_nameResolver.getNameForUid(context, uid); + myData.setUidInfo(myInfo); + myStats.add(myData); + } + } + catch( Exception e ) + { + throw e; + } + } + return myStats; + } + + + /** + * Obtain the network usage stats as a list of NetworkUsages (@see com.asksven.android.common.privateapiproxies.NetworkUsage} + * @param context a Context + * @param iStatType a type of stat @see com.asksven.android.common.privateapiproxies.BatteryStatsTypes + * @return a List of NetworkUsage s + * @throws Exception + */ + public ArrayList getKernelNetworkStats(int iStatsType) + { + + ArrayList myRet = new ArrayList(); + try + { + @SuppressWarnings("unchecked") + // we must use getDeclaredMethod as that method is private + Method method = m_ClassDefinition.getDeclaredMethod("getNetworkStatsDetailGroupedByUid"); + method.setAccessible(true); + + Object networkStats = method.invoke(m_Instance); + String myRes = "tada"; + } + catch (Exception e) + { + myRet = null; + } + return myRet; + + + } + + @SuppressWarnings("unchecked") + public ArrayList getHistory(Context context) throws Exception + { + + ArrayList myStats = new ArrayList(); + + try + { + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class classHistoryItem = cl.loadClass("android.os.BatteryStats$HistoryItem"); + + + // get constructor + Constructor cctor = classHistoryItem.getConstructor(); + + Object myHistoryItem = cctor.newInstance(); + + // prepare the method call for getNextHistoryItem + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[1]; + paramTypes[0]= classHistoryItem; + + + @SuppressWarnings("unchecked") + Method methodNext = m_ClassDefinition.getMethod("getNextHistoryLocked", paramTypes); + + //Parameters + Object[] params= new Object[1]; + + // initalize hist and iterate like this + // if (stats.startIteratingHistoryLocked()) { + // final HistoryItem rec = new HistoryItem(); + // while (stats.getNextHistoryLocked(rec)) { + + // read the time of query for history + Long statTimeRef = Long.valueOf(this.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, + BatteryStatsTypes.STATS_SINCE_CHARGED)); + statTimeRef = System.currentTimeMillis(); + + if (CommonLogSettings.DEBUG) + { + Log.d(TAG, "Reference time (" + statTimeRef + ": " + DateUtils.format(DateUtils.DATE_FORMAT_NOW, statTimeRef)); + } + // statTimeLast stores the timestamp of the last sample + Long statTimeLast = Long.valueOf(0); + + if (this.startIteratingHistoryLocked()) + { + params[0]= myHistoryItem; + Boolean bNext = (Boolean) methodNext.invoke(m_Instance, params); + while (bNext) + { + // process stats: create HistoryItems from params + Field timeField = classHistoryItem.getField("time"); // long + + + Field cmdField = classHistoryItem.getField("cmd"); // byte + Byte cmdValue = (Byte) cmdField.get(params[0]); + + // process only valid items + byte updateCmd = 0; + + // ICS has a different implementation of HistoryItems constants + if (AndroidVersion.isIcs()) + { + updateCmd = HistoryItemIcs.CMD_UPDATE; + } + else + { + updateCmd = HistoryItem.CMD_UPDATE; + } + + if (cmdValue == updateCmd) + { + Field batteryLevelField = classHistoryItem.getField("batteryLevel"); // byte + Field batteryStatusField = classHistoryItem.getField("batteryStatus"); // byte + Field batteryHealthField = classHistoryItem.getField("batteryHealth"); // byte + Field batteryPlugTypeField = classHistoryItem.getField("batteryPlugType"); // byte + + Field batteryTemperatureField = classHistoryItem.getField("batteryTemperature"); // char + Field batteryVoltageField = classHistoryItem.getField("batteryVoltage"); // char + + Field statesField = classHistoryItem.getField("states"); // int + + // retrieve all values + @SuppressWarnings("rawtypes") + Long timeValue = (Long) timeField.get(params[0]); + + // store values only once + if (!statTimeLast.equals(timeValue)) + { + Byte batteryLevelValue = (Byte) batteryLevelField.get(params[0]); + Byte batteryStatusValue = (Byte) batteryStatusField.get(params[0]); + Byte batteryHealthValue = (Byte) batteryHealthField.get(params[0]); + Byte batteryPlugTypeValue = (Byte) batteryPlugTypeField.get(params[0]); + + String batteryTemperatureValue = String.valueOf(batteryTemperatureField.get(params[0])); + String batteryVoltageValue = String.valueOf(batteryVoltageField.get(params[0])); + + Integer statesValue = (Integer) statesField.get(params[0]); + + HistoryItem myItem = null; + + // ICS has a different implementation of HistoryItems constants + if (AndroidVersion.isIcs()) + { + myItem = new HistoryItemIcs(timeValue, cmdValue, batteryLevelValue, + batteryStatusValue, batteryHealthValue, batteryPlugTypeValue, + batteryTemperatureValue, batteryVoltageValue, statesValue); + } + else + { + myItem = new HistoryItem(timeValue, cmdValue, batteryLevelValue, + batteryStatusValue, batteryHealthValue, batteryPlugTypeValue, + batteryTemperatureValue, batteryVoltageValue, statesValue); + } + + myStats.add(myItem); + + } + // overwrite the time of the last sample + statTimeLast = timeValue; + + } + else + { + Log.d(TAG, "Skipped item"); + } + + bNext = (Boolean) methodNext.invoke(m_Instance, params); + } + + // norm the time of each sample + // stat time last is the number of millis since + // the stats is being collected + // the ref time is a full plain time (with date) + Long offset = statTimeRef - statTimeLast; + + if (CommonLogSettings.DEBUG) + { + Log.d(TAG, "Reference time (" + statTimeRef + ")" + DateUtils.format(DateUtils.DATE_FORMAT_NOW, statTimeRef)); + + Log.d(TAG, "Last sample (" + statTimeLast + ")" + DateUtils.format(DateUtils.DATE_FORMAT_NOW, statTimeLast)); + + Log.d(TAG, "Correcting all HistoryItem times by an offset of (" + offset + ")" + DateUtils.formatDuration(offset * 1000)); + } + + for (int i=0; i < myStats.size(); i++) + { + myStats.get(i).setOffset(offset); + } + } + } + catch( Exception e ) + { + Log.e(TAG, "An exception occured in getHistory(). Message: " + e.getMessage() + ", cause: " + e.getCause().getMessage()); + throw e; + } + + return myStats; + } + +} + diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsTypes.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsTypes.java new file mode 100644 index 0000000..dafd49e --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/BatteryStatsTypes.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.privateapiproxies; + + + + +/** + * This class holds the required constants from BatteryStats + * Copyright (C) 2008 The Android Open Source Project applies + * @see android.os.BatteryStats + * @author sven + * + */ +public class BatteryStatsTypes +{ + /** + * A constant indicating a partial wake lock timer. + */ + public static final int WAKE_TYPE_PARTIAL = 0; + + /** + * A constant indicating a full wake lock timer. + */ + public static final int WAKE_TYPE_FULL = 1; + + /** + * A constant indicating a window wake lock timer. + */ + public static final int WAKE_TYPE_WINDOW = 2; + + /** + * Constants for data connection type + */ + public static final int DATA_CONNECTION_NONE = 0; + + /** + * Constants for signal strength + */ + public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; + public static final int SIGNAL_STRENGTH_POOR = 1; + public static final int SIGNAL_STRENGTH_MODERATE = 2; + public static final int SIGNAL_STRENGTH_GOOD = 3; + public static final int SIGNAL_STRENGTH_GREAT = 4; + + /** + * Constants for screen brightness + */ + + public static final int SCREEN_BRIGHTNESS_DARK = 0; + public static final int SCREEN_BRIGHTNESS_DIM = 1; + public static final int SCREEN_BRIGHTNESS_MEDIUM = 2; + public static final int SCREEN_BRIGHTNESS_LIGHT = 3; + public static final int SCREEN_BRIGHTNESS_BRIGHT = 4; + + /** + * Enum of valid wakelock types + */ + public static final boolean assertValidWakeType(int iWakeType) + { + boolean ret = false; + switch (iWakeType) + { + case WAKE_TYPE_PARTIAL: + ret = true; + break; + case WAKE_TYPE_FULL: + ret = true; + break; + case WAKE_TYPE_WINDOW: + ret = true; + break; + default: + ret = false; + break; + + } + return ret; + } + + /** + * Include all of the data in the stats, including previously saved data. + */ + public static final int STATS_SINCE_CHARGED = 0; + + /** + * Include only the last run in the stats. + */ + public static final int STATS_LAST = 1; + + /** + * Include only the current run in the stats. + */ + public static final int STATS_CURRENT = 2; + + /** + * Include only the run since the last time the device was unplugged in the stats. + */ + public static final int STATS_SINCE_UNPLUGGED = 3; + + /** + * Enum of valid stat types + */ + public static final boolean assertValidStatType(int iStatType) + { + boolean ret = false; + switch (iStatType) + { + case STATS_SINCE_CHARGED: + ret = true; + break; + case STATS_LAST: + ret = true; + break; + case STATS_CURRENT: + ret = true; + break; + case STATS_SINCE_UNPLUGGED: + ret = true; + break; + + default: + ret = false; + break; + + } + return ret; + } + + /** + * Enum of valid stat types + */ + public static final boolean assertValidWakelockPctRef(int iPctType) + { + boolean ret = false; + switch (iPctType) + { + case 0: // % of battery time + ret = true; + break; + case 1: // % of awake time + ret = true; + break; + case 2: // % of time awake - time with screen on + ret = true; + break; + default: + ret = false; + break; + + } + return ret; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItem.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItem.java new file mode 100644 index 0000000..1633e10 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItem.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.asksven.android.common.utils.DateUtils; + +/** + * Value holder for BatteryStats$HistoryItem + * @author sven + * + */ +public class HistoryItem implements Serializable, Parcelable +{ + static final long serialVersionUID = 1L; + static final byte CMD_UPDATE = 0; + static final byte CMD_START = 1; + static final byte CMD_OVERFLOW = 2; + // Constants from SCREEN_BRIGHTNESS_* + static final int STATE_BRIGHTNESS_MASK = 0x000000f; + static final int STATE_BRIGHTNESS_SHIFT = 0; + // Constants from SIGNAL_STRENGTH_* + static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0; + static final int STATE_SIGNAL_STRENGTH_SHIFT = 4; + // Constants from ServiceState.STATE_* + static final int STATE_PHONE_STATE_MASK = 0x0000f00; + static final int STATE_PHONE_STATE_SHIFT = 8; + // Constants from DATA_CONNECTION_* + static final int STATE_DATA_CONNECTION_MASK = 0x000f000; + static final int STATE_DATA_CONNECTION_SHIFT = 12; + + static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30; + static final int STATE_SCREEN_ON_FLAG = 1<<29; + static final int STATE_GPS_ON_FLAG = 1<<28; + static final int STATE_PHONE_IN_CALL_FLAG = 1<<27; + static final int STATE_PHONE_SCANNING_FLAG = 1<<26; + static final int STATE_WIFI_ON_FLAG = 1<<25; + static final int STATE_WIFI_RUNNING_FLAG = 1<<24; + static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<23; + static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<22; + static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<21; + static final int STATE_BLUETOOTH_ON_FLAG = 1<<20; + static final int STATE_AUDIO_ON_FLAG = 1<<19; + static final int STATE_VIDEO_ON_FLAG = 1<<18; + static final int STATE_WAKE_LOCK_FLAG = 1<<17; + static final int STATE_SENSOR_ON_FLAG = 1<<16; + + static final int MOST_INTERESTING_STATES = + STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG + | STATE_GPS_ON_FLAG | STATE_PHONE_IN_CALL_FLAG; + + protected Long m_time; + protected Long m_offset; + protected Byte m_cmd; + protected Byte m_batteryLevel; + protected Byte m_batteryStatusValue; + protected Byte m_batteryHealthValue; + protected Byte m_batteryPlugTypeValue; + protected String m_batteryTemperatureValue; + protected String m_batteryVoltageValue; + protected Integer m_statesValue; + + public HistoryItem(Long time, Byte cmd, Byte batteryLevel, Byte batteryStatusValue, + Byte batteryHealthValue, Byte batteryPlugTypeValue, + String batteryTemperatureValue, String batteryVoltageValue, + Integer statesValue) + { + m_time = time; + m_offset = Long.valueOf(0); + m_cmd = cmd; + m_batteryLevel = batteryLevel; + m_batteryStatusValue = batteryStatusValue; + m_batteryHealthValue = batteryHealthValue; + m_batteryPlugTypeValue = batteryPlugTypeValue; + m_batteryTemperatureValue = batteryTemperatureValue; + m_batteryVoltageValue = batteryVoltageValue; + m_statesValue = statesValue; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + String strTime = DateUtils.format("HH:mm:ss", m_time); + + return "HistoryItem [m_time=" + strTime + ", m_cmd=" + m_cmd + + ", m_batteryLevel=" + m_batteryLevel + + ", m_batteryStatusValue=" + m_batteryStatusValue + + ", m_batteryHealthValue=" + m_batteryHealthValue + + ", m_batteryPlugTypeValue=" + m_batteryPlugTypeValue + + ", m_statesValue=" + m_statesValue + "]"; + } + + /** + * @return the m_cmd + */ + public Byte getCmd() + { + return m_cmd; + } + + /** + * Returns the raw time of the HistoryItem as tring + * @return the RAW time as sting + */ + public String getTime() + { + return DateUtils.format("HH:mm:ss", m_time); + + } + + /** + * returns the "real" time of the HistoryItem by applying the offset + * calculated as the difference of the most recent + * sample from the stats and the current time at the point of reading + * the stats. This norms the last sample to the time the data is queried + * @return the normalized time + */ + public String getNormalizedTime() + { + return DateUtils.format("HH:mm:ss S", m_time + m_offset); + + } + + public Long getNormalizedTimeLong() + { + return m_time + m_offset; + + } + /** + * @return the m_batteryLevel + */ + public String getBatteryLevel() + { + return String.valueOf(m_batteryLevel); + } + + /** + * @return the m_batteryLevel + */ + public int getBatteryLevelInt() + { + return m_batteryLevel; + } + + /** + * @return the m_statesValue + */ + public Integer getStatesValue() + { + return m_statesValue; + } + + + /** + * @return the m_bCharging as "0" or "1" + */ + public String getCharging() + { + return getBooleanAsString(isCharging()); + } + + /** + * @return the m_bCharging as "0" or "1" + */ + public int getChargingInt() + { + return getBooleanAsInt(isCharging()); + } + + /** + * @return the m_bScreenOn as "0" or "1" + */ + public String getScreenOn() + { + + return getBooleanAsString(isScreenOn()); + } + + /** + * @return the m_bScreenOn as "0" or "1" + */ + public int getScreenOnInt() + { + + return getBooleanAsInt(isScreenOn()); + } + + + /** + * @return the m_bWakelock as "0" or "1" + */ + public String getWakelock() + { + return getBooleanAsString(isWakeLock()); + } + + /** + * @return the m_bWakelock as "0" or "1" + */ + public int getWakelockInt() + { + return getBooleanAsInt(isWakeLock()); + } + + /** + * @return the m_bWifiRunning as "0" or "1" + */ + public String getWifiRunning() + { + return getBooleanAsString(isWifiRunning()); + } + + /** + * @return the m_bWifiRunning as "0" or "1" + */ + public int getWifiRunningInt() + { + return getBooleanAsInt(isWifiRunning()); + } + + /** + * @return the m_bGpsOn as "0" or "1" + */ + public String getGpsOn() + { + + return getBooleanAsString(isGpsOn()); + } + + /** + * @return the m_bGpsOn as "0" or "1" + */ + public int getGpsOnInt() + { + + return getBooleanAsInt(isGpsOn()); + } + + /** + * @return the m_bPhoneInCall as "0" or "1" + */ + public String getPhoneInCall() + { + + return getBooleanAsString(isPhoneInCall()); + } + + /** + * @return the m_bPhoneScanning as "0" or "1" + */ + public String getPhoneScanning() + { + return getBooleanAsString(isPhoneScanning()); + } + + /** + * @return the m_bBluetoothOn as "0" or "1" + */ + public String getBluetoothOn() + { + return getBooleanAsString(isBluetoothOn()); + } + + /** + * @return the m_bBluetoothInt as integer + */ + public int getBluetoothOnInt() + { + + return getBooleanAsInt(isBluetoothOn()); + } + + + /** + * @return the m_bCharging as "0" or "1" + */ + protected String getBooleanAsString(boolean bVal) + { + if (bVal) + { + return "1"; + } + else + { + return "0"; + } + } + + /** + * @return the boolean as 0 or 1 + */ + protected int getBooleanAsInt(boolean bVal) + { + if (bVal) + { + return 1; + } + else + { + return 0; + } + } + + /** + * @return true is phone is charging + */ + public boolean isCharging() + { + boolean bCharging = (m_statesValue & STATE_BATTERY_PLUGGED_FLAG) != 0; + + return bCharging; + } + + /** + * @return true if screen is on + */ + public boolean isScreenOn() + { + boolean bScreenOn = (m_statesValue & STATE_SCREEN_ON_FLAG) != 0; + return bScreenOn; + } + + /** + * @return true is GPS is on + */ + public boolean isGpsOn() + { + boolean bGpsOn = (m_statesValue & STATE_GPS_ON_FLAG) != 0; + return bGpsOn; + } + + /** + * @return true is wifi is running + */ + public boolean isWifiRunning() + { + boolean bWifiRunning = (m_statesValue & STATE_WIFI_RUNNING_FLAG) != 0; + return bWifiRunning; + } + + /** + * @return true is a wakelock is present + */ + public boolean isWakeLock() + { + boolean bWakeLock = (m_statesValue & STATE_WAKE_LOCK_FLAG) != 0; + return bWakeLock; + } + + /** + * @return true if Phone is in Call + */ + public boolean isPhoneInCall() + { + + boolean bPhoneInCall = (m_statesValue & STATE_PHONE_IN_CALL_FLAG) != 0; + + return bPhoneInCall; + } + + /** + * @return true if Phone is Scanning + */ + public boolean isPhoneScanning() + { + boolean bPhoneScanning = (m_statesValue & STATE_PHONE_SCANNING_FLAG) != 0; + + return bPhoneScanning; + } + + /** + * @return the true if bluetooth is on + */ + public boolean isBluetoothOn() + { + boolean bBluetoothOn = (m_statesValue & STATE_BLUETOOTH_ON_FLAG) != 0; + + return bBluetoothOn; + } + + + public void setOffset(Long offset) + { + m_offset = offset; + } + + /* (non-Javadoc) + * @see android.os.Parcelable#describeContents() + */ + @Override + public int describeContents() + { + // TODO Auto-generated method stub + return 0; + } + + /* (non-Javadoc) + * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) + */ + @Override + public void writeToParcel(Parcel dest, int flags) + { + // TODO Auto-generated method stub + dest.writeLong(m_time); + dest.writeLong(m_offset); + dest.writeByte(m_cmd); + dest.writeByte(m_batteryLevel); + dest.writeByte(m_batteryStatusValue); + dest.writeByte(m_batteryHealthValue); + dest.writeByte(m_batteryPlugTypeValue); + dest.writeString(m_batteryTemperatureValue); + dest.writeString(m_batteryVoltageValue); + dest.writeInt(m_statesValue); + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItemIcs.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItemIcs.java new file mode 100644 index 0000000..a8c656b --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/HistoryItemIcs.java @@ -0,0 +1,167 @@ +package com.asksven.android.common.privateapiproxies; +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import java.io.Serializable; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.asksven.android.common.utils.DateUtils; + +/** + * ICS specific Value holder for BatteryStats$HistoryItem + * @author sven + * + */ +public class HistoryItemIcs extends HistoryItem implements Serializable, Parcelable +{ + static final long serialVersionUID = 1L; + public static final byte CMD_NULL = 0; + public static final byte CMD_UPDATE = 1; + public static final byte CMD_START = 2; + public static final byte CMD_OVERFLOW = 3; + + public byte cmd = CMD_NULL; + // Constants from SCREEN_BRIGHTNESS_* + public static final int STATE_BRIGHTNESS_MASK = 0x0000000f; + public static final int STATE_BRIGHTNESS_SHIFT = 0; + // Constants from SIGNAL_STRENGTH_* + public static final int STATE_SIGNAL_STRENGTH_MASK = 0x000000f0; + public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4; + // Constants from ServiceState.STATE_* + public static final int STATE_PHONE_STATE_MASK = 0x00000f00; + public static final int STATE_PHONE_STATE_SHIFT = 8; + // Constants from DATA_CONNECTION_* + public static final int STATE_DATA_CONNECTION_MASK = 0x0000f000; + public static final int STATE_DATA_CONNECTION_SHIFT = 12; + + // These states always appear directly in the first int token + // of a delta change; they should be ones that change relatively + // frequently. + public static final int STATE_WAKE_LOCK_FLAG = 1<<30; + public static final int STATE_SENSOR_ON_FLAG = 1<<29; + public static final int STATE_GPS_ON_FLAG = 1<<28; + public static final int STATE_PHONE_SCANNING_FLAG = 1<<27; + public static final int STATE_WIFI_RUNNING_FLAG = 1<<26; + public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25; + public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<24; + public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23; + // These are on the lower bits used for the command; if they change + // we need to write another int of data. + public static final int STATE_AUDIO_ON_FLAG = 1<<22; + public static final int STATE_VIDEO_ON_FLAG = 1<<21; + public static final int STATE_SCREEN_ON_FLAG = 1<<20; + public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; + public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18; + public static final int STATE_WIFI_ON_FLAG = 1<<17; + public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16; + + public static final int MOST_INTERESTING_STATES = + STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG + | STATE_GPS_ON_FLAG | STATE_PHONE_IN_CALL_FLAG; + + public HistoryItemIcs(Long time, Byte cmd, Byte batteryLevel, Byte batteryStatusValue, + Byte batteryHealthValue, Byte batteryPlugTypeValue, + String batteryTemperatureValue, String batteryVoltageValue, + Integer statesValue) + { + super(time, cmd, batteryLevel, batteryStatusValue, + batteryHealthValue, batteryPlugTypeValue, + batteryTemperatureValue, batteryVoltageValue, + statesValue); + } + + + + /** + * @return true is phone is charging + */ + public boolean isCharging() + { + boolean bCharging = (m_statesValue & STATE_BATTERY_PLUGGED_FLAG) != 0; + + return bCharging; + } + + /** + * @return true if screen is on + */ + public boolean isScreenOn() + { + boolean bScreenOn = (m_statesValue & STATE_SCREEN_ON_FLAG) != 0; + return bScreenOn; + } + + /** + * @return true is GPS is on + */ + public boolean isGpsOn() + { + boolean bGpsOn = (m_statesValue & STATE_GPS_ON_FLAG) != 0; + return bGpsOn; + } + + /** + * @return true is wifi is running + */ + public boolean isWifiRunning() + { + boolean bWifiRunning = (m_statesValue & STATE_WIFI_RUNNING_FLAG) != 0; + return bWifiRunning; + } + + /** + * @return true is a wakelock is present + */ + public boolean isWakeLock() + { + boolean bWakeLock = (m_statesValue & STATE_WAKE_LOCK_FLAG) != 0; + return bWakeLock; + } + + /** + * @return true if Phone is in Call + */ + public boolean isPhoneInCall() + { + + boolean bPhoneInCall = (m_statesValue & STATE_PHONE_IN_CALL_FLAG) != 0; + + return bPhoneInCall; + } + + /** + * @return true if Phone is Scanning + */ + public boolean isPhoneScanning() + { + boolean bPhoneScanning = (m_statesValue & STATE_PHONE_SCANNING_FLAG) != 0; + + return bPhoneScanning; + } + + /** + * @return the true if bluetooth is on + */ + public boolean isBluetoothOn() + { + boolean bBluetoothOn = (m_statesValue & STATE_BLUETOOTH_ON_FLAG) != 0; + + return bBluetoothOn; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/KernelWakelock.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/KernelWakelock.java new file mode 100644 index 0000000..d4723ca --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/KernelWakelock.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +import android.util.Log; + + +/** + * @author sven + * + */ +public class KernelWakelock extends StatElement implements Comparable, Serializable +{ + /** + * the tag for logging + */ + private static transient final String TAG = "KernelWakelock"; + + /** + * the name of the wakelock holder + */ + @SerializedName("name") + private String m_name; + + /** + * the duration in ms + */ + @SerializedName("duration_ms") + private long m_duration; + + /** + * the count + */ + @SerializedName("count") + private int m_count; + + /** + * Creates a wakelock instance + * @param wakeType the type of wakelock (partial or full) + * @param name the speaking name + * @param duration the duration the wakelock was held + * @param time the battery realtime + * @param count the number of time the wakelock was active + */ + public KernelWakelock(String name, long duration, long time, int count) + { + m_name = name; + m_duration = duration; + setTotal(time); + m_count = count; + } + + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + KernelWakelock myRef = (KernelWakelock) myList.get(i); + if ( (this.getName().equals(myRef.getName())) && (this.getuid() == myRef.getuid()) ) + { + this.m_duration -= myRef.getDuration(); + this.setTotal( this.getTotal() - myRef.getTotal()); + this.m_count -= myRef.getCount(); + + if ((m_count < 0) || (m_duration < 0) || (this.getTotal() < 0)) + { + Log.e(TAG, "substractFromRef generated negative values (" + this.toString() + " - " + myRef.toString() + ")"); + } + break; + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "substractFromRef was called with a wrong list type"); + } + + } + } + } + + + /** + * @return the name + */ + public String getName() + { + return m_name; + } + + /** + * @return the duration + */ + public long getDuration() + { + return m_duration; + } + + /** + * @return the count + */ + public int getCount() + { + return m_count; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "Kernel Wakelock [m_name=" + m_name + + ", m_duration=" + m_duration + + ", m_count=" + m_count+ "]"; + } + + /** + * Compare a given Wakelock with this object. + * If the duration of this object is + * greater than the received object, + * then this object is greater than the other. + */ + public int compareTo(KernelWakelock o) + { + // we want to sort in descending order + return ((int)(o.getDuration() - this.getDuration())); + } + + /** + * returns a string representation of the data + */ + public String getData() + { + return this.formatDuration(getDuration()) + + " (" + getDuration()/1000 + " s)" + + " Count:" + getCount() + + " " + this.formatRatio(getDuration(), getTotal()); + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = getDuration(); + return retVal; + } + + public static class CountComparator implements Comparator + { + public int compare(KernelWakelock a, KernelWakelock b) + { + return ((int)(b.getCount() - a.getCount())); + } + } + + public static class TimeComparator implements Comparator + { + public int compare(KernelWakelock a, KernelWakelock b) + { + return ((int)(b.getDuration() - a.getDuration())); + } + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Misc.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Misc.java new file mode 100644 index 0000000..309ad0c --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Misc.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +import android.util.Log; + +/** + * @author sven + * + */ +public class Misc extends StatElement implements Comparable, Serializable +{ + + /** + * the tag for logging + */ + private static transient final String TAG = "Misc"; + + /** + * the name of the object + */ + @SerializedName("name") + private String m_name; + + /** + * the time on in ms + */ + @SerializedName("time_on_ms") + private long m_timeOn; + + /** + * the time running in ms + */ + @SerializedName("time_runing_ms") + private long m_timeRunning; + + /** + * Constructor + * @param name + * @param timeOn + * @param ratio + */ + public Misc(String name, long timeOn, long timeRunning) + { + + m_name = name; + m_timeOn = timeOn; + m_timeRunning = timeRunning; + } + + public Misc clone() + { + Misc clone = new Misc(m_name, m_timeOn, m_timeRunning); + return clone; + } + + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + Misc myRef = (Misc) myList.get(i); + if ( (this.getName().equals(myRef.getName())) && (this.getuid() == myRef.getuid()) ) + { +// Log.d(TAG, "Substracting " + myRef.getName() + " " + myRef.getVals() +// + " from " + this.getName() + " " + this.getVals()); + + this.m_timeOn -= myRef.getTimeOn(); + this.m_timeRunning -= myRef.getTimeRunning(); + + if (this.m_timeOn > this.m_timeRunning) + { + Log.i(TAG, "Fixed rounding difference: " + this.m_timeOn + " -> " + this.m_timeRunning); + this.m_timeOn = this.m_timeRunning; + } + + if ((m_timeOn < 0) || (m_timeRunning < 0)) + { + Log.e(TAG, "substractFromRef generated negative values (" + this.toString() + " - " + myRef.toString() + ")"); + } + break; + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "substractFromRef was called with a wrong list type"); + } + + } + } + } + + /** + * @return the name + */ + public String getName() + { + return m_name; + } + + /** + * @return the time on + */ + public long getTimeOn() + { + return m_timeOn; + } + + /** + * @return the time running + */ + public long getTimeRunning() + { + return m_timeRunning; + } + + /** + * Compare a given Wakelock with this object. + * If the duration of this object is + * greater than the received object, + * then this object is greater than the other. + */ + public int compareTo(Misc o) + { + // we want to sort in descending order + return (int)( o.getTimeOn() - this.getTimeOn()); + } + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Misc [m_name=" + m_name + ", m_timeOn=" + formatDuration(getTimeOn()) + + ", m_timeRunning=" + formatDuration(getTimeRunning()) + "]"; + } + + /** + * returns a string representation of the data + */ + public String getData() + { + + return this.formatDuration(getTimeOn()) + " (" + getTimeOn()/1000 + " s)" + + " Ratio: " + formatRatio(getTimeOn(), getTimeRunning()); + } + + /** + * returns a string representation of the data + */ + public String getVals() + { + + return m_name + " " + this.formatDuration(getTimeOn()) + " (" + getTimeOn()/1000 + " s)" + + " in " + this.formatDuration(getTimeRunning()) + " (" + getTimeRunning()/1000 + " s)" + + " Ratio: " + formatRatio(getTimeOn(), getTimeRunning()); + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = getTimeOn(); + return retVal; + } + + public double getMaxValue() + { + return getTimeOn(); + } + +} + diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkQueryProxy.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkQueryProxy.java new file mode 100644 index 0000000..3587c1c --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkQueryProxy.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Map; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; + +import com.asksven.android.common.nameutils.UidInfo; +import com.asksven.android.common.nameutils.UidNameResolver; +import com.asksven.android.common.utils.DateUtils; + + + +/** + * A proxy to the non-public API BatteryStats + * http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/android/os/BatteryStats.java/?v=source + * @author sven + * + */ +public class NetworkQueryProxy +{ + /* + * Instance of the BatteryStatsImpl + */ + private Object m_Instance = null; + @SuppressWarnings("rawtypes") + private Class m_ClassDefinition = null; + + private static final String TAG = "NetworkQueryProxy"; + /* + * The UID stats are kept here as their methods / data can not be accessed + * outside of this class due to non-public types (Uid, Proc, etc.) + */ + private SparseArray m_uidStats = null; + + /** + * An instance to the UidNameResolver + */ + private UidNameResolver m_nameResolver; + + /** + * Default cctor + */ + public NetworkQueryProxy(Context context) + { + + try + { + ClassLoader cl = context.getClassLoader(); + +// ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE); +// am.restartPackage("com.android.phone"); + Class networkQueryServiceClassDefinition = cl.loadClass("com.android.phone.NetworkQueryService"); + + // get the IBinder to the "batteryinfo" service + @SuppressWarnings("rawtypes") + Class serviceManagerClass = cl.loadClass("android.os.ServiceManager"); + + // parameter types + @SuppressWarnings("rawtypes") + Class[] paramTypesGetService= new Class[1]; + paramTypesGetService[0]= String.class; + + @SuppressWarnings("unchecked") + Method methodGetService = serviceManagerClass.getMethod("getService", paramTypesGetService); + + // parameters + Object[] paramsGetService= new Object[1]; + paramsGetService[0] = "networkquery"; + + IBinder serviceBinder = (IBinder) methodGetService.invoke(serviceManagerClass, paramsGetService); + + context.startService(new Intent(context, networkQueryServiceClassDefinition)); + if (serviceBinder == null) + { + Log.e(TAG, "no binder to networkquery found"); + + } + else + { + Log.e(TAG, "binder to networkquery acquired"); + } +// // now we have a binder. Let's us that on IBatteryStats.Stub.asInterface +// // to get an IBatteryStats +// // Note the $-syntax here as Stub is a nested class +// @SuppressWarnings("rawtypes") +// Class iBatteryStatsStub = cl.loadClass("com.android.internal.app.IBatteryStats$Stub"); +// +// //Parameters Types +// @SuppressWarnings("rawtypes") +// Class[] paramTypesAsInterface= new Class[1]; +// paramTypesAsInterface[0]= IBinder.class; +// +// @SuppressWarnings("unchecked") +// Method methodAsInterface = iBatteryStatsStub.getMethod("asInterface", paramTypesAsInterface); +// +// // Parameters +// Object[] paramsAsInterface= new Object[1]; +// paramsAsInterface[0] = serviceBinder; +// +// Object iBatteryStatsInstance = methodAsInterface.invoke(iBatteryStatsStub, paramsAsInterface); +// +// // and finally we call getStatistics from that IBatteryStats to obtain a Parcel +// @SuppressWarnings("rawtypes") +// Class iBatteryStats = cl.loadClass("com.android.internal.app.IBatteryStats"); +// +// @SuppressWarnings("unchecked") +// Method methodGetStatistics = iBatteryStats.getMethod("getStatistics"); +// byte[] data = (byte[]) methodGetStatistics.invoke(iBatteryStatsInstance); +// +// Parcel parcel = Parcel.obtain(); +// parcel.unmarshall(data, 0, data.length); +// parcel.setDataPosition(0); +// +// @SuppressWarnings("rawtypes") +// Class batteryStatsImpl = cl.loadClass("com.android.internal.os.BatteryStatsImpl"); +// Field creatorField = batteryStatsImpl.getField("CREATOR"); +// +// // From here on we don't need reflection anymore +// @SuppressWarnings("rawtypes") +// Parcelable.Creator batteryStatsImpl_CREATOR = (Parcelable.Creator) creatorField.get(batteryStatsImpl); +// +// m_Instance = batteryStatsImpl_CREATOR.createFromParcel(parcel); + } + catch( Exception e ) + { + Log.e("TAG", "An exception occured in NetworkQueryProxy(). Message: " + e.getMessage() + ", cause: " + e.getCause().getMessage()); + m_Instance = null; + } + } + + +} \ No newline at end of file diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkUsage.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkUsage.java new file mode 100644 index 0000000..58e31fc --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/NetworkUsage.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +import com.asksven.android.common.nameutils.UidInfo; +import com.google.gson.annotations.SerializedName; + +import android.app.Application; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.util.Log; + +public class NetworkUsage extends StatElement implements Comparable, Serializable +{ + /** + * the tag for logging + */ + private static transient final String TAG = "NetworkUsage"; + + + /** + * tcpBytes received by the program + */ + @SerializedName("bytes_received") + private long m_bytesReceived=0; + + /** + * tcpBytes sent by the program + */ + @SerializedName("bytes_sent") + private long m_bytesSent=0; + + /** + * the interface + */ + @SerializedName("iface") + private String m_iface = ""; + + + /** + * + * @param uid + * @param iface + * @param bytesReceived + * @param bytesSent + */ + public NetworkUsage(int uid, String iface, long bytesReceived, long bytesSent) + { + super.setUid(uid); + m_iface = iface; + m_bytesReceived = bytesReceived; + m_bytesSent = bytesSent; + } + + public NetworkUsage clone() + { + NetworkUsage clone = new NetworkUsage(getuid(), m_iface, m_bytesReceived, m_bytesSent); + return clone; + } + + /** + * + * @param uid + * @param bytesReceived + * @param bytesSent + */ + public NetworkUsage(int uid, long bytesReceived, long bytesSent) + { + super.setUid(uid); + m_iface = ""; + m_bytesReceived = bytesReceived; + m_bytesSent = bytesSent; + } + + /** + * Creates an Object by passing its name + * @param name + * @param uid + * @param bytesReceived + * @param bytesSent + */ + public NetworkUsage(String name, int uid, long bytesReceived, long bytesSent) + { + m_uidInfo = new UidInfo(); + m_uidInfo.setName(name); + m_uidInfo.setUid(uid); + super.setUid(uid); + m_bytesReceived = bytesReceived; + m_bytesSent = bytesSent; + } + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + NetworkUsage myRef = (NetworkUsage) myList.get(i); + if ( (this.getInterface().equals(myRef.getInterface())) + && (this.getuid() == myRef.getuid()) ) + { + this.m_bytesReceived -= myRef.getBytesReceived(); + this.m_bytesSent -= myRef.getBytesSent(); + + if ((m_bytesReceived < 0) || (m_bytesSent < 0)) + { + Log.e(TAG, "substractFromRef generated negative values (" + this.toString() + " - " + myRef.toString() + ")"); + } + break; + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "substractFromRef was called with a wrong list type"); + } + } + } + } + + + /** + * @return the interface + */ + public String getInterface() + { + String ret = ""; + if (m_iface.startsWith("wlan")) + { + ret = "Wifi"; + } + else if (m_iface.startsWith("rmnet")) + { + ret = "Mobile"; + } + else if (m_iface.startsWith("bnep")) + { + ret = "Bluetooth"; + } + else + { + ret = m_iface; + } + + return ret; + } + + + /** + * @return the bytes received + */ + public long getBytesReceived() + { + return m_bytesReceived; + } + + public void addBytesReceived(long bytes) + { + m_bytesReceived += bytes; + } + + public void addBytesSent(long bytes) + { + m_bytesSent += bytes; + } + /** + * @return the bytes sent + */ + public long getBytesSent() + { + return m_bytesSent; + } + + /** + * @return the total bytes sent and received + */ + public long getTotalBytes() + { + return m_bytesSent + m_bytesReceived; + } + + /** + * Compare a given NetworkUsage with this object. + * If the duration of this object is + * greater than the received object, + * then this object is greater than the other. + */ + public int compareTo(NetworkUsage o) + { + // we want to sort in descending order + return ((int)((o.getBytesReceived() + o.getBytesSent()) - (this.getBytesReceived() + this.getBytesSent()))); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "NetworkUsage [m_uid=" + super.getuid() + ", m_bytesReceived=" + + m_bytesReceived + ", m_bytesSent=" + m_bytesSent + "]"; + } + + /** + * Network Stats do not have a speaking name, just the UID + */ + public String getName() + { + return String.valueOf(super.getuid() + " (" + getInterface() + ")"); + } + + /** + * returns a string representation of the data + */ + public String getData() + { + return formatVolume(getTotalBytes()) + " " + this.formatRatio(getTotalBytes(), getTotal()); + //"RX Bytes: " + String.valueOf(m_bytesReceived) + ", TX Bytes: " + String.valueOf(m_bytesSent); + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = getBytesReceived() + getBytesSent(); +// retVal[1] = getBytesReceived() + getBytesSent(); + return retVal; + } + + /** + * Formats data volumes + * @param bytes + * @return the formated string + */ + public static String formatVolume(double bytes) + { + String ret = ""; + + double kB = Math.floor(bytes / 1024); + double mB = Math.floor(bytes / 1024 / 1024); + double gB = Math.floor(bytes / 1024 / 1024 / 1024); + double tB = Math.floor(bytes / 1024 / 1024 / 1024 / 1024); + + if (tB > 0) + { + ret = tB + " TBytes"; + } + else if ( gB > 0) + { + ret = gB + " GBytes"; + } + else if ( mB > 0) + { + ret = mB + " MBytes"; + } + else if ( kB > 0) + { + ret = kB + " KBytes"; + } + else + { + ret = bytes + " Bytes"; + } + return ret; + } + + public Drawable getIcon(Context ctx) + { + if (m_icon == null) + { + // retrieve and store the icon for that package + if (m_uidInfo != null) + { + String myPackage = m_uidInfo.getNamePackage(); + if (!myPackage.equals("")) + { + PackageManager manager = ctx.getPackageManager(); + try + { + m_icon = manager.getApplicationIcon(myPackage); + } + catch (Exception e) + { + // nop: no icon found + m_icon = null; + } + + } + } + } + return m_icon; + } + + public String getPackageName() + { + if (m_uidInfo != null) + { + return m_uidInfo.getNamePackage(); + } + else + { + return ""; + } + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/PackageElement.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/PackageElement.java new file mode 100644 index 0000000..227bb3c --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/PackageElement.java @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +import com.asksven.android.common.nameutils.UidInfo; +import com.asksven.android.common.utils.StringUtils; +import com.google.gson.annotations.SerializedName; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.util.Log; + +/** + * @author sven + * + */ +public class PackageElement extends StatElement implements Comparable, Serializable +{ + /** + * the tag for logging + */ + private static transient final String TAG = "Package"; + + /** + * the package name of the wakelock holder + */ + @SerializedName("package_name") + private String m_packageName; + + /** + * the name of the wakelock holder + */ + @SerializedName("name") + private String m_name; + + /** + * the duration in ms + */ + @SerializedName("duration_ms") + private long m_duration; + + + + /** + * the count + */ + @SerializedName("count") + private int m_count; + + /** + * the number of wakeups + */ + @SerializedName("wakeups") + private long m_wakeups; + + /** + * the total data transfer + */ + @SerializedName("rxtx") + private long m_rxtx; + + /** + * Creates a package instance + * @param name the speaking name + * @param duration the duration the wakelock was held + * @param time the battery realtime + * @param count the number of time the wakelock was active + */ + public PackageElement(String packageName, String name, int uid, long duration, long time, int count, long wakeups, long rxtx) + { + m_packageName = packageName; + m_name = name; + m_duration = duration; + setTotal(time); + m_count = count; + m_wakeups = wakeups; + m_rxtx = rxtx; + super.setUid(uid); + } + + + public PackageElement clone() + { + PackageElement clone = new PackageElement(m_packageName, m_name, getuid(), m_duration, getTotal(), m_count, m_wakeups, m_rxtx); + + // Overwrite name to avoid multiple hashes + clone.m_name = m_name; + + clone.m_icon = m_icon; + clone.m_uidInfo = m_uidInfo; + clone.setUid(getuid()); + + return clone; + } + + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + PackageElement myRef = (PackageElement) myList.get(i); + if ( (this.getName().equals(myRef.getName())) && (this.getuid() == myRef.getuid()) ) + { + Log.i(TAG, "Substraction " + myRef.toString() + " from " + this.toString()); + this.m_duration -= myRef.getDuration(); + this.setTotal( getTotal() - myRef.getTotal()); + this.m_count -= myRef.getCount(); + this.m_wakeups -= myRef.m_wakeups; + this.m_rxtx -= myRef.m_rxtx; + Log.i(TAG, "Result: " + this.toString()); + if ((m_count < 0) || (m_duration < 0) || (getTotal() < 0)) + { + Log.e(TAG, "substractFromRef generated negative values (" + this.toString() + " - " + myRef.toString() + ")"); + } + break; + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "substractFromRef was called with a wrong list type"); + } + + } + } + } + + + public void add(StatElement element) + { + if (element instanceof Wakelock) + { + m_duration += ((Wakelock) element).getDuration(); + m_count += ((Wakelock) element).getCount(); + } + else if (element instanceof Alarm) + { + m_wakeups += ((Alarm) element).getWakeups(); + } + else if (element instanceof NetworkUsage) + { + m_rxtx += ((NetworkUsage) element).getTotalBytes(); + } + + else + { + Log.d(TAG, "element "+ element.toString() + " was not added."); + } + } + + /** + * @return the name + */ + public String getName() { + return m_name; + } + + /** + * @return the duration + */ + public long getDuration() { + return m_duration; + } + + /** + * @return the number of wakeups + */ + public long getWakeups() { + return m_wakeups; + } + + /** + * @return the data volume + */ + public long getDataVolume() { + return m_rxtx; + } + + /** + * @return the count + */ + public int getCount() { + return m_count; + } + + /** + * Compare a given Wakelock with this object. + * If the duration of this object is + * greater than the received object, + * then this object is greater than the other. + */ + public int compareTo(PackageElement o) + { + // we want to sort in descending order + return ((int)(o.getDuration() - this.getDuration())); + } + + /** + * returns a string representation of the data + */ + public String getData() + { + return this.formatDuration(getDuration()) + + " Wakeups:" + m_wakeups + + " Data:" + formatVolume(m_rxtx) + + " " + this.formatRatio(getDuration(), getTotal()); + } + + /** + * Formats data volumes + * @param bytes + * @return the formated string + */ + public static String formatVolume(double bytes) + { + String ret = ""; + + double kB = Math.floor(bytes / 1024); + double mB = Math.floor(bytes / 1024 / 1024); + double gB = Math.floor(bytes / 1024 / 1024 / 1024); + double tB = Math.floor(bytes / 1024 / 1024 / 1024 / 1024); + + if (tB > 0) + { + ret = tB + " TBytes"; + } + else if ( gB > 0) + { + ret = gB + " GBytes"; + } + else if ( mB > 0) + { + ret = mB + " MBytes"; + } + else if ( kB > 0) + { + ret = kB + " KBytes"; + } + else + { + ret = bytes + " Bytes"; + } + return ret; + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = getDuration(); + return retVal; + } + + public static class WakelockCountComparator implements Comparator + { + public int compare(PackageElement a, PackageElement b) + { + return ((int)(b.getCount() - a.getCount())); + } + } + + public static class WakelockTimeComparator implements Comparator + { + public int compare(PackageElement a, PackageElement b) + { + return ((int)(b.getDuration() - a.getDuration())); + } + } + + public Drawable getIcon(Context ctx) + { + if (m_icon == null) + { + // retrieve and store the icon for that package + if (m_uidInfo != null) + { + String myPackage = m_uidInfo.getNamePackage(); + if (!myPackage.equals("")) + { + PackageManager manager = ctx.getPackageManager(); + try + { + m_icon = manager.getApplicationIcon(myPackage); + } + catch (Exception e) + { + // nop: no icon found + m_icon = null; + } + + } + } + else + { + // retrieve and store the icon for that package + String myPackage = m_packageName; + if (!myPackage.equals("")) + { + PackageManager manager = ctx.getPackageManager(); + try + { + m_icon = manager.getApplicationIcon(myPackage); + } + catch (Exception e) + { + // nop: no icon found + m_icon = null; + } + + } + + } + } + return m_icon; + } + + public String getPackageName() + { + if (m_uidInfo != null) + { + return m_uidInfo.getNamePackage(); + } + else + { + return ""; + } + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "PackageElement [m_name=" + m_name + ", m_packageName=" + m_packageName + ", m_uid=" + getuid() + ", m_duration=" + m_duration + + ", m_count=" + m_count + ", m_rxtx=" + m_rxtx + ", m_wakeups=" + m_wakeups +"]"; + } + + + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Process.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Process.java new file mode 100644 index 0000000..7a68b7e --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Process.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.util.Log; + +/** + * @author sven + * + */ +public class Process extends StatElement implements Comparable, Serializable +{ + /** + * the tag for logging + */ + private transient static final String TAG = "Process"; + + /** + * the name of the process + */ + @SerializedName("name") + private String m_name; + + /** + * the system time in ms + */ + @SerializedName("system_time") + private long m_systemTime; + + /** + * the user time in ms + */ + @SerializedName("user_time") + private long m_userTime; + + /** + * the number of starts + */ + @SerializedName("starts") + private int m_starts; + + /** + * Constructor + * @param name + * @param userTime + * @param systemTime + * @param starts + */ + public Process(String name, long userTime, long systemTime, int starts) + { + + m_name = name; + m_userTime = userTime; + m_systemTime = systemTime; + m_starts = starts; + } + + public Process clone() + { + Process clone = new Process(m_name, m_userTime, m_systemTime, m_starts); + clone.setUid(getuid()); + clone.setUidInfo(getUidInfo()); + return clone; + } + + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + Process myRef = (Process) myList.get(i); + if ( (this.getName().equals(myRef.getName())) && (this.getuid() == myRef.getuid()) ) + { + this.m_userTime -= myRef.getUserTime(); + this.m_systemTime -= myRef.getSystemTime(); + this.m_starts -= myRef.getStarts(); + if ((m_userTime < 0) || (m_systemTime < 0) || (m_starts < 0)) + { + Log.e(TAG, "substractFromRef generated negative values (" + this.toString() + " - " + myRef.toString() + ")"); + } + break; + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "substractFromRef was called with a wrong list type"); + } + + } + } + } + + /** + * @return the name + */ + public String getName() + { + return m_name; + } + + /** + * @return the system time + */ + public long getSystemTime() + { + return m_systemTime; + } + + /** + * @return the user time + */ + public long getUserTime() + { + return m_userTime; + } + + /** + * @return the number of starts + */ + public int getStarts() + { + return m_starts; + } + + /** + * Compare a given Wakelock with this object. + * If the duration of this object is + * greater than the received object, + * then this object is greater than the other. + */ + public int compareTo(Process o) + { + // we want to sort in descending order + return ((int)( (o.getSystemTime() + o.getUserTime()) - (this.getSystemTime() + this.getUserTime()))); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "Process [m_name=" + m_name + ", m_systemTime=" + m_systemTime + + ", m_userTime=" + m_userTime + ", m_starts=" + m_starts + "]"; + } + + /** + * returns a string representation of the data + */ + public String getData() + { + + return "Uid: " + this.getuid() + " Sys: " + this.formatDuration(getSystemTime()) + " (" + getSystemTime()/1000 + " s)" + + " Us: " + this.formatDuration(getUserTime()) + " (" + getUserTime()/1000 + " s)" + + " Starts: " + String.valueOf(getStarts()); + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = getUserTime(); + retVal[1] = getUserTime() + getSystemTime(); + return retVal; + } + + public static class ProcessCountComparator implements Comparator + { + public int compare(Process a, Process b) + { + return ((int)(b.getStarts() - a.getStarts())); + } + } + + public static class ProcessTimeComparator implements Comparator + { + public int compare(Process a, Process b) + { + return ((int)((b.getSystemTime() + b.getUserTime()) + - (a.getSystemTime() + a.getUserTime()))); + } + } + + public Drawable getIcon(Context ctx) + { + if (m_icon == null) + { + // retrieve and store the icon for that package + if (m_uidInfo != null) + { + String myPackage = m_uidInfo.getNamePackage(); + if (!myPackage.equals("")) + { + PackageManager manager = ctx.getPackageManager(); + try + { + m_icon = manager.getApplicationIcon(myPackage); + } + catch (Exception e) + { + // nop: no icon found + m_icon = null; + } + + } + } + } + return m_icon; + } + + public String getPackageName() + { + if (m_uidInfo != null) + { + return m_uidInfo.getNamePackage(); + } + else + { + return ""; + } + } + + +} + diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/StatElement.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/StatElement.java new file mode 100644 index 0000000..240f4e8 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/StatElement.java @@ -0,0 +1,269 @@ +/** + * + */ +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.Formatter; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; + +import com.asksven.android.common.nameutils.UidInfo; +import com.asksven.android.common.nameutils.UidNameResolver; +import com.google.gson.annotations.SerializedName; + +/** + * @author sven + * + */ +public abstract class StatElement implements Serializable +{ + /** + * + */ + private static final long serialVersionUID = 1L; + /** + * The process uid + */ + @SerializedName("uid") + private int m_uid = -1; + /** + * The resolved name info + */ + @SerializedName("uid_info") + protected UidInfo m_uidInfo; + + /** + * the battery realtime time + */ + @SerializedName("total") + private long m_total; + /** + * Set the uid + * @param uid a process uid + */ + public void setUid(int uid) + { + m_uid = uid; + } + + protected transient Drawable m_icon; + + /** + * Get the uid + * @return the process uid + */ + public int getuid() + { + return m_uid; + } + + /** + * Store the name info + * @param myInfo (@see com.android.asksven.common.nameutils.UidNameResolver) + */ + public void setUidInfo(UidInfo myInfo) + { + m_uidInfo = myInfo; + } + + /** + * Returns the full qualified name + * @return the full qualified name + */ + public String getFullQualifiedName(Context context) + { + String ret = ""; + + if (m_uidInfo == null) + { + // may have been left out for lazy loading + if (m_uid != -1) + { + UidNameResolver m_nameResolver = new UidNameResolver(); + m_uidInfo = m_nameResolver.getNameForUid(context, m_uid); + } + else + { + return ret; + } + } + + if (!m_uidInfo.getNamePackage().equals("")) + { + ret = m_uidInfo.getNamePackage() + "."; + } + ret += m_uidInfo.getName(); + + return ret; + + } + + /** + * Returns the full qualified name (default, can be overwritten) + * @return the full qualified name + */ + public String getFqn(Context context) + { + return getFullQualifiedName(context); + } + + + /** + * Returns the associated UidInfo + * @return the UidInfo + */ + public UidInfo getUidInfo() + { + return m_uidInfo; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return m_uidInfo.toString(); + } + + /** + * Returns a speaking name + * @return the name + */ + public abstract String getName(); + + /** + * Returns data as displayable string + * @return the data + */ + public abstract String getData(); + + /** + * @return the m_totalTime + */ + public long getTotal() + { + return m_total; + } + + /** + * @param m_totalTime the total time to set + */ + public void setTotal(long total) + { + this.m_total = total; + } + + /** + * Formats milliseconds to a friendly form + * @param millis + * @return the formated string + */ + protected String formatDuration(double millis) + { + String ret = ""; + + int seconds = (int) Math.floor(millis / 1000); + + int days = 0, hours = 0, minutes = 0; + if (seconds > (60*60*24)) { + days = seconds / (60*60*24); + seconds -= days * (60*60*24); + } + if (seconds > (60 * 60)) { + hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + } + if (seconds > 60) { + minutes = seconds / 60; + seconds -= minutes * 60; + } + ret = ""; + if (days > 0) + { + ret += days + " d "; + } + + if (hours > 0) + { + ret += hours + " h "; + } + + if (minutes > 0) + { + ret += minutes + " m "; + } + if (seconds > 0) + { + ret += seconds + " s "; + } + + return ret; + } + + public final String formatRatio(long num, long den) + { + StringBuilder mFormatBuilder = new StringBuilder(8); + Formatter mFormatter = new Formatter(mFormatBuilder); + if (den == 0L) + { + return "---%"; + } + + float perc = ((float)num) / ((float)den) * 100; + mFormatBuilder.setLength(0); + mFormatter.format("%.1f%%", perc); + return mFormatBuilder.toString(); + } + + /** + * returns the representation of the data for file dimp + */ + public String getDumpData(Context context) + { + return this.getName() + " (" + this.getFqn(context) + "): " + this.getData(); + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = m_total; + return retVal; + } + + + /** + * returns the max of the data + */ + public double getMaxValue() + { + return getValues()[0]; //m_totalTime; + } + + public Drawable getIcon(Context ctx) + { + return m_icon; + } + + public String getPackageName() + { + return ""; + } + + /** + * returns a string representation of the data + */ + public String getVals() + { + + return getName() + this.formatDuration(m_total) + " (" + m_total/1000 + " s)"; + } + + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/SystemPropertiesProxy.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/SystemPropertiesProxy.java new file mode 100644 index 0000000..6e4c69d --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/SystemPropertiesProxy.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + + +import java.io.File; +import java.lang.reflect.Method; +import android.content.Context; +import dalvik.system.DexFile; + + +public class SystemPropertiesProxy +{ + +/** + * This class cannot be instantiated + */ +private SystemPropertiesProxy(){ + +} + + /** + * Get the value for the given key. + * @return an empty string if the key isn't found + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + public static String get(Context context, String key) throws IllegalArgumentException { + + String ret= ""; + + try{ + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class SystemProperties = cl.loadClass("android.os.SystemProperties"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[1]; + paramTypes[0]= String.class; + + @SuppressWarnings("unchecked") + Method get = SystemProperties.getMethod("get", paramTypes); + + //Parameters + Object[] params= new Object[1]; + params[0]= new String(key); + + ret= (String) get.invoke(SystemProperties, params); + + }catch( IllegalArgumentException iAE ){ + throw iAE; + }catch( Exception e ){ + ret= ""; + //TODO + } + + return ret; + + } + + /** + * Get the value for the given key. + * @return if the key isn't found, return def if it isn't null, or an empty string otherwise + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + public static String get(Context context, String key, String def) throws IllegalArgumentException { + + String ret= def; + + try{ + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class SystemProperties = cl.loadClass("android.os.SystemProperties"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= String.class; + paramTypes[1]= String.class; + + @SuppressWarnings("unchecked") + Method get = SystemProperties.getMethod("get", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new String(key); + params[1]= new String(def); + + ret= (String) get.invoke(SystemProperties, params); + + }catch( IllegalArgumentException iAE ){ + throw iAE; + }catch( Exception e ){ + ret= def; + //TODO + } + + return ret; + + } + + /** + * Get the value for the given key, and return as an integer. + * @param key the key to lookup + * @param def a default value to return + * @return the key parsed as an integer, or def if the key isn't found or + * cannot be parsed + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + public static Integer getInt(Context context, String key, int def) throws IllegalArgumentException { + + Integer ret= def; + + try{ + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class SystemProperties = cl.loadClass("android.os.SystemProperties"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= String.class; + paramTypes[1]= int.class; + + @SuppressWarnings("unchecked") + Method getInt = SystemProperties.getMethod("getInt", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new String(key); + params[1]= new Integer(def); + + ret= (Integer) getInt.invoke(SystemProperties, params); + + }catch( IllegalArgumentException iAE ){ + throw iAE; + }catch( Exception e ){ + ret= def; + //TODO + } + + return ret; + + } + + /** + * Get the value for the given key, and return as a long. + * @param key the key to lookup + * @param def a default value to return + * @return the key parsed as a long, or def if the key isn't found or + * cannot be parsed + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + public static Long getLong(Context context, String key, long def) throws IllegalArgumentException { + + Long ret= def; + + try{ + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class SystemProperties= cl.loadClass("android.os.SystemProperties"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= String.class; + paramTypes[1]= long.class; + + @SuppressWarnings("unchecked") + Method getLong = SystemProperties.getMethod("getLong", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new String(key); + params[1]= new Long(def); + + ret= (Long) getLong.invoke(SystemProperties, params); + + }catch( IllegalArgumentException iAE ){ + throw iAE; + }catch( Exception e ){ + ret= def; + //TODO + } + + return ret; + + } + + /** + * Get the value for the given key, returned as a boolean. + * Values 'n', 'no', '0', 'false' or 'off' are considered false. + * Values 'y', 'yes', '1', 'true' or 'on' are considered true. + * (case insensitive). + * If the key does not exist, or has any other value, then the default + * result is returned. + * @param key the key to lookup + * @param def a default value to return + * @return the key parsed as a boolean, or def if the key isn't found or is + * not able to be parsed as a boolean. + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + public static Boolean getBoolean(Context context, String key, boolean def) throws IllegalArgumentException { + + Boolean ret= def; + + try{ + + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class SystemProperties = cl.loadClass("android.os.SystemProperties"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= String.class; + paramTypes[1]= boolean.class; + + Method getBoolean = SystemProperties.getMethod("getBoolean", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new String(key); + params[1]= new Boolean(def); + + ret= (Boolean) getBoolean.invoke(SystemProperties, params); + + }catch( IllegalArgumentException iAE ){ + throw iAE; + }catch( Exception e ){ + ret= def; + //TODO + } + + return ret; + + } + + /** + * Set the value for the given key. + * @throws IllegalArgumentException if the key exceeds 32 characters + * @throws IllegalArgumentException if the value exceeds 92 characters + */ + public static void set(Context context, String key, String val) throws IllegalArgumentException { + + try{ + + @SuppressWarnings("unused") + DexFile df = new DexFile(new File("/system/app/Settings.apk")); + @SuppressWarnings("unused") + ClassLoader cl = context.getClassLoader(); + @SuppressWarnings("rawtypes") + Class SystemProperties = Class.forName("android.os.SystemProperties"); + + //Parameters Types + @SuppressWarnings("rawtypes") + Class[] paramTypes= new Class[2]; + paramTypes[0]= String.class; + paramTypes[1]= String.class; + + Method set = SystemProperties.getMethod("set", paramTypes); + + //Parameters + Object[] params= new Object[2]; + params[0]= new String(key); + params[1]= new String(val); + + set.invoke(SystemProperties, params); + + }catch( IllegalArgumentException iAE ){ + throw iAE; + }catch( Exception e ){ + //TODO + } + + } +} \ No newline at end of file diff --git a/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Wakelock.java b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Wakelock.java new file mode 100644 index 0000000..0631441 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/privateapiproxies/Wakelock.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.privateapiproxies; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +import com.asksven.android.common.utils.StringUtils; +import com.google.gson.annotations.SerializedName; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.util.Log; + +/** + * @author sven + * + */ +public class Wakelock extends StatElement implements Comparable, Serializable +{ + /** + * the tag for logging + */ + private static transient final String TAG = "Wakelock"; + + /** + * the wakelock type + */ + @SerializedName("wake_type") + private int m_wakeType; + + /** + * the name of the wakelock holder + */ + @SerializedName("name") + private String m_name; + + /** + * the duration in ms + */ + @SerializedName("duration_ms") + private long m_duration; + + + + /** + * the count + */ + @SerializedName("count") + private int m_count; + + /** + * Creates a wakelock instance + * @param wakeType the type of wakelock (partial or full) + * @param name the speaking name + * @param duration the duration the wakelock was held + * @param time the battery realtime + * @param count the number of time the wakelock was active + */ + public Wakelock(int wakeType, String name, long duration, long time, int count) + { + m_wakeType = wakeType; + m_name = StringUtils.maskAccountInfo(name); + + + m_duration = duration; + setTotal(time); + m_count = count; + } + + public Wakelock clone() + { + Wakelock clone = new Wakelock(m_wakeType, m_name, m_duration, getTotal(), m_count); + + // Overwrite name to avoid multiple hashes + clone.m_name = m_name; + + clone.m_icon = m_icon; + clone.m_uidInfo = m_uidInfo; + clone.setUid(getuid()); + + return clone; + } + + /** + * Substracts the values from a previous object + * found in myList from the current Process + * in order to obtain an object containing only the data since a referenc + * @param myList + */ + public void substractFromRef(List myList ) + { + if (myList != null) + { + for (int i = 0; i < myList.size(); i++) + { + try + { + Wakelock myRef = (Wakelock) myList.get(i); + if ( (this.getName().equals(myRef.getName())) && (this.getuid() == myRef.getuid()) ) + { + Log.i(TAG, "Substraction " + myRef.toString() + " from " + this.toString()); + this.m_duration -= myRef.getDuration(); + this.setTotal( getTotal() - myRef.getTotal()); + this.m_count -= myRef.getCount(); + Log.i(TAG, "Result: " + this.toString()); + if ((m_count < 0) || (m_duration < 0) || (getTotal() < 0)) + { + Log.e(TAG, "substractFromRef generated negative values (" + this.toString() + " - " + myRef.toString() + ")"); + } + break; + } + } + catch (ClassCastException e) + { + // just log as it is no error not to change the process + // being substracted from to do nothing + Log.e(TAG, "substractFromRef was called with a wrong list type"); + } + + } + } + } + + /** + * @return the wakeType + */ + public int getWakeType() { + return m_wakeType; + } + + /** + * @return the name + */ + public String getName() { + return m_name; + } + + /** + * @return the duration + */ + public long getDuration() { + return m_duration; + } + + /** + * @return the count + */ + public int getCount() { + return m_count; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Wakelock [m_wakeType=" + m_wakeType + ", m_name=" + m_name + + ", m_duration=" + m_duration + "]"; + } + + /** + * Compare a given Wakelock with this object. + * If the duration of this object is + * greater than the received object, + * then this object is greater than the other. + */ + public int compareTo(Wakelock o) + { + // we want to sort in descending order + return ((int)(o.getDuration() - this.getDuration())); + } + + /** + * returns a string representation of the data + */ + public String getData() + { + return this.formatDuration(getDuration()) + + " (" + getDuration()/1000 + " s)" + + " Count:" + getCount() + + " " + this.formatRatio(getDuration(), getTotal()); + } + + /** + * returns the values of the data + */ + public double[] getValues() + { + double[] retVal = new double[2]; + retVal[0] = getDuration(); + return retVal; + } + + public static class WakelockCountComparator implements Comparator + { + public int compare(Wakelock a, Wakelock b) + { + return ((int)(b.getCount() - a.getCount())); + } + } + + public static class WakelockTimeComparator implements Comparator + { + public int compare(Wakelock a, Wakelock b) + { + return ((int)(b.getDuration() - a.getDuration())); + } + } + + public Drawable getIcon(Context ctx) + { + if (m_icon == null) + { + // retrieve and store the icon for that package + if (m_uidInfo != null) + { + String myPackage = m_uidInfo.getNamePackage(); + if (!myPackage.equals("")) + { + PackageManager manager = ctx.getPackageManager(); + try + { + m_icon = manager.getApplicationIcon(myPackage); + } + catch (Exception e) + { + // nop: no icon found + m_icon = null; + } + + } + } + } + return m_icon; + } + + public String getPackageName() + { + if (m_uidInfo != null) + { + return m_uidInfo.getNamePackage(); + } + else + { + return ""; + } + } + + + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/settings/GpsSettings.java b/androidCommon/src/main/java/com/asksven/android/common/settings/GpsSettings.java new file mode 100644 index 0000000..23ce73d --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/settings/GpsSettings.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.settings; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; + +/** + * Manages the GPS settings ( + * @author sven + * + */ +@TargetApi(8) +public class GpsSettings +{ + @TargetApi(8) + public static void turnGPSOn(Context context) + { + String provider = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED); + Settings.Secure.setLocationProviderEnabled(context.getContentResolver(), "gps", true); + if(!provider.contains("gps")) + { + final Intent poke = new Intent(); + poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); + poke.addCategory(Intent.CATEGORY_ALTERNATIVE); + poke.setData(Uri.parse("3")); + context.sendBroadcast(poke); + } + } + + public static void turnGPSOff(Context context) + { + String provider = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED); + + if(provider.contains("gps")) + { + final Intent poke = new Intent(); + poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); + poke.addCategory(Intent.CATEGORY_ALTERNATIVE); + poke.setData(Uri.parse("3")); + context.sendBroadcast(poke); + } + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/shellutils/Exec.java b/androidCommon/src/main/java/com/asksven/android/common/shellutils/Exec.java new file mode 100644 index 0000000..6267ee6 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/shellutils/Exec.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.shellutils; + +import java.io.*; + +import android.util.Log; + +/** A class that eases the pain of running external processes + * from applications. Lets you run a program three ways: + *
    + *
  1. exec: Execute the command, returning + * immediately even if the command is still running. + * This would be appropriate for printing a file. + *
  2. execWait: Execute the command, but donÂ’t + * return until the command finishes. This would be + * appropriate for sequential commands where the first + * depends on the second having finished (e.g., + * javac followed by java). + *
  3. execPrint: Execute the command and print the + * output. This would be appropriate for the Unix + * command ls. + *
+ * Note that the PATH is not taken into account, so you must + * specify the full pathname to the command, and shell + * built-in commands will not work. For instance, on Unix the + * above three examples might look like: + *
    + *
  1. Exec.exec("/usr/ucb/lpr Some-File");
    + *
  2. Exec.execWait("/usr/local/bin/javac Foo.java");
    + *        Exec.execWait("/usr/local/bin/java Foo");
    + *
  3. Exec.execPrint("/usr/bin/ls -al");
    + *
+ * + * Adapted from Core Web Programming from + * Prentice Hall and Sun Microsystems Press, + * http://www.corewebprogramming.com/. + * © 2001 Marty Hall and Larry Brown; + * may be freely used or adapted. + */ + +public class Exec +{ + + /** Starts a process to execute the command. Returns + * immediately, even if the new process is still running. + * + * @param command The full pathname of the command to + * be executed. No shell built-ins (e.g., "cd") or shell + * meta-chars (e.g. ">") are allowed. + * @return false if a problem is known to occur, but since + * this returns immediately, problems arenÂ’t usually found + * in time. Returns true otherwise. + */ + + public static ExecResult exec(String[] command) + { + return(exec(command, false, false)); + } + + /** Starts a process to execute the command. Waits for the + * process to finish before returning. + * + * @param command The full pathname of the command to + * be executed. No shell built-ins or shell metachars are + * allowed. + * @return false if a problem is known to occur, either due + * to an exception or from the subprocess returning a + * nonzero value. Returns true otherwise. + */ + + public static ExecResult execWait(String[] command) + { + return(exec(command, false, true)); + } + + /** Starts a process to execute the command. Prints any output + * the command produces. + * + * @param command The full pathname of the command to + * be executed. No shell built-ins or shell meta-chars are + * allowed. + * @return false if a problem is known to occur, either due + * to an exception or from the subprocess returning a + * nonzero value. Returns true otherwise. + */ + + public static ExecResult execPrint(String[] command) + { + return(exec(command, true, false)); + } + + /** This creates a Process object via Runtime.getRuntime.exec() + * Depending on the flags, it may call waitFor on the process + * to avoid continuing until the process terminates, and open + * an input stream from the process to read the results. + */ + + private static ExecResult exec(String[] command, + boolean printResults, + boolean wait) + { + ExecResult oRet = new ExecResult(); + try + { + // Start running command, returning immediately. + Log.d("Exec.exec", "Executing command " + command); + Process p = Runtime.getRuntime().exec(command); + + // Print the output. Since we read until there is no more + // input, this causes us to wait until the process is + // completed. + if(printResults) + { + BufferedReader buffer = new BufferedReader( + new InputStreamReader(p.getInputStream())); + String s = null; + try + { + while ((s = buffer.readLine()) != null) + { + oRet.m_oResult.add(s); + } + buffer.close(); + if (p.exitValue() != 0) + { + oRet.m_bSuccess=false; + return(oRet); + } + } + catch (Exception e) + { + // Ignore read errors; they mean the process is done. + } + + // If not printing the results, then we should call waitFor + // to stop until the process is completed. + } + else if (wait) + { + try + { + int returnVal = p.waitFor(); + if (returnVal != 0) + { + oRet.m_bSuccess=false; + return oRet; + } + } + catch (Exception e) + { + oRet.m_oError.add(e.getMessage()); + oRet.m_bSuccess=false; + return oRet; + } + } + } + catch (Exception e) + { + oRet.m_oError.add(e.getMessage()); + oRet.m_bSuccess=false; + return oRet; + } + oRet.m_bSuccess=true; + return oRet; + } + + public static void suExec(String strCommand) + { + try + { + // dirty hack: http://code.google.com/p/market-enabler/wiki/ShellCommands + Process process = Runtime.getRuntime().exec("su"); + DataOutputStream os = new DataOutputStream(process.getOutputStream()); + Log.d("Exec.exec", "Executing command " + strCommand); + + os.writeBytes(strCommand + "\n"); + os.flush(); + os.writeBytes("exit\n"); + os.flush(); + process.waitFor(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + public static void shExec(String strCommand) + { + try + { + // dirty hack: http://code.google.com/p/market-enabler/wiki/ShellCommands + Process process = Runtime.getRuntime().exec("sh"); + DataOutputStream os = new DataOutputStream(process.getOutputStream()); + Log.d("Exec.exec", "Executing command " + strCommand); + + os.writeBytes(strCommand + "\n"); + os.flush(); + os.writeBytes("exit\n"); + os.flush(); + process.waitFor(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/shellutils/ExecResult.java b/androidCommon/src/main/java/com/asksven/android/common/shellutils/ExecResult.java new file mode 100644 index 0000000..105bcdc --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/shellutils/ExecResult.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.shellutils; + +import java.util.ArrayList; + +public class ExecResult +{ + protected boolean m_bSuccess; + protected ArrayList m_oResult; + protected ArrayList m_oError; + + public ExecResult() + { + m_oResult = new ArrayList(); + m_oError = new ArrayList(); + + } + + public boolean getSuccess() + { + return m_bSuccess; + } + + public ArrayList getResult() + { + return m_oResult; + } + + public String getResultLine() + { + String strRes = ""; + if (!m_oResult.isEmpty()) + { + strRes = m_oResult.get(0); + } + + return strRes; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/ChargerUtil.java b/androidCommon/src/main/java/com/asksven/android/common/utils/ChargerUtil.java new file mode 100644 index 0000000..1c4f965 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/ChargerUtil.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; + +/** + * Helper to detect if charger is plugged + * @author sven + * + */ +public class ChargerUtil +{ + /** + * Returns true if the charger is currently connected + * @param context + * @return true if the charger is connected + */ + public static boolean isConnected(Context context) + { + // make a synchronous call + Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/DataStorage.java b/androidCommon/src/main/java/com/asksven/android/common/utils/DataStorage.java new file mode 100644 index 0000000..2a7f6c1 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/DataStorage.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.utils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Environment; +import android.util.Log; + +/** + * @author sven + * + */ +public class DataStorage +{ + /** + * + * The logging TAG + */ + private static final String TAG = "DataStorage"; + + public static boolean isExternalStorageWritable() + { + boolean mExternalStorageAvailable = false; + boolean mExternalStorageWriteable = false; + String state = Environment.getExternalStorageState(); + + if (Environment.MEDIA_MOUNTED.equals(state)) { + // We can read and write the media + mExternalStorageAvailable = mExternalStorageWriteable = true; + } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + // We can only read the media + mExternalStorageAvailable = true; + mExternalStorageWriteable = false; + } else { + // Something else is wrong. It may be one of many other states, but all we need + // to know is we can neither read nor write + mExternalStorageAvailable = mExternalStorageWriteable = false; + } + + return ( mExternalStorageAvailable && mExternalStorageWriteable ); + } + + public static void LogToFile(String fileName, StackTraceElement[] stack) + { + LogToFile(fileName, "---- Begin Callstack ----"); + for (int i = 0; i < stack.length; i++) + { + LogToFile(fileName, stack[i].toString()); + } + LogToFile(fileName, "---- End Callstack ----"); + } + + public static void LogToFile(String fileName, String strText) + { + + try + { + // open file for writing + File root = Environment.getExternalStorageDirectory(); + if (root.canWrite()) + { + File dumpFile = new File(root, fileName); + // append + FileWriter fw = new FileWriter(dumpFile, true); + BufferedWriter out = new BufferedWriter(fw); + + // write header + out.write(DateUtils.now() + " " + strText + "\n"); + + // close file + out.close(); + } + } + catch (Exception e) + { + Log.e(TAG, "Exception: " + e.getMessage()); + } + } + + public static synchronized boolean objectToFile(Context context, String fileName, Object serializableObject) + { + boolean bRet = true; + + if (!(serializableObject instanceof java.io.Serializable)) + { + Log.e(TAG, "The object is not serializable: " + fileName); + return false; + } + try + { + FileOutputStream fos = context.openFileOutput(fileName, Context.MODE_PRIVATE); + ObjectOutputStream os = new ObjectOutputStream(fos); + os.writeObject(serializableObject); + os.close(); + } + catch (Exception e) + { + Log.e(TAG, "An error occured while writing " + fileName + " " + e.getMessage()); + + bRet = false; + } + + return bRet; + } + + public static synchronized Object fileToObject(Context context, String fileName) + { + FileInputStream fis; + Object myRet = null; + try + { + fis = context.openFileInput(fileName); + ObjectInputStream is = new ObjectInputStream(fis); + myRet = is.readObject(); + is.close(); + + } + catch (FileNotFoundException e) + { + myRet = null; + } + catch (IOException e) + { + myRet = null; + } + catch (ClassNotFoundException e) + { + myRet = null; + } + + return myRet; + } + + /** forces the mediascanner to "see" a file that was just created + * This is a workaround (see http://stackoverflow.com/questions/3300137/how-can-i-refresh-mediastore-on-android/14849652#14849652) + * + * @param file the Uri of the newly created file + */ + public static void forceMediaScanner(Context context, Uri fileUri) + { + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, fileUri)); + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/DateUtils.java b/androidCommon/src/main/java/com/asksven/android/common/utils/DateUtils.java new file mode 100644 index 0000000..72ac479 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/DateUtils.java @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.utils; + +/** + * @author sven + * + */ +import java.util.Calendar; +import java.util.Date; +import java.text.SimpleDateFormat; + +public class DateUtils +{ + public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_FORMAT_SHORT = "HH:mm:ss"; + private static final Calendar m_cal = Calendar.getInstance(); + + /** + * Returns the current date in the default format. + * @return the current formatted date/time + */ + public static String now() + { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW); + long now = System.currentTimeMillis(); + return sdf.format(new Date(now)); + } + + /** + * Returns the current date in a given format. + * DateUtils.now("dd MMMMM yyyy") + * DateUtils.now("yyyyMMdd") + * DateUtils.now("dd.MM.yy") + * DateUtils.now("MM/dd/yy") + * @param dateFormat a date format (See examples) + * @return the current formatted date/time + */ + public static String now(String dateFormat) + { + SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); + long now = System.currentTimeMillis(); + return sdf.format(new Date(now)); + } + + public static String format(String dateFormat, Date time) + { + + SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); + return sdf.format(time); + } + + public static String format(String dateFormat, Long time) + { + + SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); + return sdf.format(time); + } + + public static String format(Date time) + { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW); + return sdf.format(time); + } + + public static String format(long timeMs) + { + return format(timeMs, DATE_FORMAT_NOW); + } + + public static String formatShort(long timeMs) + { + return format(timeMs, DATE_FORMAT_SHORT); + } + + public static String format(long timeMs, String format) + { + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(timeMs); + } + + /** + * Formats milliseconds to a friendly form + * @param millis + * @return the formated string + */ + public static String formatDuration(long millis) + { + int seconds = (int) Math.floor(millis / 1000); + + int days = 0, hours = 0, minutes = 0; + if (seconds > (60*60*24)) { + days = seconds / (60*60*24); + seconds -= days * (60*60*24); + } + if (seconds > (60 * 60)) { + hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + } + if (seconds > 60) { + minutes = seconds / 60; + seconds -= minutes * 60; + } + + // use StringBuilder for better performance + StringBuilder builder = new StringBuilder(); + if (days > 0) + { + builder.append(days + " d "); + } + + if (hours > 0) + { + builder.append(hours + " h "); + } + + if (minutes > 0) + { + builder.append(minutes + " m "); + } + if (seconds > 0) + { + builder.append(seconds + " s "); + } + + String ret = builder.toString(); + if (ret.equals("")) + { + ret = "0 s"; + } + return ret; + } + + /** + * Formats milliseconds to a friendly form. Short means that seconds are truncated if value > 1 Day + * @param millis + * @return the formated string + */ + public static String formatDurationShort(long millis) + { + String ret = ""; + + int seconds = (int) Math.floor(millis / 1000); + + int days = 0, hours = 0, minutes = 0; + if (seconds > (60*60*24)) { + days = seconds / (60*60*24); + seconds -= days * (60*60*24); + } + if (seconds > (60 * 60)) { + hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + } + if (seconds > 60) { + minutes = seconds / 60; + seconds -= minutes * 60; + } + ret = ""; + if (days > 0) + { + ret += days + "d "; + } + + if (hours > 0) + { + ret += hours + "h "; + } + + if (minutes > 0) + { + ret += minutes + "m "; + } + if ( (seconds > 0) && (days == 0) ) + { + // only show seconds when value < 1 day + ret += seconds + "s"; + } + + if (ret.equals("")) + { + ret = "0s"; + } + return ret; + } + + /** + * Parses string of the format 26m 33s 343ms and returns the number of ms + * @param duration + * @return + */ + public static long durationToLong(String duration) + { + long time = 0; + + String[] parts = duration.split(" "); + for (int i=0; i < parts.length; i++) + { + if (parts[i].endsWith("ms")) + { + String val = parts[i].substring(0, parts[i].length()-2); + long dur = Long.valueOf(val); + time += dur * 1; + } + else if (parts[i].endsWith("s")) + { + String val = parts[i].substring(0, parts[i].length()-1); + long dur = Long.valueOf(val); + time += dur * 1000; + } + else if (parts[i].endsWith("m")) + { + String val = parts[i].substring(0, parts[i].length()-1); + long dur = Long.valueOf(val); + time += dur * 1000 * 60; + } + else if (parts[i].endsWith("h")) + { + String val = parts[i].substring(0, parts[i].length()-1); + long dur = Long.valueOf(val); + time += dur * 1000 * 60 * 60; + } + else if (parts[i].endsWith("d")) + { + String val = parts[i].substring(0, parts[i].length()-1); + long dur = Long.valueOf(val); + time += dur * 1000 * 60 * 60 * 24; + } + } + + + return time; + } + /** + * Formats milliseconds to a friendly non abbreviated form (days, hrs, min, sec) + * @param millis + * @return the formated string + */ + public static String formatDurationLong(long millis) + { + String ret = ""; + + int seconds = (int) Math.floor(millis / 1000); + + int days = 0, hours = 0, minutes = 0; + if (seconds > (60*60*24)) { + days = seconds / (60*60*24); + seconds -= days * (60*60*24); + } + if (seconds > (60 * 60)) { + hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + } + if (seconds > 60) { + minutes = seconds / 60; + seconds -= minutes * 60; + } + ret = ""; + if (days > 0) + { + if (days <= 1) + { + ret += days + " day "; + } + else + { + ret += days + " days "; + } + } + + if (hours > 0) + { + if (hours <= 1) + { + ret += hours + " hr "; + } + else + { + ret += hours + " hrs "; + } + } + + if (minutes > 0) + { + ret += minutes + " min. "; + } + if ( (seconds > 0) && (days == 0) ) + { + // only show seconds when value < 1 day + ret += seconds + " sec. "; + } + + if (ret.equals("")) + { + ret = "0 sec."; + } + return ret; + } + /** + * Formats milliseconds to a friendly form. Compressed means that seconds are truncated if value > 1 Hour + * @param millis + * @return the formated string + */ + public static String formatDurationCompressed(long millis) + { + String ret = ""; + + int seconds = (int) Math.floor(millis / 1000); + + int days = 0, hours = 0, minutes = 0; + if (seconds > (60*60*24)) { + days = seconds / (60*60*24); + seconds -= days * (60*60*24); + } + if (seconds > (60 * 60)) { + hours = seconds / (60 * 60); + seconds -= hours * (60 * 60); + } + if (seconds > 60) { + minutes = seconds / 60; + seconds -= minutes * 60; + } + ret = ""; + if (days > 0) + { + ret += days + "d"; + } + + if (hours > 0) + { + ret += hours + "h"; + } + + if (minutes > 0) + { + ret += minutes + "m"; + } + if ( (seconds > 0) && (hours == 0) ) + { + // only show seconds when value < 1 day + ret += seconds + "s"; + } + + if (ret.equals("")) + { + ret = "0s"; + } + return ret; + } + +} \ No newline at end of file diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/GenericLogger.java b/androidCommon/src/main/java/com/asksven/android/common/utils/GenericLogger.java new file mode 100644 index 0000000..0fb12ba --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/GenericLogger.java @@ -0,0 +1,61 @@ +/** + * + */ +package com.asksven.android.common.utils; + +import android.util.Log; + +import com.asksven.android.common.utils.DataStorage; + +/** + * @author sven + * + */ +public abstract class GenericLogger +{ + + + + public static void d(String strLogfile, String strTag, String strMessage) + { + Log.d(strTag, strMessage); + DataStorage.LogToFile(strLogfile, strMessage); + } + + public static void e(String strLogfile, String strTag, String strMessage) + { + Log.e(strTag, strMessage); + DataStorage.LogToFile(strLogfile, strMessage); + } + + public static void i(String strLogFile, String strTag, String strMessage) + { + Log.i(strTag, strMessage); + DataStorage.LogToFile(strLogFile, strMessage); + } + + public static void e(String strLogFile, String strTag, StackTraceElement[] stack) + { + Log.e(strTag, "An Exception occured. Stacktrace:"); + for (int i=0; i < stack.length; i++) + { + Log.e(strTag, stack[i].toString()); + } + DataStorage.LogToFile(strLogFile, stack); + } + + public static void stackTrace(String strTag, StackTraceElement[] stack) + { + Log.e(strTag, "An Exception occured. Stacktrace:"); + for (int i=0; i < stack.length; i++) + { + Log.e(strTag, ">>> " + stack[i].toString()); + } + } + + private static void writeLog(String strLogFile, String strTag, String strMessage) + { + DataStorage.LogToFile(strLogFile, strMessage); + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/MathUtils.java b/androidCommon/src/main/java/com/asksven/android/common/utils/MathUtils.java new file mode 100644 index 0000000..72aa4d2 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/MathUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011-2013 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.utils; + +import android.location.Location; + +/** + * A collection of math functions + * @author sven + * + */ +public class MathUtils +{ + /** + * calculate great circle distance in meters + * @param pos1 a Location object + * @param pos2 a Location object + * @return the distance between the two locations in meters + */ + public static double getDistanceGreatCircle(Location pos1, Location pos2) + { + double lat1 = pos1.getLatitude(); + double long1 = pos1.getLongitude(); + double lat2 = pos2.getLatitude(); + double long2 = pos2.getLongitude(); + + double earthRadius = 3958.75; + double dLat = Math.toRadians(lat2-lat1); + double dLng = Math.toRadians(long2-long1); + double a = Math.sin(dLat/2) * Math.sin(dLat/2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.sin(dLng/2) * Math.sin(dLng/2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + double dist = earthRadius * c; + + int meterConversion = 1609; + + return dist * meterConversion; + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/StringUtils.java b/androidCommon/src/main/java/com/asksven/android/common/utils/StringUtils.java new file mode 100644 index 0000000..63e00f3 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/StringUtils.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.asksven.android.common.utils; + +import java.lang.reflect.Method; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.HashMap; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.provider.Settings; +import android.util.Log; + +/** + * @author sven + * + */ +public class StringUtils +{ + + private static String TAG = "StringUtils"; + + static Pattern emailPattern = Pattern.compile("(.*/)([A-Za-z0-9._%-+]+)@([a-z0-9.-]+\\.[a-z]{2,4})(.*)"); + static Pattern accountnamePattern = Pattern.compile("(.*\\{name\\=)(.*)(\\,.*)"); + + public static final String formatRatio(long num, long den) + { + StringBuilder mFormatBuilder = new StringBuilder(8); + Formatter mFormatter = new Formatter(mFormatBuilder); + if (den == 0L) + { + return "---%"; + } + + float perc = ((float) num) / ((float) den) * 100; + mFormatBuilder.setLength(0); + mFormatter.format("%.1f%%", perc); + return mFormatBuilder.toString(); + } + + public static String join(String[] array, String sep, boolean merge) + { + String ret = ""; + for (int i = 0; i < array.length; i++) + { + if (ret.equals("")) + { + ret = array[i]; + } else + { + if (merge) + { + // check if the string is alread present + if (ret.indexOf(array[i]) == -1) + { + // add + ret += sep + array[i]; + } + } else + { + ret += sep + array[i]; + } + } + } + return ret; + } + + public static void splitLine(String line, ArrayList outSplit) + { + outSplit.clear(); + final StringTokenizer t = new StringTokenizer(line, " \t\n\r\f:"); + while (t.hasMoreTokens()) + { + outSplit.add(t.nextToken()); + } + } + + public static void splitLine(String line, ArrayList outSplit, String sep) + { + outSplit.clear(); + final StringTokenizer t = new StringTokenizer(line, sep); + while (t.hasMoreTokens()) + { + outSplit.add(t.nextToken()); + } + } + + public static void parseLine(ArrayList keys, ArrayList values, HashMap outParsed) + { + outParsed.clear(); + final int size = Math.min(keys.size(), values.size()); + for (int i = 0; i < size; i++) + { + outParsed.put(keys.get(i), values.get(i)); + } + } + + public static int getParsedInt(HashMap parsed, String key) + { + final String value = parsed.get(key); + return value != null ? Integer.parseInt(value) : 0; + } + + public static long getParsedLong(HashMap parsed, String key) + { + final String value = parsed.get(key); + return value != null ? Long.parseLong(value) : 0; + } + + public static String stripLeadingAndTrailingQuotes(String str) + { + if (str == null) + { + return str; + } + + if (str.startsWith("\"")) + { + str = str.substring(1, str.length()); + } + if (str.endsWith("\"")) + { + str = str.substring(0, str.length() - 1); + } + return str; + } + + public static String maskAccountInfo(String str) + { + String ret = str; + + String serial = ""; + + try + { + Class c = Class.forName("android.os.SystemProperties"); + Method get = c.getMethod("get", String.class); + serial = (String) get.invoke(c, "ro.serialno"); + } + catch (Exception ignored) + { + } + + Matcher email = emailPattern.matcher(str); + if ( email.find() ) + { + String strName = email.group(2); + try + { + // generate some long noise + byte[] bytesOfSerial = serial.getBytes("UTF-8"); + MessageDigest mdSha = MessageDigest.getInstance("SHA-256"); + byte[] theShaDigest = mdSha.digest(bytesOfSerial); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < theShaDigest.length; ++i) + { + sb.append(Integer.toHexString((theShaDigest[i] & 0xFF) | 0x100).substring(1,3)); + } + serial = sb.toString(); + + byte[] bytesOfMessage = strName.concat(serial).getBytes("UTF-8"); + + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] thedigest = md.digest(bytesOfMessage); + sb = new StringBuffer(); + for (int i = 0; i < thedigest.length; ++i) + { + sb.append(Integer.toHexString((thedigest[i] & 0xFF) | 0x100).substring(1,3)); + } + ret = email.group(1) + sb.toString() + "@" + email.group(3) + email.group(4); + } + catch (Exception e) + { + Log.e(TAG, "An error occured: " + e.getMessage()); + } + + } + else + { + Matcher account = accountnamePattern.matcher(str); + if ( account.find() ) + { + String strName = account.group(2); + try + { + byte[] bytesOfMessage = strName.getBytes("UTF-8"); + + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] thedigest = md.digest(bytesOfMessage); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < thedigest.length; ++i) + { + sb.append(Integer.toHexString((thedigest[i] & 0xFF) | 0x100).substring(1,3)); + } + ret = account.group(1) + sb.toString() + account.group(3); + } + catch (Exception e) + { + Log.e(TAG, "An error occured: " + e.getMessage()); + } + } + } + return ret; + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/SysUtils.java b/androidCommon/src/main/java/com/asksven/android/common/utils/SysUtils.java new file mode 100644 index 0000000..a7b47f8 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/SysUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.utils; + +import android.content.Context; +import android.content.pm.PackageManager; + +/** + * @author sven + * A collection of system utilities + * + */ +public class SysUtils +{ + + /** + * Checks if we have BATTERY_STATS permission + * @param context + * @return true if the permission was granted + */ + public static boolean hasBatteryStatsPermission(Context context) + { + return wasPermissionGranted(context, android.Manifest.permission.BATTERY_STATS); + } + + /** + * Checks if we have DUMP permission + * @param context + * @return true if the permission was granted + */ + + public static boolean hasDumpsysPermission(Context context) + { + return wasPermissionGranted(context, android.Manifest.permission.DUMP); + } + + private static boolean wasPermissionGranted(Context context, String permission) + { + PackageManager pm = context.getPackageManager(); + int hasPerm = pm.checkPermission( + permission, + context.getPackageName()); + return (hasPerm == PackageManager.PERMISSION_GRANTED); + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/utils/SystemAppInstaller.java b/androidCommon/src/main/java/com/asksven/android/common/utils/SystemAppInstaller.java new file mode 100644 index 0000000..f80e607 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/utils/SystemAppInstaller.java @@ -0,0 +1,374 @@ +/** + * + */ +package com.asksven.android.common.utils; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.content.res.AssetManager; +import android.os.Build; +import android.util.Log; +import android.widget.Toast; + +import com.asksven.android.common.RootShell; + +/** + * @author sven + * + */ +public class SystemAppInstaller +{ + static final String TAG = "SystemAppInstaller"; + + static final String SYSTEM_DIR_4_4 = "/system/priv-app"; + static final String SYSTEM_DIR = "/system/app"; + + static final String REMOUNT_SYSTEM_RW = "mount -o rw,remount /system"; + static final String REMOUNT_SYSTEM_RO = "mount -o ro,remount /system"; + // returns ro or rw +// static final String CHECK_MOUNT_STATE = "mount | grep /system | awk '{print $4}' | awk -F\",\" '{print $1}'"; + static final String CHECK_MOUNT_STATE = "mount | grep /system"; + + + public static boolean mountSystemRw() + { + if (isSystemRw()) return true; + + Log.i(TAG, "Remount system rw"); + RootShell.getInstance().run(REMOUNT_SYSTEM_RW); + + return isSystemRw(); + + } + + public static boolean mountSystemRo() + { + if (!isSystemRw()) return true; + + Log.i(TAG, "Remount system ro"); + RootShell.getInstance().run(REMOUNT_SYSTEM_RO); + + return !isSystemRw(); + + } + + public static boolean isSystemRw() + { + boolean ret = false; + Log.i(TAG, "Checking if system is mounted rw"); + List res = RootShell.getInstance().run(CHECK_MOUNT_STATE); + if (res.size() > 0) + { + String[] tokens = res.get(0).split(" |,"); + String mountState = tokens[3]; + Log.i(TAG, "Mount status: " + mountState); + ret = (mountState.equals("rw")); + } + + return ret; + } + + public static boolean isSystemApp(String apk) + { + boolean ret = false; + List res; + + String command = ""; + if (Build.VERSION.SDK_INT >= 19) + { + command = "ls " + SYSTEM_DIR_4_4 + "/" + apk; + } + else + { + command = "ls " + SYSTEM_DIR + "/" + apk; + } + + Log.i(TAG, "Checking if " + apk + " is a system app"); + res = RootShell.getInstance().run(command); + + if (res.size() > 0) + { + Log.i(TAG, "Command returned "+ res.get(0)); + ret = !res.get(0).contains("No such file or directory"); + } + + return ret; + } + +// static boolean installAsSystemApp(Context ctx, String apk) +// { +// String command = ""; +// String commandCopyBack = ""; +// String commandTouch = ""; +// +// // get the original filename +// command = "ls /data/app/" + apk + "*"; +// List res = RootShell.getInstance().run(command); +// +// // we copy all the instances of the file to /system (preserving timestamp) +// // at that point the APK will get deleted from /data/app (by the system) +// // so we need to copy the APK back to /data/app (with a new timestamp) +// for (int i=0; i < res.size(); i++) +// { +// String fileName = res.get(0).split("/")[3]; +// // remove the -1 from filename to make sure the target filename is different +// +// if (Build.VERSION.SDK_INT >= 19) +// { +// commandTouch = "touch -t 20080801 " + SYSTEM_DIR_4_4 + "/" + fileName; +// command = "cp -p /data/app/" + fileName + " " +SYSTEM_DIR_4_4 +// + " && chmod 644 " + SYSTEM_DIR_4_4 + "/" + fileName +// + " && chown root:root " + SYSTEM_DIR_4_4 + "/" + fileName; +// commandCopyBack = "cp " + SYSTEM_DIR_4_4 + "/" + fileName + " /data/app/" +// + " && chmod 644 /data/app/" + fileName +// + " && chown system:system /data/app/" + fileName; +// } +// else +// { +// commandTouch = "touch -t 20080801 " + SYSTEM_DIR + "/" + fileName; +// command = "cp -p /data/app/" + fileName + " " + SYSTEM_DIR +// + " && chmod 644 " + SYSTEM_DIR + "/" + fileName +// + " && chown root:root " + SYSTEM_DIR + "/" + fileName; +// commandCopyBack = "cp " + SYSTEM_DIR + "/" + fileName + " /data/app/" +// + " && chmod 644 /data/app/" + fileName +// + " && chown system:systems /data/app/" + fileName; +// } +// +// +// Log.i(TAG, "Installing app as system app: " + command); +// RootShell.getInstance().run(command); +// +// Log.i(TAG, "Copy APK back to /data/app: " + commandCopyBack); +// RootShell.getInstance().run(commandCopyBack); +// +// Log.i(TAG, "Changing timestamp: " + commandTouch); +// RootShell.getInstance().run(commandTouch); +// +// } +// return isSystemApp(apk); +// } + + static boolean installAsSystemApp(Context ctx, String apk) + { + String command = ""; + String tempPath = "/sdcard/"; + + // actions: + // copy apk from /sdcard to /system in order to be able to set ownership and perms + // then copy the file to the target. The sequence is important as copying first and setting perms and ownership + // afterward will cause PackageParser to fail parsing the package + if (Build.VERSION.SDK_INT >= 19) + { + command = "cp " + tempPath + apk + " /system" + " && chmod 644 " + "/system/" + apk + + " && chown root:root /system/" + apk + " && cp -p /system/" + apk + " " + SYSTEM_DIR_4_4 + " && rm " + tempPath + apk + " && rm /system/" + apk; + } + else + { + command = "cp " + tempPath + apk + " /system" + " && chmod 644 " + "/system/" + apk + + " && chown root:root /system/" + apk + " && cp -p /system/" + apk + " " + SYSTEM_DIR + " && rm " + tempPath + apk + " && rm /system/" + apk; + } + + + copyAsset(ctx, apk, tempPath); + Log.i(TAG, "Copying, setting permissions and owner and cleaning up: " + command); + RootShell.getInstance().run(command); + + return isSystemApp(apk); + } + + static boolean uninstallAsSystemApp(String apk) + { + String command = ""; + + if (Build.VERSION.SDK_INT >= 19) + { + command = "rm " + SYSTEM_DIR_4_4 + "/" + apk + "*"; + } + else + { + command = "rm " + SYSTEM_DIR + "/" + apk + "*"; + } + + Log.i(TAG, "Uninstalling system app: " + command); + RootShell.getInstance().run(command); + + return !isSystemApp(apk); + } + + public static Status install(Context ctx, String apk) + { + Status status = new Status(); + + SystemAppInstaller.mountSystemRw(); + if (SystemAppInstaller.isSystemRw()) + { + status.add("Mounted system rw"); + SystemAppInstaller.installAsSystemApp(ctx, apk); + status.add("Install as system app"); + if (SystemAppInstaller.isSystemApp(apk)) + { + SystemAppInstaller.mountSystemRo(); + if (!SystemAppInstaller.isSystemRw()) + { + status.add("Mounted system ro. Finished"); + } + else + { + status.add("An error while remounting system to ro. Aborted"); + status.m_success = false; + } + } + else + { + status.add("An error while installing app. Aborted"); + status.m_success = false; + } + + } + else + { + status.add("An error occured mounting system rw. Aborted"); + status.m_success = false; + } + + return status; + } + + private static void copyAsset(Context ctx, String assetName, String targetPath) + { + AssetManager assetManager = ctx.getAssets(); + String[] files = null; + try + { + files = assetManager.list(""); + } + catch (IOException e) + { + Log.e("tag", e.getMessage()); + } + for(String filename : files) + { + if (filename.equals(assetName)) + { + InputStream in = null; + OutputStream out = null; + try + { + in = assetManager.open(filename); + String strOutFile = targetPath + "/" + filename; + out = new FileOutputStream(strOutFile); + copyFile(in, out); + in.close(); + in = null; + out.flush(); + out.close(); + out = null; + } + catch(Exception e) + { + Log.e(TAG, "An error occured while reading " + filename); + } + } + } + } + + /** + * Write a single file + * @param in the source (in assets) + * @param out the target + * @throws IOException + */ + private static void copyFile(InputStream in, OutputStream out) throws IOException + { + byte[] buffer = new byte[1024]; + int read; + while((read = in.read(buffer)) != -1) + { + out.write(buffer, 0, read); + } + } + + /** + * Value holder for status + * @param apk + * @return + */ + public static Status uninstall(String apk) + { + Status status = new Status(); + SystemAppInstaller.mountSystemRw(); + if (SystemAppInstaller.isSystemRw()) + { + status.add("Mounted system rw"); + SystemAppInstaller.uninstallAsSystemApp(apk); + status.add("Uninstall as system app"); + if (!SystemAppInstaller.isSystemApp(apk)) + { + SystemAppInstaller.mountSystemRo(); + if (!SystemAppInstaller.isSystemRw()) + { + status.add("Mounted system ro. Finished"); + } + else + { + status.add("An error while remounting system to ro. Aborted"); + status.m_success = false; + } + } + else + { + status.add("An error while uninstalling app. Aborted"); + status.m_success = false; + } + } + else + { + status.add("An error occured mounting system rw. Aborted"); + status.m_success = false; + } + + return status; + } + + public static class Status + { + List m_status = new ArrayList(); + boolean m_success = true; + + void add(String text) + { + Log.i(TAG, "Status: " + text); + m_status.add(text); + } + + public boolean success() + { + return m_success; + } + + public boolean getSuccess() + { + return m_success; + } + + public String toString() + { + String ret = ""; + for (int i=0; i < m_status.size(); i++) + { + ret += m_status.get(i) + "\n"; + } + + return ret; + } + } + + + +} diff --git a/androidCommon/src/main/java/com/asksven/android/common/wifi/WifiManagerProxy.java b/androidCommon/src/main/java/com/asksven/android/common/wifi/WifiManagerProxy.java new file mode 100644 index 0000000..b50f212 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/common/wifi/WifiManagerProxy.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.common.wifi; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * A proxy for accessing WifiManager's private API + * @author sven + * + */ +public class WifiManagerProxy +{ + + private static final String TAG = "WifiManagerProxy"; + private static WifiManager m_manager = null; + + private WifiManagerProxy(Context ctx) + { + + } + + private static void init(Context ctx) + { + if (m_manager == null) + { + m_manager = (WifiManager) ctx.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + } + } + public static boolean hasWifiLock(Context ctx) + { + init(ctx); + return (getWifiLocks(ctx) > 0); + } + + + /** + * returns the number of help WifiLocks + * @return + */ + public static int getWifiLocks(Context ctx) + { + init(ctx); + + int ret = 0; + try + { + Field privateStringField = WifiManager.class.getDeclaredField("mActiveLockCount"); + + privateStringField.setAccessible(true); + + Integer fieldValue = (Integer) privateStringField.get(m_manager); + Log.d(TAG, "mActiveLockCount is " + fieldValue); + ret = fieldValue; + } + catch (Exception e) + { + Log.e(TAG, "An exception occured in getWifiLocks(): " + e.getMessage()); + ret = -1; + } + + Log.d(TAG, ret + " Wifilocks detected"); + return ret; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/system/AndroidVersion.java b/androidCommon/src/main/java/com/asksven/android/system/AndroidVersion.java new file mode 100644 index 0000000..aeec1f0 --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/system/AndroidVersion.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.system; + +import android.os.Build; +/** + * Handles android version detection + * @author sven + * + */ +public class AndroidVersion +{ + public static boolean isFroyo() + { + boolean bRet = false; + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.FROYO) + { + bRet = true; + } + + return bRet; + } + + public static boolean isGingerbread() + { + boolean bRet = false; + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD) + { + bRet = true; + } + + return bRet; + } + + public static boolean isIcs() + { + boolean bRet = false; + if (Build.VERSION.SDK_INT >= 14) // Build.VERSION_ICE_CREAM_SANDWICH + { + bRet = true; + } + + return bRet; + } + +} diff --git a/androidCommon/src/main/java/com/asksven/android/system/Devices.java b/androidCommon/src/main/java/com/asksven/android/system/Devices.java new file mode 100644 index 0000000..77c0aeb --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/system/Devices.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.system; + +import android.content.Context; +import android.content.res.Configuration; + +/** + * @author sven + * + */ +public class Devices +{ + public static boolean isTablet(Context context) + { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } +} diff --git a/androidCommon/src/main/java/com/asksven/android/system/Installation.java b/androidCommon/src/main/java/com/asksven/android/system/Installation.java new file mode 100644 index 0000000..5f6e9fb --- /dev/null +++ b/androidCommon/src/main/java/com/asksven/android/system/Installation.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 asksven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.asksven.android.system; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build.VERSION; + +/** + * @author sven + * + */ +public class Installation +{ + public static boolean isInstalledOnSdCard(Context context) + { + + // check for API level 8 and higher + if (VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) + { + PackageManager pm = context.getPackageManager(); + try + { + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + ApplicationInfo ai = pi.applicationInfo; + return (ai.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo.FLAG_EXTERNAL_STORAGE; + } + catch (NameNotFoundException e) + { + // ignore + } + } + + // check for API level 7 - check files dir + try + { + String filesDir = context.getFilesDir().getAbsolutePath(); + if (filesDir.startsWith("/data/")) + { + return false; + } else if (filesDir.contains("/mnt/") + || filesDir.contains("/sdcard/")) + { + return true; + } + } + catch (Throwable e) + { + // ignore + } + + return false; + } +} diff --git a/androidCommon/src/main/res/drawable-hdpi/icon.png b/androidCommon/src/main/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8074c4c571b8cd19e27f4ee5545df367420686d7 GIT binary patch literal 4147 zcmV-35X|q1P)OwvMs$Q8_8nISM!^>PxsujeDCl4&hPxrxkp%Qc^^|l zp6LqAcf3zf1H4aA1Gv-O6ha)ktct9Y+VA@N^9i;p0H%6v>ZJZYQ`zEa396z-gi{r_ zDz)D=vgRv62GCVeRjK{15j7V@v6|2nafFX6W7z2j1_T0a zLyT3pGTubf1lB5)32>bl0*BflrA!$|_(WD2)iJIfV}37=ZKAC zSe3boYtQ=;o0i>)RtBvsI#iT{0!oF1VFeW`jDjF2Q4aE?{pGCAd>o8Kg#neIh*AMY zLl{;F!vLiem7s*x0<9FKAd6LoPz3~G32P+F+cuGOJ5gcC@pU_?C2fmix7g2)SUaQO$NS07~H)#fn!Q<}KQWtX}wW`g2>cMld+`7Rxgq zChaey66SG560JhO66zA!;sK1cWa2AG$9k~VQY??6bOmJsw9@3uL*z;WWa7(Nm{^TA zilc?y#N9O3LcTo2c)6d}SQl-v-pE4^#wb=s(RxaE28f3FQW(yp$ulG9{KcQ7r>7mQ zE!HYxUYex~*7IinL+l*>HR*UaD;HkQhkL(5I@UwN%Wz504M^d!ylo>ANvKPF_TvA< zkugG5;F6x}$s~J8cnev->_(Ic7%lGQgUi3n#XVo36lUpcS9s z)ympRr7}@|6WF)Ae;D{owN1;aZSR50al9h~?-WhbtKK%bDd zhML131oi1Bu1&Qb$Cp199LJ#;j5d|FhW8_i4KO1OI>}J^p2DfreMSVGY9aFlr&90t zyI2FvxQiKMFviSQeP$Ixh#70qj5O%I+O_I2t2XHWqmh2!1~tHpN3kA4n=1iHj?`@c<~3q^X6_Q$AqTDjBU`|!y<&lkqL|m5tG(b z8a!z&j^m(|;?SW(l*?tZ*{m2H9d&3jqBtXh>O-5e4Qp-W*a5=2NL&Oi62BUM)>zE3 zbSHb>aU3d@3cGggA`C-PsT9^)oy}%dHCaO~nwOrm5E54=aDg(&HR4S23Oa#-a^=}w%g?ZP-1iq8PSjE8jYaGZu z$I)?YN8he?F9>)2d$G6a*zm0XB*Rf&gZAjq(8l@CUDSY1tB#!i> zW$VfG%#SYSiZ};)>pHA`qlfDTEYQEwN6>NNEp+uxuqx({Fgr zjI@!4xRc?vk^9+~eU|mzH__dCDI=xb{Cd}4bELS9xRaS!*FXMwtMR-RR%SLMh0Cjl zencr8#Su<4(%}$yGVBU-HX{18v=yPH*+%^Vtknc>2A;%-~DrYFx^3XfuVgvZ{#1tA== zm3>IzAM2{3Iv_d1XG{P6^tN3|PkJMnjs&CWN7%7_CmjoVakUhsa&dMv==2~^ri?&x zVdv*rnfVyM+I1^Kg*S=23mR@+0T9BWFZUu~@toA8d)fw6be=`Yb6DSX6D?jB%2YT~ z*aHjtIOozfMhA!Jd*?u5_n!SnX>vX`=Ti-1HA4RiE>eI3vTn zz+>Ccf0HX6Ans-ebOB>RJST-Cyr#4XAk+mAlJgdQnoE{^iIN)OcYFSpgJUmXtl@tT z-^ZuUeSj5hSFrQwqX>~EtZ*{>Gi8Bu9_|o06oNtaXP?E936!a@DsvS*tsB@fa6kEA z5GkjwmH?EgpiG&itsB_Tb1NxtFnvxh_s@9KYX1Sttf?AlI~)z zT=6Y7ulx=}<8Scr_UqU-_z)5gPo%050PsbM*ZLno;_-ow&k?FZJtYmb2hPA$LkP)8 z=^d0Q6PImh6Y|QT?{grxj)S=uBKvY2EQUbm@ns9^yKiP~$DcD)c$5Em`zDSScH%iH zVov&m=cMo`1tYwA=!a}vb_ef_{)Q2?FUqn>BR$6phXQRv^1%=YfyE-F$AR4Q?9D!f zCzB^^#td~4u&l~l#rp2QLfe3+_ub9@+|x+m;=2(sQ`s%gO|j$XBb>A7Q(UydipiMw%igcweV#Cr~SP);q>w`bxts_4} znKHg?X==JDkQl3Y>Ckt%`s{n?Nq-1Fw5~%Mq$CAsi-`yu_bKm zxs#QdE7&vgJD%M84f4SNzSDv)S|V?|$!d5a#lhT5>>YWE4NGqa9-fbmV$=)@k&32kdEYetna>=j@0>V8+wRsL;po!3ivVwh<9tn z2S<1u9DAAQ>x1Sn=fk`)At|quvleV($B|#Kap_lB-F^*yV=wZ{9baUu(uXfokr95^ zA*!*W=5a>$2Ps`-F^+qRQT^{*cN>vipT*4!r#p%{(#I7s z0NN94*q?ib$KJjfDI_sjHNdmEVp5wB&j54O#VoFqBwy)gfA$%)4d_X4q${L9Xom2R3xy&ZBSNgt4a1d7K^CDWa9r zVb-_52m}Vp)`9;ZSKd#|U4ZYj5}Gp49{4utST|=c`~(#>KHF6}CCov1iHYw zt{bWo)A@yF2$~c(nR$rSAaFQ$(Wh{vkG1AlutDMw=mM`C`T=X&|Ad9fb5Od}ROt1z zOpczHqrb4Jo^rSCiW#&o(m7jFamnrsTpQb;*h4o8r#$aZ}2RaT-x2u^^ z%u@YyIv$U^u~@9(XGbSwU@fk6SikH>j+D1jQrYTKGJpW%vUT{!d}7THI5&Sa?~MKy zS0-mvMl+BOcroEJ@hN!2H_?coTEJ5Q<;Nd?yx;eIj4{$$E2?YUO|NtNPJ-PdDf;s} zab;}Mz0kbOI}5*w@3gROcnl#5)wQnEhDBfn!Xhy`u>C}*E~vWpO^HS)FC>8^umI=+ z&H;LW6w#;EF`}vQd_9Muru`KnQVPI9U?(sD)&Dg-0j3#(!fNKVZ_GoYH{la~d*1Yh$TI-TL>mI4vpNb@sU2=IZ8vL%AXUx0 zz{K0|nK(yizLHaeW#ZhRfQXoK^}1$=$#1{Yn002ovPDHLkV1n#w+^+xt literal 0 HcmV?d00001 diff --git a/androidCommon/src/main/res/drawable-ldpi/icon.png b/androidCommon/src/main/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1095584ec21f71cd0afc9e0993aa2209671b590c GIT binary patch literal 1723 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 + + + diff --git a/androidCommon/src/main/res/layout/readmewebview.xml b/androidCommon/src/main/res/layout/readmewebview.xml new file mode 100644 index 0000000..6276358 --- /dev/null +++ b/androidCommon/src/main/res/layout/readmewebview.xml @@ -0,0 +1,37 @@ + + + + + + android:text="@string/apply"> \ No newline at end of file diff --git a/res/layout/cpu_wakelock_row.xml b/app/src/main/res/layout/cpu_wakelock_row.xml similarity index 89% rename from res/layout/cpu_wakelock_row.xml rename to app/src/main/res/layout/cpu_wakelock_row.xml index bb5a52b..739f80e 100644 --- a/res/layout/cpu_wakelock_row.xml +++ b/app/src/main/res/layout/cpu_wakelock_row.xml @@ -2,19 +2,18 @@ + android:orientation="horizontal"> - + android:layout_gravity="center_vertical"> + android:orientation="vertical"> - + android:paddingBottom="2dp"> \ No newline at end of file diff --git a/res/layout/disk_control_fragment.xml b/app/src/main/res/layout/disk_control_fragment.xml similarity index 91% rename from res/layout/disk_control_fragment.xml rename to app/src/main/res/layout/disk_control_fragment.xml index 1fddbf3..09d0c6c 100644 --- a/res/layout/disk_control_fragment.xml +++ b/app/src/main/res/layout/disk_control_fragment.xml @@ -1,7 +1,7 @@ + android:layout_height="match_parent"> + android:paddingTop="3dp"> + android:paddingBottom="2dp"> + android:text="@string/apply"> \ No newline at end of file diff --git a/res/layout/drawer_list_item.xml b/app/src/main/res/layout/drawer_list_item.xml similarity index 95% rename from res/layout/drawer_list_item.xml rename to app/src/main/res/layout/drawer_list_item.xml index 8ed8a74..b08647e 100644 --- a/res/layout/drawer_list_item.xml +++ b/app/src/main/res/layout/drawer_list_item.xml @@ -2,7 +2,7 @@ + android:orientation="horizontal"> - + + \ No newline at end of file diff --git a/res/xml/preference.xml b/app/src/main/res/xml/preference.xml similarity index 81% rename from res/xml/preference.xml rename to app/src/main/res/xml/preference.xml index d3f3df1..ad3e90d 100644 --- a/res/xml/preference.xml +++ b/app/src/main/res/xml/preference.xml @@ -1,7 +1,7 @@ - + - + - + - + - + - - + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..476c4e8 --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' + } +} + +allprojects { + repositories { + mavenCentral() + } + +} diff --git a/build.xml b/build.xml deleted file mode 100644 index 1c50b32..0000000 --- a/build.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ic_launcher-web.png b/ic_launcher-web.png deleted file mode 100644 index 075fec4b13f9c5ae36ef147320203f0c71fd230a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112818 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 M)HBsB(s2s?ABqjq@Bjb+ diff --git a/libraries/AndroidCommon b/libraries/AndroidCommon new file mode 160000 index 0000000..4d80535 --- /dev/null +++ b/libraries/AndroidCommon @@ -0,0 +1 @@ +Subproject commit 4d805357c6729593a1f9f4b1580207046fa30d0d diff --git a/project.properties b/project.properties deleted file mode 100644 index 57bad93..0000000 --- a/project.properties +++ /dev/null @@ -1,18 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: #sdk.dir, user.home): -proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt -#proguard.config=proguard.cfg - -# Project target. -target=android-19 -android.library.reference.1=../SherlockNavigationDrawer -android.library.reference.2=../../Development/android-spinnerwheel/library -android.library.reference.3=../AndroidCommon diff --git a/src/com/phantomLord/cpufrequtils/app/adapters/AlarmTriggerAdapter.java b/src/com/phantomLord/cpufrequtils/app/adapters/AlarmTriggerAdapter.java deleted file mode 100644 index 41c9a14..0000000 --- a/src/com/phantomLord/cpufrequtils/app/adapters/AlarmTriggerAdapter.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.phantomLord.cpufrequtils.app.adapters; - -import java.util.ArrayList; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.asksven.android.common.nameutils.UidNameResolver; -import com.asksven.android.common.privateapiproxies.Alarm; -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.utils.BatteryStatsUtils; - -public class AlarmTriggerAdapter extends BaseAdapter { - ArrayList alarms; - Context context; - int totaltime; - LayoutInflater infalter; - - public AlarmTriggerAdapter(Context ctx) { - this.context = ctx; - alarms = BatteryStatsUtils.getAlarmStats(context); - totaltime = 0; - for (Alarm e : alarms) { - totaltime += e.getWakeups(); - } - infalter = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public View getView(int position, View convertView, ViewGroup container) { - - View row = infalter.inflate(R.layout.alarm_trigger_custom_list_item1, - container, false); - TextView AlarmPackageName = (TextView) row - .findViewById(R.id.alarm_package_name); - TextView WakeupCount = (TextView) row.findViewById(R.id.wakeup_count); - TextView name = (TextView) row.findViewById(R.id.alarm_appname); - ImageView icon = (ImageView) row.findViewById(R.id.alarm_icon); - ProgressBar progress = (ProgressBar) row - .findViewById(R.id.alrm_progress); - Alarm alarm = alarms.get(position); - - icon.setImageDrawable(alarm.getIcon(context)); - String packageName = alarm.getPackageName(); - AlarmPackageName.setText(new UidNameResolver().getLabel(context, - packageName)); - name.setText(packageName); - WakeupCount.setText(context.getString(R.string.wakeups) + " " - + alarm.getWakeups()); - progress.setMax(totaltime); - progress.setProgress((int) alarm.getWakeups()); - return row; - } - - @Override - public int getCount() { - if (alarms != null) - return alarms.size(); - else - return 0; - } - - @Override - public Object getItem(int arg0) { - return alarms.get(arg0); - } - - @Override - public long getItemId(int arg0) { - return alarms.indexOf(arg0); - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/adapters/CpuControlActionBarSpinner.java b/src/com/phantomLord/cpufrequtils/app/adapters/CpuControlActionBarSpinner.java deleted file mode 100644 index 668e98f..0000000 --- a/src/com/phantomLord/cpufrequtils/app/adapters/CpuControlActionBarSpinner.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.phantomLord.cpufrequtils.app.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.utils.Constants; -import com.phantomLord.cpufrequtils.app.utils.SysUtils; - -public class CpuControlActionBarSpinner extends BaseAdapter { - int noOfCpuCores = 0; - Context context; - String[] cores; - LayoutInflater inflator; - - public CpuControlActionBarSpinner(Context ctx) { - this.context = ctx; - noOfCpuCores = SysUtils.getCoreCount(); - if (noOfCpuCores == 1) { - cores = new String[noOfCpuCores]; - } else { - cores = new String[noOfCpuCores + 1]; - cores[noOfCpuCores] = Constants.CPU_ALL; - } - for (int i = 0; i < noOfCpuCores; i++) { - cores[i] = context.getString(R.string.core) + i; - } - inflator = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public int getCount() { - if (noOfCpuCores == 1) { - return noOfCpuCores; - } else if (noOfCpuCores != 0) { - return cores.length; - } else - return 0; - } - - @Override - public Object getItem(int arg0) { - return cores[arg0]; - } - - @Override - public long getItemId(int arg0) { - return cores[arg0].hashCode(); - } - - @Override - public View getView(int position, View convertView, ViewGroup container) { - View row = inflator.inflate(R.layout.sherlock_spinner_dropdown_item, - container, false); - TextView text = (TextView) row.findViewById(android.R.id.text1); - text.setText(cores[position]); - return row; - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/adapters/CpuWakelocksAdapter.java b/src/com/phantomLord/cpufrequtils/app/adapters/CpuWakelocksAdapter.java deleted file mode 100644 index a59dc0e..0000000 --- a/src/com/phantomLord/cpufrequtils/app/adapters/CpuWakelocksAdapter.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.phantomLord.cpufrequtils.app.adapters; - -import java.util.ArrayList; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.asksven.android.common.privateapiproxies.Wakelock; -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.utils.BatteryStatsUtils; -import com.phantomLord.cpufrequtils.app.utils.SysUtils; - -public class CpuWakelocksAdapter extends BaseAdapter { - ArrayList partialWakelocks; - Context context; - int totaltime; - LayoutInflater infalter; - - public CpuWakelocksAdapter(Context ctx) { - this.context = ctx; - partialWakelocks = BatteryStatsUtils - .getCpuWakelocksStats(context, true); - /* - * calculate total time - */ - totaltime = 0; - for (Wakelock wl : partialWakelocks) { - totaltime += wl.getDuration() / 1000; - } - infalter = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public int getCount() { - if (partialWakelocks != null) - return partialWakelocks.size(); - else - return 0; - } - - @Override - public Object getItem(int arg0) { - return partialWakelocks.get(arg0); - } - - @Override - public long getItemId(int arg0) { - return partialWakelocks.indexOf(arg0); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - View row = infalter.inflate(R.layout.cpu_wakelock_row, parent, false); - TextView wakelockName = (TextView) row - .findViewById(R.id.cpu_wakelock_name); - TextView duration = (TextView) row - .findViewById(R.id.cpu_wakelock_duration); - TextView wakeCount = (TextView) row - .findViewById(R.id.cpu_wakelock_count); - ImageView icon = (ImageView) row.findViewById(R.id.package_icon); - ProgressBar progress = (ProgressBar) row - .findViewById(R.id.cpu_wakelock_progress); - - Wakelock mWakelock = partialWakelocks.get(position); - Drawable drawable = mWakelock.getIcon(context); - if (drawable != null) { - icon.setImageDrawable(drawable); - } else { - icon.setImageResource(R.drawable.logo); - } - wakelockName.setText(mWakelock.getName()); - duration.setText(SysUtils.secToString(mWakelock.getDuration() / 1000)); - wakeCount.setText("x" + mWakelock.getCount() - + context.getString(R.string.times)); - progress.setMax(totaltime); - progress.setProgress((int) mWakelock.getDuration() / 1000); - return row; - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/adapters/KernelWakelockAdapter.java b/src/com/phantomLord/cpufrequtils/app/adapters/KernelWakelockAdapter.java deleted file mode 100644 index 997e509..0000000 --- a/src/com/phantomLord/cpufrequtils/app/adapters/KernelWakelockAdapter.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.phantomLord.cpufrequtils.app.adapters; - -import java.util.ArrayList; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.asksven.android.common.kernelutils.NativeKernelWakelock; -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.utils.BatteryStatsUtils; -import com.phantomLord.cpufrequtils.app.utils.SysUtils; - -public class KernelWakelockAdapter extends BaseAdapter { - ArrayList kernelWakelocks; - Context context; - LayoutInflater inflator; - int totaltime; - - public KernelWakelockAdapter(Context ctx) { - this.context = ctx; - kernelWakelocks = BatteryStatsUtils.getNativeKernelWakelocks(context, - true); - totaltime = 0; - for (NativeKernelWakelock element : kernelWakelocks) { - totaltime += (int) element.getDuration() / 1000; - } - inflator = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public View getView(int position, View arg1, ViewGroup arg2) { - View rowView = inflator.inflate(R.layout.kernel_wakelock_row, arg2, - false); - TextView mKernelWakelock = (TextView) rowView - .findViewById(R.id.kernel_wakelock_name); - TextView WakeupInfo = (TextView) rowView - .findViewById(R.id.wakelock_duration); - TextView wakeUpCount = (TextView) rowView - .findViewById(R.id.kernel_wakelock_count); - ProgressBar progress = (ProgressBar) rowView - .findViewById(R.id.kernel_progress); - - NativeKernelWakelock nativeWakeLock = kernelWakelocks.get(position); - String kernelWakelock = kernelWakelocks.get(position).getName(); - mKernelWakelock.setText(kernelWakelock); - WakeupInfo - .setText(SysUtils.secToString(nativeWakeLock.getDuration() / 1000)); - progress.setMax(totaltime); - progress.setProgress((int) nativeWakeLock.getDuration() / 1000); - wakeUpCount.setText("x" + nativeWakeLock.getCount() + " " - + context.getString(R.string.times)); - - return rowView; - } - - @Override - public int getCount() { - if (kernelWakelocks != null) - return kernelWakelocks.size(); - else - return 0; - } - - @Override - public Object getItem(int pos) { - return kernelWakelocks.get(pos); - } - - @Override - public long getItemId(int pos) { - return kernelWakelocks.indexOf(pos); - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/adapters/NavigationDrawerListAdapter.java b/src/com/phantomLord/cpufrequtils/app/adapters/NavigationDrawerListAdapter.java deleted file mode 100644 index 90c6356..0000000 --- a/src/com/phantomLord/cpufrequtils/app/adapters/NavigationDrawerListAdapter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.phantomLord.cpufrequtils.app.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.utils.Constants; - -public class NavigationDrawerListAdapter extends BaseAdapter { - private String[] fragmentItems; - Context context; - LayoutInflater inflater; - ImageView imageIcon; - TextView fragmentName; - - public NavigationDrawerListAdapter(Context ctx) { - this.context = ctx; - fragmentItems = Constants.mFragmentsArray; - inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public int getCount() { - return fragmentItems.length; - } - - @Override - public Object getItem(int position) { - return fragmentItems[position]; - } - - @Override - public long getItemId(int position) { - return fragmentItems[position].indexOf(position); - } - - @Override - public View getView(int pos, View convertView, ViewGroup container) { - View row = inflater - .inflate(R.layout.drawer_list_item, container, false); - fragmentName = (TextView) row.findViewById(R.id.fragmentName); - imageIcon = (ImageView) row.findViewById(R.id.fragmentImage); - fragmentName.setText(fragmentItems[pos]); - imageIcon.setImageResource(Constants.icons[pos]); - return row; - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/adapters/TimeInStatesListAdapter.java b/src/com/phantomLord/cpufrequtils/app/adapters/TimeInStatesListAdapter.java deleted file mode 100644 index 0b3b977..0000000 --- a/src/com/phantomLord/cpufrequtils/app/adapters/TimeInStatesListAdapter.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.phantomLord.cpufrequtils.app.adapters; - -import java.util.ArrayList; -import java.util.HashMap; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.utils.Constants; -import com.phantomLord.cpufrequtils.app.utils.CpuState; -import com.phantomLord.cpufrequtils.app.utils.SysUtils; -import com.phantomLord.cpufrequtils.app.utils.TimeInStateReader; - -@SuppressLint("UseSparseArrays") -public class TimeInStatesListAdapter extends BaseAdapter { - - Context context; - ArrayList states = new ArrayList<>(); - HashMap _states = new HashMap(); - public long totaltime = 0; - TimeInStateReader statesReader; - LayoutInflater infalter; - SharedPreferences prefs; - boolean filterNonZeroVals; - - public TimeInStatesListAdapter(Context context) { - this.context = context; - prefs = PreferenceManager.getDefaultSharedPreferences(context); - filterNonZeroVals = prefs.getBoolean(Constants.PREF_ZERO_VALS, true); - statesReader = TimeInStateReader.TimeInStatesReader(); - states = statesReader.getCpuStateTime(true, filterNonZeroVals); - totaltime = statesReader.getTotalTimeInState(); - /* - * remove zero values - */ - - infalter = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View rowView = infalter.inflate(R.layout.time_in_stat_list_item, - parent, false); - TextView frequencyTextView = (TextView) rowView - .findViewById(R.id.frequency); - ProgressBar mProgressBar = (ProgressBar) rowView - .findViewById(R.id.progress); - TextView time = (TextView) rowView.findViewById(R.id.time); - TextView percentage = (TextView) rowView.findViewById(R.id.percentage); - if (states.get(position).getFrequency() == 0) - frequencyTextView.setText(context.getString(R.string.deep_sleep)); - else - frequencyTextView - .setText((states.get(position).getFrequency() / 1000) - + " Mhz"); - time.setText(SysUtils.secToString(states.get(position).getTime() / 100)); - - mProgressBar.setMax((int) (totaltime)); - mProgressBar.setProgress((int) (states.get(position).getTime())); - - /* - * calculate percentage of time - */ - long percent = (states.get(position).getTime() * 100) / totaltime; - percentage.setText(percent + "%"); - - return rowView; - } - - @Override - public int getCount() { - if (states != null) - return states.size(); - else - return 0; - } - - @Override - public Object getItem(int position) { - return states.get(position); - } - - @Override - public long getItemId(int position) { - return states.indexOf(position); - } - - public void refresh() { - states = statesReader.getCpuStateTime(true, filterNonZeroVals); - totaltime = statesReader.getTotalTimeInState(); - notifyDataSetChanged(); - } - - public void saveOffsets() { - String data = ""; - ArrayList newStates = statesReader.getCpuStateTime(true, - filterNonZeroVals); - for (CpuState state : newStates) { - data += state.getFrequency() + " " + state.getTime() + ","; - } - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(Constants.PREF_TIS_RESET_STATS, data); - editor.commit(); - } - - public void reset() { - removeOffsets(); - saveOffsets(); - loadPreviousStats(); - states = statesReader.getCpuStateTime(true, filterNonZeroVals); - totaltime = statesReader.getTotalTimeInState(); - refresh(); - } - - public void loadPreviousStats() { - String data = prefs.getString(Constants.PREF_TIS_RESET_STATS, null); - - if (data.length() > 0) { - String[] line = data.split(","); - for (String str : line) { - String[] val = str.split(" "); - _states.put(Integer.parseInt(val[0]), Long.parseLong(val[1])); - } - } - statesReader.newStates = _states; - } - - public void removeOffsets() { - if (prefs.getString(Constants.PREF_TIS_RESET_STATS, null) != null) { - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(Constants.PREF_TIS_RESET_STATS, null); - editor.commit(); - } - statesReader.clearNewStates(); - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/adapters/WakelockActionBarSpinnerAdapter.java b/src/com/phantomLord/cpufrequtils/app/adapters/WakelockActionBarSpinnerAdapter.java deleted file mode 100644 index 9be2865..0000000 --- a/src/com/phantomLord/cpufrequtils/app/adapters/WakelockActionBarSpinnerAdapter.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.phantomLord.cpufrequtils.app.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.utils.Constants; - -public class WakelockActionBarSpinnerAdapter extends BaseAdapter { - private String[] itemNames; - Context context; - LayoutInflater inflator; - - public WakelockActionBarSpinnerAdapter(Context ctx) { - this.context = ctx; - itemNames = ctx.getResources().getStringArray( - R.array.wakelock_actionbar_spinner_items); - inflator = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - @Override - public int getCount() { - return itemNames.length; - } - - @Override - public Object getItem(int index) { - return itemNames[index]; - } - - @Override - public long getItemId(int index) { - return itemNames[index].indexOf(index); - } - - @Override - public View getView(int pos, View convertView, ViewGroup container) { - View row = inflator.inflate(R.layout.simple_action_bar_spinner_item, - container, false); - /* - * ImageView image = (ImageView) row - * .findViewById(R.id.actionBar_spinner_image); - */ - TextView text = (TextView) row.findViewById(R.id.spinnerItem); - text.setText(Constants.wakelockTypes[pos]); - // image.setImageResource(Constants.actionBarIcons[pos]); - return row; - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/dialogs/AboutDialogBox.java b/src/com/phantomLord/cpufrequtils/app/dialogs/AboutDialogBox.java deleted file mode 100644 index b46617d..0000000 --- a/src/com/phantomLord/cpufrequtils/app/dialogs/AboutDialogBox.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.phantomLord.cpufrequtils.app.dialogs; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; - -import com.actionbarsherlock.app.SherlockDialogFragment; -import com.phantomLord.cpufrequtils.app.R; - -public class AboutDialogBox extends SherlockDialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(getActivity()) - .setTitle(getString(R.string.about)) - .setMessage(getString(R.string.about_content)) - .setPositiveButton(getString(R.string.okbutton), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - - } - }).create(); - - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/dialogs/CpuFrequencyScalingNotSupportedDialog.java b/src/com/phantomLord/cpufrequtils/app/dialogs/CpuFrequencyScalingNotSupportedDialog.java deleted file mode 100644 index ecce167..0000000 --- a/src/com/phantomLord/cpufrequtils/app/dialogs/CpuFrequencyScalingNotSupportedDialog.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.phantomLord.cpufrequtils.app.dialogs; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; - -import com.actionbarsherlock.app.SherlockDialogFragment; -import com.phantomLord.cpufrequtils.app.R; - -public class CpuFrequencyScalingNotSupportedDialog extends - SherlockDialogFragment { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(getActivity()) - .setIcon(R.drawable.ic_launcher) - .setTitle(getString(R.string.error)) - .setMessage(getString(R.string.doesnt_support_cpufs)) - .setPositiveButton(getString(R.string.continu), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - } - }) - .setNegativeButton(getString(R.string.exit), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - getActivity().finish(); - } - }).create(); - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/dialogs/RootNotFoundAlertDialog.java b/src/com/phantomLord/cpufrequtils/app/dialogs/RootNotFoundAlertDialog.java deleted file mode 100644 index 863c34c..0000000 --- a/src/com/phantomLord/cpufrequtils/app/dialogs/RootNotFoundAlertDialog.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.phantomLord.cpufrequtils.app.dialogs; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; - -import com.actionbarsherlock.app.SherlockDialogFragment; -import com.phantomLord.cpufrequtils.app.R; - -public class RootNotFoundAlertDialog extends SherlockDialogFragment { - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(getActivity()) - .setIcon(R.drawable.ic_launcher) - .setTitle(getString(R.string.error)) - .setMessage(getString(R.string.noroot)) - .setPositiveButton(getString(R.string.continu), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - - } - }) - .setNegativeButton(R.string.exit, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - getActivity().finish(); - } - }).create(); - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/services/BootService.java b/src/com/phantomLord/cpufrequtils/app/services/BootService.java deleted file mode 100644 index ffa2742..0000000 --- a/src/com/phantomLord/cpufrequtils/app/services/BootService.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.phantomLord.cpufrequtils.app.services; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import com.phantomLord.cpufrequtils.app.utils.Constants; -import com.phantomLord.cpufrequtils.app.utils.SysUtils; - -public class BootService extends IntentService { - - SharedPreferences prefs; - Context context; - - public BootService() { - super("Boot Service"); - } - - @Override - protected void onHandleIntent(Intent intent) { - context = getApplicationContext(); - prefs = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(Constants.PREF_TIS_RESET_STATS, null); - editor.commit(); - try { - Thread.sleep(30000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - if (SysUtils.isRooted()) { - if (prefs.getBoolean(Constants.PREF_CPU_APPLY_ON_BOOT, false)) { - String max, min, gov; - max = prefs.getString(Constants.PREF_MAX_FREQ, null); - min = prefs.getString(Constants.PREF_MIN_FREQ, null); - gov = prefs.getString(Constants.PREF_GOV, null); - - if (max != null || min != null || gov != null) { - SysUtils.setFrequencyAndGovernor(max, min, gov, context); - } - } - if (prefs.getBoolean(Constants.PREF_IO_APPLY_ON_BOOT, false)) { - String ioscheduler = prefs.getString( - Constants.PREF_IO_SCHEDULER, null); - String readAhead = prefs.getString(Constants.PREF_READ_AHEAD, - null); - - if (ioscheduler != null || readAhead != null) { - SysUtils.setDiskSchedulerandReadAhead(ioscheduler, - readAhead, context); - } - } - } - stopSelf(); - - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/ui/MainActivity.java b/src/com/phantomLord/cpufrequtils/app/ui/MainActivity.java deleted file mode 100644 index 5ef5f5a..0000000 --- a/src/com/phantomLord/cpufrequtils/app/ui/MainActivity.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.phantomLord.cpufrequtils.app.ui; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v4.app.Fragment; -import android.support.v4.view.GravityCompat; -import android.support.v4.widget.DrawerLayout; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.MenuItem; -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.adapters.NavigationDrawerListAdapter; -import com.phantomLord.cpufrequtils.app.dialogs.RootNotFoundAlertDialog; -import com.phantomLord.cpufrequtils.app.utils.Constants; -import com.phantomLord.cpufrequtils.app.utils.SysUtils; -import com.sherlock.navigationdrawer.compat.SherlockActionBarDrawerToggle; - -public class MainActivity extends SherlockFragmentActivity { - Context themedContext, context; - boolean isLight; - String key; - - private DrawerLayout mDrawerLayout; - private ListView listView; - - private ActionBar actionBar; - - private SherlockActionBarDrawerToggle mDrawerToggle; - - @Override - protected void onCreate(Bundle savedInstanceState) { - SharedPreferences mPrefs = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - key = mPrefs.getString("listPref", "Light"); - this.setTheme(Constants.THEMES_MAP.get(key)); - super.onCreate(savedInstanceState); - setContentView(R.layout.fragment_main_layout_navbar); - // BugSenseHandler.initAndStartSession(MainActivity.this, "4cdc31a1"); - - themedContext = getSupportActionBar().getThemedContext(); - context = getBaseContext(); - - mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - listView = (ListView) findViewById(R.id.left_drawer); - - mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, - GravityCompat.START); - - listView.setAdapter(new NavigationDrawerListAdapter(context)); - listView.setOnItemClickListener(new DrawerItemClickListener()); - listView.setCacheColorHint(0); - listView.setScrollingCacheEnabled(false); - listView.setScrollContainer(false); - listView.setFastScrollEnabled(true); - listView.setSmoothScrollbarEnabled(true); - listView.setSelection(0); - - actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setHomeButtonEnabled(true); - - mDrawerToggle = new SherlockActionBarDrawerToggle(this, mDrawerLayout, - R.drawable.ic_drawer_light, R.string.about, - R.string.about_content); - mDrawerToggle.syncState(); - this.getSupportFragmentManager().beginTransaction() - .replace(R.id.main_content, new CpuFrequencyFragment()) - .commit(); - - if (!(SysUtils.isRooted())) - new RootNotFoundAlertDialog().show(getSupportFragmentManager(), - getString(R.string.app_name)); - - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - /* - * The action bar home/up action should open or close the drawer. - * mDrawerToggle will take care of this. - */ - if (mDrawerToggle.onOptionsItemSelected(item)) { - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mDrawerToggle.onConfigurationChanged(newConfig); - } - - private class DrawerItemClickListener implements - ListView.OnItemClickListener { - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Fragment mfragment = null; - String[] fragmentNames = Constants.mFragmentsArray; - switch (position) { - case 0: - mfragment = new CpuFrequencyFragment(); - actionBar.setTitle(fragmentNames[position]); - break; - case 1: - mfragment = new TimeInStatesFragment(); - actionBar.setTitle(fragmentNames[position]); - break; - case 2: - mfragment = new IOControlFragment(); - actionBar.setTitle(fragmentNames[position]); - break; - case 3: - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); - mfragment = new WakeLocksDetectorFragment(); - actionBar.setTitle(fragmentNames[position]); - break; - case 4: - mfragment = new SettingsFragment(); - actionBar.setTitle(getString(R.string.action_settings)); - break; - } - if (mfragment != null) { - getSupportFragmentManager().beginTransaction() - .replace(R.id.main_content, mfragment).commit(); - } - mDrawerLayout.closeDrawer(listView); - } - - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/ui/SettingsFragment.java b/src/com/phantomLord/cpufrequtils/app/ui/SettingsFragment.java deleted file mode 100644 index 0127f69..0000000 --- a/src/com/phantomLord/cpufrequtils/app/ui/SettingsFragment.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.phantomLord.cpufrequtils.app.ui; - -import android.os.Bundle; - -import com.phantomLord.cpufrequtils.app.R; - -public class SettingsFragment extends - android.support.v4.preference.PreferenceFragment { - - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - addPreferencesFromResource(R.xml.preference); - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/ui/TimeInStatesFragment.java b/src/com/phantomLord/cpufrequtils/app/ui/TimeInStatesFragment.java deleted file mode 100644 index 3c023c2..0000000 --- a/src/com/phantomLord/cpufrequtils/app/ui/TimeInStatesFragment.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.phantomLord.cpufrequtils.app.ui; - -import java.util.ArrayList; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; -import android.widget.TextView; - -import com.actionbarsherlock.app.SherlockFragment; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.adapters.TimeInStatesListAdapter; -import com.phantomLord.cpufrequtils.app.utils.Constants; -import com.phantomLord.cpufrequtils.app.utils.CpuState; -import com.phantomLord.cpufrequtils.app.utils.SysUtils; - -public class TimeInStatesFragment extends SherlockFragment { - - View view; - ListView listView; - ArrayList states; - TimeInStatesListAdapter timeInStateAdapter; - TextView kernelVersion; - TextView totalTimeInState; - - SharedPreferences prefs; - Context context; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - setHasOptionsMenu(true); - view = inflater.inflate(R.layout.time_in_states, container, false); - listView = (ListView) view.findViewById(R.id.time_in_state_listView); - totalTimeInState = (TextView) view.findViewById(R.id.total_time); - kernelVersion = (TextView) view.findViewById(R.id.kernelInfo); - return view; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - kernelVersion.setText(SysUtils.getKernelInfo()); - context = view.getContext(); - timeInStateAdapter = new TimeInStatesListAdapter(context); - - prefs = PreferenceManager.getDefaultSharedPreferences(context); - String previousStats = prefs.getString(Constants.PREF_TIS_RESET_STATS, - null); - - if (previousStats != null) { - timeInStateAdapter.loadPreviousStats(); - } - - listView.setAdapter(timeInStateAdapter); - timeInStateAdapter.refresh(); - - totalTimeInState.setText(getString(R.string.total_time) + " " - + SysUtils.secToString(timeInStateAdapter.totaltime / 100)); - timeInStateAdapter.refresh(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.time_in_stat_menu, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - switch (id) { - case R.id.refresh: - timeInStateAdapter.refresh(); - totalTimeInState.setText(getString(R.string.total_time) + " " - + SysUtils.secToString(timeInStateAdapter.totaltime / 100)); - break; - - case R.id.reset_timers: - timeInStateAdapter.reset(); - totalTimeInState.setText(getString(R.string.total_time) + " " - + SysUtils.secToString(timeInStateAdapter.totaltime / 100)); - break; - - case R.id.restore_timers: - timeInStateAdapter.removeOffsets(); - timeInStateAdapter.refresh(); - default: - break; - } - - return super.onOptionsItemSelected(item); - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/ui/WakeLocksDetectorFragment.java b/src/com/phantomLord/cpufrequtils/app/ui/WakeLocksDetectorFragment.java deleted file mode 100644 index 9bed825..0000000 --- a/src/com/phantomLord/cpufrequtils/app/ui/WakeLocksDetectorFragment.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.phantomLord.cpufrequtils.app.ui; - -import android.content.Context; -import android.os.Bundle; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.ActionBar.OnNavigationListener; -import com.actionbarsherlock.app.SherlockFragment; -import com.phantomLord.cpufrequtils.app.R; -import com.phantomLord.cpufrequtils.app.adapters.AlarmTriggerAdapter; -import com.phantomLord.cpufrequtils.app.adapters.CpuWakelocksAdapter; -import com.phantomLord.cpufrequtils.app.adapters.KernelWakelockAdapter; -import com.phantomLord.cpufrequtils.app.adapters.WakelockActionBarSpinnerAdapter; - -public class WakeLocksDetectorFragment extends SherlockFragment implements - OnNavigationListener { - - ListView wakelockList; - ActionBar actionBar; - View view; - Context themedContext, context; - TextView timeSince; - BaseAdapter adapter; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - view = inflater.inflate(R.layout.wakelocksfragment, container, false); - actionBar = getSherlockActivity().getSupportActionBar(); - themedContext = actionBar.getThemedContext(); - context = getSherlockActivity().getBaseContext(); - wakelockList = (ListView) view - .findViewById(R.id.wakelock_data_listview1); - timeSince = (TextView) view.findViewById(R.id.stats_since); - return view; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); - adapter = new WakelockActionBarSpinnerAdapter(themedContext); - actionBar.setListNavigationCallbacks(adapter, this); - actionBar.setSelectedNavigationItem(0); - } - - @Override - public void onDestroyView() { - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); - super.onDestroyView(); - } - - @Override - public boolean onNavigationItemSelected(int itemPosition, long itemId) { - BaseAdapter adapter = null; - - switch (itemPosition) { - case 0: - adapter = new KernelWakelockAdapter(context); - break; - case 1: - adapter = new CpuWakelocksAdapter(context); - break; - case 2: - adapter = new AlarmTriggerAdapter(context); - break; - } - if (adapter.getCount() != 0) { - wakelockList.setVisibility(View.VISIBLE); - timeSince.setTextSize(15); - wakelockList.setAdapter(adapter); - - } else { - wakelockList.setVisibility(View.GONE); - timeSince.setTextSize(20); - timeSince.setGravity(Gravity.CENTER); - timeSince.setText(getString(R.string.stats_not_available)); - } - return true; - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/utils/BatteryStatsUtils.java b/src/com/phantomLord/cpufrequtils/app/utils/BatteryStatsUtils.java deleted file mode 100644 index 91fca3a..0000000 --- a/src/com/phantomLord/cpufrequtils/app/utils/BatteryStatsUtils.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.phantomLord.cpufrequtils.app.utils; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; - -import android.content.Context; -import android.os.Build; - -import com.asksven.android.common.kernelutils.AlarmsDumpsys; -import com.asksven.android.common.kernelutils.NativeKernelWakelock; -import com.asksven.android.common.kernelutils.Wakelocks; -import com.asksven.android.common.kernelutils.WakeupSources; -import com.asksven.android.common.privateapiproxies.Alarm; -import com.asksven.android.common.privateapiproxies.BatteryInfoUnavailableException; -import com.asksven.android.common.privateapiproxies.BatteryStatsProxy; -import com.asksven.android.common.privateapiproxies.BatteryStatsTypes; -import com.asksven.android.common.privateapiproxies.StatElement; -import com.asksven.android.common.privateapiproxies.Wakelock; -import com.phantomLord.cpufrequtils.app.R; - -public class BatteryStatsUtils { - /* - * This class acts as a bridge between Android-Common Library and the rest - * of the Application - */ - - public static ArrayList getNativeKernelWakelocks( - Context mContext, boolean filterZeroValues) { - - ArrayList nativeKernelWakelocks = new ArrayList<>(); - ArrayList kernelWakelocks; - if (Wakelocks.fileExists()) { - kernelWakelocks = Wakelocks.parseProcWakelocks(mContext); - } else { - kernelWakelocks = WakeupSources.parseWakeupSources(mContext); - } - - for (StatElement statElement : kernelWakelocks) { - NativeKernelWakelock wakelock = (NativeKernelWakelock) statElement; - - if (filterZeroValues) { - if (wakelock.getDuration() / 1000 > 0) { - nativeKernelWakelocks.add(wakelock); - } - } else { - nativeKernelWakelocks.add(wakelock); - } - } - /* - * sort the data on the basis of duration - */ - Comparator timeComaprator = new NativeKernelWakelock.TimeComparator(); - Collections.sort(nativeKernelWakelocks, timeComaprator); - return nativeKernelWakelocks; - } - - public static ArrayList getCpuWakelocksStats(Context context, - boolean filterZeroValues) { - ArrayList myWakelocks = new ArrayList(); - ArrayList cpuWakelocks = new ArrayList<>(); - /* - * code for kitkat is missing - */ - if (Build.VERSION.SDK_INT >= 19) { - try { - throw new BatteryInfoUnavailableException( - "Battery info not available"); - } catch (BatteryInfoUnavailableException e) { - e.printStackTrace(); - } - - myWakelocks.add(new Wakelock(1, - "Feature not currently supported for kitkat", 0, 0, 0)); - - return myWakelocks; - } - BatteryStatsProxy stats = BatteryStatsProxy.getInstance(context); - try { - cpuWakelocks = stats.getWakelockStats(context, - BatteryStatsTypes.WAKE_TYPE_PARTIAL, - BatteryStatsTypes.STATS_CURRENT, 0); - } catch (Exception e) { - e.printStackTrace(); - } - for (int i = 0; i < cpuWakelocks.size(); i++) { - Wakelock wl = (Wakelock) cpuWakelocks.get(i); - if (filterZeroValues) { - if ((wl.getDuration() / 1000) > 0) { - myWakelocks.add(wl); - } - } else { - myWakelocks.add(wl); - } - } - - /* - * Sort the data - */ - Comparator comparator = new Wakelock.WakelockTimeComparator(); - Collections.sort(myWakelocks, comparator); - - return myWakelocks; - } - - public static ArrayList getAlarmStats(Context context) { - ArrayList myWakelocks = new ArrayList<>(); - ArrayList alarms; - if (SysUtils.isRooted()) { - alarms = AlarmsDumpsys.getAlarms(); - } else { - myWakelocks.add(new Alarm(context.getString(R.string.noroot))); - return myWakelocks; - } - - for (StatElement statElement : alarms) { - Alarm alarm = (Alarm) statElement; - - if (alarm.getWakeups() > 0) - myWakelocks.add(alarm); - - } - Collections.sort(myWakelocks); - - return myWakelocks; - - } - - static void serializeReferences(WakelockReference wr, Context context) { - try { - FileOutputStream fos = context.openFileOutput("aaa", - Context.MODE_PRIVATE); - ObjectOutputStream outputStream = new ObjectOutputStream(fos); - outputStream.writeObject(wr); - outputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/utils/Constants.java b/src/com/phantomLord/cpufrequtils/app/utils/Constants.java deleted file mode 100644 index bb22b69..0000000 --- a/src/com/phantomLord/cpufrequtils/app/utils/Constants.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.phantomLord.cpufrequtils.app.utils; - -import java.util.HashMap; -import java.util.Map; - -import com.phantomLord.cpufrequtils.app.R; - -public class Constants { - - public final static String App_Tag = "Performace Tweaker"; - public final static String cpufreq_sys_dir = "/sys/devices/system/cpu/cpu0/cpufreq/"; - public final static String scaling_min_freq = cpufreq_sys_dir - + "scaling_min_freq"; - public final static String cpuinfo_min_freq = cpufreq_sys_dir - + "cpuinfo_min_freq"; - public final static String scaling_max_freq = cpufreq_sys_dir - + "scaling_max_freq"; - - public final static String cpuinfo_max_freq = cpufreq_sys_dir - + "cpuinfo_max_freq"; - public final static String scaling_cur_freq = cpufreq_sys_dir - + "scaling_cur_freq"; - public final static String cpuinfo_cur_freq = cpufreq_sys_dir - + "cpuinfo_cur_freq"; - public final static String scaling_governor = cpufreq_sys_dir - + "scaling_governor"; - public final static String scaling_available_freq = cpufreq_sys_dir - + "scaling_available_frequencies"; - public final static String scaling_available_governors = cpufreq_sys_dir - + "scaling_available_governors"; - public final static String available_blockdevices = "/sys/block/"; - public final static String available_schedulers = "/sys/block/mmcblk0/queue/scheduler"; - public final static String available_schedulers_path = "/sys/block/mmcblk1/queue/scheduler"; - public static final String time_in_states = "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state"; - public final static String ioscheduler_mtd = "/sys/block/mtdblock0/queue/scheduler"; - public static final String SD_CACHE = "/sys/devices/virtual/bdi/179:0/read_ahead_kb"; - - public static final String[] readAheadKb = { "64", "128", "256", "384", - "512", "640", "768", "896", "1024", "1152", "1280", "1408", "1536", - "1664", "1792", "1920", "2048", "2176", "2304", "2432", "2560", - "2688", "2816", "2944", "3072", "3200", "3328", "3456", "3584", - "3712", "3840", "3968", "4096" }; - - public final static String[] mFragmentsArray = new String[] { - "Cpu Frequency", "Time In State", "I/0 Control", "Wakelocks", - "Settings" }; - public final static String[] wakelockTypes = new String[] { - "Kernel Wakelocks", "Cpu Wakelocks", "Wakeup Triggers" }; - - public static final int icons[] = new int[] { R.drawable.meter, - R.drawable.bar_chart, R.drawable.backup, R.drawable.battery_med, - R.drawable.prefs_widget }; - public static final int theme[] = new int[] { R.style.Theme_Sherlock, - R.style.Theme_Sherlock_Light, - R.style.Theme_Sherlock_Light_DarkActionBar }; - public static String CPU_0 = "Core 0"; - public static String CPU_1 = "Core 1"; - public static String CPU_2 = "Core 2"; - public static String CPU_3 = "Core 3"; - public static String CPU_ALL = "All Cores"; - - public static final Map THEMES_MAP = new HashMap() { - - private static final long serialVersionUID = 1552737519285513057L; - { - put("Dark", R.style.Theme_Sherlock); - put("Light", R.style.Theme_Sherlock_Light); - put("Light_DarkActionBar", - R.style.Theme_Sherlock_Light_DarkActionBar); - } - }; - /* - * Constants related to preferences - */ - public final static String PREF_ZERO_VALS = "non_zero_vals_only"; - public final static String PREF_MAX_FREQ = "max_freq"; - public final static String PREF_MIN_FREQ = "min_freq"; - public final static String PREF_GOV = "governor"; - public final static String PREF_CPU_APPLY_ON_BOOT = "cpu_apply_on_boot"; - public final static String PREF_IO_APPLY_ON_BOOT = "io_apply_boot"; - public final static String PREF_IO_SCHEDULER = "io_scheduler"; - public final static String PREF_READ_AHEAD = "read_ahead"; - public final static String PREF_TIS_RESET_STATS = "tis_reset_stats"; - -} diff --git a/src/com/phantomLord/cpufrequtils/app/utils/CpuState.java b/src/com/phantomLord/cpufrequtils/app/utils/CpuState.java deleted file mode 100644 index 9e18abc..0000000 --- a/src/com/phantomLord/cpufrequtils/app/utils/CpuState.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.phantomLord.cpufrequtils.app.utils; - -public class CpuState extends Object implements Comparable { - - public int frequency; - public long time; - - public CpuState(int freq, long t) { - this.frequency = freq; - this.time = t; - } - - public long getTime() { - return time; - } - - public int getFrequency() { - return frequency; - } - - @Override - public int compareTo(CpuState state) { - if (this.frequency < state.frequency) - return 1; - else if (this.frequency > state.frequency) - return -1; - else - return 0; - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/utils/SysUtils.java b/src/com/phantomLord/cpufrequtils/app/utils/SysUtils.java deleted file mode 100644 index 556ea4c..0000000 --- a/src/com/phantomLord/cpufrequtils/app/utils/SysUtils.java +++ /dev/null @@ -1,391 +0,0 @@ -package com.phantomLord.cpufrequtils.app.utils; - -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collections; - -import android.content.Context; -import android.util.Log; -import android.widget.Toast; - -import com.phantomLord.cpufrequtils.app.R; - -public class SysUtils { - - public static String[] getAvailableFrequencies() { - String[] frequencies = null; - if (new File(Constants.scaling_available_freq).exists()) { - frequencies = readOutputFromFile(Constants.scaling_available_freq) - .split(" "); - return frequencies; - } else if (new File(Constants.time_in_states).exists()) { - ArrayList states = new ArrayList(); - int i = 0; - states = TimeInStateReader.TimeInStatesReader().getCpuStateTime( - false, false); - Collections.sort(states); - frequencies = new String[states.size()]; - for (CpuState object : states) { - frequencies[i] = String.valueOf(object.getFrequency()); - i++; - } - return frequencies; - } else { - return new String[] {}; - } - } - - public static String getCurrentMaxFrequeny() { - return readOutputFromFile(Constants.scaling_max_freq); - } - - public static String getCurrentMinFrequency() { - return readOutputFromFile(Constants.scaling_min_freq); - - } - - public static String[] getAvailableGovernors() { - return readOutputFromFile(Constants.scaling_available_governors).split( - " "); - } - - public static String getCurrentScalingGovernor() { - return readOutputFromFile(Constants.scaling_governor); - } - - public static final String[] getAvailableIOScheduler() { - String schedulerPath = new String(); - if (new File(Constants.available_schedulers).exists()) { - schedulerPath = Constants.available_schedulers; - - } else if (new File(Constants.available_schedulers_path).exists()) { - schedulerPath = Constants.available_schedulers_path; - /* - * Some devices don't have mmcblk0 block device so we instead use - * mtdblock0 to read the available schedulers - */ - } else if (new File(Constants.ioscheduler_mtd).exists()) { - schedulerPath = Constants.ioscheduler_mtd; - } else { - return new String[] {}; - /* - * need to return an empty string and not a null because the wheel - * widget would just crash if a null value is returned - */ - } - String[] schedulers = readOutputFromFile(schedulerPath).split(" "); - for (int i = 0; i < schedulers.length; i++) { - if (schedulers[i].contains("]")) { - String temp = schedulers[i].substring(1, - schedulers[i].length() - 1); - schedulers[i] = temp; - } - } - return schedulers; - } - - public static final String getCurrentIOScheduler() { - String currentScheduler = null; - String[] schedulers = readOutputFromFile(Constants.available_schedulers) - .split(" "); - for (String string : schedulers) { - if (string.contains("[")) { - currentScheduler = string; - } - } - if (currentScheduler != null) { - return currentScheduler.substring(1, currentScheduler.length() - 1); - } else - return ""; - } - - public static String getReadAhead() { - String res = new String(); - for (int i = 0; i < 2; i++) { - File device = new File(Constants.available_blockdevices + "mmcblk" - + i); - if (device.exists()) { - device = new File(Constants.available_blockdevices + "mmcblk" - + i + "/queue/read_ahead_kb"); - res = readOutputFromFile(device.getAbsolutePath()); - } - } - return res; - - } - - public static final void setFrequencyAndGovernor(String maxFrequency, - String minFrequency, String governor, Context context) { - int noOfCpus = getCoreCount(); - ArrayList commands = new ArrayList(); - /* - * prepare commands for each core - */ - if (maxFrequency != null && minFrequency != null) - for (int i = 0; i < noOfCpus; i++) { - commands.add("chmod 0644 " - + Constants.scaling_governor.replace("cpu0", "cpu" + i) - + "\n"); - commands.add("chmod 0664 " - + Constants.scaling_min_freq.replace("cpu0", "cpu" + i) - + "\n"); - commands.add("chmod 0664 " - + Constants.scaling_max_freq.replace("cpu0", "cpu" + i) - + "\n"); - commands.add("echo " + governor + " > " - + Constants.scaling_governor.replace("cpu0", "cpu" + i) - + "\n"); - commands.add("echo " + minFrequency + " > " - + Constants.scaling_min_freq.replace("cpu0", "cpu" + i) - + "\n"); - commands.add("echo " + maxFrequency.replace("cpu0", "cpu" + i) - + " > " + Constants.scaling_max_freq + "\n"); - - } - - commands.add("exit" + "\n"); - boolean success = executeRootCommand(commands); - if (success) { - String msg = context.getString(R.string.ok_message); - Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); - } - } - - public static int getCoreCount() { - int cores = 0; - while (true) { - File file = new File(Constants.cpufreq_sys_dir.replace("cpu0", - "cpu" + cores)); - if (file.exists()) - cores++; - else - return cores; - } - } - - public static void setDiskSchedulerandReadAhead(String ioScheduler, - String readAhead, Context ctx) { - - ArrayList mCommands = new ArrayList(); - if (ioScheduler != null) { - File[] devices = new File(Constants.available_blockdevices) - .listFiles(); - for (int i = 0; i < devices.length; i++) { - String devicePath = devices[i].getAbsolutePath(); - if (!(devicePath.contains("ram") || devicePath.contains("loop") || devicePath - .contains("dm"))) { - File blockDevice = new File(devices[i].getAbsolutePath() - + "/queue/scheduler"); - if (blockDevice.exists()) { - mCommands.add("chmod 0644 " - + blockDevice.getAbsolutePath() + "\n"); - mCommands.add("echo " + ioScheduler + " > " - + blockDevice.getAbsolutePath() + " \n "); - } - } - } - } - /* - * prepare commands for changing the read ahead cache - */ - if (readAhead != null) { - File block; - for (int i = 0; i < 2; i++) { - block = new File(Constants.available_blockdevices + "mmcblk" - + i + "/queue/read_ahead_kb"); - if (block.exists()) { - mCommands.add("chmod 0644 " + block.getAbsolutePath() - + "\n"); - mCommands.add("echo " + readAhead + " > " - + block.getAbsolutePath() + "\n"); - } - } - mCommands.add("chmod 0644 " + Constants.SD_CACHE + "\n"); - mCommands.add("echo " + readAhead + " > " + Constants.SD_CACHE - + "\n"); - - } - mCommands.add(" exit \n"); - boolean success = executeRootCommand(mCommands); - if (success) { - String msg = ctx.getString(R.string.ok_message, - getCurrentIOScheduler(), getReadAhead()); - Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show(); - } - } - - public static String getKernelInfo() { - String data = readOutputFromFile("/proc/version"); - if (data != null) - return data; - return ""; - } - - public static String[] toMhz(String[] values) { - String[] frequency = new String[values.length]; - if (values != null) { - for (int i = 0; i < values.length; i++) { - try { - frequency[i] = (Integer.parseInt(values[i]) / 1000 + " Mhz"); - } catch (NumberFormatException nfe) { - nfe.printStackTrace(); - return new String[] {}; - /* - * need to return an empty string in case an exception is - * thrown , if we return a null value the spinner wheel - * widget would just crash and we don't want that do we?? - */ - } - } - - } - return frequency; - } - - public static boolean isRooted() { - if (new File("/system/bin/su").exists() - || new File("/system/xbin/su").exists()) - return true; - else - return false; - } - - public static String readOutputFromFile(String pathToFile) { - - StringBuffer buffer = new StringBuffer(); - String data = null; - Process process; - BufferedReader stdinput; - File file = new File(pathToFile); - if (!(file.exists())) { - return ""; - } - if (file.canRead()) { - try { - process = Runtime.getRuntime().exec("cat " + pathToFile); - stdinput = new BufferedReader(new InputStreamReader( - process.getInputStream())); - while ((data = stdinput.readLine()) != null) { - buffer.append(data); - } - } catch (Exception e) { - e.printStackTrace(); - } - - return buffer.toString(); - - } - /* - * try reading the file as root - */ - else { - InputStream inputStream; - DataOutputStream dos; - try { - process = prepareRootShell(); - dos = new DataOutputStream(process.getOutputStream()); - dos.writeBytes("cat " + process); - dos.flush(); - dos.writeBytes("\n exit "); - dos.flush(); - dos.close(); - if (process.waitFor() == 0) { - inputStream = process.getInputStream(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(inputStream)); - String line; - while ((line = reader.readLine()) != null) { - data = line; - } - } - - } catch (IOException ioe) { - ioe.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return data; - } - - } - - public static boolean executeRootCommand(ArrayList commands) { - InputStream is = null; - DataOutputStream dos; - try { - Process mProcess = prepareRootShell(); - dos = new DataOutputStream(mProcess.getOutputStream()); - for (String cmd : commands) { - dos.writeBytes(cmd); - dos.flush(); - } - if (mProcess.waitFor() == 0) { - return true; - } else { - is = mProcess.getErrorStream(); - } - dos.close(); - if (is != null) - printOutputOnStdout(is); - is.close(); - - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return false; - } - - private static void printOutputOnStdout(InputStream is) { - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String line = null; - try { - while ((line = br.readLine()) != null) { - Log.e(Constants.App_Tag, line); - } - } catch (IOException e) { - e.printStackTrace(); - } - - } - - public static Process prepareRootShell() throws IOException { - Process mProcess = Runtime.getRuntime().exec(getSUbinaryPath()); - return mProcess; - } - - public static String getSUbinaryPath() { - String path = "/system/bin/su"; - if (new File(path).exists()) { - return path; - } - path = "/system/xbin/su"; - if (new File(path).exists()) { - return path; - } - return null; - } - - public static String secToString(long tSec) { - long h = (long) Math.floor(tSec / (60 * 60)); - long m = (long) Math.floor((tSec - h * 60 * 60) / 60); - long s = tSec % 60; - String sDur; - sDur = h + ":"; - if (m < 10) - sDur += "0"; - sDur += m + ":"; - if (s < 10) - sDur += "0"; - sDur += s; - - return sDur; - - } -} diff --git a/src/com/phantomLord/cpufrequtils/app/utils/TimeInStateReader.java b/src/com/phantomLord/cpufrequtils/app/utils/TimeInStateReader.java deleted file mode 100644 index 1b2dc2b..0000000 --- a/src/com/phantomLord/cpufrequtils/app/utils/TimeInStateReader.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.phantomLord.cpufrequtils.app.utils; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import android.os.SystemClock; -import android.util.Log; - -public class TimeInStateReader { - private ArrayList states = new ArrayList<>(); - private ArrayList _states = new ArrayList<>(); - public Map newStates = new HashMap(); - - private long totaltime; - - private TimeInStateReader() { - } - - public static TimeInStateReader TimeInStatesReader() { - return new TimeInStateReader(); - } - - public ArrayList getCpuStateTime(boolean withDeepSleep, - boolean filterZeroValues) { - states.clear(); - BufferedReader bufferedReader; - Process process = null; - File statsFile = new File(Constants.time_in_states); - if (statsFile.exists()) { - if (statsFile.canRead()) { - String line; - try { - process = Runtime.getRuntime().exec( - "cat " + Constants.time_in_states); - } catch (IOException ioe) { - ioe.printStackTrace(); - } - bufferedReader = new BufferedReader(new InputStreamReader( - process.getInputStream())); - try { - while ((line = bufferedReader.readLine()) != null) { - String entries[] = line.split(" "); - long time = Long.parseLong(entries[1]); - states.add(new CpuState(Integer.parseInt(entries[0]), - time)); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - /* - * calculate deep sleep time - */ - - if (withDeepSleep) { - long deepSleepTime = (SystemClock.elapsedRealtime() - SystemClock - .uptimeMillis()) / 10; - if (deepSleepTime > 0) - states.add(new CpuState(0, deepSleepTime)); - } - if (states != null) { - Collections.sort(states); - } - Log.d("bloody", "sabbath " + newStates.size()); - if (newStates.size() > 0) { - int index = 0; - for (CpuState iterable_element : states) { - if (newStates.containsKey(iterable_element.frequency)) { - states.get(index).time -= newStates - .get(iterable_element.frequency); - } - index++; - } - } - return removeZeroValues(); - } - - private ArrayList removeZeroValues() { - _states.clear(); - for (CpuState s : states) { - if (s.time > 0) { - _states.add(s); - } - } - return _states; - } - - public long getTotalTimeInState() { - totaltime = 0; - for (CpuState state : states) { - totaltime += state.getTime(); - } - return totaltime; - } - - public void setNewStates(HashMap state) { - clearNewStates(); - Log.d("sizeofnew", state.size() + ""); - newStates = state; - - } - - public void clearNewStates() { - newStates.clear(); - } - -} diff --git a/src/com/phantomLord/cpufrequtils/app/utils/WakelockReference.java b/src/com/phantomLord/cpufrequtils/app/utils/WakelockReference.java deleted file mode 100644 index 904fa4c..0000000 --- a/src/com/phantomLord/cpufrequtils/app/utils/WakelockReference.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.phantomLord.cpufrequtils.app.utils; - -import java.io.Serializable; -import java.util.ArrayList; - -import com.asksven.android.common.privateapiproxies.StatElement; - -public class WakelockReference implements Serializable { - - private static final long serialVersionUID = 1L; - - private String STATS_TYPE; - private String STATS_SINCE_UNPLUGGED = "since_unplugged"; - private String STATS_SINCE_BOOT = "since_boot"; - private String STAT_CUSTOM_REFERENCE = "custom_red"; - - protected ArrayList mRefWakelocks = null; - protected ArrayList mRefKernelWakelock = null; - protected ArrayList mRefAlarms = null; - - protected long timeSince = 100; - - private WakelockReference() { - - } - - public WakelockReference(String statsType) { - - } - -} \ No newline at end of file