diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..9934962 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle____PowerTutor2_libs_achartengine_1_0_0_jar.xml b/.idea/libraries/Gradle____PowerTutor2_libs_achartengine_1_0_0_jar.xml new file mode 100644 index 0000000..b853066 --- /dev/null +++ b/.idea/libraries/Gradle____PowerTutor2_libs_achartengine_1_0_0_jar.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_activity_activity_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_activity_activity_1_0_0_aar.xml new file mode 100644 index 0000000..4e8e425 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_activity_activity_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_annotation_annotation_1_1_0_jar.xml b/.idea/libraries/Gradle__androidx_annotation_annotation_1_1_0_jar.xml new file mode 100644 index 0000000..5b17db6 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_annotation_annotation_1_1_0_jar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_1_0_aar.xml new file mode 100644 index 0000000..c618869 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_1_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_1_0_aar.xml new file mode 100644 index 0000000..1ea74dd --- /dev/null +++ b/.idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0_jar.xml b/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0_jar.xml new file mode 100644 index 0000000..a7f501b --- /dev/null +++ b/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml b/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml new file mode 100644 index 0000000..8b6395d --- /dev/null +++ b/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_collection_collection_1_1_0_jar.xml b/.idea/libraries/Gradle__androidx_collection_collection_1_1_0_jar.xml new file mode 100644 index 0000000..ecb16c3 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_collection_collection_1_1_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_core_core_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_core_core_1_1_0_aar.xml new file mode 100644 index 0000000..1d18987 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_core_core_1_1_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml new file mode 100644 index 0000000..e06ff7f --- /dev/null +++ b/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml new file mode 100644 index 0000000..e770802 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml new file mode 100644 index 0000000..485fc9d --- /dev/null +++ b/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_fragment_fragment_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_fragment_fragment_1_1_0_aar.xml new file mode 100644 index 0000000..709e94a --- /dev/null +++ b/.idea/libraries/Gradle__androidx_fragment_fragment_1_1_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml new file mode 100644 index 0000000..3a464a2 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_1_0_jar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_1_0_jar.xml new file mode 100644 index 0000000..8c4384c --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_1_0_jar.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml new file mode 100644 index 0000000..9381a45 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml new file mode 100644 index 0000000..e8f4dbd --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_1_0_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_1_0_aar.xml new file mode 100644 index 0000000..321d8e4 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml new file mode 100644 index 0000000..02ec159 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml new file mode 100644 index 0000000..229b0de --- /dev/null +++ b/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml new file mode 100644 index 0000000..6531f10 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml new file mode 100644 index 0000000..260dd80 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml new file mode 100644 index 0000000..007480e --- /dev/null +++ b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_0_aar.xml new file mode 100644 index 0000000..b301bcc --- /dev/null +++ b/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml new file mode 100644 index 0000000..918d22e --- /dev/null +++ b/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..37a7509 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5d466ca --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/PowerTutor2/PowerTutorII-PowerTutor2.iml b/.idea/modules/PowerTutor2/PowerTutorII-PowerTutor2.iml new file mode 100644 index 0000000..5f350fb --- /dev/null +++ b/.idea/modules/PowerTutor2/PowerTutorII-PowerTutor2.iml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/PowerTutorII.iml b/.idea/modules/PowerTutorII.iml new file mode 100644 index 0000000..659a1b4 --- /dev/null +++ b/.idea/modules/PowerTutorII.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENCE b/LICENCE deleted file mode 100644 index d3bb00c..0000000 --- a/LICENCE +++ /dev/null @@ -1,16 +0,0 @@ -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu diff --git a/Makefile b/Makefile deleted file mode 100644 index bce0d17..0000000 --- a/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -all: package - -ANDROID_LIB=android-9.jar -CLASSPATH=$(ANDROID_LIB):libs/achartengine-1.0.0.jar - -genres: - mkdir -p gen bin - aapt package -m -J gen -M AndroidManifest.xml -S res -I $(ANDROID_LIB) - -aidl: - find src/ -type f | \ - grep '\.aidl$$' | \ - xargs -n 1 aidl -Isrc -I$(ANDROID_LIB) -ogen - -gen: genres aidl - -compile: gen - mkdir -p bin - find src/ gen/ -type f | \ - grep '\.java$$' | \ - xargs javac -cp $(CLASSPATH) -d bin - ndk-build - -dex: compile - dx --dex --output=bin/classes.dex bin/ libs/ - -package: dex - aapt package -M AndroidManifest.xml -S res \ - -F bin/PowerTutor.apk -I $(ANDROID_LIB) - cd bin; zip PowerTutor.apk classes.dex - zip bin/PowerTutor.apk -r libs -i \*.so - jarsigner -storepass android -keystore debug.keystore \ - bin/PowerTutor.apk androiddebugkey - -install: package - adb install bin/PowerTutor.apk - -clean: - rm -rf bin/ gen/ diff --git a/PowerTutor2/.gitignore b/PowerTutor2/.gitignore new file mode 100644 index 0000000..705b8a2 --- /dev/null +++ b/PowerTutor2/.gitignore @@ -0,0 +1,38 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ +.gradle/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/PowerTutor2/build.gradle b/PowerTutor2/build.gradle new file mode 100644 index 0000000..273ca07 --- /dev/null +++ b/PowerTutor2/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + applicationId "edu.umich.PowerTutor" + minSdkVersion 14 + targetSdkVersion 29 + + ndk { + moduleName "bindings" + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + //compile files('libs/achartengine-1.0.0.jar') + + implementation 'androidx.appcompat:appcompat:1.1.0' +} diff --git a/PowerTutor2/gradle.properties b/PowerTutor2/gradle.properties new file mode 100644 index 0000000..c73d239 --- /dev/null +++ b/PowerTutor2/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true diff --git a/libs/achartengine-1.0.0.jar b/PowerTutor2/libs/achartengine-1.0.0.jar similarity index 100% rename from libs/achartengine-1.0.0.jar rename to PowerTutor2/libs/achartengine-1.0.0.jar diff --git a/AndroidManifest.xml b/PowerTutor2/src/main/AndroidManifest.xml similarity index 95% rename from AndroidManifest.xml rename to PowerTutor2/src/main/AndroidManifest.xml index 74956b0..da0ce76 100644 --- a/AndroidManifest.xml +++ b/PowerTutor2/src/main/AndroidManifest.xml @@ -1,7 +1,16 @@ - + + + + + + + + + + + - + + + @@ -82,12 +94,4 @@ android:resource="@xml/widget_info" /> - - - - - - - - diff --git a/src/edu/umich/PowerTutor/PowerNotifications.aidl b/PowerTutor2/src/main/aidl/edu/umich/PowerTutor/PowerNotifications.aidl similarity index 100% rename from src/edu/umich/PowerTutor/PowerNotifications.aidl rename to PowerTutor2/src/main/aidl/edu/umich/PowerTutor/PowerNotifications.aidl diff --git a/src/edu/umich/PowerTutor/service/ICounterService.aidl b/PowerTutor2/src/main/aidl/edu/umich/PowerTutor/service/ICounterService.aidl similarity index 97% rename from src/edu/umich/PowerTutor/service/ICounterService.aidl rename to PowerTutor2/src/main/aidl/edu/umich/PowerTutor/service/ICounterService.aidl index 96e5b38..531ab56 100644 --- a/src/edu/umich/PowerTutor/service/ICounterService.aidl +++ b/PowerTutor2/src/main/aidl/edu/umich/PowerTutor/service/ICounterService.aidl @@ -1,79 +1,79 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.service; - -interface ICounterService { - // Returns the name of the components that are being logged. - String[] getComponents(); - - // Returns the maximum power usage for each of the components being logged. - int[] getComponentsMaxPower(); - - // Returns a bit mask with a 1 in the ith bit if component i doesn't have - // uid specific information. - int getNoUidMask(); - - // Returns the power consumption in mW for component componentId for the last - // count iterations. uid can be specified to make this data only include a - // specific user id or you can provide SystemInfo.AID_ALL to request - // global power state information. - int[] getComponentHistory(int count, int componentId, int uid); - - // Returns the total energy consumption for each component in the same order - // that the components were returned in getComponents() and in the same order - // that the components are populated by PhoneSelector.generateComponents(). - // - // uid can be specified to make the information specific to a single user - // id or SystemInfo.AID_ALL can be specified to request global information. - // - // windowType should be one of Counter.WINDOW_MINUTE, Counter.WINDOW_HOUR, - // Counter.WINDOW_DAY, Counter.WINDOW_TOTAL to request the window that the - // energy usage will be calculated over. - // - // The returned result is given in mJ. - long[] getTotals(int uid, int windowType); - - // Like getTotals() except that each entry is divided by how long the given - // uid was running. If SystemInfo.AID_ALL is provided this is effectively - // like dividing each entry by the window size. (unless PowerTutor hasn't - // been running that long). - long[] getMeans(int uid, int windowType); - - // Gets the total time that this uid has been running in seconds. - long getRuntime(int uid, int windowType); - - // Returns a byte array representing a serialized array of UidInfo structures. - // See UidInfo.java for what information is given. Note that members marked - // as transient are not filled in. - // Power contributions from component i will be dropped if the ith bit is set - // in ignoreMask. Providing 0 for ignoreMask will give results for all - // components. - // - // Example Usage: - // byte[] rawUidInfo = counterService.getUidInfo(windowType); - // UidInfo[] uidInfos = (UidInfo[])new ObjectInputStream( - // new ByteArrayInputStream(rawUidInfo)).readObject(); - byte[] getUidInfo(int windowType, int ignoreMask); - - // Return miscellaneous data point for the passed uid. - // Current extras included: - // OLEDSCORE - long getUidExtra(String name, int uid); -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.service; + +interface ICounterService { + // Returns the name of the components that are being logged. + String[] getComponents(); + + // Returns the maximum power usage for each of the components being logged. + int[] getComponentsMaxPower(); + + // Returns a bit mask with a 1 in the ith bit if component i doesn't have + // uid specific information. + int getNoUidMask(); + + // Returns the power consumption in mW for component componentId for the last + // count iterations. uid can be specified to make this data only include a + // specific user id or you can provide SystemInfo.AID_ALL to request + // global power state information. + int[] getComponentHistory(int count, int componentId, int uid); + + // Returns the total energy consumption for each component in the same order + // that the components were returned in getComponents() and in the same order + // that the components are populated by PhoneSelector.generateComponents(). + // + // uid can be specified to make the information specific to a single user + // id or SystemInfo.AID_ALL can be specified to request global information. + // + // windowType should be one of Counter.WINDOW_MINUTE, Counter.WINDOW_HOUR, + // Counter.WINDOW_DAY, Counter.WINDOW_TOTAL to request the window that the + // energy usage will be calculated over. + // + // The returned result is given in mJ. + long[] getTotals(int uid, int windowType); + + // Like getTotals() except that each entry is divided by how long the given + // uid was running. If SystemInfo.AID_ALL is provided this is effectively + // like dividing each entry by the window size. (unless PowerTutor hasn't + // been running that long). + long[] getMeans(int uid, int windowType); + + // Gets the total time that this uid has been running in seconds. + long getRuntime(int uid, int windowType); + + // Returns a byte array representing a serialized array of UidInfo structures. + // See UidInfo.java for what information is given. Note that members marked + // as transient are not filled in. + // Power contributions from component i will be dropped if the ith bit is set + // in ignoreMask. Providing 0 for ignoreMask will give results for all + // components. + // + // Example Usage: + // byte[] rawUidInfo = counterService.getUidInfo(windowType); + // UidInfo[] uidInfos = (UidInfo[])new ObjectInputStream( + // new ByteArrayInputStream(rawUidInfo)).readObject(); + byte[] getUidInfo(int windowType, int ignoreMask); + + // Return miscellaneous data point for the passed uid. + // Current extras included: + // OLEDSCORE + long getUidExtra(String name, int uid); +} diff --git a/src/edu/umich/PowerTutor/components/Audio.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Audio.java similarity index 96% rename from src/edu/umich/PowerTutor/components/Audio.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Audio.java index b370b83..0057aef 100644 --- a/src/edu/umich/PowerTutor/components/Audio.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Audio.java @@ -1,188 +1,188 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import edu.umich.PowerTutor.PowerNotifications; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.util.NotificationService; -import edu.umich.PowerTutor.util.Recycler; - -import android.content.Context; -import android.media.AudioManager; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.TreeSet; - -/**This class aims to log the audio device status once per log interval*/ -public class Audio extends PowerComponent { - /**This class is the logger data file corresponding to Audio*/ - public static class AudioData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static AudioData obtain() { - AudioData result = recycler.obtain(); - if(result != null) return result; - return new AudioData(); - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - public boolean musicOn; - - private AudioData() { - } - - public void init(boolean musicOn) { - this.musicOn = musicOn; - } - - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - out.write("Audio-on " + musicOn + "\n"); - } - } - - private static class MediaData implements Comparable { - private static Recycler recycler = new Recycler(); - - public static MediaData obtain() { - MediaData result = recycler.obtain(); - if(result != null) return result; - return new MediaData(); - } - - public void recycle() { - recycler.recycle(this); - } - - public int uid; - public int id; - public int assignUid; - - public int compareTo(Object obj) { - MediaData x = (MediaData)obj; - if(uid < x.uid) return -1; - if(uid > x.uid) return 1; - if(id < x.id) return -1; - if(id > x.id) return 1; - return 0; - } - - public boolean equals(Object obj) { - MediaData x = (MediaData)obj; - return uid == x.uid && id == x.id; - } - } - - private AudioManager audioManager; - private PowerNotifications audioNotif; - private TreeSet uidData; - - public Audio(Context context) { - if(NotificationService.available()) { - uidData = new TreeSet(); - audioNotif = new NotificationService.DefaultReceiver() { - private int sysUid = -1; - - @Override - public void noteSystemMediaCall(int uid) { - sysUid = uid; - } - - @Override - public void noteStartMedia(int uid, int id) { - MediaData data = MediaData.obtain(); - data.uid = uid; - data.id = id; - if(uid == 1000 && sysUid != -1) { - data.assignUid = sysUid; - sysUid = -1; - } else { - data.assignUid = uid; - } - synchronized(uidData) { - if(!uidData.add(data)) { - data.recycle(); - } - } - } - - @Override - public void noteStopMedia(int uid, int id) { - MediaData data = MediaData.obtain(); - data.uid = uid; - data.id = id; - synchronized(uidData) { - uidData.remove(data); - } - data.recycle(); - } - }; - NotificationService.addHook(audioNotif); - } - - audioManager = (AudioManager)context.getSystemService( - Context.AUDIO_SERVICE); - } - - @Override - protected void onExit() { - if(audioNotif != null) { - NotificationService.removeHook(audioNotif); - } - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - AudioData data = AudioData.obtain(); - data.init(uidData != null && !uidData.isEmpty() || - audioManager.isMusicActive()); - result.setPowerData(data); - - if(uidData != null) synchronized(uidData) { - int last_uid = -1; - for(MediaData dat : uidData) { - if(dat.uid != last_uid) { - AudioData audioPower = AudioData.obtain(); - audioPower.init(true); - result.addUidPowerData(dat.assignUid, audioPower); - } - last_uid = dat.uid; - } - } - - return result; - } - - @Override - public boolean hasUidInformation() { - return audioNotif != null; - } - - @Override - public String getComponentName() { - return "Audio"; - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import edu.umich.PowerTutor.PowerNotifications; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.util.NotificationService; +import edu.umich.PowerTutor.util.Recycler; + +import android.content.Context; +import android.media.AudioManager; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.TreeSet; + +/**This class aims to log the audio device status once per log interval*/ +public class Audio extends PowerComponent { + /**This class is the logger data file corresponding to Audio*/ + public static class AudioData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static AudioData obtain() { + AudioData result = recycler.obtain(); + if(result != null) return result; + return new AudioData(); + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + public boolean musicOn; + + private AudioData() { + } + + public void init(boolean musicOn) { + this.musicOn = musicOn; + } + + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + out.write("Audio-on " + musicOn + "\n"); + } + } + + private static class MediaData implements Comparable { + private static Recycler recycler = new Recycler(); + + public static MediaData obtain() { + MediaData result = recycler.obtain(); + if(result != null) return result; + return new MediaData(); + } + + public void recycle() { + recycler.recycle(this); + } + + public int uid; + public int id; + public int assignUid; + + public int compareTo(Object obj) { + MediaData x = (MediaData)obj; + if(uid < x.uid) return -1; + if(uid > x.uid) return 1; + if(id < x.id) return -1; + if(id > x.id) return 1; + return 0; + } + + public boolean equals(Object obj) { + MediaData x = (MediaData)obj; + return uid == x.uid && id == x.id; + } + } + + private AudioManager audioManager; + private PowerNotifications audioNotif; + private TreeSet uidData; + + public Audio(Context context) { + if(NotificationService.available()) { + uidData = new TreeSet(); + audioNotif = new NotificationService.DefaultReceiver() { + private int sysUid = -1; + + @Override + public void noteSystemMediaCall(int uid) { + sysUid = uid; + } + + @Override + public void noteStartMedia(int uid, int id) { + MediaData data = MediaData.obtain(); + data.uid = uid; + data.id = id; + if(uid == 1000 && sysUid != -1) { + data.assignUid = sysUid; + sysUid = -1; + } else { + data.assignUid = uid; + } + synchronized(uidData) { + if(!uidData.add(data)) { + data.recycle(); + } + } + } + + @Override + public void noteStopMedia(int uid, int id) { + MediaData data = MediaData.obtain(); + data.uid = uid; + data.id = id; + synchronized(uidData) { + uidData.remove(data); + } + data.recycle(); + } + }; + NotificationService.addHook(audioNotif); + } + + audioManager = (AudioManager)context.getSystemService( + Context.AUDIO_SERVICE); + } + + @Override + protected void onExit() { + if(audioNotif != null) { + NotificationService.removeHook(audioNotif); + } + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + AudioData data = AudioData.obtain(); + data.init(uidData != null && !uidData.isEmpty() || + audioManager.isMusicActive()); + result.setPowerData(data); + + if(uidData != null) synchronized(uidData) { + int last_uid = -1; + for(MediaData dat : uidData) { + if(dat.uid != last_uid) { + AudioData audioPower = AudioData.obtain(); + audioPower.init(true); + result.addUidPowerData(dat.assignUid, audioPower); + } + last_uid = dat.uid; + } + } + + return result; + } + + @Override + public boolean hasUidInformation() { + return audioNotif != null; + } + + @Override + public String getComponentName() { + return "Audio"; + } +} diff --git a/src/edu/umich/PowerTutor/components/CPU.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/CPU.java similarity index 96% rename from src/edu/umich/PowerTutor/components/CPU.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/CPU.java index 283e991..6ed1eb8 100644 --- a/src/edu/umich/PowerTutor/components/CPU.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/CPU.java @@ -1,358 +1,358 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import edu.umich.PowerTutor.phone.PhoneConstants; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.util.Recycler; -import edu.umich.PowerTutor.util.SystemInfo; - -import android.util.Log; -import android.os.Process; -import android.os.SystemClock; -import android.util.SparseArray; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; - -public class CPU extends PowerComponent { - public static class CpuData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static CpuData obtain() { - CpuData result = recycler.obtain(); - if(result != null) return result; - return new CpuData(); - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - public double sysPerc; - public double usrPerc; - public double freq; - - private CpuData() { - } - - public void init(double sysPerc, double usrPerc, double freq) { - this.sysPerc = sysPerc; - this.usrPerc = usrPerc; - this.freq = freq; - } - - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - StringBuilder res = new StringBuilder(); - res.append("CPU-sys ").append((long)Math.round(sysPerc)) - .append("\nCPU-usr ").append((long)Math.round(usrPerc)) - .append("\nCPU-freq ").append(freq) - .append("\n"); - out.write(res.toString()); - } - } - - private static final String TAG = "CPU"; - private static final String CPU_FREQ_FILE = "/proc/cpuinfo"; - private static final String STAT_FILE = "/proc/stat"; - - private CpuStateKeeper cpuState; - private SparseArray pidStates; - private SparseArray uidLinks; - - private int[] pids; - private long[] statsBuf; - - private PhoneConstants constants; - - public CPU(PhoneConstants constants) { - this.constants = constants; - cpuState = new CpuStateKeeper(SystemInfo.AID_ALL); - pidStates = new SparseArray(); - uidLinks = new SparseArray(); - statsBuf = new long[7]; - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - - SystemInfo sysInfo = SystemInfo.getInstance(); - double freq = readCpuFreq(sysInfo); - if(freq < 0) { - Log.w(TAG, "Failed to read cpu frequency"); - return result; - } - - if(!sysInfo.getUsrSysTotalTime(statsBuf)) { - Log.w(TAG, "Failed to read cpu times"); - return result; - } - - long usrTime = statsBuf[SystemInfo.INDEX_USER_TIME]; - long sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME]; - long totalTime = statsBuf[SystemInfo.INDEX_TOTAL_TIME]; - - boolean init = cpuState.isInitialized(); - cpuState.updateState(usrTime, sysTime, totalTime, iteration); - - if(init) { - CpuData data = CpuData.obtain(); - data.init(cpuState.getUsrPerc(), cpuState.getSysPerc(), freq); - result.setPowerData(data); - } - - uidLinks.clear(); - pids = sysInfo.getPids(pids); - int pidInd = 0; - if(pids != null) for(int pid : pids) { - if(pid < 0) { - break; - } - - CpuStateKeeper pidState; - if(pidInd < pidStates.size() && pidStates.keyAt(pidInd) == pid) { - pidState = pidStates.valueAt(pidInd); - } else { - int uid = sysInfo.getUidForPid(pid); - if(uid >= 0) { - pidState = new CpuStateKeeper(uid); - pidStates.put(pid, pidState); - } else { - /* Assume that this process no longer exists. */ - continue; - } - } - pidInd++; - - if(!pidState.isStale(iteration)) { - /* Nothing much is going on with this pid recently. We'll just - * assume that it's not using any of the cpu for this iteration. - */ - pidState.updateIteration(iteration, totalTime); - } else if(sysInfo.getPidUsrSysTime(pid, statsBuf)) { - usrTime = statsBuf[SystemInfo.INDEX_USER_TIME]; - sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME]; - - init = pidState.isInitialized(); - pidState.updateState(usrTime, sysTime, totalTime, iteration); - - if(!init) { - continue; - } - } - - CpuStateKeeper linkState = uidLinks.get(pidState.getUid()); - if(linkState == null) { - uidLinks.put(pidState.getUid(), pidState); - } else { - linkState.absorb(pidState); - } - } - - /* Remove processes that are no longer active. */ - for(int i = 0; i < pidStates.size(); i++) { - if(!pidStates.valueAt(i).isAlive(iteration)) { - pidStates.remove(pidStates.keyAt(i--)); - } - } - - /* Collect the summed uid information. */ - for(int i = 0; i < uidLinks.size(); i++) { - int uid = uidLinks.keyAt(i); - CpuStateKeeper linkState = uidLinks.valueAt(i); - - CpuData uidData = CpuData.obtain(); - predictAppUidState(uidData, linkState.getUsrPerc(), - linkState.getSysPerc(), freq); - result.addUidPowerData(uid, uidData); - } - - return result; - } - - /* This is the function that is responsible for predicting the cpu frequency - * state of the individual uid as though it were the only thing running. It - * simply is finding the lowest frequency that keeps the cpu usage under - * 70% assuming there is a linear relationship to the cpu utilization at - * different frequencies. - */ - private void predictAppUidState(CpuData uidData, double usrPerc, - double sysPerc, double freq) { - double[] freqs = constants.cpuFreqs(); - if(usrPerc + sysPerc < 1e-6) { - /* Don't waste time with the binary search if there is no utilization - * which will be the case a lot. - */ - uidData.init(sysPerc, usrPerc, freqs[0]); - return; - } - int lo = 0; - int hi = freqs.length - 1; - double perc = sysPerc + usrPerc; - while(lo < hi) { - int mid = (lo + hi) / 2; - double nperc = perc * freq / freqs[mid]; - if(nperc < 70) { - hi = mid; - } else { - lo = mid + 1; - } - } - uidData.init(sysPerc * freq / freqs[lo], usrPerc * freq / freqs[lo], - freqs[lo]); - } - - private static class CpuStateKeeper { - private int uid; - private long iteration; - private long lastUpdateIteration; - private long inactiveIterations; - - private long lastUsr; - private long lastSys; - private long lastTotal; - - private long sumUsr; - private long sumSys; - private long deltaTotal; - - private CpuStateKeeper(int uid) { - this.uid = uid; - lastUsr = lastSys = -1; - lastUpdateIteration = iteration = -1; - inactiveIterations = 0; - } - - public boolean isInitialized() { - return lastUsr != -1; - } - - public void updateIteration(long iteration, long totalTime) { - /* Process is still running but actually reading the cpu utilization has - * been skipped this iteration to avoid wasting cpu cycles as this process - * has not been very active recently. */ - sumUsr = 0; - sumSys = 0; - deltaTotal = totalTime - lastTotal; - if(deltaTotal < 1) deltaTotal = 1; - lastTotal = totalTime; - this.iteration = iteration; - } - - public void updateState(long usrTime, long sysTime, long totalTime, - long iteration) { - sumUsr = usrTime - lastUsr; - sumSys = sysTime - lastSys; - deltaTotal = totalTime - lastTotal; - if(deltaTotal < 1) deltaTotal = 1; - lastUsr = usrTime; - lastSys = sysTime; - lastTotal = totalTime; - lastUpdateIteration = this.iteration = iteration; - - if(getUsrPerc() + getSysPerc() < 0.1) { - inactiveIterations++; - } else { - inactiveIterations = 0; - } - } - - public int getUid() { - return uid; - } - - public void absorb(CpuStateKeeper s) { - sumUsr += s.sumUsr; - sumSys += s.sumSys; - } - - public double getUsrPerc() { - return 100.0 * sumUsr / Math.max(sumUsr + sumSys, deltaTotal); - } - - public double getSysPerc() { - return 100.0 * sumSys / Math.max(sumUsr + sumSys, deltaTotal); - } - - public boolean isAlive(long iteration) { - return this.iteration == iteration; - } - - public boolean isStale(long iteration) { - return 1L << (iteration - lastUpdateIteration) > - inactiveIterations * inactiveIterations; - } - } - - @Override - public boolean hasUidInformation() { - return true; - } - - @Override - public String getComponentName() { - return "CPU"; - } - - /* Returns the frequency of the processor in Mhz. If the frequency cannot - * be determined returns a negative value instead. - */ - private double readCpuFreq(SystemInfo sysInfo) { - /* Try to read from the /sys/devices file first. If that doesn't work - * try manually inspecting the /proc/cpuinfo file. - */ - long cpuFreqKhz = sysInfo.readLongFromFile( - "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"); - if(cpuFreqKhz != -1) { - return cpuFreqKhz / 1000.0; - } - - FileReader fstream; - try { - fstream = new FileReader(CPU_FREQ_FILE); - } catch (FileNotFoundException e) { - Log.w(TAG, "Could not read cpu frequency file"); - return -1; - } - BufferedReader in = new BufferedReader(fstream, 500); - String line; - try { - while((line = in.readLine()) != null) { - if(line.startsWith("BogoMIPS")) { - return Double.parseDouble(line.trim().split("[ :]+")[1]); - } - } - } catch(IOException e) { - /* Failed to read from the cpu freq file. */ - } catch(NumberFormatException e) { - /* Frequency not formatted properly as a double. */ - } - Log.w(TAG, "Failed to read cpu frequency"); - return -1; - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import edu.umich.PowerTutor.phone.PhoneConstants; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.util.Recycler; +import edu.umich.PowerTutor.util.SystemInfo; + +import android.util.Log; +import android.os.Process; +import android.os.SystemClock; +import android.util.SparseArray; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; + +public class CPU extends PowerComponent { + public static class CpuData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static CpuData obtain() { + CpuData result = recycler.obtain(); + if(result != null) return result; + return new CpuData(); + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + public double sysPerc; + public double usrPerc; + public double freq; + + private CpuData() { + } + + public void init(double sysPerc, double usrPerc, double freq) { + this.sysPerc = sysPerc; + this.usrPerc = usrPerc; + this.freq = freq; + } + + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + StringBuilder res = new StringBuilder(); + res.append("CPU-sys ").append((long)Math.round(sysPerc)) + .append("\nCPU-usr ").append((long)Math.round(usrPerc)) + .append("\nCPU-freq ").append(freq) + .append("\n"); + out.write(res.toString()); + } + } + + private static final String TAG = "CPU"; + private static final String CPU_FREQ_FILE = "/proc/cpuinfo"; + private static final String STAT_FILE = "/proc/stat"; + + private CpuStateKeeper cpuState; + private SparseArray pidStates; + private SparseArray uidLinks; + + private int[] pids; + private long[] statsBuf; + + private PhoneConstants constants; + + public CPU(PhoneConstants constants) { + this.constants = constants; + cpuState = new CpuStateKeeper(SystemInfo.AID_ALL); + pidStates = new SparseArray(); + uidLinks = new SparseArray(); + statsBuf = new long[7]; + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + + SystemInfo sysInfo = SystemInfo.getInstance(); + double freq = readCpuFreq(sysInfo); + if(freq < 0) { + Log.w(TAG, "Failed to read cpu frequency"); + return result; + } + + if(!sysInfo.getUsrSysTotalTime(statsBuf)) { + Log.w(TAG, "Failed to read cpu times"); + return result; + } + + long usrTime = statsBuf[SystemInfo.INDEX_USER_TIME]; + long sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME]; + long totalTime = statsBuf[SystemInfo.INDEX_TOTAL_TIME]; + + boolean init = cpuState.isInitialized(); + cpuState.updateState(usrTime, sysTime, totalTime, iteration); + + if(init) { + CpuData data = CpuData.obtain(); + data.init(cpuState.getUsrPerc(), cpuState.getSysPerc(), freq); + result.setPowerData(data); + } + + uidLinks.clear(); + pids = sysInfo.getPids(pids); + int pidInd = 0; + if(pids != null) for(int pid : pids) { + if(pid < 0) { + break; + } + + CpuStateKeeper pidState; + if(pidInd < pidStates.size() && pidStates.keyAt(pidInd) == pid) { + pidState = pidStates.valueAt(pidInd); + } else { + int uid = sysInfo.getUidForPid(pid); + if(uid >= 0) { + pidState = new CpuStateKeeper(uid); + pidStates.put(pid, pidState); + } else { + /* Assume that this process no longer exists. */ + continue; + } + } + pidInd++; + + if(!pidState.isStale(iteration)) { + /* Nothing much is going on with this pid recently. We'll just + * assume that it's not using any of the cpu for this iteration. + */ + pidState.updateIteration(iteration, totalTime); + } else if(sysInfo.getPidUsrSysTime(pid, statsBuf)) { + usrTime = statsBuf[SystemInfo.INDEX_USER_TIME]; + sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME]; + + init = pidState.isInitialized(); + pidState.updateState(usrTime, sysTime, totalTime, iteration); + + if(!init) { + continue; + } + } + + CpuStateKeeper linkState = uidLinks.get(pidState.getUid()); + if(linkState == null) { + uidLinks.put(pidState.getUid(), pidState); + } else { + linkState.absorb(pidState); + } + } + + /* Remove processes that are no longer active. */ + for(int i = 0; i < pidStates.size(); i++) { + if(!pidStates.valueAt(i).isAlive(iteration)) { + pidStates.remove(pidStates.keyAt(i--)); + } + } + + /* Collect the summed uid information. */ + for(int i = 0; i < uidLinks.size(); i++) { + int uid = uidLinks.keyAt(i); + CpuStateKeeper linkState = uidLinks.valueAt(i); + + CpuData uidData = CpuData.obtain(); + predictAppUidState(uidData, linkState.getUsrPerc(), + linkState.getSysPerc(), freq); + result.addUidPowerData(uid, uidData); + } + + return result; + } + + /* This is the function that is responsible for predicting the cpu frequency + * state of the individual uid as though it were the only thing running. It + * simply is finding the lowest frequency that keeps the cpu usage under + * 70% assuming there is a linear relationship to the cpu utilization at + * different frequencies. + */ + private void predictAppUidState(CpuData uidData, double usrPerc, + double sysPerc, double freq) { + double[] freqs = constants.cpuFreqs(); + if(usrPerc + sysPerc < 1e-6) { + /* Don't waste time with the binary search if there is no utilization + * which will be the case a lot. + */ + uidData.init(sysPerc, usrPerc, freqs[0]); + return; + } + int lo = 0; + int hi = freqs.length - 1; + double perc = sysPerc + usrPerc; + while(lo < hi) { + int mid = (lo + hi) / 2; + double nperc = perc * freq / freqs[mid]; + if(nperc < 70) { + hi = mid; + } else { + lo = mid + 1; + } + } + uidData.init(sysPerc * freq / freqs[lo], usrPerc * freq / freqs[lo], + freqs[lo]); + } + + private static class CpuStateKeeper { + private int uid; + private long iteration; + private long lastUpdateIteration; + private long inactiveIterations; + + private long lastUsr; + private long lastSys; + private long lastTotal; + + private long sumUsr; + private long sumSys; + private long deltaTotal; + + private CpuStateKeeper(int uid) { + this.uid = uid; + lastUsr = lastSys = -1; + lastUpdateIteration = iteration = -1; + inactiveIterations = 0; + } + + public boolean isInitialized() { + return lastUsr != -1; + } + + public void updateIteration(long iteration, long totalTime) { + /* Process is still running but actually reading the cpu utilization has + * been skipped this iteration to avoid wasting cpu cycles as this process + * has not been very active recently. */ + sumUsr = 0; + sumSys = 0; + deltaTotal = totalTime - lastTotal; + if(deltaTotal < 1) deltaTotal = 1; + lastTotal = totalTime; + this.iteration = iteration; + } + + public void updateState(long usrTime, long sysTime, long totalTime, + long iteration) { + sumUsr = usrTime - lastUsr; + sumSys = sysTime - lastSys; + deltaTotal = totalTime - lastTotal; + if(deltaTotal < 1) deltaTotal = 1; + lastUsr = usrTime; + lastSys = sysTime; + lastTotal = totalTime; + lastUpdateIteration = this.iteration = iteration; + + if(getUsrPerc() + getSysPerc() < 0.1) { + inactiveIterations++; + } else { + inactiveIterations = 0; + } + } + + public int getUid() { + return uid; + } + + public void absorb(CpuStateKeeper s) { + sumUsr += s.sumUsr; + sumSys += s.sumSys; + } + + public double getUsrPerc() { + return 100.0 * sumUsr / Math.max(sumUsr + sumSys, deltaTotal); + } + + public double getSysPerc() { + return 100.0 * sumSys / Math.max(sumUsr + sumSys, deltaTotal); + } + + public boolean isAlive(long iteration) { + return this.iteration == iteration; + } + + public boolean isStale(long iteration) { + return 1L << (iteration - lastUpdateIteration) > + inactiveIterations * inactiveIterations; + } + } + + @Override + public boolean hasUidInformation() { + return true; + } + + @Override + public String getComponentName() { + return "CPU"; + } + + /* Returns the frequency of the processor in Mhz. If the frequency cannot + * be determined returns a negative value instead. + */ + private double readCpuFreq(SystemInfo sysInfo) { + /* Try to read from the /sys/devices file first. If that doesn't work + * try manually inspecting the /proc/cpuinfo file. + */ + long cpuFreqKhz = sysInfo.readLongFromFile( + "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"); + if(cpuFreqKhz != -1) { + return cpuFreqKhz / 1000.0; + } + + FileReader fstream; + try { + fstream = new FileReader(CPU_FREQ_FILE); + } catch (FileNotFoundException e) { + Log.w(TAG, "Could not read cpu frequency file"); + return -1; + } + BufferedReader in = new BufferedReader(fstream, 500); + String line; + try { + while((line = in.readLine()) != null) { + if(line.startsWith("BogoMIPS")) { + return Double.parseDouble(line.trim().split("[ :]+")[1]); + } + } + } catch(IOException e) { + /* Failed to read from the cpu freq file. */ + } catch(NumberFormatException e) { + /* Frequency not formatted properly as a double. */ + } + Log.w(TAG, "Failed to read cpu frequency"); + return -1; + } +} diff --git a/src/edu/umich/PowerTutor/components/GPS.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/GPS.java similarity index 97% rename from src/edu/umich/PowerTutor/components/GPS.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/GPS.java index c350819..3f8882b 100644 --- a/src/edu/umich/PowerTutor/components/GPS.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/GPS.java @@ -1,456 +1,456 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import edu.umich.PowerTutor.PowerNotifications; -import edu.umich.PowerTutor.phone.PhoneConstants; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.util.NotificationService; -import edu.umich.PowerTutor.util.Recycler; -import edu.umich.PowerTutor.util.SystemInfo; - -import android.content.Context; -import android.location.GpsSatellite; -import android.location.GpsStatus; -import android.location.LocationManager; -import android.os.Build; -import android.os.SystemClock; -import android.util.Log; -import android.util.SparseArray; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.Map; - -public class GPS extends PowerComponent { - public static class GpsData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static GpsData obtain() { - GpsData result = recycler.obtain(); - if(result != null) return result; - return new GpsData(); - } - - /* The time in seconds since the last iteration of data. */ - public double[] stateTimes; - /* The number of satellites. This number is only available while the GPS is - * in the on state. Otherwise it is 0. - */ - public int satellites; - - private GpsData() { - stateTimes = new double[GPS.POWER_STATES]; - } - - public void init(double[] stateTimes, int satellites) { - for(int i = 0; i < GPS.POWER_STATES; i++) { - this.stateTimes[i] = stateTimes[i]; - } - this.satellites = satellites; - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - @Override - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - StringBuilder res = new StringBuilder(); - res.append("GPS-state-times"); - for(int i = 0; i < GPS.POWER_STATES; i++) { - res.append(" ").append(stateTimes[i]); - } - res.append("\nGPS-sattelites ").append(satellites).append("\n"); - out.write(res.toString()); - } - } - - public static final int POWER_STATES = 3; - public static final int POWER_STATE_OFF = 0; - public static final int POWER_STATE_SLEEP = 1; - public static final int POWER_STATE_ON = 2; - public static final String[] POWER_STATE_NAMES = {"OFF", "SLEEP", "ON"}; - - private static final String TAG = "GPS"; - - private static final int HOOK_LIBGPS = 1; - private static final int HOOK_GPS_STATUS_LISTENER = 2; - private static final int HOOK_NOTIFICATIONS = 4; - private static final int HOOK_TIMER = 8; - - /* A named pipe written to by the hacked libgps library. */ - private static String HOOK_GPS_STATUS_FILE = "/data/misc/gps.status"; - - private GpsStatus.Listener gpsListener; - private Thread statusThread; - private PowerNotifications notificationReceiver; - - private Context context; - private LocationManager locationManager; - private GpsStatus lastStatus; - private boolean hasUidInfo; - private long sleepTime; - private long lastTime; - - private GpsStateKeeper gpsState; - private SparseArray uidStates; - - private static final int GPS_STATUS_SESSION_BEGIN = 1; - private static final int GPS_STATUS_SESSION_END = 2; - private static final int GPS_STATUS_ENGINE_ON = 3; - private static final int GPS_STATUS_ENGINE_OFF = 4; - - public GPS(Context context, PhoneConstants constants) { - this.context = context; - uidStates = new SparseArray(); - sleepTime = (long)Math.round(1000.0 * constants.gpsSleepTime()); - - hasUidInfo = NotificationService.available(); - - int hookMethod = 0; - final File gpsStatusFile = new File(HOOK_GPS_STATUS_FILE); - if(gpsStatusFile.exists()) { - /* The libgps hack appears to be available. Let's use this to gather - * our status updates from the GPS. - */ - hookMethod = HOOK_LIBGPS; - } else { - /* We can always use the status listener hook and perhaps the notification - * hook if we are running eclaire or higher and the notification hook - * is installed. We can only do this on eclaire or higher because it - * wasn't until eclaire that they fixed a bug where they didn't maintain - * a wakelock while the gps engine was on. - */ - hookMethod = HOOK_GPS_STATUS_LISTENER; - try { - if(NotificationService.available() && - Integer.parseInt(Build.VERSION.SDK) >= 5 /* eclaire or higher */) { - hookMethod |= HOOK_NOTIFICATIONS; - } - } catch(NumberFormatException e) { - Log.w(TAG, "Could not parse sdk version: " + Build.VERSION.SDK); - } - } - /* If we don't have a way of getting the off<->sleep transitions through - * notifications let's just use a timer and simulat the state of the gps - * instead. - */ - if((hookMethod & (HOOK_LIBGPS | HOOK_NOTIFICATIONS)) == 0) { - hookMethod |= HOOK_TIMER; - } - - /* Create the object that keeps track of the physical GPS state. */ - gpsState = new GpsStateKeeper(hookMethod, sleepTime); - - /* No matter what we are going to register a GpsStatus listener so that we - * can get the satellite count. Also if anything goes wrong with the - * libgps hook we will revert to using this. - */ - locationManager = (LocationManager) - context.getSystemService(Context.LOCATION_SERVICE); - gpsListener = new GpsStatus.Listener() { - public void onGpsStatusChanged(int event){ - if(event == GpsStatus.GPS_EVENT_STARTED) { - gpsState.updateEvent(GPS_STATUS_SESSION_BEGIN, - HOOK_GPS_STATUS_LISTENER); - } else if(event == GpsStatus.GPS_EVENT_STOPPED) { - gpsState.updateEvent(GPS_STATUS_SESSION_END, - HOOK_GPS_STATUS_LISTENER); - } - synchronized(GPS.this) { - lastStatus = locationManager.getGpsStatus(lastStatus); - } - } - }; - locationManager.addGpsStatusListener(gpsListener); - - /* No matter what we register a notification service listener as well so - * that we can get uid information if it's available. - */ - if(hasUidInfo) { - notificationReceiver = new NotificationService.DefaultReceiver() { - public void noteStartWakelock(int uid, String name, int type) { - if(uid == SystemInfo.AID_SYSTEM && - "GpsLocationProvider".equals(name)) { - gpsState.updateEvent(GPS_STATUS_ENGINE_ON, HOOK_NOTIFICATIONS); - } - } - - public void noteStopWakelock(int uid, String name, int type) { - if(uid == SystemInfo.AID_SYSTEM && - "GpsLocationProvider".equals(name)) { - gpsState.updateEvent(GPS_STATUS_ENGINE_OFF, HOOK_NOTIFICATIONS); - } - } - - public void noteStartGps(int uid) { - updateUidEvent(uid, GPS_STATUS_SESSION_BEGIN, HOOK_NOTIFICATIONS); - } - - public void noteStopGps(int uid) { - updateUidEvent(uid, GPS_STATUS_SESSION_END, HOOK_NOTIFICATIONS); - } - }; - NotificationService.addHook(notificationReceiver); - } - - if(gpsStatusFile.exists()) { - /* Start a thread to read from the named pipe and feed us status updates. - */ - statusThread = new Thread() { - public void run() { - try { - java.io.FileInputStream fin = - new java.io.FileInputStream(gpsStatusFile); - for(int event = fin.read(); !interrupted() && event != -1; - event = fin.read()) { - gpsState.updateEvent(event, HOOK_LIBGPS); - } - } catch(IOException e) { - e.printStackTrace(); - } - if(!interrupted()) { - // TODO: Have this instead just switch to use different hooks. - Log.w(TAG, "GPS status thread exited. " + - "No longer gathering gps data."); - } - } - }; - statusThread.start(); - } - } - - private void updateUidEvent(int uid, int event, int source) { - synchronized(uidStates) { - GpsStateKeeper state = uidStates.get(uid); - if(state == null) { - state = new GpsStateKeeper(HOOK_NOTIFICATIONS | HOOK_TIMER, sleepTime, - lastTime); - uidStates.put(uid, state); - } - state.updateEvent(event, source); - } - } - - @Override - protected void onExit() { - if(gpsListener != null) { - locationManager.removeGpsStatusListener(gpsListener); - } - if(statusThread != null) { - statusThread.interrupt(); - } - if(notificationReceiver != null) { - NotificationService.removeHook(notificationReceiver); - } - super.onExit(); - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - - /* Get the number of satellites that were available in the last update. */ - int satellites = 0; - synchronized(this) { - if(lastStatus != null) { - for(GpsSatellite satellite : lastStatus.getSatellites()) { - satellites++; - } - } - } - - /* Get the power data for the physical gps device. */ - GpsData power = GpsData.obtain(); - synchronized(gpsState) { - double[] stateTimes = gpsState.getStateTimesLocked(); - int curState = gpsState.getCurrentStateLocked(); - power.init(stateTimes, curState == POWER_STATE_ON ? satellites : 0); - gpsState.resetTimesLocked(); - } - result.setPowerData(power); - - /* Get the power data for each uid if we have information on it. */ - if(hasUidInfo) synchronized(uidStates) { - lastTime = beginTime + iterationInterval * iteration; - for(int i = 0; i < uidStates.size(); i++) { - int uid = uidStates.keyAt(i); - GpsStateKeeper state = uidStates.valueAt(i); - - double[] stateTimes = state.getStateTimesLocked(); - int curState = state.getCurrentStateLocked(); - GpsData uidPower = GpsData.obtain(); - uidPower.init(stateTimes, curState == POWER_STATE_ON ? satellites : 0); - state.resetTimesLocked(); - - result.addUidPowerData(uid, uidPower); - - /* Remove state information for uids no longer using the gps. */ - if(curState == POWER_STATE_OFF) { - uidStates.remove(uid); - i--; - } - } - } - - return result; - } - - @Override - public boolean hasUidInformation() { - return hasUidInfo; - } - - /* This class is used to maintain the actual GPS state in addition to - * simulating individual uid states. - */ - private static class GpsStateKeeper { - private double[] stateTimes; - private long lastTime; - private int curState; - - /* The sum of whatever hook sources are valid. See the HOOK_ constants. */ - private int hookMask; - /* The time that the GPS hardware should turn off. This is only used - * if HOOK_TIMER is in the hookMask. - */ - private long offTime; - /* Gives the time that the GPS stays in the sleep state after the session - * has ended in milliseconds. - */ - private long sleepTime; - - public GpsStateKeeper(int hookMask, long sleepTime) { - this(hookMask, sleepTime, SystemClock.elapsedRealtime()); - } - - public GpsStateKeeper(int hookMask, long sleepTime, long lastTime) { - this.hookMask = hookMask; - this.sleepTime = sleepTime; /* This isn't required if HOOK_TIEMR is not - * set. */ - this.lastTime = lastTime; - stateTimes = new double[POWER_STATES]; - curState = POWER_STATE_OFF; - offTime = -1; - } - - /* Make sure that you have a lock on this before calling. */ - public double[] getStateTimesLocked() { - updateTimesLocked(); - - /* Let's normalize the times so that power measurements are consistent. */ - double total = 0; - for(int i = 0; i < POWER_STATES; i++) { - total += stateTimes[i]; - } - if(total == 0) total = 1; - for(int i = 0; i < POWER_STATES; i++) { - stateTimes[i] /= total; - } - - return stateTimes; - } - - public void resetTimesLocked() { - for(int i = 0; i < POWER_STATES; i++) { - stateTimes[i] = 0; - } - } - - public int getCurrentStateLocked() { - return curState; - } - - /* Make sure that you have a lock on this before calling. */ - private void updateTimesLocked() { - /* Update the time we were in the previous state. */ - long curTime = SystemClock.elapsedRealtime(); - - /* Check if the GPS has gone to sleep as a result of a timer. */ - if((hookMask & HOOK_TIMER) != 0 && offTime != -1 && - offTime < curTime) { - stateTimes[curState] += (offTime - lastTime) / 1000.0; - curState = POWER_STATE_OFF; - offTime = -1; - } - - /* Update the amount of time that we've been in the current state. */ - stateTimes[curState] += (curTime - lastTime) / 1000.0; - lastTime = curTime; - } - - /* When a hook source gets an event it should report it to updateEvent. - * The only exception is HOOK_TIMER which is handled within this class - * itself. - */ - public void updateEvent(int event, int source) { - synchronized(this) { - if((hookMask & source) == 0) { - /* We are not using this hook source, ignore. */ - return; - } - - updateTimesLocked(); - int oldState = curState; - switch(event) { - case GPS_STATUS_SESSION_BEGIN: - curState = POWER_STATE_ON; - break; - case GPS_STATUS_SESSION_END: - if(curState == POWER_STATE_ON) { - curState = POWER_STATE_SLEEP; - } - break; - case GPS_STATUS_ENGINE_ON: - if(curState == POWER_STATE_OFF) { - curState = POWER_STATE_SLEEP; - } - break; - case GPS_STATUS_ENGINE_OFF: - curState = POWER_STATE_OFF; - break; - default: - Log.w(TAG, "Unknown GPS event captured"); - } - if(curState != oldState) { - if(oldState == POWER_STATE_ON && curState == POWER_STATE_SLEEP) { - offTime = SystemClock.elapsedRealtime() + sleepTime; - } else { - /* Any other state transition should reset the off timer. */ - offTime = -1; - } - } - } - } - } - - @Override - public String getComponentName() { - return "GPS"; - } -} - +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import edu.umich.PowerTutor.PowerNotifications; +import edu.umich.PowerTutor.phone.PhoneConstants; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.util.NotificationService; +import edu.umich.PowerTutor.util.Recycler; +import edu.umich.PowerTutor.util.SystemInfo; + +import android.content.Context; +import android.location.GpsSatellite; +import android.location.GpsStatus; +import android.location.LocationManager; +import android.os.Build; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Map; + +public class GPS extends PowerComponent { + public static class GpsData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static GpsData obtain() { + GpsData result = recycler.obtain(); + if(result != null) return result; + return new GpsData(); + } + + /* The time in seconds since the last iteration of data. */ + public double[] stateTimes; + /* The number of satellites. This number is only available while the GPS is + * in the on state. Otherwise it is 0. + */ + public int satellites; + + private GpsData() { + stateTimes = new double[GPS.POWER_STATES]; + } + + public void init(double[] stateTimes, int satellites) { + for(int i = 0; i < GPS.POWER_STATES; i++) { + this.stateTimes[i] = stateTimes[i]; + } + this.satellites = satellites; + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + @Override + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + StringBuilder res = new StringBuilder(); + res.append("GPS-state-times"); + for(int i = 0; i < GPS.POWER_STATES; i++) { + res.append(" ").append(stateTimes[i]); + } + res.append("\nGPS-sattelites ").append(satellites).append("\n"); + out.write(res.toString()); + } + } + + public static final int POWER_STATES = 3; + public static final int POWER_STATE_OFF = 0; + public static final int POWER_STATE_SLEEP = 1; + public static final int POWER_STATE_ON = 2; + public static final String[] POWER_STATE_NAMES = {"OFF", "SLEEP", "ON"}; + + private static final String TAG = "GPS"; + + private static final int HOOK_LIBGPS = 1; + private static final int HOOK_GPS_STATUS_LISTENER = 2; + private static final int HOOK_NOTIFICATIONS = 4; + private static final int HOOK_TIMER = 8; + + /* A named pipe written to by the hacked libgps library. */ + private static String HOOK_GPS_STATUS_FILE = "/data/misc/gps.status"; + + private GpsStatus.Listener gpsListener; + private Thread statusThread; + private PowerNotifications notificationReceiver; + + private Context context; + private LocationManager locationManager; + private GpsStatus lastStatus; + private boolean hasUidInfo; + private long sleepTime; + private long lastTime; + + private GpsStateKeeper gpsState; + private SparseArray uidStates; + + private static final int GPS_STATUS_SESSION_BEGIN = 1; + private static final int GPS_STATUS_SESSION_END = 2; + private static final int GPS_STATUS_ENGINE_ON = 3; + private static final int GPS_STATUS_ENGINE_OFF = 4; + + public GPS(Context context, PhoneConstants constants) { + this.context = context; + uidStates = new SparseArray(); + sleepTime = (long)Math.round(1000.0 * constants.gpsSleepTime()); + + hasUidInfo = NotificationService.available(); + + int hookMethod = 0; + final File gpsStatusFile = new File(HOOK_GPS_STATUS_FILE); + if(gpsStatusFile.exists()) { + /* The libgps hack appears to be available. Let's use this to gather + * our status updates from the GPS. + */ + hookMethod = HOOK_LIBGPS; + } else { + /* We can always use the status listener hook and perhaps the notification + * hook if we are running eclaire or higher and the notification hook + * is installed. We can only do this on eclaire or higher because it + * wasn't until eclaire that they fixed a bug where they didn't maintain + * a wakelock while the gps engine was on. + */ + hookMethod = HOOK_GPS_STATUS_LISTENER; + try { + if(NotificationService.available() && + Integer.parseInt(Build.VERSION.SDK) >= 5 /* eclaire or higher */) { + hookMethod |= HOOK_NOTIFICATIONS; + } + } catch(NumberFormatException e) { + Log.w(TAG, "Could not parse sdk version: " + Build.VERSION.SDK); + } + } + /* If we don't have a way of getting the off<->sleep transitions through + * notifications let's just use a timer and simulat the state of the gps + * instead. + */ + if((hookMethod & (HOOK_LIBGPS | HOOK_NOTIFICATIONS)) == 0) { + hookMethod |= HOOK_TIMER; + } + + /* Create the object that keeps track of the physical GPS state. */ + gpsState = new GpsStateKeeper(hookMethod, sleepTime); + + /* No matter what we are going to register a GpsStatus listener so that we + * can get the satellite count. Also if anything goes wrong with the + * libgps hook we will revert to using this. + */ + locationManager = (LocationManager) + context.getSystemService(Context.LOCATION_SERVICE); + gpsListener = new GpsStatus.Listener() { + public void onGpsStatusChanged(int event){ + if(event == GpsStatus.GPS_EVENT_STARTED) { + gpsState.updateEvent(GPS_STATUS_SESSION_BEGIN, + HOOK_GPS_STATUS_LISTENER); + } else if(event == GpsStatus.GPS_EVENT_STOPPED) { + gpsState.updateEvent(GPS_STATUS_SESSION_END, + HOOK_GPS_STATUS_LISTENER); + } + synchronized(GPS.this) { + lastStatus = locationManager.getGpsStatus(lastStatus); + } + } + }; + locationManager.addGpsStatusListener(gpsListener); + + /* No matter what we register a notification service listener as well so + * that we can get uid information if it's available. + */ + if(hasUidInfo) { + notificationReceiver = new NotificationService.DefaultReceiver() { + public void noteStartWakelock(int uid, String name, int type) { + if(uid == SystemInfo.AID_SYSTEM && + "GpsLocationProvider".equals(name)) { + gpsState.updateEvent(GPS_STATUS_ENGINE_ON, HOOK_NOTIFICATIONS); + } + } + + public void noteStopWakelock(int uid, String name, int type) { + if(uid == SystemInfo.AID_SYSTEM && + "GpsLocationProvider".equals(name)) { + gpsState.updateEvent(GPS_STATUS_ENGINE_OFF, HOOK_NOTIFICATIONS); + } + } + + public void noteStartGps(int uid) { + updateUidEvent(uid, GPS_STATUS_SESSION_BEGIN, HOOK_NOTIFICATIONS); + } + + public void noteStopGps(int uid) { + updateUidEvent(uid, GPS_STATUS_SESSION_END, HOOK_NOTIFICATIONS); + } + }; + NotificationService.addHook(notificationReceiver); + } + + if(gpsStatusFile.exists()) { + /* Start a thread to read from the named pipe and feed us status updates. + */ + statusThread = new Thread() { + public void run() { + try { + java.io.FileInputStream fin = + new java.io.FileInputStream(gpsStatusFile); + for(int event = fin.read(); !interrupted() && event != -1; + event = fin.read()) { + gpsState.updateEvent(event, HOOK_LIBGPS); + } + } catch(IOException e) { + e.printStackTrace(); + } + if(!interrupted()) { + // TODO: Have this instead just switch to use different hooks. + Log.w(TAG, "GPS status thread exited. " + + "No longer gathering gps data."); + } + } + }; + statusThread.start(); + } + } + + private void updateUidEvent(int uid, int event, int source) { + synchronized(uidStates) { + GpsStateKeeper state = uidStates.get(uid); + if(state == null) { + state = new GpsStateKeeper(HOOK_NOTIFICATIONS | HOOK_TIMER, sleepTime, + lastTime); + uidStates.put(uid, state); + } + state.updateEvent(event, source); + } + } + + @Override + protected void onExit() { + if(gpsListener != null) { + locationManager.removeGpsStatusListener(gpsListener); + } + if(statusThread != null) { + statusThread.interrupt(); + } + if(notificationReceiver != null) { + NotificationService.removeHook(notificationReceiver); + } + super.onExit(); + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + + /* Get the number of satellites that were available in the last update. */ + int satellites = 0; + synchronized(this) { + if(lastStatus != null) { + for(GpsSatellite satellite : lastStatus.getSatellites()) { + satellites++; + } + } + } + + /* Get the power data for the physical gps device. */ + GpsData power = GpsData.obtain(); + synchronized(gpsState) { + double[] stateTimes = gpsState.getStateTimesLocked(); + int curState = gpsState.getCurrentStateLocked(); + power.init(stateTimes, curState == POWER_STATE_ON ? satellites : 0); + gpsState.resetTimesLocked(); + } + result.setPowerData(power); + + /* Get the power data for each uid if we have information on it. */ + if(hasUidInfo) synchronized(uidStates) { + lastTime = beginTime + iterationInterval * iteration; + for(int i = 0; i < uidStates.size(); i++) { + int uid = uidStates.keyAt(i); + GpsStateKeeper state = uidStates.valueAt(i); + + double[] stateTimes = state.getStateTimesLocked(); + int curState = state.getCurrentStateLocked(); + GpsData uidPower = GpsData.obtain(); + uidPower.init(stateTimes, curState == POWER_STATE_ON ? satellites : 0); + state.resetTimesLocked(); + + result.addUidPowerData(uid, uidPower); + + /* Remove state information for uids no longer using the gps. */ + if(curState == POWER_STATE_OFF) { + uidStates.remove(uid); + i--; + } + } + } + + return result; + } + + @Override + public boolean hasUidInformation() { + return hasUidInfo; + } + + /* This class is used to maintain the actual GPS state in addition to + * simulating individual uid states. + */ + private static class GpsStateKeeper { + private double[] stateTimes; + private long lastTime; + private int curState; + + /* The sum of whatever hook sources are valid. See the HOOK_ constants. */ + private int hookMask; + /* The time that the GPS hardware should turn off. This is only used + * if HOOK_TIMER is in the hookMask. + */ + private long offTime; + /* Gives the time that the GPS stays in the sleep state after the session + * has ended in milliseconds. + */ + private long sleepTime; + + public GpsStateKeeper(int hookMask, long sleepTime) { + this(hookMask, sleepTime, SystemClock.elapsedRealtime()); + } + + public GpsStateKeeper(int hookMask, long sleepTime, long lastTime) { + this.hookMask = hookMask; + this.sleepTime = sleepTime; /* This isn't required if HOOK_TIEMR is not + * set. */ + this.lastTime = lastTime; + stateTimes = new double[POWER_STATES]; + curState = POWER_STATE_OFF; + offTime = -1; + } + + /* Make sure that you have a lock on this before calling. */ + public double[] getStateTimesLocked() { + updateTimesLocked(); + + /* Let's normalize the times so that power measurements are consistent. */ + double total = 0; + for(int i = 0; i < POWER_STATES; i++) { + total += stateTimes[i]; + } + if(total == 0) total = 1; + for(int i = 0; i < POWER_STATES; i++) { + stateTimes[i] /= total; + } + + return stateTimes; + } + + public void resetTimesLocked() { + for(int i = 0; i < POWER_STATES; i++) { + stateTimes[i] = 0; + } + } + + public int getCurrentStateLocked() { + return curState; + } + + /* Make sure that you have a lock on this before calling. */ + private void updateTimesLocked() { + /* Update the time we were in the previous state. */ + long curTime = SystemClock.elapsedRealtime(); + + /* Check if the GPS has gone to sleep as a result of a timer. */ + if((hookMask & HOOK_TIMER) != 0 && offTime != -1 && + offTime < curTime) { + stateTimes[curState] += (offTime - lastTime) / 1000.0; + curState = POWER_STATE_OFF; + offTime = -1; + } + + /* Update the amount of time that we've been in the current state. */ + stateTimes[curState] += (curTime - lastTime) / 1000.0; + lastTime = curTime; + } + + /* When a hook source gets an event it should report it to updateEvent. + * The only exception is HOOK_TIMER which is handled within this class + * itself. + */ + public void updateEvent(int event, int source) { + synchronized(this) { + if((hookMask & source) == 0) { + /* We are not using this hook source, ignore. */ + return; + } + + updateTimesLocked(); + int oldState = curState; + switch(event) { + case GPS_STATUS_SESSION_BEGIN: + curState = POWER_STATE_ON; + break; + case GPS_STATUS_SESSION_END: + if(curState == POWER_STATE_ON) { + curState = POWER_STATE_SLEEP; + } + break; + case GPS_STATUS_ENGINE_ON: + if(curState == POWER_STATE_OFF) { + curState = POWER_STATE_SLEEP; + } + break; + case GPS_STATUS_ENGINE_OFF: + curState = POWER_STATE_OFF; + break; + default: + Log.w(TAG, "Unknown GPS event captured"); + } + if(curState != oldState) { + if(oldState == POWER_STATE_ON && curState == POWER_STATE_SLEEP) { + offTime = SystemClock.elapsedRealtime() + sleepTime; + } else { + /* Any other state transition should reset the off timer. */ + offTime = -1; + } + } + } + } + } + + @Override + public String getComponentName() { + return "GPS"; + } +} + diff --git a/src/edu/umich/PowerTutor/components/LCD.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/LCD.java similarity index 96% rename from src/edu/umich/PowerTutor/components/LCD.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/LCD.java index 869cd59..1587c93 100644 --- a/src/edu/umich/PowerTutor/components/LCD.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/LCD.java @@ -1,179 +1,179 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import edu.umich.PowerTutor.PowerNotifications; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.util.ForegroundDetector; -import edu.umich.PowerTutor.util.NotificationService; -import edu.umich.PowerTutor.util.Recycler; -import edu.umich.PowerTutor.util.SystemInfo; - -import android.app.ActivityManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.provider.Settings; -import android.os.Process; -import android.util.Log; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.List; - -public class LCD extends PowerComponent { - public static class LcdData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static LcdData obtain() { - LcdData result = recycler.obtain(); - if(result != null) return result; - return new LcdData(); - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - public int brightness; - public boolean screenOn; - - private LcdData() { - } - - public void init(int brightness, boolean screenOn) { - this.brightness = brightness; - this.screenOn = screenOn; - } - - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - StringBuilder res = new StringBuilder(); - res.append("LCD-brightness ").append(brightness) - .append("\nLCD-screen-on ").append(screenOn).append("\n"); - out.write(res.toString()); - } - } - - private final String TAG = "LCD"; - private static final String[] BACKLIGHT_BRIGHTNESS_FILES = { - "/sys/devices/virtual/leds/lcd-backlight/brightness", - "/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness", - }; - - private Context context; - private ForegroundDetector foregroundDetector; - private BroadcastReceiver broadcastReceiver; - private boolean screenOn; - - private String brightnessFile; - - public LCD(Context context) { - this.context = context; - screenOn = true; - - if(context == null) { - return; - } - - foregroundDetector = new ForegroundDetector((ActivityManager) - context.getSystemService(context.ACTIVITY_SERVICE)); - broadcastReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - synchronized(this) { - if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { - screenOn = false; - } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { - screenOn = true; - } - } - }; - }; - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - context.registerReceiver(broadcastReceiver, intentFilter); - - for(int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) { - if(new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) { - brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i]; - } - } - } - - @Override - protected void onExit() { - context.unregisterReceiver(broadcastReceiver); - super.onExit(); - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - - boolean screen; - synchronized(this) { - screen = screenOn; - } - - int brightness; - if(brightnessFile != null) { - brightness = (int)SystemInfo.getInstance() - .readLongFromFile(brightnessFile); - } else { - try { - brightness = Settings.System.getInt(context.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS); - } catch(Settings.SettingNotFoundException ex) { - Log.w(TAG, "Could not retrieve brightness information"); - return result; - } - } - if(brightness < 0 || 255 < brightness) { - Log.w(TAG, "Could not retrieve brightness information"); - return result; - } - - LcdData data = LcdData.obtain(); - data.init(brightness, screen); - result.setPowerData(data); - - if(screen) { - LcdData uidData = LcdData.obtain(); - uidData.init(brightness, screen); - result.addUidPowerData(foregroundDetector.getForegroundUid(), uidData); - } - - return result; - } - - @Override - public boolean hasUidInformation() { - return true; - } - - @Override - public String getComponentName() { - return "LCD"; - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import edu.umich.PowerTutor.PowerNotifications; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.util.ForegroundDetector; +import edu.umich.PowerTutor.util.NotificationService; +import edu.umich.PowerTutor.util.Recycler; +import edu.umich.PowerTutor.util.SystemInfo; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.provider.Settings; +import android.os.Process; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.List; + +public class LCD extends PowerComponent { + public static class LcdData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static LcdData obtain() { + LcdData result = recycler.obtain(); + if(result != null) return result; + return new LcdData(); + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + public int brightness; + public boolean screenOn; + + private LcdData() { + } + + public void init(int brightness, boolean screenOn) { + this.brightness = brightness; + this.screenOn = screenOn; + } + + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + StringBuilder res = new StringBuilder(); + res.append("LCD-brightness ").append(brightness) + .append("\nLCD-screen-on ").append(screenOn).append("\n"); + out.write(res.toString()); + } + } + + private final String TAG = "LCD"; + private static final String[] BACKLIGHT_BRIGHTNESS_FILES = { + "/sys/devices/virtual/leds/lcd-backlight/brightness", + "/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness", + }; + + private Context context; + private ForegroundDetector foregroundDetector; + private BroadcastReceiver broadcastReceiver; + private boolean screenOn; + + private String brightnessFile; + + public LCD(Context context) { + this.context = context; + screenOn = true; + + if(context == null) { + return; + } + + foregroundDetector = new ForegroundDetector((ActivityManager) + context.getSystemService(context.ACTIVITY_SERVICE)); + broadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + synchronized(this) { + if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + screenOn = false; + } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { + screenOn = true; + } + } + }; + }; + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + context.registerReceiver(broadcastReceiver, intentFilter); + + for(int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) { + if(new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) { + brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i]; + } + } + } + + @Override + protected void onExit() { + context.unregisterReceiver(broadcastReceiver); + super.onExit(); + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + + boolean screen; + synchronized(this) { + screen = screenOn; + } + + int brightness; + if(brightnessFile != null) { + brightness = (int)SystemInfo.getInstance() + .readLongFromFile(brightnessFile); + } else { + try { + brightness = Settings.System.getInt(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS); + } catch(Settings.SettingNotFoundException ex) { + Log.w(TAG, "Could not retrieve brightness information"); + return result; + } + } + if(brightness < 0 || 255 < brightness) { + Log.w(TAG, "Could not retrieve brightness information"); + return result; + } + + LcdData data = LcdData.obtain(); + data.init(brightness, screen); + result.setPowerData(data); + + if(screen) { + LcdData uidData = LcdData.obtain(); + uidData.init(brightness, screen); + result.addUidPowerData(foregroundDetector.getForegroundUid(), uidData); + } + + return result; + } + + @Override + public boolean hasUidInformation() { + return true; + } + + @Override + public String getComponentName() { + return "LCD"; + } +} diff --git a/src/edu/umich/PowerTutor/components/OLED.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/OLED.java similarity index 96% rename from src/edu/umich/PowerTutor/components/OLED.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/OLED.java index 9987709..4be813f 100644 --- a/src/edu/umich/PowerTutor/components/OLED.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/OLED.java @@ -1,311 +1,311 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import edu.umich.PowerTutor.PowerNotifications; -import edu.umich.PowerTutor.phone.PhoneConstants; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.util.NativeLoader; -import edu.umich.PowerTutor.util.NotificationService; -import edu.umich.PowerTutor.util.Recycler; -import edu.umich.PowerTutor.util.SystemInfo; -import edu.umich.PowerTutor.util.ForegroundDetector; - -import android.app.ActivityManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.provider.Settings; -import android.os.Process; -import android.util.Log; -import android.util.DisplayMetrics; -import android.view.WindowManager; - -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.RandomAccessFile; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.List; -import java.util.Random; - -import java.io.*; -import java.nio.*; -import java.nio.channels.*; - -public class OLED extends PowerComponent { - public static class OledData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static OledData obtain() { - OledData result = recycler.obtain(); - if(result != null) return result; - return new OledData(); - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - public int brightness; - public double pixPower; - public boolean screenOn; - - private OledData() { - } - - public void init() { - this.screenOn = false; - } - - public void init(int brightness, double pixPower) { - screenOn = true; - this.brightness = brightness; - this.pixPower = pixPower; - } - - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - out.write("OLED-brightness " + brightness + "\n"); - out.write("OLED-pix-power " + pixPower + "\n"); - out.write("OLED-screen-on " + screenOn + "\n"); - } - } - - private static final String TAG = "OLED"; - private static final String[] BACKLIGHT_BRIGHTNESS_FILES = { - "/sys/class/leds/lcd-backlight/brightness", - "/sys/devices/virtual/leds/lcd-backlight/brightness", - "/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness", - }; - - private Context context; - private ForegroundDetector foregroundDetector; - private BroadcastReceiver broadcastReceiver; - private boolean screenOn; - - private File frameBufferFile; - - private int screenWidth; - private int screenHeight; - - private static final int NUMBER_OF_SAMPLES = 500; - private int[] samples; - - private String brightnessFile; - - /* Coefficients pre-computed for pix power calculations. - */ - private double rcoef; - private double gcoef; - private double bcoef; - private double modul_coef; - - public OLED(Context context, PhoneConstants constants) { - this.context = context; - screenOn = true; - - foregroundDetector = new ForegroundDetector((ActivityManager) - context.getSystemService(context.ACTIVITY_SERVICE)); - broadcastReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - synchronized(this) { - if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { - screenOn = false; - } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { - screenOn = true; - } - } - }; - }; - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - context.registerReceiver(broadcastReceiver, intentFilter); - - frameBufferFile = new File("/dev/fb0"); - if(!frameBufferFile.exists()) { - frameBufferFile = new File("/dev/graphics/fb0"); - } - if(frameBufferFile.exists()) try { - /* Check if we already have permission to read the frame buffer. */ - boolean readOk = false; - try { - RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r"); - int b = fin.read(); - fin.close(); - readOk = true; - } catch(IOException e) { - } - /* Don't have permission, try to change permission as root. */ - if(!readOk) { - java.lang.Process p = Runtime.getRuntime().exec("su"); - DataOutputStream os = new DataOutputStream(p.getOutputStream()); - os.writeBytes("chown " + android.os.Process.myUid() + - " " + frameBufferFile.getAbsolutePath() + "\n"); - os.writeBytes("chown app_" + (android.os.Process.myUid() - - SystemInfo.AID_APP) + - " " + frameBufferFile.getAbsolutePath() + "\n"); - os.writeBytes("chmod 660 " + frameBufferFile.getAbsolutePath() + "\n"); - os.writeBytes("exit\n"); - os.flush(); - p.waitFor(); - if(p.exitValue() != 0) { - Log.i(TAG, "failed to change permissions on frame buffer"); - } - } - } catch (InterruptedException e) { - Log.i(TAG, "changing permissions on frame buffer interrupted"); - } catch (IOException e) { - Log.i(TAG, "unexpected exception while changing permission on " + - "frame buffer"); - e.printStackTrace(); - } - - DisplayMetrics metrics = new DisplayMetrics(); - WindowManager windowManager = - (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); - windowManager.getDefaultDisplay().getMetrics(metrics); - screenWidth = metrics.widthPixels; - screenHeight = metrics.heightPixels; - - Random r = new Random(); - samples = new int[NUMBER_OF_SAMPLES]; - for(int i = 0; i < NUMBER_OF_SAMPLES; i++) { - int a = screenWidth * screenHeight * i / NUMBER_OF_SAMPLES; - int b = screenWidth * screenHeight * (i + 1) / NUMBER_OF_SAMPLES; - samples[i] = a + r.nextInt(b - a); - } - - double[] channel = constants.oledChannelPower(); - rcoef = channel[0] / 255 / 255; - gcoef = channel[1] / 255 / 255; - bcoef = channel[2] / 255 / 255; - modul_coef = constants.oledModulation() / 255 / 255 / 3 / 3; - - for(int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) { - if(new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) { - brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i]; - } - } - } - - @Override - protected void onExit() { - context.unregisterReceiver(broadcastReceiver); - super.onExit(); - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - - boolean screen; - synchronized(this) { - screen = screenOn; - } - - int brightness; - if(brightnessFile != null) { - brightness = (int)SystemInfo.getInstance() - .readLongFromFile(brightnessFile); - } else { - try { - brightness = Settings.System.getInt(context.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS); - } catch(Settings.SettingNotFoundException ex) { - Log.w(TAG, "Could not retrieve brightness information"); - return result; - } - } - if(brightness < 0 || 255 < brightness) { - Log.w(TAG, "Could not retrieve brightness information"); - return result; - } - - double pixPower = 0; - if(screen && frameBufferFile.exists()) { - if(NativeLoader.jniLoaded()) { - pixPower = getScreenPixPower(rcoef, gcoef, bcoef, modul_coef); - } else try { - RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r"); - - for(int x : samples) { - fin.seek(x * 4); - int px = fin.readInt(); - int b = px >> 8 & 0xFF; - int g = px >> 16 & 0xFF; - int r = px >> 24 & 0xFF; - - /* Calculate the power usage of this one pixel if it were at full - * brightness. Linearly scale by brightness to get true power - * consumption. To calculate whole screen compute average of sampled - * region and multiply by number of pixels. - */ - int modul_val = r + g + b; - pixPower += rcoef * (r * r) + gcoef * (g * g) + bcoef * (b * b) - - modul_coef * (modul_val * modul_val); - } - fin.close(); - } catch(FileNotFoundException e) { - pixPower = -1; - } catch(IOException e) { - pixPower = -1; - e.printStackTrace(); - } - if(pixPower >= 0) { - pixPower *= 1.0 * screenWidth * screenHeight / NUMBER_OF_SAMPLES; - } - } - - OledData data = OledData.obtain(); - if(!screen) { - data.init(); - } else { - data.init(brightness, pixPower); - } - result.setPowerData(data); - - if(screen) { - OledData uidData = OledData.obtain(); - uidData.init(brightness, pixPower); - result.addUidPowerData(foregroundDetector.getForegroundUid(), uidData); - } - - return result; - } - - @Override - public boolean hasUidInformation() { - return true; - } - - @Override - public String getComponentName() { - return "OLED"; - } - - public static native double getScreenPixPower(double rcoef, double gcoef, - double bcoef, double modul_coef); -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import edu.umich.PowerTutor.PowerNotifications; +import edu.umich.PowerTutor.phone.PhoneConstants; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.util.NativeLoader; +import edu.umich.PowerTutor.util.NotificationService; +import edu.umich.PowerTutor.util.Recycler; +import edu.umich.PowerTutor.util.SystemInfo; +import edu.umich.PowerTutor.util.ForegroundDetector; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.provider.Settings; +import android.os.Process; +import android.util.Log; +import android.util.DisplayMetrics; +import android.view.WindowManager; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.RandomAccessFile; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.List; +import java.util.Random; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; + +public class OLED extends PowerComponent { + public static class OledData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static OledData obtain() { + OledData result = recycler.obtain(); + if(result != null) return result; + return new OledData(); + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + public int brightness; + public double pixPower; + public boolean screenOn; + + private OledData() { + } + + public void init() { + this.screenOn = false; + } + + public void init(int brightness, double pixPower) { + screenOn = true; + this.brightness = brightness; + this.pixPower = pixPower; + } + + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + out.write("OLED-brightness " + brightness + "\n"); + out.write("OLED-pix-power " + pixPower + "\n"); + out.write("OLED-screen-on " + screenOn + "\n"); + } + } + + private static final String TAG = "OLED"; + private static final String[] BACKLIGHT_BRIGHTNESS_FILES = { + "/sys/class/leds/lcd-backlight/brightness", + "/sys/devices/virtual/leds/lcd-backlight/brightness", + "/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness", + }; + + private Context context; + private ForegroundDetector foregroundDetector; + private BroadcastReceiver broadcastReceiver; + private boolean screenOn; + + private File frameBufferFile; + + private int screenWidth; + private int screenHeight; + + private static final int NUMBER_OF_SAMPLES = 500; + private int[] samples; + + private String brightnessFile; + + /* Coefficients pre-computed for pix power calculations. + */ + private double rcoef; + private double gcoef; + private double bcoef; + private double modul_coef; + + public OLED(Context context, PhoneConstants constants) { + this.context = context; + screenOn = true; + + foregroundDetector = new ForegroundDetector((ActivityManager) + context.getSystemService(context.ACTIVITY_SERVICE)); + broadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + synchronized(this) { + if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + screenOn = false; + } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { + screenOn = true; + } + } + }; + }; + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + context.registerReceiver(broadcastReceiver, intentFilter); + + frameBufferFile = new File("/dev/fb0"); + if(!frameBufferFile.exists()) { + frameBufferFile = new File("/dev/graphics/fb0"); + } + if(frameBufferFile.exists()) try { + /* Check if we already have permission to read the frame buffer. */ + boolean readOk = false; + try { + RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r"); + int b = fin.read(); + fin.close(); + readOk = true; + } catch(IOException e) { + } + /* Don't have permission, try to change permission as root. */ + if(!readOk) { + java.lang.Process p = Runtime.getRuntime().exec("su"); + DataOutputStream os = new DataOutputStream(p.getOutputStream()); + os.writeBytes("chown " + android.os.Process.myUid() + + " " + frameBufferFile.getAbsolutePath() + "\n"); + os.writeBytes("chown app_" + (android.os.Process.myUid() - + SystemInfo.AID_APP) + + " " + frameBufferFile.getAbsolutePath() + "\n"); + os.writeBytes("chmod 660 " + frameBufferFile.getAbsolutePath() + "\n"); + os.writeBytes("exit\n"); + os.flush(); + p.waitFor(); + if(p.exitValue() != 0) { + Log.i(TAG, "failed to change permissions on frame buffer"); + } + } + } catch (InterruptedException e) { + Log.i(TAG, "changing permissions on frame buffer interrupted"); + } catch (IOException e) { + Log.i(TAG, "unexpected exception while changing permission on " + + "frame buffer"); + e.printStackTrace(); + } + + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager windowManager = + (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getMetrics(metrics); + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + + Random r = new Random(); + samples = new int[NUMBER_OF_SAMPLES]; + for(int i = 0; i < NUMBER_OF_SAMPLES; i++) { + int a = screenWidth * screenHeight * i / NUMBER_OF_SAMPLES; + int b = screenWidth * screenHeight * (i + 1) / NUMBER_OF_SAMPLES; + samples[i] = a + r.nextInt(b - a); + } + + double[] channel = constants.oledChannelPower(); + rcoef = channel[0] / 255 / 255; + gcoef = channel[1] / 255 / 255; + bcoef = channel[2] / 255 / 255; + modul_coef = constants.oledModulation() / 255 / 255 / 3 / 3; + + for(int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) { + if(new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) { + brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i]; + } + } + } + + @Override + protected void onExit() { + context.unregisterReceiver(broadcastReceiver); + super.onExit(); + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + + boolean screen; + synchronized(this) { + screen = screenOn; + } + + int brightness; + if(brightnessFile != null) { + brightness = (int)SystemInfo.getInstance() + .readLongFromFile(brightnessFile); + } else { + try { + brightness = Settings.System.getInt(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS); + } catch(Settings.SettingNotFoundException ex) { + Log.w(TAG, "Could not retrieve brightness information"); + return result; + } + } + if(brightness < 0 || 255 < brightness) { + Log.w(TAG, "Could not retrieve brightness information"); + return result; + } + + double pixPower = 0; + if(screen && frameBufferFile.exists()) { + if(NativeLoader.jniLoaded()) { + pixPower = getScreenPixPower(rcoef, gcoef, bcoef, modul_coef); + } else try { + RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r"); + + for(int x : samples) { + fin.seek(x * 4); + int px = fin.readInt(); + int b = px >> 8 & 0xFF; + int g = px >> 16 & 0xFF; + int r = px >> 24 & 0xFF; + + /* Calculate the power usage of this one pixel if it were at full + * brightness. Linearly scale by brightness to get true power + * consumption. To calculate whole screen compute average of sampled + * region and multiply by number of pixels. + */ + int modul_val = r + g + b; + pixPower += rcoef * (r * r) + gcoef * (g * g) + bcoef * (b * b) - + modul_coef * (modul_val * modul_val); + } + fin.close(); + } catch(FileNotFoundException e) { + pixPower = -1; + } catch(IOException e) { + pixPower = -1; + e.printStackTrace(); + } + if(pixPower >= 0) { + pixPower *= 1.0 * screenWidth * screenHeight / NUMBER_OF_SAMPLES; + } + } + + OledData data = OledData.obtain(); + if(!screen) { + data.init(); + } else { + data.init(brightness, pixPower); + } + result.setPowerData(data); + + if(screen) { + OledData uidData = OledData.obtain(); + uidData.init(brightness, pixPower); + result.addUidPowerData(foregroundDetector.getForegroundUid(), uidData); + } + + return result; + } + + @Override + public boolean hasUidInformation() { + return true; + } + + @Override + public String getComponentName() { + return "OLED"; + } + + public static native double getScreenPixPower(double rcoef, double gcoef, + double bcoef, double modul_coef); +} diff --git a/src/edu/umich/PowerTutor/components/PowerComponent.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/PowerComponent.java similarity index 100% rename from src/edu/umich/PowerTutor/components/PowerComponent.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/PowerComponent.java diff --git a/src/edu/umich/PowerTutor/components/Sensors.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Sensors.java similarity index 96% rename from src/edu/umich/PowerTutor/components/Sensors.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Sensors.java index e0917fb..f80d62e 100644 --- a/src/edu/umich/PowerTutor/components/Sensors.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Sensors.java @@ -1,217 +1,217 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import edu.umich.PowerTutor.PowerNotifications; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.util.NotificationService; -import edu.umich.PowerTutor.util.Recycler; -import edu.umich.PowerTutor.util.SystemInfo; - -import android.content.Context; -import android.hardware.SensorManager; -import android.os.SystemClock; -import android.util.Log; -import android.util.SparseArray; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.Set; -import java.util.TreeSet; -import java.util.Map; -import java.util.TreeMap; - -public class Sensors extends PowerComponent { - private final String TAG = "Sensors"; - public static final int MAX_SENSORS = 10; - - public static class SensorData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static SensorData obtain() { - SensorData result = recycler.obtain(); - if(result != null) return result; - return new SensorData(); - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - public double[] onTime; - - private SensorData() { - onTime = new double[MAX_SENSORS]; - } - - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - StringBuilder res = new StringBuilder(); - for(int i = 0; i < MAX_SENSORS; i++) { - if(onTime[i] > 1e-7) { - res.append("Sensors-time ").append(i).append(" ") - .append(onTime[i]).append("\n"); - } - } - out.write(res.toString()); - } - } - - private Context context; - private SensorManager sensorManager; - private PowerNotifications sensorHook; - - private SensorStateKeeper sensorState; - private SparseArray uidStates; - - public Sensors(Context context) { - this.context = context; - sensorState = new SensorStateKeeper(); - uidStates = new SparseArray(); - - if(!NotificationService.available()) { - Log.w(TAG, "Sensor component created although no notification service " + - "available to receive sensor usage information"); - return; - } - sensorManager = (SensorManager)context.getSystemService( - Context.SENSOR_SERVICE); - sensorHook = new NotificationService.DefaultReceiver() { - public void noteStartSensor(int uid, int sensor) { - if(sensor < 0 || MAX_SENSORS <= sensor) { - Log.w(TAG, "Received sensor outside of accepted range"); - return; - } - synchronized(sensorState) { - sensorState.startSensor(sensor); - SensorStateKeeper uidState = uidStates.get(uid); - if(uidState == null) { - uidState = new SensorStateKeeper(); - uidStates.put(uid, uidState); - } - uidState.startSensor(sensor); - } - } - - public void noteStopSensor(int uid, int sensor) { - if(sensor < 0 || MAX_SENSORS <= sensor) { - Log.w(TAG, "Received sensor outside of accepted range"); - return; - } - synchronized(sensorState) { - sensorState.stopSensor(sensor); - SensorStateKeeper uidState = uidStates.get(uid); - if(uidState == null) { - uidState = new SensorStateKeeper(); - uidStates.put(uid, uidState); - } - uidState.stopSensor(sensor); - } - } - }; - NotificationService.addHook(sensorHook); - } - - @Override - protected void onExit() { - super.onExit(); - NotificationService.removeHook(sensorHook); - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - synchronized(sensorState) { - SensorData globalData = SensorData.obtain(); - sensorState.setupSensorTimes(globalData.onTime, iterationInterval); - result.setPowerData(globalData); - - for(int i = 0; i < uidStates.size(); i++) { - int uid = uidStates.keyAt(i); - SensorStateKeeper uidState = uidStates.valueAt(i); - SensorData uidData = SensorData.obtain(); - uidState.setupSensorTimes(uidData.onTime, iterationInterval); - result.addUidPowerData(uid, uidData); - - if(uidState.sensorsOn() == 0) { - uidStates.remove(uid); - i--; - } - } - } - return result; - } - - private static class SensorStateKeeper { - private int[] nesting; - private long[] times; - private long lastTime; - private int count; - - public SensorStateKeeper() { - nesting = new int[MAX_SENSORS]; - times = new long[MAX_SENSORS]; - lastTime = SystemClock.elapsedRealtime(); - } - - public void startSensor(int sensor) { - if(nesting[sensor]++ == 0) { - times[sensor] -= SystemClock.elapsedRealtime() - lastTime; - count++; - } - } - - public void stopSensor(int sensor) { - if(nesting[sensor] == 0) { - return; - } else if(--nesting[sensor] == 0) { - times[sensor] += SystemClock.elapsedRealtime() - lastTime; - count--; - } - } - - public int sensorsOn() { - return count; - } - - public void setupSensorTimes(double[] sensorTimes, long iterationInterval) { - long now = SystemClock.elapsedRealtime(); - long div = now - lastTime; - if(div <= 0) div = 1; - for(int i = 0; i < MAX_SENSORS; i++) { - sensorTimes[i] = 1.0 * (times[i] + - (nesting[i] > 0 ? now - lastTime : 0)) / div; - times[i] = 0; - } - lastTime = now; - } - } - - @Override - public boolean hasUidInformation() { - return true; - } - - @Override - public String getComponentName() { - return "Sensors"; - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import edu.umich.PowerTutor.PowerNotifications; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.util.NotificationService; +import edu.umich.PowerTutor.util.Recycler; +import edu.umich.PowerTutor.util.SystemInfo; + +import android.content.Context; +import android.hardware.SensorManager; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Set; +import java.util.TreeSet; +import java.util.Map; +import java.util.TreeMap; + +public class Sensors extends PowerComponent { + private final String TAG = "Sensors"; + public static final int MAX_SENSORS = 10; + + public static class SensorData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static SensorData obtain() { + SensorData result = recycler.obtain(); + if(result != null) return result; + return new SensorData(); + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + public double[] onTime; + + private SensorData() { + onTime = new double[MAX_SENSORS]; + } + + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + StringBuilder res = new StringBuilder(); + for(int i = 0; i < MAX_SENSORS; i++) { + if(onTime[i] > 1e-7) { + res.append("Sensors-time ").append(i).append(" ") + .append(onTime[i]).append("\n"); + } + } + out.write(res.toString()); + } + } + + private Context context; + private SensorManager sensorManager; + private PowerNotifications sensorHook; + + private SensorStateKeeper sensorState; + private SparseArray uidStates; + + public Sensors(Context context) { + this.context = context; + sensorState = new SensorStateKeeper(); + uidStates = new SparseArray(); + + if(!NotificationService.available()) { + Log.w(TAG, "Sensor component created although no notification service " + + "available to receive sensor usage information"); + return; + } + sensorManager = (SensorManager)context.getSystemService( + Context.SENSOR_SERVICE); + sensorHook = new NotificationService.DefaultReceiver() { + public void noteStartSensor(int uid, int sensor) { + if(sensor < 0 || MAX_SENSORS <= sensor) { + Log.w(TAG, "Received sensor outside of accepted range"); + return; + } + synchronized(sensorState) { + sensorState.startSensor(sensor); + SensorStateKeeper uidState = uidStates.get(uid); + if(uidState == null) { + uidState = new SensorStateKeeper(); + uidStates.put(uid, uidState); + } + uidState.startSensor(sensor); + } + } + + public void noteStopSensor(int uid, int sensor) { + if(sensor < 0 || MAX_SENSORS <= sensor) { + Log.w(TAG, "Received sensor outside of accepted range"); + return; + } + synchronized(sensorState) { + sensorState.stopSensor(sensor); + SensorStateKeeper uidState = uidStates.get(uid); + if(uidState == null) { + uidState = new SensorStateKeeper(); + uidStates.put(uid, uidState); + } + uidState.stopSensor(sensor); + } + } + }; + NotificationService.addHook(sensorHook); + } + + @Override + protected void onExit() { + super.onExit(); + NotificationService.removeHook(sensorHook); + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + synchronized(sensorState) { + SensorData globalData = SensorData.obtain(); + sensorState.setupSensorTimes(globalData.onTime, iterationInterval); + result.setPowerData(globalData); + + for(int i = 0; i < uidStates.size(); i++) { + int uid = uidStates.keyAt(i); + SensorStateKeeper uidState = uidStates.valueAt(i); + SensorData uidData = SensorData.obtain(); + uidState.setupSensorTimes(uidData.onTime, iterationInterval); + result.addUidPowerData(uid, uidData); + + if(uidState.sensorsOn() == 0) { + uidStates.remove(uid); + i--; + } + } + } + return result; + } + + private static class SensorStateKeeper { + private int[] nesting; + private long[] times; + private long lastTime; + private int count; + + public SensorStateKeeper() { + nesting = new int[MAX_SENSORS]; + times = new long[MAX_SENSORS]; + lastTime = SystemClock.elapsedRealtime(); + } + + public void startSensor(int sensor) { + if(nesting[sensor]++ == 0) { + times[sensor] -= SystemClock.elapsedRealtime() - lastTime; + count++; + } + } + + public void stopSensor(int sensor) { + if(nesting[sensor] == 0) { + return; + } else if(--nesting[sensor] == 0) { + times[sensor] += SystemClock.elapsedRealtime() - lastTime; + count--; + } + } + + public int sensorsOn() { + return count; + } + + public void setupSensorTimes(double[] sensorTimes, long iterationInterval) { + long now = SystemClock.elapsedRealtime(); + long div = now - lastTime; + if(div <= 0) div = 1; + for(int i = 0; i < MAX_SENSORS; i++) { + sensorTimes[i] = 1.0 * (times[i] + + (nesting[i] > 0 ? now - lastTime : 0)) / div; + times[i] = 0; + } + lastTime = now; + } + } + + @Override + public boolean hasUidInformation() { + return true; + } + + @Override + public String getComponentName() { + return "Sensors"; + } +} diff --git a/src/edu/umich/PowerTutor/components/Threeg.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Threeg.java similarity index 97% rename from src/edu/umich/PowerTutor/components/Threeg.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Threeg.java index 88d8736..607d461 100644 --- a/src/edu/umich/PowerTutor/components/Threeg.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Threeg.java @@ -1,398 +1,398 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import edu.umich.PowerTutor.phone.PhoneConstants; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.service.PowerEstimator; -import edu.umich.PowerTutor.util.Recycler; -import edu.umich.PowerTutor.util.SystemInfo; - -import android.content.Context; -import android.os.SystemClock; -import android.util.Log; -import android.util.SparseArray; -import android.telephony.TelephonyManager; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.RandomAccessFile; - -public class Threeg extends PowerComponent { - public static class ThreegData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static ThreegData obtain() { - ThreegData result = recycler.obtain(); - if(result != null) return result; - return new ThreegData(); - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - public boolean threegOn; - public long packets; - public long uplinkBytes; - public long downlinkBytes; - public int powerState; - public String oper; - - private ThreegData() { - } - - public void init() { - threegOn = false; - } - - public void init(long packets, long uplinkBytes, long downlinkBytes, - int powerState, String oper) { - threegOn = true; - this.packets = packets; - this.uplinkBytes = uplinkBytes; - this.downlinkBytes = downlinkBytes; - this.powerState = powerState; - this.oper = oper; - } - - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - StringBuilder res = new StringBuilder(); - res.append("3G-on ").append(threegOn).append("\n"); - if(threegOn) { - res.append("3G-uplinkBytes ").append(uplinkBytes) - .append("\n3G-downlinkBytes ").append(downlinkBytes) - .append("\n3G-packets ").append(packets) - .append("\n3G-state ").append(Threeg.POWER_STATE_NAMES[powerState]) - .append("\n3G-oper ").append(oper) - .append("\n"); - } - out.write(res.toString()); - } - } - - public static final int POWER_STATE_IDLE = 0; - public static final int POWER_STATE_FACH = 1; - public static final int POWER_STATE_DCH = 2; - public static final String[] POWER_STATE_NAMES = {"IDLE", "FACH", "DCH"}; - - private static final String TAG = "Threeg"; - - private PhoneConstants phoneConstants; - private TelephonyManager telephonyManager; - private SystemInfo sysInfo; - - private String oper; - private int dchFachDelay; - private int fachIdleDelay; - private int uplinkQueueSize; - private int downlinkQueueSize; - - private int[] lastUids; - private ThreegStateKeeper threegState; - private SparseArray uidStates; - - private String transPacketsFile; - private String readPacketsFile; - private String readBytesFile; - private String transBytesFile; - private File uidStatsFolder; - - public Threeg(Context context, PhoneConstants phoneConstants) { - this.phoneConstants = phoneConstants; - telephonyManager = (TelephonyManager)context.getSystemService( - Context.TELEPHONY_SERVICE); - - String interfaceName = phoneConstants.threegInterface(); - threegState = new ThreegStateKeeper(); - uidStates = new SparseArray(); - transPacketsFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/tx_packets"; - readPacketsFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/rx_packets"; - readBytesFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/rx_bytes"; - transBytesFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/tx_bytes"; - uidStatsFolder = new File("/proc/uid_stat"); - sysInfo = SystemInfo.getInstance(); - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - - int netType = telephonyManager.getNetworkType(); - - if((netType != TelephonyManager.NETWORK_TYPE_UMTS && - netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) { - // TODO: Actually get models for the different network types. - netType = TelephonyManager.NETWORK_TYPE_UMTS; - } - - if(telephonyManager.getDataState() != TelephonyManager.DATA_CONNECTED || - (netType != TelephonyManager.NETWORK_TYPE_UMTS && - netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) { - /* We need to allow the real iterface state keeper to reset it's state - * so that the next update it knows it's coming back from an off state. - * We also need to clear all the uid information. - */ - oper = null; - threegState.interfaceOff(); - uidStates.clear(); - - ThreegData data = ThreegData.obtain(); - data.init(); - result.setPowerData(data); - return result; - } - - if(oper == null) { - oper = telephonyManager.getNetworkOperatorName(); - dchFachDelay = phoneConstants.threegDchFachDelay(oper); - fachIdleDelay = phoneConstants.threegFachIdleDelay(oper); - uplinkQueueSize = phoneConstants.threegUplinkQueue(oper); - downlinkQueueSize = phoneConstants.threegDownlinkQueue(oper); - } - - long transmitPackets = readLongFromFile(transPacketsFile); - long receivePackets = readLongFromFile(readPacketsFile); - long transmitBytes = readLongFromFile(transBytesFile); - long receiveBytes = readLongFromFile(readBytesFile); - if(transmitBytes == -1 || receiveBytes == -1) { - /* Couldn't read interface data files. */ - Log.w(TAG, "Failed to read packet and byte counts from wifi interface"); - return result; - } - - if(threegState.isInitialized()) { - threegState.updateState(transmitPackets, receivePackets, - transmitBytes, receiveBytes, - dchFachDelay, fachIdleDelay, - uplinkQueueSize, downlinkQueueSize); - ThreegData data = ThreegData.obtain(); - data.init(threegState.getPackets(), threegState.getUplinkBytes(), - threegState.getDownlinkBytes(), threegState.getPowerState(), - oper); - result.setPowerData(data); - } else { - threegState.updateState(transmitPackets, receivePackets, - transmitBytes, receiveBytes, - dchFachDelay, fachIdleDelay, - uplinkQueueSize, downlinkQueueSize); - } - - lastUids = sysInfo.getUids(lastUids); - if(lastUids != null) for(int uid : lastUids) { - if(uid == -1) { - continue; - } - try { - ThreegStateKeeper uidState = uidStates.get(uid); - if(uidState == null) { - uidState = new ThreegStateKeeper(); - uidStates.put(uid, uidState); - } - - if(!uidState.isStale()) { - /* We use a huerstic here so that we don't poll for uids that haven't - * had much activity recently. - */ - continue; - } - - /* These read operations are the expensive part of polling. */ - receiveBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_rcv"); - transmitBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_snd"); - - if(receiveBytes == -1 || transmitBytes == -1) { - Log.w(TAG, "Failed to read uid read/write byte counts"); - } else if(uidState.isInitialized()) { - uidState.updateState(-1, -1, transmitBytes, receiveBytes, - dchFachDelay, fachIdleDelay, - uplinkQueueSize, downlinkQueueSize); - - if(uidState.getUplinkBytes() + uidState.getDownlinkBytes() != 0 || - uidState.getPowerState() != POWER_STATE_IDLE) { - ThreegData uidData = ThreegData.obtain(); - uidData.init(uidState.getPackets(), - uidState.getUplinkBytes(), uidState.getDownlinkBytes(), - uidState.getPowerState(), oper); - result.addUidPowerData(uid, uidData); - } - } else { - uidState.updateState(-1, -1, transmitBytes, receiveBytes, - dchFachDelay, fachIdleDelay, - uplinkQueueSize, downlinkQueueSize); - } - } catch(NumberFormatException e) { - Log.w(TAG, "Non-uid files in /proc/uid_stat"); - } - } - - return result; - } - - private static class ThreegStateKeeper { - private long lastTransmitPackets; - private long lastReceivePackets; - private long lastTransmitBytes; - private long lastReceiveBytes; - private long lastTime; - - private long deltaPackets; - private long deltaUplinkBytes; - private long deltaDownlinkBytes; - - private int powerState; - private int stateTime; - - private long inactiveTime; - - public ThreegStateKeeper() { - lastTransmitBytes = lastReceiveBytes = lastTime = -1; - deltaUplinkBytes = deltaDownlinkBytes = -1; - powerState = POWER_STATE_IDLE; - stateTime = 0; - inactiveTime = 0; - } - - public void interfaceOff() { - lastTime = SystemClock.elapsedRealtime(); - powerState = POWER_STATE_IDLE; - } - - public boolean isInitialized() { - return lastTime != -1; - } - - public void updateState(long transmitPackets, long receivePackets, - long transmitBytes, long receiveBytes, - int dchFachDelay, int fachIdleDelay, - int uplinkQueueSize, int downlinkQueueSize) { - long curTime = SystemClock.elapsedRealtime(); - if(lastTime != -1 && curTime > lastTime) { - double deltaTime = curTime - lastTime; - deltaPackets = transmitPackets + receivePackets - - lastTransmitPackets - lastReceivePackets; - deltaUplinkBytes = transmitBytes - lastTransmitBytes; - deltaDownlinkBytes = receiveBytes - lastReceiveBytes; - boolean inactive = deltaUplinkBytes == 0 && deltaDownlinkBytes == 0; - inactiveTime = inactive ? inactiveTime + curTime - lastTime : 0; - - // TODO: make this always work. - int timeMult = 1; - if(1000 % PowerEstimator.ITERATION_INTERVAL != 0) { - Log.w(TAG, - "Cannot handle iteration intervals that are a factor of 1 second"); - } else { - timeMult = 1000 / PowerEstimator.ITERATION_INTERVAL; - } - - switch(powerState) { - case POWER_STATE_IDLE: - if(!inactive) { - powerState = POWER_STATE_FACH; - } - break; - case POWER_STATE_FACH: - if(inactive) { - stateTime++; - if(stateTime >= fachIdleDelay * timeMult) { - stateTime = 0; - powerState = POWER_STATE_IDLE; - } - } else { - stateTime = 0; - if(deltaUplinkBytes > 0 || - deltaDownlinkBytes > 0) { - powerState = POWER_STATE_DCH; - } - } - break; - default: // case POWER_STATE_DCH: - if(inactive) { - stateTime++; - if(stateTime >= dchFachDelay * timeMult) { - stateTime = 0; - powerState = POWER_STATE_FACH; - } - } else { - stateTime = 0; - } - } - } - lastTime = curTime; - lastTransmitPackets = transmitPackets; - lastReceivePackets = receivePackets; - lastTransmitBytes = transmitBytes; - lastReceiveBytes = receiveBytes; - } - - public int getPowerState() { - return powerState; - } - - public long getPackets() { - return deltaPackets; - } - - public long getUplinkBytes() { - return deltaUplinkBytes; - } - - public long getDownlinkBytes() { - return deltaDownlinkBytes; - } - - /* The idea here is that we don't want to have to read uid information - * every single iteration for each uid as it just takes too long. So here - * we are designing a hueristic that helps us avoid polling for too many - * uids. - */ - public boolean isStale() { - if(powerState != POWER_STATE_IDLE) return true; - long curTime = SystemClock.elapsedRealtime(); - return curTime - lastTime > (long)Math.min(10000, inactiveTime); - } - } - - private final static byte[] buf = new byte[16]; - - private long readLongFromFile(String filePath) { - return sysInfo.readLongFromFile(filePath); - } - - @Override - public boolean hasUidInformation() { - return uidStatsFolder.exists(); - } - - @Override - public String getComponentName() { - return "3G"; - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import edu.umich.PowerTutor.phone.PhoneConstants; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.service.PowerEstimator; +import edu.umich.PowerTutor.util.Recycler; +import edu.umich.PowerTutor.util.SystemInfo; + +import android.content.Context; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; +import android.telephony.TelephonyManager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.RandomAccessFile; + +public class Threeg extends PowerComponent { + public static class ThreegData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static ThreegData obtain() { + ThreegData result = recycler.obtain(); + if(result != null) return result; + return new ThreegData(); + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + public boolean threegOn; + public long packets; + public long uplinkBytes; + public long downlinkBytes; + public int powerState; + public String oper; + + private ThreegData() { + } + + public void init() { + threegOn = false; + } + + public void init(long packets, long uplinkBytes, long downlinkBytes, + int powerState, String oper) { + threegOn = true; + this.packets = packets; + this.uplinkBytes = uplinkBytes; + this.downlinkBytes = downlinkBytes; + this.powerState = powerState; + this.oper = oper; + } + + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + StringBuilder res = new StringBuilder(); + res.append("3G-on ").append(threegOn).append("\n"); + if(threegOn) { + res.append("3G-uplinkBytes ").append(uplinkBytes) + .append("\n3G-downlinkBytes ").append(downlinkBytes) + .append("\n3G-packets ").append(packets) + .append("\n3G-state ").append(Threeg.POWER_STATE_NAMES[powerState]) + .append("\n3G-oper ").append(oper) + .append("\n"); + } + out.write(res.toString()); + } + } + + public static final int POWER_STATE_IDLE = 0; + public static final int POWER_STATE_FACH = 1; + public static final int POWER_STATE_DCH = 2; + public static final String[] POWER_STATE_NAMES = {"IDLE", "FACH", "DCH"}; + + private static final String TAG = "Threeg"; + + private PhoneConstants phoneConstants; + private TelephonyManager telephonyManager; + private SystemInfo sysInfo; + + private String oper; + private int dchFachDelay; + private int fachIdleDelay; + private int uplinkQueueSize; + private int downlinkQueueSize; + + private int[] lastUids; + private ThreegStateKeeper threegState; + private SparseArray uidStates; + + private String transPacketsFile; + private String readPacketsFile; + private String readBytesFile; + private String transBytesFile; + private File uidStatsFolder; + + public Threeg(Context context, PhoneConstants phoneConstants) { + this.phoneConstants = phoneConstants; + telephonyManager = (TelephonyManager)context.getSystemService( + Context.TELEPHONY_SERVICE); + + String interfaceName = phoneConstants.threegInterface(); + threegState = new ThreegStateKeeper(); + uidStates = new SparseArray(); + transPacketsFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/tx_packets"; + readPacketsFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/rx_packets"; + readBytesFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/rx_bytes"; + transBytesFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/tx_bytes"; + uidStatsFolder = new File("/proc/uid_stat"); + sysInfo = SystemInfo.getInstance(); + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + + int netType = telephonyManager.getNetworkType(); + + if((netType != TelephonyManager.NETWORK_TYPE_UMTS && + netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) { + // TODO: Actually get models for the different network types. + netType = TelephonyManager.NETWORK_TYPE_UMTS; + } + + if(telephonyManager.getDataState() != TelephonyManager.DATA_CONNECTED || + (netType != TelephonyManager.NETWORK_TYPE_UMTS && + netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) { + /* We need to allow the real iterface state keeper to reset it's state + * so that the next update it knows it's coming back from an off state. + * We also need to clear all the uid information. + */ + oper = null; + threegState.interfaceOff(); + uidStates.clear(); + + ThreegData data = ThreegData.obtain(); + data.init(); + result.setPowerData(data); + return result; + } + + if(oper == null) { + oper = telephonyManager.getNetworkOperatorName(); + dchFachDelay = phoneConstants.threegDchFachDelay(oper); + fachIdleDelay = phoneConstants.threegFachIdleDelay(oper); + uplinkQueueSize = phoneConstants.threegUplinkQueue(oper); + downlinkQueueSize = phoneConstants.threegDownlinkQueue(oper); + } + + long transmitPackets = readLongFromFile(transPacketsFile); + long receivePackets = readLongFromFile(readPacketsFile); + long transmitBytes = readLongFromFile(transBytesFile); + long receiveBytes = readLongFromFile(readBytesFile); + if(transmitBytes == -1 || receiveBytes == -1) { + /* Couldn't read interface data files. */ + Log.w(TAG, "Failed to read packet and byte counts from wifi interface"); + return result; + } + + if(threegState.isInitialized()) { + threegState.updateState(transmitPackets, receivePackets, + transmitBytes, receiveBytes, + dchFachDelay, fachIdleDelay, + uplinkQueueSize, downlinkQueueSize); + ThreegData data = ThreegData.obtain(); + data.init(threegState.getPackets(), threegState.getUplinkBytes(), + threegState.getDownlinkBytes(), threegState.getPowerState(), + oper); + result.setPowerData(data); + } else { + threegState.updateState(transmitPackets, receivePackets, + transmitBytes, receiveBytes, + dchFachDelay, fachIdleDelay, + uplinkQueueSize, downlinkQueueSize); + } + + lastUids = sysInfo.getUids(lastUids); + if(lastUids != null) for(int uid : lastUids) { + if(uid == -1) { + continue; + } + try { + ThreegStateKeeper uidState = uidStates.get(uid); + if(uidState == null) { + uidState = new ThreegStateKeeper(); + uidStates.put(uid, uidState); + } + + if(!uidState.isStale()) { + /* We use a huerstic here so that we don't poll for uids that haven't + * had much activity recently. + */ + continue; + } + + /* These read operations are the expensive part of polling. */ + receiveBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_rcv"); + transmitBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_snd"); + + if(receiveBytes == -1 || transmitBytes == -1) { + Log.w(TAG, "Failed to read uid read/write byte counts"); + } else if(uidState.isInitialized()) { + uidState.updateState(-1, -1, transmitBytes, receiveBytes, + dchFachDelay, fachIdleDelay, + uplinkQueueSize, downlinkQueueSize); + + if(uidState.getUplinkBytes() + uidState.getDownlinkBytes() != 0 || + uidState.getPowerState() != POWER_STATE_IDLE) { + ThreegData uidData = ThreegData.obtain(); + uidData.init(uidState.getPackets(), + uidState.getUplinkBytes(), uidState.getDownlinkBytes(), + uidState.getPowerState(), oper); + result.addUidPowerData(uid, uidData); + } + } else { + uidState.updateState(-1, -1, transmitBytes, receiveBytes, + dchFachDelay, fachIdleDelay, + uplinkQueueSize, downlinkQueueSize); + } + } catch(NumberFormatException e) { + Log.w(TAG, "Non-uid files in /proc/uid_stat"); + } + } + + return result; + } + + private static class ThreegStateKeeper { + private long lastTransmitPackets; + private long lastReceivePackets; + private long lastTransmitBytes; + private long lastReceiveBytes; + private long lastTime; + + private long deltaPackets; + private long deltaUplinkBytes; + private long deltaDownlinkBytes; + + private int powerState; + private int stateTime; + + private long inactiveTime; + + public ThreegStateKeeper() { + lastTransmitBytes = lastReceiveBytes = lastTime = -1; + deltaUplinkBytes = deltaDownlinkBytes = -1; + powerState = POWER_STATE_IDLE; + stateTime = 0; + inactiveTime = 0; + } + + public void interfaceOff() { + lastTime = SystemClock.elapsedRealtime(); + powerState = POWER_STATE_IDLE; + } + + public boolean isInitialized() { + return lastTime != -1; + } + + public void updateState(long transmitPackets, long receivePackets, + long transmitBytes, long receiveBytes, + int dchFachDelay, int fachIdleDelay, + int uplinkQueueSize, int downlinkQueueSize) { + long curTime = SystemClock.elapsedRealtime(); + if(lastTime != -1 && curTime > lastTime) { + double deltaTime = curTime - lastTime; + deltaPackets = transmitPackets + receivePackets - + lastTransmitPackets - lastReceivePackets; + deltaUplinkBytes = transmitBytes - lastTransmitBytes; + deltaDownlinkBytes = receiveBytes - lastReceiveBytes; + boolean inactive = deltaUplinkBytes == 0 && deltaDownlinkBytes == 0; + inactiveTime = inactive ? inactiveTime + curTime - lastTime : 0; + + // TODO: make this always work. + int timeMult = 1; + if(1000 % PowerEstimator.ITERATION_INTERVAL != 0) { + Log.w(TAG, + "Cannot handle iteration intervals that are a factor of 1 second"); + } else { + timeMult = 1000 / PowerEstimator.ITERATION_INTERVAL; + } + + switch(powerState) { + case POWER_STATE_IDLE: + if(!inactive) { + powerState = POWER_STATE_FACH; + } + break; + case POWER_STATE_FACH: + if(inactive) { + stateTime++; + if(stateTime >= fachIdleDelay * timeMult) { + stateTime = 0; + powerState = POWER_STATE_IDLE; + } + } else { + stateTime = 0; + if(deltaUplinkBytes > 0 || + deltaDownlinkBytes > 0) { + powerState = POWER_STATE_DCH; + } + } + break; + default: // case POWER_STATE_DCH: + if(inactive) { + stateTime++; + if(stateTime >= dchFachDelay * timeMult) { + stateTime = 0; + powerState = POWER_STATE_FACH; + } + } else { + stateTime = 0; + } + } + } + lastTime = curTime; + lastTransmitPackets = transmitPackets; + lastReceivePackets = receivePackets; + lastTransmitBytes = transmitBytes; + lastReceiveBytes = receiveBytes; + } + + public int getPowerState() { + return powerState; + } + + public long getPackets() { + return deltaPackets; + } + + public long getUplinkBytes() { + return deltaUplinkBytes; + } + + public long getDownlinkBytes() { + return deltaDownlinkBytes; + } + + /* The idea here is that we don't want to have to read uid information + * every single iteration for each uid as it just takes too long. So here + * we are designing a hueristic that helps us avoid polling for too many + * uids. + */ + public boolean isStale() { + if(powerState != POWER_STATE_IDLE) return true; + long curTime = SystemClock.elapsedRealtime(); + return curTime - lastTime > (long)Math.min(10000, inactiveTime); + } + } + + private final static byte[] buf = new byte[16]; + + private long readLongFromFile(String filePath) { + return sysInfo.readLongFromFile(filePath); + } + + @Override + public boolean hasUidInformation() { + return uidStatsFolder.exists(); + } + + @Override + public String getComponentName() { + return "3G"; + } +} diff --git a/src/edu/umich/PowerTutor/components/Wifi.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Wifi.java similarity index 97% rename from src/edu/umich/PowerTutor/components/Wifi.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Wifi.java index 0e10292..524a50a 100644 --- a/src/edu/umich/PowerTutor/components/Wifi.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/components/Wifi.java @@ -1,418 +1,418 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.components; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStreamWriter; - -import android.content.Context; -import android.net.wifi.WifiManager; -import android.os.SystemClock; -import android.util.Log; -import android.util.SparseArray; -import edu.umich.PowerTutor.phone.PhoneConstants; -import edu.umich.PowerTutor.service.IterationData; -import edu.umich.PowerTutor.service.PowerData; -import edu.umich.PowerTutor.util.Recycler; -import edu.umich.PowerTutor.util.SystemInfo; - -public class Wifi extends PowerComponent { - public static class WifiData extends PowerData { - private static Recycler recycler = new Recycler(); - - public static WifiData obtain() { - WifiData result = recycler.obtain(); - if(result != null) return result; - return new WifiData(); - } - - @Override - public void recycle() { - recycler.recycle(this); - } - - public boolean wifiOn; - public double packets; - public long uplinkBytes; - public long downlinkBytes; - public double uplinkRate; - public double linkSpeed; - public int powerState; - - private WifiData() { - } - - public void init(double packets, long uplinkBytes, long downlinkBytes, - double uplinkRate, double linkSpeed, int powerState) { - wifiOn = true; - this.packets = packets; - this.uplinkBytes = uplinkBytes; - this.downlinkBytes = downlinkBytes; - this.uplinkRate = uplinkRate; - this.linkSpeed = linkSpeed; - this.powerState = powerState; - } - - public void init() { - wifiOn = false; - } - - public void writeLogDataInfo(OutputStreamWriter out) throws IOException { - StringBuilder res = new StringBuilder(); - res.append("Wifi-on ").append(wifiOn).append("\n"); - if(wifiOn) { - res.append("Wifi-packets ").append((long)Math.round(packets)) - .append("\nWifi-uplinkBytes ").append(uplinkBytes) - .append("\nWifi-downlinkBytes ").append(downlinkBytes) - .append("\nWifi-uplink ").append((long)Math.round(uplinkRate)) - .append("\nWifi-speed ").append((long)Math.round(linkSpeed)) - .append("\nWifi-state ").append(Wifi.POWER_STATE_NAMES[powerState]) - .append("\n"); - } - out.write(res.toString()); - } - } - - public static final int POWER_STATE_LOW = 0; - public static final int POWER_STATE_HIGH = 1; - public static final String[] POWER_STATE_NAMES = {"LOW", "HIGH"}; - - private static final String TAG = "Wifi"; - - private PhoneConstants phoneConstants; - private WifiManager wifiManager; - private SystemInfo sysInfo; - - private long lastLinkSpeed; - private int[] lastUids; - private WifiStateKeeper wifiState; - private SparseArray uidStates; - - private String transPacketsFile; - private String readPacketsFile; - private String transBytesFile; - private String readBytesFile; - private File uidStatsFolder; - - public Wifi(Context context, PhoneConstants phoneConstants) { - this.phoneConstants = phoneConstants; - wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); - sysInfo = SystemInfo.getInstance(); - - /* Try to grab the interface name. If we can't find it will take a wild - * stab in the dark. - */ - String interfaceName = SystemInfo.getInstance().getProperty("wifi.interface"); - if(interfaceName == null) interfaceName = "eth0"; - - lastLinkSpeed = -1; - wifiState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(), - phoneConstants.wifiLowHighTransition()); - uidStates = new SparseArray(); - transPacketsFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/tx_packets"; - readPacketsFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/rx_packets"; - transBytesFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/tx_bytes"; - readBytesFile = "/sys/devices/virtual/net/" + - interfaceName + "/statistics/rx_bytes"; - uidStatsFolder = new File("/proc/uid_stat"); - } - - @Override - public IterationData calculateIteration(long iteration) { - IterationData result = IterationData.obtain(); - - int wifiStateFlag = wifiManager.getWifiState(); - if(wifiStateFlag != WifiManager.WIFI_STATE_ENABLED && - wifiStateFlag != WifiManager.WIFI_STATE_DISABLING) { - /* We need to allow the real iterface state keeper to reset it's state - * so that the next update it knows it's coming back from an off state. - * We also need to clear all the uid information. - */ - wifiState.interfaceOff(); - uidStates.clear(); - lastLinkSpeed = -1; - - WifiData data = WifiData.obtain(); - data.init(); - result.setPowerData(data); - return result; - } - - long transmitPackets = sysInfo.readLongFromFile(transPacketsFile); - long receivePackets = sysInfo.readLongFromFile(readPacketsFile); - long transmitBytes = sysInfo.readLongFromFile(transBytesFile); - long receiveBytes = sysInfo.readLongFromFile(readBytesFile); - if(transmitPackets == -1 || receivePackets == -1 || - transmitBytes == -1 || receiveBytes == -1) { - /* Couldn't read interface data files. */ - Log.w(TAG, "Failed to read packet and byte counts from wifi interface"); - return result; - } - - /* Update the link speed every 15 seconds as pulling the WifiInfo structure - * from WifiManager is a little bit expensive. This isn't really something - * that is likely to change very frequently anyway. - */ - if(iteration % 15 == 0 || lastLinkSpeed == -1) { - lastLinkSpeed = wifiManager.getConnectionInfo().getLinkSpeed(); - } - double linkSpeed = lastLinkSpeed; - - if(wifiState.isInitialized()) { - wifiState.updateState(transmitPackets, receivePackets, - transmitBytes, receiveBytes); - WifiData data = WifiData.obtain(); - data.init(wifiState.getPackets(), wifiState.getUplinkBytes(), - wifiState.getDownlinkBytes(), wifiState.getUplinkRate(), - linkSpeed, wifiState.getPowerState()); - result.setPowerData(data); - } else { - wifiState.updateState(transmitPackets, receivePackets, - transmitBytes, receiveBytes); - } - - lastUids = sysInfo.getUids(lastUids); - if(lastUids != null) for(int uid : lastUids) { - if(uid == -1) { - continue; - } - try { - WifiStateKeeper uidState = uidStates.get(uid); - if(uidState == null) { - uidState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(), - phoneConstants.wifiLowHighTransition()); - uidStates.put(uid, uidState); - } - - if(!uidState.isStale()) { - /* We use a huerstic here so that we don't poll for uids that haven't - * had much activity recently. - */ - continue; - } - - /* These read operations are the expensive part of polling. */ - receiveBytes = sysInfo.readLongFromFile( - "/proc/uid_stat/" + uid + "/tcp_rcv"); - transmitBytes = sysInfo.readLongFromFile( - "/proc/uid_stat/" + uid + "/tcp_snd"); - - if(receiveBytes == -1 || transmitBytes == -1) { - Log.w(TAG, "Failed to read uid read/write byte counts"); - } else if(uidState.isInitialized()) { - /* We only have information about bytes received but what we really - * want is the number of packets received so we just have to - * estimate it. - */ - long deltaTransmitBytes = transmitBytes - uidState.getTransmitBytes(); - long deltaReceiveBytes = receiveBytes - uidState.getReceiveBytes(); - long estimatedTransmitPackets = (long)Math.round(deltaTransmitBytes / - wifiState.getAverageTransmitPacketSize()); - long estimatedReceivePackets = (long)Math.round(deltaReceiveBytes / - wifiState.getAverageReceivePacketSize()); - if(deltaTransmitBytes > 0 && estimatedTransmitPackets == 0) { - estimatedTransmitPackets = 1; - } - if(deltaReceiveBytes > 0 && estimatedReceivePackets == 0) { - estimatedReceivePackets = 1; - } - - boolean active = transmitBytes != uidState.getTransmitBytes() || - receiveBytes != uidState.getReceiveBytes(); - uidState.updateState( - uidState.getTransmitPackets() + estimatedTransmitPackets, - uidState.getReceivePackets() + estimatedReceivePackets, - transmitBytes, receiveBytes); - - if(active) { - WifiData uidData = WifiData.obtain(); - uidData.init(uidState.getPackets(), uidState.getUplinkBytes(), - uidState.getDownlinkBytes(), uidState.getUplinkRate(), - linkSpeed, uidState.getPowerState()); - result.addUidPowerData(uid, uidData); - } - } else { - uidState.updateState(0, 0, transmitBytes, receiveBytes); - } - } catch(NumberFormatException e) { - Log.w(TAG, "Non-uid files in /proc/uid_stat"); - } - } - - return result; - } - - private static class WifiStateKeeper { - private long lastTransmitPackets; - private long lastReceivePackets; - private long lastTransmitBytes; - private long lastReceiveBytes; - private long lastTime; - - private int powerState; - private double lastPackets; - private double lastUplinkRate; - private double lastAverageTransmitPacketSize; - private double lastAverageReceivePacketSize; - - private long deltaUplinkBytes; - private long deltaDownlinkBytes; - - private double highLowTransition; - private double lowHighTransition; - - private long inactiveTime; - - public WifiStateKeeper(double highLowTransition, double lowHighTransition) { - this.highLowTransition = highLowTransition; - this.lowHighTransition = lowHighTransition; - lastTransmitPackets = lastReceivePackets = lastTransmitBytes = - lastTime = -1; - powerState = POWER_STATE_LOW; - lastPackets = lastUplinkRate = 0; - lastAverageTransmitPacketSize = 1000; - lastAverageReceivePacketSize = 1000; - inactiveTime = 0; - } - - public void interfaceOff() { - lastTime = SystemClock.elapsedRealtime(); - powerState = POWER_STATE_LOW; - } - - public boolean isInitialized() { - return lastTime != -1; - } - - public void updateState(long transmitPackets, long receivePackets, - long transmitBytes, long receiveBytes) { - long curTime = SystemClock.elapsedRealtime(); - if(lastTime != -1 && curTime > lastTime) { - double deltaTime = curTime - lastTime; - lastUplinkRate = (transmitBytes - lastTransmitBytes) / 1024.0 * - 7.8125 / deltaTime; - lastPackets = receivePackets + transmitPackets - - lastReceivePackets - lastTransmitPackets; - deltaUplinkBytes = transmitBytes - lastTransmitBytes; - deltaDownlinkBytes = receiveBytes - lastReceiveBytes; - if(transmitPackets != lastTransmitPackets) { - lastAverageTransmitPacketSize = 0.9 * lastAverageTransmitPacketSize + - 0.1 * (transmitBytes - lastTransmitBytes) / - (transmitPackets - lastTransmitPackets); - } - if(receivePackets != lastReceivePackets) { - lastAverageReceivePacketSize = 0.9 * lastAverageReceivePacketSize + - 0.1 * (receiveBytes - lastReceiveBytes) / - (receivePackets - lastReceivePackets); - } - - if(receiveBytes != lastReceiveBytes || - transmitBytes != lastTransmitBytes) { - inactiveTime = 0; - } else { - inactiveTime += curTime - lastTime; - } - - if(lastPackets < highLowTransition) { - powerState = POWER_STATE_LOW; - } else if(lastPackets > lowHighTransition) { - powerState = POWER_STATE_HIGH; - } - } - lastTime = curTime; - lastTransmitPackets = transmitPackets; - lastReceivePackets = receivePackets; - lastTransmitBytes = transmitBytes; - lastReceiveBytes = receiveBytes; - } - - public int getPowerState() { - return powerState; - } - - public double getPackets() { - return lastPackets; - } - - public long getUplinkBytes() { - return deltaUplinkBytes; - } - - public long getDownlinkBytes() { - return deltaDownlinkBytes; - } - - public double getUplinkRate() { - return lastUplinkRate; - } - - public double getAverageTransmitPacketSize() { - return lastAverageTransmitPacketSize; - } - - public double getAverageReceivePacketSize() { - return lastAverageReceivePacketSize; - } - - public long getTransmitPackets() { - return lastTransmitPackets; - } - - public long getReceivePackets() { - return lastReceivePackets; - } - - public long getTransmitBytes() { - return lastTransmitBytes; - } - - public long getReceiveBytes() { - return lastReceiveBytes; - } - - /* The idea here is that we don't want to have to read uid information - * every single iteration for each uid as it just takes too long. So here - * we are designing a hueristic that helps us avoid polling for too many - * uids. - */ - public boolean isStale() { - long curTime = SystemClock.elapsedRealtime(); - return curTime - lastTime > (long)Math.min(10000, inactiveTime); - } - } - - private long readLongFromFile(String filePath) { - return sysInfo.readLongFromFile(filePath); - } - - @Override - public boolean hasUidInformation() { - return uidStatsFolder.exists(); - } - - @Override - public String getComponentName() { - return "Wifi"; - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.components; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; +import edu.umich.PowerTutor.phone.PhoneConstants; +import edu.umich.PowerTutor.service.IterationData; +import edu.umich.PowerTutor.service.PowerData; +import edu.umich.PowerTutor.util.Recycler; +import edu.umich.PowerTutor.util.SystemInfo; + +public class Wifi extends PowerComponent { + public static class WifiData extends PowerData { + private static Recycler recycler = new Recycler(); + + public static WifiData obtain() { + WifiData result = recycler.obtain(); + if(result != null) return result; + return new WifiData(); + } + + @Override + public void recycle() { + recycler.recycle(this); + } + + public boolean wifiOn; + public double packets; + public long uplinkBytes; + public long downlinkBytes; + public double uplinkRate; + public double linkSpeed; + public int powerState; + + private WifiData() { + } + + public void init(double packets, long uplinkBytes, long downlinkBytes, + double uplinkRate, double linkSpeed, int powerState) { + wifiOn = true; + this.packets = packets; + this.uplinkBytes = uplinkBytes; + this.downlinkBytes = downlinkBytes; + this.uplinkRate = uplinkRate; + this.linkSpeed = linkSpeed; + this.powerState = powerState; + } + + public void init() { + wifiOn = false; + } + + public void writeLogDataInfo(OutputStreamWriter out) throws IOException { + StringBuilder res = new StringBuilder(); + res.append("Wifi-on ").append(wifiOn).append("\n"); + if(wifiOn) { + res.append("Wifi-packets ").append((long)Math.round(packets)) + .append("\nWifi-uplinkBytes ").append(uplinkBytes) + .append("\nWifi-downlinkBytes ").append(downlinkBytes) + .append("\nWifi-uplink ").append((long)Math.round(uplinkRate)) + .append("\nWifi-speed ").append((long)Math.round(linkSpeed)) + .append("\nWifi-state ").append(Wifi.POWER_STATE_NAMES[powerState]) + .append("\n"); + } + out.write(res.toString()); + } + } + + public static final int POWER_STATE_LOW = 0; + public static final int POWER_STATE_HIGH = 1; + public static final String[] POWER_STATE_NAMES = {"LOW", "HIGH"}; + + private static final String TAG = "Wifi"; + + private PhoneConstants phoneConstants; + private WifiManager wifiManager; + private SystemInfo sysInfo; + + private long lastLinkSpeed; + private int[] lastUids; + private WifiStateKeeper wifiState; + private SparseArray uidStates; + + private String transPacketsFile; + private String readPacketsFile; + private String transBytesFile; + private String readBytesFile; + private File uidStatsFolder; + + public Wifi(Context context, PhoneConstants phoneConstants) { + this.phoneConstants = phoneConstants; + wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); + sysInfo = SystemInfo.getInstance(); + + /* Try to grab the interface name. If we can't find it will take a wild + * stab in the dark. + */ + String interfaceName = SystemInfo.getInstance().getProperty("wifi.interface"); + if(interfaceName == null) interfaceName = "eth0"; + + lastLinkSpeed = -1; + wifiState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(), + phoneConstants.wifiLowHighTransition()); + uidStates = new SparseArray(); + transPacketsFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/tx_packets"; + readPacketsFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/rx_packets"; + transBytesFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/tx_bytes"; + readBytesFile = "/sys/devices/virtual/net/" + + interfaceName + "/statistics/rx_bytes"; + uidStatsFolder = new File("/proc/uid_stat"); + } + + @Override + public IterationData calculateIteration(long iteration) { + IterationData result = IterationData.obtain(); + + int wifiStateFlag = wifiManager.getWifiState(); + if(wifiStateFlag != WifiManager.WIFI_STATE_ENABLED && + wifiStateFlag != WifiManager.WIFI_STATE_DISABLING) { + /* We need to allow the real iterface state keeper to reset it's state + * so that the next update it knows it's coming back from an off state. + * We also need to clear all the uid information. + */ + wifiState.interfaceOff(); + uidStates.clear(); + lastLinkSpeed = -1; + + WifiData data = WifiData.obtain(); + data.init(); + result.setPowerData(data); + return result; + } + + long transmitPackets = sysInfo.readLongFromFile(transPacketsFile); + long receivePackets = sysInfo.readLongFromFile(readPacketsFile); + long transmitBytes = sysInfo.readLongFromFile(transBytesFile); + long receiveBytes = sysInfo.readLongFromFile(readBytesFile); + if(transmitPackets == -1 || receivePackets == -1 || + transmitBytes == -1 || receiveBytes == -1) { + /* Couldn't read interface data files. */ + Log.w(TAG, "Failed to read packet and byte counts from wifi interface"); + return result; + } + + /* Update the link speed every 15 seconds as pulling the WifiInfo structure + * from WifiManager is a little bit expensive. This isn't really something + * that is likely to change very frequently anyway. + */ + if(iteration % 15 == 0 || lastLinkSpeed == -1) { + lastLinkSpeed = wifiManager.getConnectionInfo().getLinkSpeed(); + } + double linkSpeed = lastLinkSpeed; + + if(wifiState.isInitialized()) { + wifiState.updateState(transmitPackets, receivePackets, + transmitBytes, receiveBytes); + WifiData data = WifiData.obtain(); + data.init(wifiState.getPackets(), wifiState.getUplinkBytes(), + wifiState.getDownlinkBytes(), wifiState.getUplinkRate(), + linkSpeed, wifiState.getPowerState()); + result.setPowerData(data); + } else { + wifiState.updateState(transmitPackets, receivePackets, + transmitBytes, receiveBytes); + } + + lastUids = sysInfo.getUids(lastUids); + if(lastUids != null) for(int uid : lastUids) { + if(uid == -1) { + continue; + } + try { + WifiStateKeeper uidState = uidStates.get(uid); + if(uidState == null) { + uidState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(), + phoneConstants.wifiLowHighTransition()); + uidStates.put(uid, uidState); + } + + if(!uidState.isStale()) { + /* We use a huerstic here so that we don't poll for uids that haven't + * had much activity recently. + */ + continue; + } + + /* These read operations are the expensive part of polling. */ + receiveBytes = sysInfo.readLongFromFile( + "/proc/uid_stat/" + uid + "/tcp_rcv"); + transmitBytes = sysInfo.readLongFromFile( + "/proc/uid_stat/" + uid + "/tcp_snd"); + + if(receiveBytes == -1 || transmitBytes == -1) { + Log.w(TAG, "Failed to read uid read/write byte counts"); + } else if(uidState.isInitialized()) { + /* We only have information about bytes received but what we really + * want is the number of packets received so we just have to + * estimate it. + */ + long deltaTransmitBytes = transmitBytes - uidState.getTransmitBytes(); + long deltaReceiveBytes = receiveBytes - uidState.getReceiveBytes(); + long estimatedTransmitPackets = (long)Math.round(deltaTransmitBytes / + wifiState.getAverageTransmitPacketSize()); + long estimatedReceivePackets = (long)Math.round(deltaReceiveBytes / + wifiState.getAverageReceivePacketSize()); + if(deltaTransmitBytes > 0 && estimatedTransmitPackets == 0) { + estimatedTransmitPackets = 1; + } + if(deltaReceiveBytes > 0 && estimatedReceivePackets == 0) { + estimatedReceivePackets = 1; + } + + boolean active = transmitBytes != uidState.getTransmitBytes() || + receiveBytes != uidState.getReceiveBytes(); + uidState.updateState( + uidState.getTransmitPackets() + estimatedTransmitPackets, + uidState.getReceivePackets() + estimatedReceivePackets, + transmitBytes, receiveBytes); + + if(active) { + WifiData uidData = WifiData.obtain(); + uidData.init(uidState.getPackets(), uidState.getUplinkBytes(), + uidState.getDownlinkBytes(), uidState.getUplinkRate(), + linkSpeed, uidState.getPowerState()); + result.addUidPowerData(uid, uidData); + } + } else { + uidState.updateState(0, 0, transmitBytes, receiveBytes); + } + } catch(NumberFormatException e) { + Log.w(TAG, "Non-uid files in /proc/uid_stat"); + } + } + + return result; + } + + private static class WifiStateKeeper { + private long lastTransmitPackets; + private long lastReceivePackets; + private long lastTransmitBytes; + private long lastReceiveBytes; + private long lastTime; + + private int powerState; + private double lastPackets; + private double lastUplinkRate; + private double lastAverageTransmitPacketSize; + private double lastAverageReceivePacketSize; + + private long deltaUplinkBytes; + private long deltaDownlinkBytes; + + private double highLowTransition; + private double lowHighTransition; + + private long inactiveTime; + + public WifiStateKeeper(double highLowTransition, double lowHighTransition) { + this.highLowTransition = highLowTransition; + this.lowHighTransition = lowHighTransition; + lastTransmitPackets = lastReceivePackets = lastTransmitBytes = + lastTime = -1; + powerState = POWER_STATE_LOW; + lastPackets = lastUplinkRate = 0; + lastAverageTransmitPacketSize = 1000; + lastAverageReceivePacketSize = 1000; + inactiveTime = 0; + } + + public void interfaceOff() { + lastTime = SystemClock.elapsedRealtime(); + powerState = POWER_STATE_LOW; + } + + public boolean isInitialized() { + return lastTime != -1; + } + + public void updateState(long transmitPackets, long receivePackets, + long transmitBytes, long receiveBytes) { + long curTime = SystemClock.elapsedRealtime(); + if(lastTime != -1 && curTime > lastTime) { + double deltaTime = curTime - lastTime; + lastUplinkRate = (transmitBytes - lastTransmitBytes) / 1024.0 * + 7.8125 / deltaTime; + lastPackets = receivePackets + transmitPackets - + lastReceivePackets - lastTransmitPackets; + deltaUplinkBytes = transmitBytes - lastTransmitBytes; + deltaDownlinkBytes = receiveBytes - lastReceiveBytes; + if(transmitPackets != lastTransmitPackets) { + lastAverageTransmitPacketSize = 0.9 * lastAverageTransmitPacketSize + + 0.1 * (transmitBytes - lastTransmitBytes) / + (transmitPackets - lastTransmitPackets); + } + if(receivePackets != lastReceivePackets) { + lastAverageReceivePacketSize = 0.9 * lastAverageReceivePacketSize + + 0.1 * (receiveBytes - lastReceiveBytes) / + (receivePackets - lastReceivePackets); + } + + if(receiveBytes != lastReceiveBytes || + transmitBytes != lastTransmitBytes) { + inactiveTime = 0; + } else { + inactiveTime += curTime - lastTime; + } + + if(lastPackets < highLowTransition) { + powerState = POWER_STATE_LOW; + } else if(lastPackets > lowHighTransition) { + powerState = POWER_STATE_HIGH; + } + } + lastTime = curTime; + lastTransmitPackets = transmitPackets; + lastReceivePackets = receivePackets; + lastTransmitBytes = transmitBytes; + lastReceiveBytes = receiveBytes; + } + + public int getPowerState() { + return powerState; + } + + public double getPackets() { + return lastPackets; + } + + public long getUplinkBytes() { + return deltaUplinkBytes; + } + + public long getDownlinkBytes() { + return deltaDownlinkBytes; + } + + public double getUplinkRate() { + return lastUplinkRate; + } + + public double getAverageTransmitPacketSize() { + return lastAverageTransmitPacketSize; + } + + public double getAverageReceivePacketSize() { + return lastAverageReceivePacketSize; + } + + public long getTransmitPackets() { + return lastTransmitPackets; + } + + public long getReceivePackets() { + return lastReceivePackets; + } + + public long getTransmitBytes() { + return lastTransmitBytes; + } + + public long getReceiveBytes() { + return lastReceiveBytes; + } + + /* The idea here is that we don't want to have to read uid information + * every single iteration for each uid as it just takes too long. So here + * we are designing a hueristic that helps us avoid polling for too many + * uids. + */ + public boolean isStale() { + long curTime = SystemClock.elapsedRealtime(); + return curTime - lastTime > (long)Math.min(10000, inactiveTime); + } + } + + private long readLongFromFile(String filePath) { + return sysInfo.readLongFromFile(filePath); + } + + @Override + public boolean hasUidInformation() { + return uidStatsFolder.exists(); + } + + @Override + public String getComponentName() { + return "Wifi"; + } +} diff --git a/src/edu/umich/PowerTutor/phone/DreamConstants.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/DreamConstants.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/DreamConstants.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/DreamConstants.java diff --git a/src/edu/umich/PowerTutor/phone/DreamPowerCalculator.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/DreamPowerCalculator.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/DreamPowerCalculator.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/DreamPowerCalculator.java diff --git a/src/edu/umich/PowerTutor/phone/PassionConstants.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PassionConstants.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/PassionConstants.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PassionConstants.java diff --git a/src/edu/umich/PowerTutor/phone/PassionPowerCalculator.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PassionPowerCalculator.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/PassionPowerCalculator.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PassionPowerCalculator.java diff --git a/src/edu/umich/PowerTutor/phone/PhoneConstants.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PhoneConstants.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/PhoneConstants.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PhoneConstants.java diff --git a/src/edu/umich/PowerTutor/phone/PhonePowerCalculator.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PhonePowerCalculator.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/PhonePowerCalculator.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PhonePowerCalculator.java diff --git a/src/edu/umich/PowerTutor/phone/PhoneSelector.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PhoneSelector.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/PhoneSelector.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PhoneSelector.java diff --git a/src/edu/umich/PowerTutor/phone/PowerFunction.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PowerFunction.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/PowerFunction.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/PowerFunction.java diff --git a/src/edu/umich/PowerTutor/phone/SapphireConstants.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/SapphireConstants.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/SapphireConstants.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/SapphireConstants.java diff --git a/src/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java similarity index 100% rename from src/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java diff --git a/src/edu/umich/PowerTutor/service/IterationData.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/IterationData.java similarity index 100% rename from src/edu/umich/PowerTutor/service/IterationData.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/service/IterationData.java diff --git a/src/edu/umich/PowerTutor/service/LogUploader.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/LogUploader.java similarity index 96% rename from src/edu/umich/PowerTutor/service/LogUploader.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/service/LogUploader.java index b07f542..cbc4e97 100644 --- a/src/edu/umich/PowerTutor/service/LogUploader.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/LogUploader.java @@ -1,68 +1,68 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.service; - -import android.content.Context; - -/* This class is responsible for implementing all policy decisions on when to - * send log information back to an external server. Upon a successful call to - * shouldUpload() PowerEstimator will then call upload(file). After the call to - * upload the file with the passed path may be overwritten. - * - * This is a stub implementation not supporting log uploading. - */ -public class LogUploader { - public LogUploader(Context context) { - } - - /* Returns true if this module supports uploading logs. */ - public static boolean uploadSupported() { - return false; - } - - /* Returns true if the log should be uploaded now. This may depend on log - * file size, network conditions, etc. */ - // TODO: This should probably give the file name of the log - public boolean shouldUpload() { - return false; - } - - /* Called when the device is plugged in or unplugged. The intended use of - * this is to improve upload policy decisions. */ - public void plug(boolean plugged) { - } - - /* Initiate the upload of the file with the passed location. */ - public void upload(String origFile) { - } - - /* Returns true if a file is currently being uploaded. */ - public boolean isUploading() { - return false; - } - - /* Interrupt any threads doing upload work. */ - public void interrupt() { - } - - /* Join any threads that may be performing log upload work. */ - public void join() throws InterruptedException { - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.service; + +import android.content.Context; + +/* This class is responsible for implementing all policy decisions on when to + * send log information back to an external server. Upon a successful call to + * shouldUpload() PowerEstimator will then call upload(file). After the call to + * upload the file with the passed path may be overwritten. + * + * This is a stub implementation not supporting log uploading. + */ +public class LogUploader { + public LogUploader(Context context) { + } + + /* Returns true if this module supports uploading logs. */ + public static boolean uploadSupported() { + return false; + } + + /* Returns true if the log should be uploaded now. This may depend on log + * file size, network conditions, etc. */ + // TODO: This should probably give the file name of the log + public boolean shouldUpload() { + return false; + } + + /* Called when the device is plugged in or unplugged. The intended use of + * this is to improve upload policy decisions. */ + public void plug(boolean plugged) { + } + + /* Initiate the upload of the file with the passed location. */ + public void upload(String origFile) { + } + + /* Returns true if a file is currently being uploaded. */ + public boolean isUploading() { + return false; + } + + /* Interrupt any threads doing upload work. */ + public void interrupt() { + } + + /* Join any threads that may be performing log upload work. */ + public void join() throws InterruptedException { + } +} diff --git a/src/edu/umich/PowerTutor/service/PowerData.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/PowerData.java similarity index 96% rename from src/edu/umich/PowerTutor/service/PowerData.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/service/PowerData.java index 2033518..f415661 100644 --- a/src/edu/umich/PowerTutor/service/PowerData.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/PowerData.java @@ -1,48 +1,48 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.service; - -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.util.Vector; - -public abstract class PowerData { - private int cachedPower; - - public PowerData() { - } - - public void setCachedPower(int power) { - cachedPower = power; - } - - public int getCachedPower() { - return cachedPower; - } - - /* To be called when the PowerData object is no longer in use so that it can - * be used again in the next iteration if it chooses to be. - */ - public void recycle() {} - - /* Simply writes out log information to the passed stream. */ - public abstract void writeLogDataInfo(OutputStreamWriter out) - throws IOException; -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.service; + +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.Vector; + +public abstract class PowerData { + private int cachedPower; + + public PowerData() { + } + + public void setCachedPower(int power) { + cachedPower = power; + } + + public int getCachedPower() { + return cachedPower; + } + + /* To be called when the PowerData object is no longer in use so that it can + * be used again in the next iteration if it chooses to be. + */ + public void recycle() {} + + /* Simply writes out log information to the passed stream. */ + public abstract void writeLogDataInfo(OutputStreamWriter out) + throws IOException; +} diff --git a/src/edu/umich/PowerTutor/service/PowerEstimator.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/PowerEstimator.java similarity index 97% rename from src/edu/umich/PowerTutor/service/PowerEstimator.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/service/PowerEstimator.java index ed0325a..60b752c 100644 --- a/src/edu/umich/PowerTutor/service/PowerEstimator.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/PowerEstimator.java @@ -1,586 +1,586 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.service; - -import edu.umich.PowerTutor.components.PowerComponent; -import edu.umich.PowerTutor.components.OLED; -import edu.umich.PowerTutor.phone.PhoneSelector; -import edu.umich.PowerTutor.phone.PhoneConstants; -import edu.umich.PowerTutor.phone.PowerFunction; -import edu.umich.PowerTutor.util.BatteryStats; -import edu.umich.PowerTutor.util.Counter; -import edu.umich.PowerTutor.util.HistoryBuffer; -import edu.umich.PowerTutor.util.NotificationService; -import edu.umich.PowerTutor.util.SystemInfo; -import edu.umich.PowerTutor.widget.PowerWidget; - -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.SystemClock; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.util.Log; -import android.util.SparseArray; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.text.DecimalFormat; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Map; -import java.util.Vector; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -/** This class is responsible for starting the individual power component - * loggers (CPU, GPS, etc...) and collecting the information they generate. - * This information is used both to write a log file that will be send back - * to spidermoneky (or looked at by the user) and to implement the - * ICounterService IPC interface. - */ -public class PowerEstimator implements Runnable { - private static final String TAG = "PowerEstimator"; - - /* A dictionary used to assist in compression of the log files. Strings that - * appear more frequently should be put towards the end of the dictionary. It - * is not critical that every string that be written to the log appear here. - */ - private static final String DEFLATE_DICTIONARY = - "onoffidleoff-hookringinglowairplane-modebatteryedgeGPRS3Gunknown" + - "in-serviceemergency-onlyout-of-servicepower-offdisconnectedconnecting" + - "associateconnectedsuspendedphone-callservicenetworkbegin.0123456789" + - "GPSAudioWifi3GLCDCPU-power "; - - public static final int ALL_COMPONENTS = -1; - public static final int ITERATION_INTERVAL = 1000; // 1 second - - private UMLoggerService context; - private SharedPreferences prefs; - private boolean plugged; - - private Vector powerComponents; - private Vector powerFunctions; - private Vector histories; - private Map uidAppIds; - - // Miscellaneous data. - private HistoryBuffer oledScoreHistory; - - private Object fileWriteLock = new Object(); - private LogUploader logUploader; - private OutputStreamWriter logStream; - private DeflaterOutputStream deflateStream; - - private Object iterationLock = new Object(); - private long lastWrittenIteration; - - public PowerEstimator(UMLoggerService context){ - this.context = context; - prefs = PreferenceManager.getDefaultSharedPreferences(context); - powerComponents = new Vector(); - powerFunctions = new Vector(); - uidAppIds = new HashMap(); - PhoneSelector.generateComponents(context, powerComponents, powerFunctions); - - histories = new Vector(); - for(int i = 0; i < powerComponents.size(); i++) { - histories.add(new HistoryBuffer(300)); - } - oledScoreHistory = new HistoryBuffer(0); - - logUploader = new LogUploader(context); - openLog(true); - } - - private void openLog(boolean init) { - /* Open up the log file if possible. */ - try { - String logFilename = context.getFileStreamPath( - "PowerTrace.log").getAbsolutePath(); - if(init && prefs.getBoolean("sendPermission", true) && - new File(logFilename).length() > 0) { - /* There is data to send. Make sure that gets going in the sending - * process before we write over any old logs. - */ - logUploader.upload(logFilename); - } - Deflater deflater = new Deflater(); - deflater.setDictionary(DEFLATE_DICTIONARY.getBytes()); - deflateStream = new DeflaterOutputStream( - new FileOutputStream(logFilename)); - logStream = new OutputStreamWriter(deflateStream); - } catch(IOException e) { - logStream = null; - Log.e(TAG, "Failed to open log file. No log will be kept."); - } - } - - /** This is the loop that keeps updating the power profile - */ - public void run() { - SystemInfo sysInfo = SystemInfo.getInstance(); - PackageManager pm = context.getPackageManager(); - BatteryStats bst = BatteryStats.getInstance(); - - int components = powerComponents.size(); - long beginTime = SystemClock.elapsedRealtime(); - for(int i = 0; i < components; i++) { - powerComponents.get(i).init(beginTime, ITERATION_INTERVAL); - powerComponents.get(i).start(); - } - IterationData[] dataTemp = new IterationData[components]; - - PhoneConstants phoneConstants = PhoneSelector.getConstants(context); - long[] memInfo = new long[4]; - - int oledId = -1; - for(int i = 0; i < components; i++) { - if("OLED".equals(powerComponents.get(i).getComponentName())) { - oledId = i; - break; - } - } - - double lastCurrent = -1; - - /* Indefinitely collect data on each of the power components. */ - boolean firstLogIteration = true; - for(long iter = -1; !Thread.interrupted(); ) { - long curTime = SystemClock.elapsedRealtime(); - /* Compute the next iteration that we can make the ending of. We wait - for the end of the iteration so that the components had a chance to - collect data already. - */ - iter = (long)Math.max(iter + 1, - (curTime - beginTime) / ITERATION_INTERVAL); - /* Sleep until the next iteration completes. */ - try { - Thread.currentThread().sleep( - beginTime + (iter + 1) * ITERATION_INTERVAL - curTime); - } catch(InterruptedException e) { - break; - } - - int totalPower = 0; - for(int i = 0; i < components; i++) { - PowerComponent comp = powerComponents.get(i); - IterationData data = comp.getData(iter); - dataTemp[i] = data; - if(data == null) { - /* No data present for this timestamp. No power charged. - */ - continue; - } - - SparseArray uidPower = data.getUidPowerData(); - for(int j = 0; j < uidPower.size(); j++) { - int uid = uidPower.keyAt(j); - PowerData powerData = uidPower.valueAt(j); - int power = (int)powerFunctions.get(i).calculate(powerData); - powerData.setCachedPower(power); - histories.get(i).add(uid, iter, power); - if(uid == SystemInfo.AID_ALL) { - totalPower += power; - } - if(i == oledId) { - OLED.OledData oledData = (OLED.OledData)powerData; - if(oledData.pixPower >= 0) { - oledScoreHistory.add(uid, iter, (int)(1000 * oledData.pixPower)); - } - } - } - } - - /* Update the uid set. */ - synchronized(fileWriteLock) { synchronized(uidAppIds) { - for(int i = 0; i < components; i++) { - IterationData data = dataTemp[i]; - if(data == null) { - continue; - } - SparseArray uidPower = data.getUidPowerData(); - for(int j = 0; j < uidPower.size(); j++) { - int uid = uidPower.keyAt(j); - if(uid < SystemInfo.AID_APP) { - uidAppIds.put(uid, null); - } else { - /* We only want to update app names when logging so the associcate - * message gets written. - */ - String appId = uidAppIds.get(uid); - String newAppId = sysInfo.getAppId(uid, pm); - if(!firstLogIteration && logStream != null && - (appId == null || !appId.equals(newAppId))) { - try { - logStream.write("associate " + uid + " " + newAppId + "\n"); - } catch(IOException e) { - Log.w(TAG, "Failed to write to log file"); - } - } - uidAppIds.put(uid, newAppId); - } - } - } - }} - - synchronized(iterationLock) { - lastWrittenIteration = iter; - } - - /* Update the icon display every 15 iterations. */ - if(iter % 15 == 14) { - final double POLY_WEIGHT = 0.02; - int count = 0; - int[] history = getComponentHistory(5 * 60, -1, - SystemInfo.AID_ALL, -1); - double weightedAvgPower = 0; - for(int i = history.length - 1; i >= 0; i--) { - if(history[i] != 0) { - count++; - weightedAvgPower *= 1.0 - POLY_WEIGHT; - weightedAvgPower += POLY_WEIGHT * history[i] / 1000.0; - } - } - double avgPower = -1; - if(count != 0) { - avgPower = weightedAvgPower / - (1.0 - Math.pow(1.0 - POLY_WEIGHT, count)); - } - avgPower *= 1000; - - context.updateNotification((int)Math.min(8, 1 + - 8 * avgPower / phoneConstants.maxPower()), - avgPower); - } - - /* Update the widget. */ - if(iter % 60 == 0) { - PowerWidget.updateWidget(context, this); - } - - if(bst.hasCurrent()) { - double current = bst.getCurrent(); - if(current != lastCurrent) { - writeToLog("batt_current " + current + "\n"); - lastCurrent = current; - } - } - if(iter % (5*60) == 0) { - if(bst.hasTemp()) { - writeToLog("batt_temp " + bst.getTemp() + "\n"); - } - if(bst.hasCharge()) { - writeToLog("batt_charge " + bst.getCharge() + "\n"); - } - } - if(iter % (30*60) == 0) { - if(Settings.System.getInt(context.getContentResolver(), - "screen_brightness_mode", 0) != 0) { - writeToLog("setting_brightness automatic\n"); - } else { - int brightness = Settings.System.getInt( - context.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS, -1); - if(brightness != -1) { - writeToLog("setting_brightness " + brightness + "\n"); - } - } - int timeout = Settings.System.getInt( - context.getContentResolver(), - Settings.System.SCREEN_OFF_TIMEOUT, -1); - if(timeout != -1) { - writeToLog("setting_screen_timeout " + timeout + "\n"); - } - String httpProxy = Settings.Secure.getString( - context.getContentResolver(), - Settings.Secure.HTTP_PROXY); - if(httpProxy != null) { - writeToLog("setting_httpproxy " + httpProxy + "\n"); - } - } - - /* Let's only grab memory information every 10 seconds to try to keep log - * file size down and the notice_data table size down. - */ - boolean hasMem = false; - if(iter % 10 == 0) { - hasMem = sysInfo.getMemInfo(memInfo); - } - - synchronized(fileWriteLock) { - if(logStream != null) try { - if(firstLogIteration) { - firstLogIteration = false; - logStream.write("time " + System.currentTimeMillis() + "\n"); - Calendar cal = new GregorianCalendar(); - logStream.write("localtime_offset " + - (cal.get(Calendar.ZONE_OFFSET) + - cal.get(Calendar.DST_OFFSET)) + "\n"); - logStream.write("model " + phoneConstants.modelName() + "\n"); - if(NotificationService.available()) { - logStream.write("notifications-active\n"); - } - if(bst.hasFullCapacity()) { - logStream.write("batt_full_capacity " + bst.getFullCapacity() - + "\n"); - } - synchronized(uidAppIds) { - for(int uid : uidAppIds.keySet()) { - if(uid < SystemInfo.AID_APP) { - continue; - } - logStream.write("associate " + uid + " " + uidAppIds.get(uid) - + "\n"); - } - } - } - logStream.write("begin " + iter + "\n"); - logStream.write("total-power " + (long)Math.round(totalPower) + '\n'); - if(hasMem) { - logStream.write("meminfo " + memInfo[0] + " " + memInfo[1] + - " " + memInfo[2] + " " + memInfo[3] + "\n"); - } - for(int i = 0; i < components; i++) { - IterationData data = dataTemp[i]; - if(data != null) { - String name = powerComponents.get(i).getComponentName(); - SparseArray uidData = data.getUidPowerData(); - for(int j = 0; j < uidData.size(); j++) { - int uid = uidData.keyAt(j); - PowerData powerData = uidData.valueAt(j); - if(uid == SystemInfo.AID_ALL) { - logStream.write(name + " " + (long)Math.round( - powerData.getCachedPower()) + "\n"); - powerData.writeLogDataInfo(logStream); - } else { - logStream.write(name + "-" + uid + " " + (long)Math.round( - powerData.getCachedPower()) + "\n"); - } - } - data.recycle(); - } - } - } catch(IOException e) { - Log.w(TAG, "Failed to write to log file"); - } - - if(iter % 15 == 0 && prefs.getBoolean("sendPermission", true)) { - /* Allow for LogUploader to decide if the log needs to be uploaded and - * begin uploading if it decides it's necessary. - */ - if(logUploader.shouldUpload()) { - try { - logStream.close(); - } catch(IOException e) { - Log.w(TAG, "Failed to flush and close log stream"); - } - logStream = null; - logUploader.upload(context.getFileStreamPath( - "PowerTrace.log").getAbsolutePath()); - openLog(false); - firstLogIteration = true; - } - } - } - } - - /* Blank the widget's display and turn off power button. */ - PowerWidget.updateWidgetDone(context); - - /* Have all of the power component threads exit. */ - logUploader.interrupt(); - for(int i = 0; i < components; i++) { - powerComponents.get(i).interrupt(); - } - try { - logUploader.join(); - } catch(InterruptedException e) { - } - for(int i = 0; i < components; i++) { - try { - powerComponents.get(i).join(); - } catch(InterruptedException e) { - } - } - - /* Close the logstream so that everything gets flushed and written to file - * before we have to quit. - */ - synchronized(fileWriteLock) { - if(logStream != null) try { - logStream.close(); - } catch(IOException e) { - Log.w(TAG, "Failed to flush log file on exit"); - } - } - } - - public void plug(boolean plugged) { - logUploader.plug(plugged); - } - - public void writeToLog(String m) { - synchronized(fileWriteLock) { - if(logStream != null) try { - logStream.write(m); - } catch(IOException e) { - Log.w(TAG, "Failed to write message to power log"); - } - } - } - - public String[] getComponents() { - int components = powerComponents.size(); - String[] ret = new String[components]; - for(int i = 0; i < components; i++) { - ret[i] = powerComponents.get(i).getComponentName(); - } - return ret; - } - - public int[] getComponentsMaxPower() { - PhoneConstants constants = PhoneSelector.getConstants(context); - int components = powerComponents.size(); - int[] ret = new int[components]; - for(int i = 0; i < components; i++) { - ret[i] = (int)constants.getMaxPower( - powerComponents.get(i).getComponentName()); - } - return ret; - } - - public int getNoUidMask() { - int components = powerComponents.size(); - int ret = 0; - for(int i = 0; i < components; i++) { - if(!powerComponents.get(i).hasUidInformation()) { - ret |= 1 << i; - } - } - return ret; - } - - public int[] getComponentHistory(int count, int componentId, int uid, - long iteration) { - if(iteration == -1) synchronized(iterationLock) { - iteration = lastWrittenIteration; - } - int components = powerComponents.size(); - if(componentId == ALL_COMPONENTS) { - int[] result = new int[count]; - for(int i = 0; i < components; i++) { - int[] comp = histories.get(i).get(uid, iteration, count); - for(int j = 0; j < count; j++) { - result[j] += comp[j]; - } - } - return result; - } - if(componentId < 0 || components <= componentId) return null; - return histories.get(componentId).get(uid, iteration, count); - } - - public long[] getTotals(int uid, int windowType) { - int components = powerComponents.size(); - long[] ret = new long[components]; - for(int i = 0; i < components; i++) { - ret[i] = histories.get(i).getTotal(uid, windowType) * - ITERATION_INTERVAL / 1000; - } - return ret; - } - - public long getRuntime(int uid, int windowType) { - long runningTime = 0; - int components = powerComponents.size(); - for(int i = 0; i < components; i++) { - long entries = histories.get(i).getCount(uid, windowType); - runningTime = entries > runningTime ? entries : runningTime; - } - return runningTime * ITERATION_INTERVAL / 1000; - } - - public long[] getMeans(int uid, int windowType) { - long[] ret = getTotals(uid, windowType); - long runningTime = getRuntime(uid, windowType); - runningTime = runningTime == 0 ? 1 : runningTime; - for(int i = 0; i < ret.length; i++) { - ret[i] /= runningTime; - } - return ret; - } - - public UidInfo[] getUidInfo(int windowType, int ignoreMask) { - long iteration; - synchronized(iterationLock) { - iteration = lastWrittenIteration; - } - int components = powerComponents.size(); - synchronized(uidAppIds) { - int pos = 0; - UidInfo[] result = new UidInfo[uidAppIds.size()]; - for(Integer uid : uidAppIds.keySet()) { - UidInfo info = UidInfo.obtain(); - int currentPower = 0; - for(int i = 0; i < components; i++) { - if((ignoreMask & 1 << i) == 0) { - currentPower += histories.get(i).get(uid, iteration, 1)[0]; - } - } - double scale = ITERATION_INTERVAL / 1000.0; - info.init(uid, currentPower, - sumArray(getTotals(uid, windowType), ignoreMask) * - ITERATION_INTERVAL / 1000, - getRuntime(uid, windowType) * ITERATION_INTERVAL / 1000); - result[pos++] = info; - } - return result; - } - } - - private long sumArray(long[] A, int ignoreMask) { - long ret = 0; - for(int i = 0; i < A.length; i++) { - if((ignoreMask & 1 << i) == 0) { - ret += A[i]; - } - } - return ret; - } - - public long getUidExtra(String name, int uid) { - if("OLEDSCORE".equals(name)) { - long entries = oledScoreHistory.getCount(uid, Counter.WINDOW_TOTAL); - if(entries <= 0) return -2; - double result = oledScoreHistory.getTotal(uid, Counter.WINDOW_TOTAL) / - 1000.0; - result /= entries; - PhoneConstants phoneConstants = PhoneSelector.getConstants(context); - result *= 255 / (phoneConstants.getMaxPower("OLED") - - phoneConstants.oledBasePower()); - return (long)Math.round(result * 100); - } - return -1; - } -} - +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.service; + +import edu.umich.PowerTutor.components.PowerComponent; +import edu.umich.PowerTutor.components.OLED; +import edu.umich.PowerTutor.phone.PhoneSelector; +import edu.umich.PowerTutor.phone.PhoneConstants; +import edu.umich.PowerTutor.phone.PowerFunction; +import edu.umich.PowerTutor.util.BatteryStats; +import edu.umich.PowerTutor.util.Counter; +import edu.umich.PowerTutor.util.HistoryBuffer; +import edu.umich.PowerTutor.util.NotificationService; +import edu.umich.PowerTutor.util.SystemInfo; +import edu.umich.PowerTutor.widget.PowerWidget; + +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.util.Log; +import android.util.SparseArray; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.text.DecimalFormat; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +/** This class is responsible for starting the individual power component + * loggers (CPU, GPS, etc...) and collecting the information they generate. + * This information is used both to write a log file that will be send back + * to spidermoneky (or looked at by the user) and to implement the + * ICounterService IPC interface. + */ +public class PowerEstimator implements Runnable { + private static final String TAG = "PowerEstimator"; + + /* A dictionary used to assist in compression of the log files. Strings that + * appear more frequently should be put towards the end of the dictionary. It + * is not critical that every string that be written to the log appear here. + */ + private static final String DEFLATE_DICTIONARY = + "onoffidleoff-hookringinglowairplane-modebatteryedgeGPRS3Gunknown" + + "in-serviceemergency-onlyout-of-servicepower-offdisconnectedconnecting" + + "associateconnectedsuspendedphone-callservicenetworkbegin.0123456789" + + "GPSAudioWifi3GLCDCPU-power "; + + public static final int ALL_COMPONENTS = -1; + public static final int ITERATION_INTERVAL = 1000; // 1 second + + private UMLoggerService context; + private SharedPreferences prefs; + private boolean plugged; + + private Vector powerComponents; + private Vector powerFunctions; + private Vector histories; + private Map uidAppIds; + + // Miscellaneous data. + private HistoryBuffer oledScoreHistory; + + private Object fileWriteLock = new Object(); + private LogUploader logUploader; + private OutputStreamWriter logStream; + private DeflaterOutputStream deflateStream; + + private Object iterationLock = new Object(); + private long lastWrittenIteration; + + public PowerEstimator(UMLoggerService context){ + this.context = context; + prefs = PreferenceManager.getDefaultSharedPreferences(context); + powerComponents = new Vector(); + powerFunctions = new Vector(); + uidAppIds = new HashMap(); + PhoneSelector.generateComponents(context, powerComponents, powerFunctions); + + histories = new Vector(); + for(int i = 0; i < powerComponents.size(); i++) { + histories.add(new HistoryBuffer(300)); + } + oledScoreHistory = new HistoryBuffer(0); + + logUploader = new LogUploader(context); + openLog(true); + } + + private void openLog(boolean init) { + /* Open up the log file if possible. */ + try { + String logFilename = context.getFileStreamPath( + "PowerTrace.log").getAbsolutePath(); + if(init && prefs.getBoolean("sendPermission", true) && + new File(logFilename).length() > 0) { + /* There is data to send. Make sure that gets going in the sending + * process before we write over any old logs. + */ + logUploader.upload(logFilename); + } + Deflater deflater = new Deflater(); + deflater.setDictionary(DEFLATE_DICTIONARY.getBytes()); + deflateStream = new DeflaterOutputStream( + new FileOutputStream(logFilename)); + logStream = new OutputStreamWriter(deflateStream); + } catch(IOException e) { + logStream = null; + Log.e(TAG, "Failed to open log file. No log will be kept."); + } + } + + /** This is the loop that keeps updating the power profile + */ + public void run() { + SystemInfo sysInfo = SystemInfo.getInstance(); + PackageManager pm = context.getPackageManager(); + BatteryStats bst = BatteryStats.getInstance(); + + int components = powerComponents.size(); + long beginTime = SystemClock.elapsedRealtime(); + for(int i = 0; i < components; i++) { + powerComponents.get(i).init(beginTime, ITERATION_INTERVAL); + powerComponents.get(i).start(); + } + IterationData[] dataTemp = new IterationData[components]; + + PhoneConstants phoneConstants = PhoneSelector.getConstants(context); + long[] memInfo = new long[4]; + + int oledId = -1; + for(int i = 0; i < components; i++) { + if("OLED".equals(powerComponents.get(i).getComponentName())) { + oledId = i; + break; + } + } + + double lastCurrent = -1; + + /* Indefinitely collect data on each of the power components. */ + boolean firstLogIteration = true; + for(long iter = -1; !Thread.interrupted(); ) { + long curTime = SystemClock.elapsedRealtime(); + /* Compute the next iteration that we can make the ending of. We wait + for the end of the iteration so that the components had a chance to + collect data already. + */ + iter = (long)Math.max(iter + 1, + (curTime - beginTime) / ITERATION_INTERVAL); + /* Sleep until the next iteration completes. */ + try { + Thread.currentThread().sleep( + beginTime + (iter + 1) * ITERATION_INTERVAL - curTime); + } catch(InterruptedException e) { + break; + } + + int totalPower = 0; + for(int i = 0; i < components; i++) { + PowerComponent comp = powerComponents.get(i); + IterationData data = comp.getData(iter); + dataTemp[i] = data; + if(data == null) { + /* No data present for this timestamp. No power charged. + */ + continue; + } + + SparseArray uidPower = data.getUidPowerData(); + for(int j = 0; j < uidPower.size(); j++) { + int uid = uidPower.keyAt(j); + PowerData powerData = uidPower.valueAt(j); + int power = (int)powerFunctions.get(i).calculate(powerData); + powerData.setCachedPower(power); + histories.get(i).add(uid, iter, power); + if(uid == SystemInfo.AID_ALL) { + totalPower += power; + } + if(i == oledId) { + OLED.OledData oledData = (OLED.OledData)powerData; + if(oledData.pixPower >= 0) { + oledScoreHistory.add(uid, iter, (int)(1000 * oledData.pixPower)); + } + } + } + } + + /* Update the uid set. */ + synchronized(fileWriteLock) { synchronized(uidAppIds) { + for(int i = 0; i < components; i++) { + IterationData data = dataTemp[i]; + if(data == null) { + continue; + } + SparseArray uidPower = data.getUidPowerData(); + for(int j = 0; j < uidPower.size(); j++) { + int uid = uidPower.keyAt(j); + if(uid < SystemInfo.AID_APP) { + uidAppIds.put(uid, null); + } else { + /* We only want to update app names when logging so the associcate + * message gets written. + */ + String appId = uidAppIds.get(uid); + String newAppId = sysInfo.getAppId(uid, pm); + if(!firstLogIteration && logStream != null && + (appId == null || !appId.equals(newAppId))) { + try { + logStream.write("associate " + uid + " " + newAppId + "\n"); + } catch(IOException e) { + Log.w(TAG, "Failed to write to log file"); + } + } + uidAppIds.put(uid, newAppId); + } + } + } + }} + + synchronized(iterationLock) { + lastWrittenIteration = iter; + } + + /* Update the icon display every 15 iterations. */ + if(iter % 15 == 14) { + final double POLY_WEIGHT = 0.02; + int count = 0; + int[] history = getComponentHistory(5 * 60, -1, + SystemInfo.AID_ALL, -1); + double weightedAvgPower = 0; + for(int i = history.length - 1; i >= 0; i--) { + if(history[i] != 0) { + count++; + weightedAvgPower *= 1.0 - POLY_WEIGHT; + weightedAvgPower += POLY_WEIGHT * history[i] / 1000.0; + } + } + double avgPower = -1; + if(count != 0) { + avgPower = weightedAvgPower / + (1.0 - Math.pow(1.0 - POLY_WEIGHT, count)); + } + avgPower *= 1000; + + context.updateNotification((int)Math.min(8, 1 + + 8 * avgPower / phoneConstants.maxPower()), + avgPower); + } + + /* Update the widget. */ + if(iter % 60 == 0) { + PowerWidget.updateWidget(context, this); + } + + if(bst.hasCurrent()) { + double current = bst.getCurrent(); + if(current != lastCurrent) { + writeToLog("batt_current " + current + "\n"); + lastCurrent = current; + } + } + if(iter % (5*60) == 0) { + if(bst.hasTemp()) { + writeToLog("batt_temp " + bst.getTemp() + "\n"); + } + if(bst.hasCharge()) { + writeToLog("batt_charge " + bst.getCharge() + "\n"); + } + } + if(iter % (30*60) == 0) { + if(Settings.System.getInt(context.getContentResolver(), + "screen_brightness_mode", 0) != 0) { + writeToLog("setting_brightness automatic\n"); + } else { + int brightness = Settings.System.getInt( + context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, -1); + if(brightness != -1) { + writeToLog("setting_brightness " + brightness + "\n"); + } + } + int timeout = Settings.System.getInt( + context.getContentResolver(), + Settings.System.SCREEN_OFF_TIMEOUT, -1); + if(timeout != -1) { + writeToLog("setting_screen_timeout " + timeout + "\n"); + } + String httpProxy = Settings.Secure.getString( + context.getContentResolver(), + Settings.Secure.HTTP_PROXY); + if(httpProxy != null) { + writeToLog("setting_httpproxy " + httpProxy + "\n"); + } + } + + /* Let's only grab memory information every 10 seconds to try to keep log + * file size down and the notice_data table size down. + */ + boolean hasMem = false; + if(iter % 10 == 0) { + hasMem = sysInfo.getMemInfo(memInfo); + } + + synchronized(fileWriteLock) { + if(logStream != null) try { + if(firstLogIteration) { + firstLogIteration = false; + logStream.write("time " + System.currentTimeMillis() + "\n"); + Calendar cal = new GregorianCalendar(); + logStream.write("localtime_offset " + + (cal.get(Calendar.ZONE_OFFSET) + + cal.get(Calendar.DST_OFFSET)) + "\n"); + logStream.write("model " + phoneConstants.modelName() + "\n"); + if(NotificationService.available()) { + logStream.write("notifications-active\n"); + } + if(bst.hasFullCapacity()) { + logStream.write("batt_full_capacity " + bst.getFullCapacity() + + "\n"); + } + synchronized(uidAppIds) { + for(int uid : uidAppIds.keySet()) { + if(uid < SystemInfo.AID_APP) { + continue; + } + logStream.write("associate " + uid + " " + uidAppIds.get(uid) + + "\n"); + } + } + } + logStream.write("begin " + iter + "\n"); + logStream.write("total-power " + (long)Math.round(totalPower) + '\n'); + if(hasMem) { + logStream.write("meminfo " + memInfo[0] + " " + memInfo[1] + + " " + memInfo[2] + " " + memInfo[3] + "\n"); + } + for(int i = 0; i < components; i++) { + IterationData data = dataTemp[i]; + if(data != null) { + String name = powerComponents.get(i).getComponentName(); + SparseArray uidData = data.getUidPowerData(); + for(int j = 0; j < uidData.size(); j++) { + int uid = uidData.keyAt(j); + PowerData powerData = uidData.valueAt(j); + if(uid == SystemInfo.AID_ALL) { + logStream.write(name + " " + (long)Math.round( + powerData.getCachedPower()) + "\n"); + powerData.writeLogDataInfo(logStream); + } else { + logStream.write(name + "-" + uid + " " + (long)Math.round( + powerData.getCachedPower()) + "\n"); + } + } + data.recycle(); + } + } + } catch(IOException e) { + Log.w(TAG, "Failed to write to log file"); + } + + if(iter % 15 == 0 && prefs.getBoolean("sendPermission", true)) { + /* Allow for LogUploader to decide if the log needs to be uploaded and + * begin uploading if it decides it's necessary. + */ + if(logUploader.shouldUpload()) { + try { + logStream.close(); + } catch(IOException e) { + Log.w(TAG, "Failed to flush and close log stream"); + } + logStream = null; + logUploader.upload(context.getFileStreamPath( + "PowerTrace.log").getAbsolutePath()); + openLog(false); + firstLogIteration = true; + } + } + } + } + + /* Blank the widget's display and turn off power button. */ + PowerWidget.updateWidgetDone(context); + + /* Have all of the power component threads exit. */ + logUploader.interrupt(); + for(int i = 0; i < components; i++) { + powerComponents.get(i).interrupt(); + } + try { + logUploader.join(); + } catch(InterruptedException e) { + } + for(int i = 0; i < components; i++) { + try { + powerComponents.get(i).join(); + } catch(InterruptedException e) { + } + } + + /* Close the logstream so that everything gets flushed and written to file + * before we have to quit. + */ + synchronized(fileWriteLock) { + if(logStream != null) try { + logStream.close(); + } catch(IOException e) { + Log.w(TAG, "Failed to flush log file on exit"); + } + } + } + + public void plug(boolean plugged) { + logUploader.plug(plugged); + } + + public void writeToLog(String m) { + synchronized(fileWriteLock) { + if(logStream != null) try { + logStream.write(m); + } catch(IOException e) { + Log.w(TAG, "Failed to write message to power log"); + } + } + } + + public String[] getComponents() { + int components = powerComponents.size(); + String[] ret = new String[components]; + for(int i = 0; i < components; i++) { + ret[i] = powerComponents.get(i).getComponentName(); + } + return ret; + } + + public int[] getComponentsMaxPower() { + PhoneConstants constants = PhoneSelector.getConstants(context); + int components = powerComponents.size(); + int[] ret = new int[components]; + for(int i = 0; i < components; i++) { + ret[i] = (int)constants.getMaxPower( + powerComponents.get(i).getComponentName()); + } + return ret; + } + + public int getNoUidMask() { + int components = powerComponents.size(); + int ret = 0; + for(int i = 0; i < components; i++) { + if(!powerComponents.get(i).hasUidInformation()) { + ret |= 1 << i; + } + } + return ret; + } + + public int[] getComponentHistory(int count, int componentId, int uid, + long iteration) { + if(iteration == -1) synchronized(iterationLock) { + iteration = lastWrittenIteration; + } + int components = powerComponents.size(); + if(componentId == ALL_COMPONENTS) { + int[] result = new int[count]; + for(int i = 0; i < components; i++) { + int[] comp = histories.get(i).get(uid, iteration, count); + for(int j = 0; j < count; j++) { + result[j] += comp[j]; + } + } + return result; + } + if(componentId < 0 || components <= componentId) return null; + return histories.get(componentId).get(uid, iteration, count); + } + + public long[] getTotals(int uid, int windowType) { + int components = powerComponents.size(); + long[] ret = new long[components]; + for(int i = 0; i < components; i++) { + ret[i] = histories.get(i).getTotal(uid, windowType) * + ITERATION_INTERVAL / 1000; + } + return ret; + } + + public long getRuntime(int uid, int windowType) { + long runningTime = 0; + int components = powerComponents.size(); + for(int i = 0; i < components; i++) { + long entries = histories.get(i).getCount(uid, windowType); + runningTime = entries > runningTime ? entries : runningTime; + } + return runningTime * ITERATION_INTERVAL / 1000; + } + + public long[] getMeans(int uid, int windowType) { + long[] ret = getTotals(uid, windowType); + long runningTime = getRuntime(uid, windowType); + runningTime = runningTime == 0 ? 1 : runningTime; + for(int i = 0; i < ret.length; i++) { + ret[i] /= runningTime; + } + return ret; + } + + public UidInfo[] getUidInfo(int windowType, int ignoreMask) { + long iteration; + synchronized(iterationLock) { + iteration = lastWrittenIteration; + } + int components = powerComponents.size(); + synchronized(uidAppIds) { + int pos = 0; + UidInfo[] result = new UidInfo[uidAppIds.size()]; + for(Integer uid : uidAppIds.keySet()) { + UidInfo info = UidInfo.obtain(); + int currentPower = 0; + for(int i = 0; i < components; i++) { + if((ignoreMask & 1 << i) == 0) { + currentPower += histories.get(i).get(uid, iteration, 1)[0]; + } + } + double scale = ITERATION_INTERVAL / 1000.0; + info.init(uid, currentPower, + sumArray(getTotals(uid, windowType), ignoreMask) * + ITERATION_INTERVAL / 1000, + getRuntime(uid, windowType) * ITERATION_INTERVAL / 1000); + result[pos++] = info; + } + return result; + } + } + + private long sumArray(long[] A, int ignoreMask) { + long ret = 0; + for(int i = 0; i < A.length; i++) { + if((ignoreMask & 1 << i) == 0) { + ret += A[i]; + } + } + return ret; + } + + public long getUidExtra(String name, int uid) { + if("OLEDSCORE".equals(name)) { + long entries = oledScoreHistory.getCount(uid, Counter.WINDOW_TOTAL); + if(entries <= 0) return -2; + double result = oledScoreHistory.getTotal(uid, Counter.WINDOW_TOTAL) / + 1000.0; + result /= entries; + PhoneConstants phoneConstants = PhoneSelector.getConstants(context); + result *= 255 / (phoneConstants.getMaxPower("OLED") - + phoneConstants.oledBasePower()); + return (long)Math.round(result * 100); + } + return -1; + } +} + diff --git a/src/edu/umich/PowerTutor/service/UMLoggerService.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/UMLoggerService.java similarity index 88% rename from src/edu/umich/PowerTutor/service/UMLoggerService.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/service/UMLoggerService.java index 0c144df..1827d83 100644 --- a/src/edu/umich/PowerTutor/service/UMLoggerService.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/UMLoggerService.java @@ -1,394 +1,427 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.service; - -import edu.umich.PowerTutor.R; -import edu.umich.PowerTutor.ui.PowerTabs; -import edu.umich.PowerTutor.ui.UMLogger; -import edu.umich.PowerTutor.util.BatteryStats; -import edu.umich.PowerTutor.util.SystemInfo; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -import android.os.Bundle; -import android.os.IBinder; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.TelephonyManager; -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.io.ObjectOutputStream; -import java.io.IOException; - -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; - -public class UMLoggerService extends Service{ - private static final String TAG = "UMLoggerService"; - - private static final int NOTIFICATION_ID = 1; - private static final int NOTIFICATION_ID_LETTER = 2; - - private Thread estimatorThread; - private PowerEstimator powerEstimator; - - private Notification notification; - - private NotificationManager notificationManager; - private TelephonyManager phoneManager; - - @Override - public IBinder onBind(Intent intent) { - return binder; - } - - @Override - public void onCreate() { - powerEstimator = new PowerEstimator(this); - - /* Register to receive phone state messages. */ - phoneManager = (TelephonyManager)this.getSystemService(TELEPHONY_SERVICE); - phoneManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE | - PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | - PhoneStateListener.LISTEN_SERVICE_STATE | - PhoneStateListener.LISTEN_SIGNAL_STRENGTH); - - /* Register to receive airplane mode and battery low messages. */ - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); - filter.addAction(Intent.ACTION_BATTERY_LOW); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_REPLACED); - registerReceiver(broadcastIntentReceiver, filter); - - notificationManager = (NotificationManager)getSystemService( - NOTIFICATION_SERVICE); - } - - @Override - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); -//android.os.Debug.startMethodTracing("pt.trace"); - - if(intent.getBooleanExtra("stop", false)) { - stopSelf(); - return; - } else if(estimatorThread != null) { - return; - } - showNotification(); - estimatorThread = new Thread(powerEstimator); - estimatorThread.start(); - } - - @Override - public void onDestroy() { -//android.os.Debug.stopMethodTracing(); - if(estimatorThread != null) { - estimatorThread.interrupt(); - while(estimatorThread.isAlive()) { - try { - estimatorThread.join(); - } catch(InterruptedException e) { - } - } - } - unregisterReceiver(broadcastIntentReceiver); - - /* See comments in showNotification() for why we are using reflection here. - */ - boolean foregroundSet = false; - try { - Method stopForeground = getClass().getMethod("stopForeground", - boolean.class); - stopForeground.invoke(this, true); - foregroundSet = true; - } catch (InvocationTargetException e) { - } catch (IllegalAccessException e) { - } catch(NoSuchMethodException e) { - } - if(!foregroundSet) { - setForeground(false); - notificationManager.cancel(NOTIFICATION_ID); - } - - super.onDestroy(); - }; - - /** This function is to construct the real-time updating notification*/ - public void showNotification(){ - int icon = R.drawable.level; - - // icon from resources - CharSequence tickerText = "PowerTutor"; // ticker-text - long when = System.currentTimeMillis(); // notification time - Context context = getApplicationContext(); // application Context - CharSequence contentTitle = "PowerTutor"; // expanded message title - CharSequence contentText = ""; // expanded message text - - Intent notificationIntent = new Intent(this, UMLogger.class); - notificationIntent.putExtra("isFromIcon", true); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - notificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - /* the next two lines initialize the Notification, using the - * configurations above. - */ - notification = new Notification(icon, tickerText, when); - notification.iconLevel = 2; - notification.setLatestEventInfo(context, contentTitle, - contentText, contentIntent); - - /* We need to set the service to run in the foreground so that system - * won't try to destroy the power logging service except in the most - * critical situations (which should be fairly rare). Due to differences - * in apis across versions of android we have to use reflection. The newer - * api simultaneously sets an app to be in the foreground while adding a - * notification icon so services can't 'hide' in the foreground. - * In the new api the old call, setForeground, does nothing. - * See: http://developer.android.com/reference/android/app/Service.html#startForeground%28int,%20android.app.Notification%29 - */ - boolean foregroundSet = false; - try { - Method startForeground = getClass().getMethod("startForeground", - int.class, Notification.class); - startForeground.invoke(this, NOTIFICATION_ID, notification); - foregroundSet = true; - } catch (InvocationTargetException e) { - } catch (IllegalAccessException e) { - } catch(NoSuchMethodException e) { - } - if(!foregroundSet) { - setForeground(true); - notificationManager.notify(NOTIFICATION_ID, notification); - } - } - - /* This function is to update the notification in real time. This function - * is apparently fairly expensive cpu wise. Updating once a second caused a - * 8% cpu utilization penalty. - */ - public void updateNotification(int level, double totalPower) { - notification.icon = R.drawable.level; - notification.iconLevel = level; - - // If we know how much charge the battery has left we'll override the - // normal icon with one that indicates how much time the user can expect - // left. - BatteryStats bst = BatteryStats.getInstance(); - if(bst.hasCharge() && bst.hasVoltage()) { - double charge = bst.getCharge(); - double volt = bst.getVoltage(); - if(charge > 0 && volt > 0) { - notification.icon = R.drawable.time; - - double minutes = charge * volt / (totalPower / 1000) / 60; - if(minutes < 55) { - notification.iconLevel = 1 + - (int)Math.max(0, Math.round(minutes / 10.0) - 1); - } else { - notification.iconLevel = (int)Math.min(13, - 6 + Math.max(0, Math.round(minutes / 60.0) - 1)); - } - } - } - - CharSequence contentTitle = "PowerTutor"; - CharSequence contentText = "Total Power: " + (int)Math.round(totalPower) + - " mW"; - - /* When the user selects the notification the tab view for global power - * usage will appear. - */ - Intent notificationIntent = new Intent(this, UMLogger.class); - notificationIntent.putExtra("isFromIcon", true); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - notificationIntent, 0); - notification.setLatestEventInfo(this, contentTitle, contentText, - contentIntent); - notificationManager.notify(NOTIFICATION_ID, notification); - } - - private final ICounterService.Stub binder = - new ICounterService.Stub() { - public String[] getComponents() { - return powerEstimator.getComponents(); - } - - public int[] getComponentsMaxPower() { - return powerEstimator.getComponentsMaxPower(); - } - - public int getNoUidMask() { - return powerEstimator.getNoUidMask(); - } - - public int[] getComponentHistory(int count, int componentId, int uid) { - return powerEstimator.getComponentHistory(count, componentId, uid, -1); - } - - public long[] getTotals(int uid, int windowType) { - return powerEstimator.getTotals(uid, windowType); - } - - public long getRuntime(int uid, int windowType) { - return powerEstimator.getRuntime(uid, windowType); - } - - public long[] getMeans(int uid, int windowType) { - return powerEstimator.getMeans(uid, windowType); - } - - public byte[] getUidInfo(int windowType, int ignoreMask) { - UidInfo[] infos = powerEstimator.getUidInfo(windowType, ignoreMask); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - try { - new ObjectOutputStream(output).writeObject(infos); - } catch(IOException e) { - return null; - } - for(UidInfo info : infos) { - info.recycle(); - } - return output.toByteArray(); - } - - public long getUidExtra(String name, int uid) { - return powerEstimator.getUidExtra(name, uid); - } - }; - - - BroadcastReceiver broadcastIntentReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - if(intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { - Bundle extra = intent.getExtras(); - try { - if ((Boolean)extra.get("state")) { - powerEstimator.writeToLog("airplane-mode on\n"); - } else { - powerEstimator.writeToLog("airplane-mode off\n"); - } - } catch(ClassCastException e) { - // Some people apparently are having this problem. I'm not really - // sure why this should happen. - Log.w(TAG, "Couldn't determine airplane mode state"); - } - } else if(intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) { - powerEstimator.writeToLog("battery low\n"); - } else if(intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { - powerEstimator.writeToLog("battery-change " + - intent.getIntExtra("plugged", -1) + " " + - intent.getIntExtra("level", -1) + "/" + - intent.getIntExtra("scale", -1) + " " + - intent.getIntExtra("voltage", -1) + - intent.getIntExtra("temperature", -1) + "\n"); - powerEstimator.plug( - intent.getIntExtra("plugged", -1) != 0); - } else if(intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) || - intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) { - // A package has either been removed or its metadata has changed and we - // need to clear the cache of metadata for that app. - SystemInfo.getInstance().voidUidCache( - intent.getIntExtra(Intent.EXTRA_UID, -1)); - } - }; - }; - - PhoneStateListener phoneListener = new PhoneStateListener() { - public void onServiceStateChanged(ServiceState serviceState) { - switch(serviceState.getState()) { - case ServiceState.STATE_EMERGENCY_ONLY: - powerEstimator.writeToLog("phone-service emergency-only\n"); - break; - case ServiceState.STATE_IN_SERVICE: - powerEstimator.writeToLog("phone-service in-service\n"); - switch(phoneManager.getNetworkType()) { - case(TelephonyManager.NETWORK_TYPE_EDGE): - powerEstimator.writeToLog("phone-network edge\n"); - break; - case(TelephonyManager.NETWORK_TYPE_GPRS): - powerEstimator.writeToLog("phone-network GPRS\n"); - break; - case 8: - powerEstimator.writeToLog("phone-network HSDPA\n"); - break; - case(TelephonyManager.NETWORK_TYPE_UMTS): - powerEstimator.writeToLog("phone-network UMTS\n"); - break; - default: - powerEstimator.writeToLog("phone-network " + - phoneManager.getNetworkType() + "\n"); - } - break; - case ServiceState.STATE_OUT_OF_SERVICE: - powerEstimator.writeToLog("phone-service out-of-service\n"); - break; - case ServiceState.STATE_POWER_OFF: - powerEstimator.writeToLog("phone-service power-off\n"); - break; - } - } - - public void onCallStateChanged(int state, String incomingNumber) { - switch(state) { - case TelephonyManager.CALL_STATE_IDLE: - powerEstimator.writeToLog("phone-call idle\n"); - break; - case TelephonyManager.CALL_STATE_OFFHOOK: - powerEstimator.writeToLog("phone-call off-hook\n"); - break; - case TelephonyManager.CALL_STATE_RINGING: - powerEstimator.writeToLog("phone-call ringing\n"); - break; - } - } - - public void onDataConnectionStateChanged(int state) { - switch(state) { - case TelephonyManager.DATA_DISCONNECTED: - powerEstimator.writeToLog("data disconnected\n"); - break; - case TelephonyManager.DATA_CONNECTING: - powerEstimator.writeToLog("data connecting\n"); - break; - case TelephonyManager.DATA_CONNECTED: - powerEstimator.writeToLog("data connected\n"); - break; - case TelephonyManager.DATA_SUSPENDED: - powerEstimator.writeToLog("data suspended\n"); - break; - } - } - - public void onSignalStrengthChanged(int asu) { - powerEstimator.writeToLog("signal " + asu + "\n"); - } - }; -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.service; + +import edu.umich.PowerTutor.R; +import edu.umich.PowerTutor.ui.UMLogger; +import edu.umich.PowerTutor.util.BatteryStats; +import edu.umich.PowerTutor.util.SystemInfo; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.Log; + +import androidx.core.app.NotificationCompat; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; + +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; + +public class UMLoggerService extends Service{ + public static final String CHANNEL_ID = "edu.umich.PowerTutor.channel"; + private static final String TAG = "UMLoggerService"; + + private static final int NOTIFICATION_ID = 1; + private static final int NOTIFICATION_ID_LETTER = 2; + + private Thread estimatorThread; + private PowerEstimator powerEstimator; + + private Notification notification; + + private NotificationManager notificationManager; + private TelephonyManager phoneManager; + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onCreate() { + powerEstimator = new PowerEstimator(this); + + /* Register to receive phone state messages. */ + phoneManager = (TelephonyManager)this.getSystemService(TELEPHONY_SERVICE); + phoneManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE | + PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | + PhoneStateListener.LISTEN_SERVICE_STATE | + PhoneStateListener.LISTEN_SIGNAL_STRENGTH); + + /* Register to receive airplane mode and battery low messages. */ + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + filter.addAction(Intent.ACTION_BATTERY_LOW); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + registerReceiver(broadcastIntentReceiver, filter); + + notificationManager = (NotificationManager)getSystemService( + NOTIFICATION_SERVICE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); +//android.os.Debug.startMethodTracing("pt.trace"); + + createNotificationChannel(); + + if (intent.getBooleanExtra("stop", false)) { + stopSelf(); + return START_NOT_STICKY; + } else if (estimatorThread != null) { + return START_NOT_STICKY; + } + showNotification(); + estimatorThread = new Thread(powerEstimator); + estimatorThread.start(); + + return START_NOT_STICKY; + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel serviceChannel = new NotificationChannel( + CHANNEL_ID, + "Foreground Service Channel", + NotificationManager.IMPORTANCE_DEFAULT + ); + NotificationManager manager = getSystemService(NotificationManager.class); + manager.createNotificationChannel(serviceChannel); + } + } + + @Override + public void onDestroy() { +//android.os.Debug.stopMethodTracing(); + if(estimatorThread != null) { + estimatorThread.interrupt(); + while(estimatorThread.isAlive()) { + try { + estimatorThread.join(); + } catch(InterruptedException e) { + e.printStackTrace(); + } + } + } + unregisterReceiver(broadcastIntentReceiver); + + /* See comments in showNotification() for why we are using reflection here. + */ + boolean foregroundSet = false; + try { + Method stopForeground = getClass().getMethod("stopForeground", + boolean.class); + stopForeground.invoke(this, true); + foregroundSet = true; + } catch (InvocationTargetException e) { + } catch (IllegalAccessException e) { + } catch(NoSuchMethodException e) { + } + if(!foregroundSet) { + //setForeground(false); // TODO: Uncommented + notificationManager.cancel(NOTIFICATION_ID); + } + + super.onDestroy(); + }; + + /** This function is to construct the real-time updating notification*/ + public void showNotification(){ + int icon = R.drawable.level; + + // icon from resources + CharSequence tickerText = "PowerTutor"; // ticker-text + long when = System.currentTimeMillis(); // notification time + Context context = getApplicationContext(); // application Context + CharSequence contentTitle = "PowerTutor"; // expanded message title + CharSequence contentText = ""; // expanded message text + + Intent notificationIntent = new Intent(this, UMLogger.class); + notificationIntent.putExtra("isFromIcon", true); + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + notificationIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + /* the next two lines initialize the Notification, using the + * configurations above. + */ + Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setWhen(when) + .setSmallIcon(R.drawable.icon) + .setContentIntent(contentIntent) + .build(); + notification.tickerText = tickerText; + notification.iconLevel = 2; + + //notification = new Notification(icon, tickerText, when); + //notification.iconLevel = 2; + //notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent); + + /* We need to set the service to run in the foreground so that system + * won't try to destroy the power logging service except in the most + * critical situations (which should be fairly rare). Due to differences + * in apis across versions of android we have to use reflection. The newer + * api simultaneously sets an app to be in the foreground while adding a + * notification icon so services can't 'hide' in the foreground. + * In the new api the old call, setForeground, does nothing. + * See: http://developer.android.com/reference/android/app/Service.html#startForeground%28int,%20android.app.Notification%29 + */ + boolean foregroundSet = false; + try { + Method startForeground = getClass().getMethod("startForeground", + int.class, Notification.class); + startForeground.invoke(this, NOTIFICATION_ID, notification); + foregroundSet = true; + } catch (InvocationTargetException e) { + } catch (IllegalAccessException e) { + } catch(NoSuchMethodException e) { + } + if(!foregroundSet) { + //setForeground(true); + startForeground(1, notification); + notificationManager.notify(NOTIFICATION_ID, notification); + } + } + + /* This function is to update the notification in real time. This function + * is apparently fairly expensive cpu wise. Updating once a second caused a + * 8% cpu utilization penalty. + */ + public void updateNotification(int level, double totalPower) { + // TODO: Updating notifcation not working right now + if (true) return; + + notification.icon = R.drawable.level; + notification.iconLevel = level; + + // If we know how much charge the battery has left we'll override the + // normal icon with one that indicates how much time the user can expect + // left. + BatteryStats bst = BatteryStats.getInstance(); + if(bst.hasCharge() && bst.hasVoltage()) { + double charge = bst.getCharge(); + double volt = bst.getVoltage(); + if(charge > 0 && volt > 0) { + notification.icon = R.drawable.time; + + double minutes = charge * volt / (totalPower / 1000) / 60; + if(minutes < 55) { + notification.iconLevel = 1 + + (int)Math.max(0, Math.round(minutes / 10.0) - 1); + } else { + notification.iconLevel = (int)Math.min(13, + 6 + Math.max(0, Math.round(minutes / 60.0) - 1)); + } + } + } + + CharSequence contentTitle = "PowerTutor"; + CharSequence contentText = "Total Power: " + (int)Math.round(totalPower) + + " mW"; + + /* When the user selects the notification the tab view for global power + * usage will appear. + */ + Intent notificationIntent = new Intent(this, UMLogger.class); + notificationIntent.putExtra("isFromIcon", true); + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + notificationIntent, 0); + // TODO: Uncommented + //notification.setLatestEventInfo(this, contentTitle, contentText, contentIntent); + notificationManager.notify(NOTIFICATION_ID, notification); + } + + private final ICounterService.Stub binder = + new ICounterService.Stub() { + public String[] getComponents() { + return powerEstimator.getComponents(); + } + + public int[] getComponentsMaxPower() { + return powerEstimator.getComponentsMaxPower(); + } + + public int getNoUidMask() { + return powerEstimator.getNoUidMask(); + } + + public int[] getComponentHistory(int count, int componentId, int uid) { + return powerEstimator.getComponentHistory(count, componentId, uid, -1); + } + + public long[] getTotals(int uid, int windowType) { + return powerEstimator.getTotals(uid, windowType); + } + + public long getRuntime(int uid, int windowType) { + return powerEstimator.getRuntime(uid, windowType); + } + + public long[] getMeans(int uid, int windowType) { + return powerEstimator.getMeans(uid, windowType); + } + + public byte[] getUidInfo(int windowType, int ignoreMask) { + UidInfo[] infos = powerEstimator.getUidInfo(windowType, ignoreMask); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + new ObjectOutputStream(output).writeObject(infos); + } catch(IOException e) { + return null; + } + for(UidInfo info : infos) { + info.recycle(); + } + return output.toByteArray(); + } + + public long getUidExtra(String name, int uid) { + return powerEstimator.getUidExtra(name, uid); + } + }; + + + BroadcastReceiver broadcastIntentReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if(intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { + Bundle extra = intent.getExtras(); + try { + if ((Boolean)extra.get("state")) { + powerEstimator.writeToLog("airplane-mode on\n"); + } else { + powerEstimator.writeToLog("airplane-mode off\n"); + } + } catch(ClassCastException e) { + // Some people apparently are having this problem. I'm not really + // sure why this should happen. + Log.w(TAG, "Couldn't determine airplane mode state"); + } + } else if(intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) { + powerEstimator.writeToLog("battery low\n"); + } else if(intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { + powerEstimator.writeToLog("battery-change " + + intent.getIntExtra("plugged", -1) + " " + + intent.getIntExtra("level", -1) + "/" + + intent.getIntExtra("scale", -1) + " " + + intent.getIntExtra("voltage", -1) + + intent.getIntExtra("temperature", -1) + "\n"); + powerEstimator.plug( + intent.getIntExtra("plugged", -1) != 0); + } else if(intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) || + intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) { + // A package has either been removed or its metadata has changed and we + // need to clear the cache of metadata for that app. + SystemInfo.getInstance().voidUidCache( + intent.getIntExtra(Intent.EXTRA_UID, -1)); + } + }; + }; + + PhoneStateListener phoneListener = new PhoneStateListener() { + public void onServiceStateChanged(ServiceState serviceState) { + switch(serviceState.getState()) { + case ServiceState.STATE_EMERGENCY_ONLY: + powerEstimator.writeToLog("phone-service emergency-only\n"); + break; + case ServiceState.STATE_IN_SERVICE: + powerEstimator.writeToLog("phone-service in-service\n"); + switch(phoneManager.getNetworkType()) { + case(TelephonyManager.NETWORK_TYPE_EDGE): + powerEstimator.writeToLog("phone-network edge\n"); + break; + case(TelephonyManager.NETWORK_TYPE_GPRS): + powerEstimator.writeToLog("phone-network GPRS\n"); + break; + case 8: + powerEstimator.writeToLog("phone-network HSDPA\n"); + break; + case(TelephonyManager.NETWORK_TYPE_UMTS): + powerEstimator.writeToLog("phone-network UMTS\n"); + break; + default: + powerEstimator.writeToLog("phone-network " + + phoneManager.getNetworkType() + "\n"); + } + break; + case ServiceState.STATE_OUT_OF_SERVICE: + powerEstimator.writeToLog("phone-service out-of-service\n"); + break; + case ServiceState.STATE_POWER_OFF: + powerEstimator.writeToLog("phone-service power-off\n"); + break; + } + } + + public void onCallStateChanged(int state, String incomingNumber) { + switch(state) { + case TelephonyManager.CALL_STATE_IDLE: + powerEstimator.writeToLog("phone-call idle\n"); + break; + case TelephonyManager.CALL_STATE_OFFHOOK: + powerEstimator.writeToLog("phone-call off-hook\n"); + break; + case TelephonyManager.CALL_STATE_RINGING: + powerEstimator.writeToLog("phone-call ringing\n"); + break; + } + } + + public void onDataConnectionStateChanged(int state) { + switch(state) { + case TelephonyManager.DATA_DISCONNECTED: + powerEstimator.writeToLog("data disconnected\n"); + break; + case TelephonyManager.DATA_CONNECTING: + powerEstimator.writeToLog("data connecting\n"); + break; + case TelephonyManager.DATA_CONNECTED: + powerEstimator.writeToLog("data connected\n"); + break; + case TelephonyManager.DATA_SUSPENDED: + powerEstimator.writeToLog("data suspended\n"); + break; + } + } + + public void onSignalStrengthChanged(int asu) { + powerEstimator.writeToLog("signal " + asu + "\n"); + } + }; +} diff --git a/src/edu/umich/PowerTutor/service/UidInfo.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/service/UidInfo.java similarity index 100% rename from src/edu/umich/PowerTutor/service/UidInfo.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/service/UidInfo.java diff --git a/src/edu/umich/PowerTutor/ui/EditPreferences.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/EditPreferences.java similarity index 100% rename from src/edu/umich/PowerTutor/ui/EditPreferences.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/EditPreferences.java diff --git a/src/edu/umich/PowerTutor/ui/Help.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/Help.java similarity index 96% rename from src/edu/umich/PowerTutor/ui/Help.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/Help.java index 67f29c4..6c0bd59 100644 --- a/src/edu/umich/PowerTutor/ui/Help.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/Help.java @@ -1,49 +1,49 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.ui; - -import edu.umich.PowerTutor.R; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.View; -import android.widget.TextView; - -/**This function implements the UI for help view*/ -public class Help extends Activity { - private static final String powerTutorUrl = "http://powertutor.org"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.help); - TextView s2 = (TextView)this.findViewById(R.id.S2); - - s2.setOnClickListener(new TextView.OnClickListener() { - public void onClick(View v) { - Intent myIntent = new Intent(Intent.ACTION_VIEW, - Uri.parse(powerTutorUrl)); - startActivity(myIntent); - } - }); - } -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.ui; + +import edu.umich.PowerTutor.R; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +/**This function implements the UI for help view*/ +public class Help extends Activity { + private static final String powerTutorUrl = "http://powertutor.org"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.help); + TextView s2 = (TextView)this.findViewById(R.id.S2); + + s2.setOnClickListener(new TextView.OnClickListener() { + public void onClick(View v) { + Intent myIntent = new Intent(Intent.ACTION_VIEW, + Uri.parse(powerTutorUrl)); + startActivity(myIntent); + } + }); + } +} diff --git a/src/edu/umich/PowerTutor/ui/MiscView.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/MiscView.java similarity index 94% rename from src/edu/umich/PowerTutor/ui/MiscView.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/MiscView.java index 3b8e484..7541cc3 100644 --- a/src/edu/umich/PowerTutor/ui/MiscView.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/MiscView.java @@ -1,478 +1,468 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.ui; - -import edu.umich.PowerTutor.R; -import edu.umich.PowerTutor.phone.PhoneSelector; -import edu.umich.PowerTutor.service.ICounterService; -import edu.umich.PowerTutor.service.PowerEstimator; -import edu.umich.PowerTutor.service.UMLoggerService; -import edu.umich.PowerTutor.util.Counter; -import edu.umich.PowerTutor.util.BatteryStats; -import edu.umich.PowerTutor.util.SystemInfo; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ListActivity; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.graphics.Paint; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.SystemClock; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.ListView; - -import java.util.ArrayList; -import java.io.File; - -public class MiscView extends Activity { - private static final String TAG = "MiscView"; - - private SharedPreferences prefs; - private int uid; - - private Runnable collector; - - private Intent serviceIntent; - private CounterServiceConnection conn; - private ICounterService counterService; - private Handler handler; - - private BatteryStats batteryStats; - - private String[] componentNames; - - public void refreshView() { - final ListView listView = new ListView(this); - - ArrayAdapter adapter = new ArrayAdapter(this, 0) { - public View getView(int position, View convertView, ViewGroup parent) { - View itemView = getLayoutInflater() - .inflate(R.layout.misc_item_layout, listView, false); - TextView title = (TextView)itemView.findViewById(R.id.title); - TextView summary = (TextView)itemView.findViewById(R.id.summary); - LinearLayout widgetGroup = - (LinearLayout)itemView.findViewById(R.id.widget_frame); - InfoItem item = (InfoItem)getItem(position); - item.initViews(title, summary, widgetGroup); - item.setupView(); - return itemView; - } - }; - - final ArrayList allItems = new ArrayList(); - allItems.add(new UidItem()); - allItems.add(new PackageItem()); - allItems.add(new OLEDItem()); - allItems.add(new InstantPowerItem()); - allItems.add(new AveragePowerItem()); - allItems.add(new CurrentItem()); - allItems.add(new ChargeItem()); - allItems.add(new VoltageItem()); - allItems.add(new TempItem()); - - for(InfoItem inf : allItems) { - if(inf.available()) { - adapter.add(inf); - } - } - - listView.setAdapter(adapter); - setContentView(listView); - - collector = new Runnable() { - public void run() { - for(InfoItem inf : allItems) { - if(inf.available()) { - inf.setupView(); - } - } - if(handler != null) { - handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL); - } - } - }; - if(handler != null) { - handler.post(collector); - } - } - - class CounterServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName className, - IBinder boundService ) { - counterService = ICounterService.Stub.asInterface((IBinder)boundService); - try { - componentNames = counterService.getComponents(); - } catch(RemoteException e) { - componentNames = new String[0]; - } - refreshView(); - } - - public void onServiceDisconnected(ComponentName className) { - counterService = null; - getApplicationContext().unbindService(conn); - getApplicationContext().bindService(serviceIntent, conn, 0); - Log.w(TAG, "Unexpectedly lost connection to service"); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - prefs = PreferenceManager.getDefaultSharedPreferences(this); - uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL); - if(savedInstanceState != null) { - componentNames = savedInstanceState.getStringArray("componentNames"); - } - batteryStats = BatteryStats.getInstance(); - serviceIntent = new Intent(this, UMLoggerService.class); - conn = new CounterServiceConnection(); - } - - @Override - protected void onResume() { - super.onResume(); - handler = new Handler(); - getApplicationContext().bindService(serviceIntent, conn, 0); - refreshView(); - } - - @Override - protected void onPause() { - super.onPause(); - getApplicationContext().unbindService(conn); - if(collector != null) { - handler.removeCallbacks(collector); - collector = null; - handler = null; - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putStringArray("componentNames", componentNames); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return false; - } - - @Override - protected Dialog onCreateDialog(int id) { - return null; - } - - private abstract class InfoItem { - protected TextView title; - protected TextView summary; - protected TextView txt; - - public void initViews(TextView title, TextView summary, - LinearLayout widgetGroup) { - this.title = title; - this.summary = summary; - txt = new TextView(MiscView.this); - widgetGroup.addView(txt); - } - - public abstract boolean available(); - - public abstract void setupView(); - } - - private class CurrentItem extends InfoItem { - public boolean available() { - return uid == SystemInfo.AID_ALL && batteryStats.hasCurrent(); - } - - public void setupView() { - if(txt == null) return; - double current = batteryStats.getCurrent(); - if(current <= 0) { - txt.setText(String.format("%1$.1f mA", -current * 1000)); - } else { - double cp = batteryStats.getCapacity(); - if(0.01 <= cp && cp <= 0.99 && batteryStats.hasCharge()) { - long time = (long)(batteryStats.getCharge() / cp * (1.0 - cp) / - current); - txt.setText(String.format( - "%1$.1f mA\n(Charge time %2$d:%3$02d:%4$02d)", current * 1000, - time / 60 / 60, time / 60 % 60, time % 60)); - } - } - txt.setGravity(Gravity.CENTER); - - title.setText("Current"); - summary.setText("Battery current sensor reading"); - } - } - - private class ChargeItem extends InfoItem { - public boolean available() { - return uid == SystemInfo.AID_ALL && batteryStats.hasCharge(); - } - - public void setupView() { - if(txt == null) return; - double charge = batteryStats.getCharge() / 60 / 60 * 1e3; //As->mAh - double perc = batteryStats.getCapacity(); - if(perc < 0) { - txt.setText(String.format("%1$.1f mAh", charge)); - } else { - txt.setText(String.format("%1$.1f mAh\n(%2$.0f%%)", - charge, 100 * perc)); - } - txt.setGravity(Gravity.CENTER); - - title.setText("Charge"); - summary.setText("Battery charge sensor reading"); - } - } - - private class InstantPowerItem extends InfoItem { - private static final double POLY_WEIGHT = 0.10; - - public boolean available() { - return true; - } - - public void setupView() { - if(txt == null) return; - if(counterService != null) try { - // Compute what we're going to call the temporal power usage. - int count = 0; - int[] history = counterService.getComponentHistory( - 5 * 60, -1, uid); - double weightedAvgPower = 0; - for(int i = history.length - 1; i >= 0; i--) { - if(history[i] != 0) { - count++; - weightedAvgPower *= 1.0 - POLY_WEIGHT; - weightedAvgPower += POLY_WEIGHT * history[i] / 1000.0; - } - } - if(count > 0) { - double charge = batteryStats.getCharge(); - double volt = batteryStats.getVoltage(); - if(charge > 0 && volt > 0) { - weightedAvgPower /= 1.0 - Math.pow(1.0 - POLY_WEIGHT, count); - long time = (long)(charge * volt / weightedAvgPower); - txt.setText(String.format("%1$.0f mW\n" + - "time: %2$d:%3$02d:%4$02d", weightedAvgPower * 1000.0, - time / 60 / 60, time / 60 % 60, time % 60)); - } else { - txt.setText(String.format("%1$.0f mW", weightedAvgPower * 1000.0)); - } - } else { - txt.setText("No data"); - } - } catch(RemoteException e) { - txt.setText("Error"); - } else { - txt.setText("No data"); - } - - txt.setGravity(Gravity.CENTER); - title.setText("Current Power"); - summary.setText("Weighted average of power consumption over the last " + - "five minutes"); - } - } - - private class AveragePowerItem extends InfoItem { - public boolean available() { - return true; - } - - public void setupView() { - if(txt == null) return; - if(counterService != null) try { - // Compute what we're going to call the temporal power usage. - double power = 0; - long[] means = counterService.getMeans(uid, Counter.WINDOW_TOTAL); - if(means != null) for(long p : means) { - power += p / 1000.0; - } - - if(power > 0) { - double charge = batteryStats.getCharge(); - double volt = batteryStats.getVoltage(); - if(charge > 0 && volt > 0) { - long time = (long)(charge * volt / power); - txt.setText(String.format("%1$.0f mW\n" + - "time: %2$d:%3$02d:%4$02d", power * 1000.0, - time / 60 / 60, time / 60 % 60, time % 60)); - } else { - txt.setText(String.format("%1$.0f mW", power * 1000.0)); - } - } else { - txt.setText("No data"); - } - } catch(RemoteException e) { - txt.setText("Error"); - } else { - txt.setText("No data"); - } - - txt.setGravity(Gravity.CENTER); - title.setText("Average Power"); - summary.setText("Average power consumption since profiler started"); - } - } - - private class VoltageItem extends InfoItem { - public boolean available() { - return uid == SystemInfo.AID_ALL && batteryStats.hasVoltage(); - } - - public void setupView() { - if(txt == null) return; - double voltage = batteryStats.getVoltage(); - txt.setText(String.format("%1$.2f V", voltage)); - txt.setGravity(Gravity.CENTER); - - title.setText("Voltage"); - summary.setText("Battery voltage sensor reading"); - } - } - - private class TempItem extends InfoItem { - public boolean available() { - return uid == SystemInfo.AID_ALL && batteryStats.hasTemp(); - } - - public void setupView() { - if(txt == null) return; - double celcius = batteryStats.getTemp(); - double farenheit = 32 + celcius * 9.0 / 5.0; - txt.setText(String.format("%1$.1f \u00b0C\n(%2$.1f \u00b0F)", - celcius, farenheit)); - txt.setGravity(Gravity.CENTER); - - title.setText("Battery Temperature"); - summary.setText("Battery temperature sensor reading"); - } - } - - private class UidItem extends InfoItem { - public boolean available() { - return uid != SystemInfo.AID_ALL; - } - - public void setupView() { - if(txt == null) return; - txt.setText("" + uid); - txt.setGravity(Gravity.CENTER); - - title.setText("User ID"); - summary.setText("User ID for " + SystemInfo.getInstance().getUidName(uid, - getApplicationContext().getPackageManager())); - - } - } - - private class PackageItem extends InfoItem { - public boolean available() { - return uid >= SystemInfo.AID_APP; - } - - public void setupView() { - if(txt == null) return; - txt.setText(""); - - title.setText("Packages"); - - PackageManager pm = getApplicationContext().getPackageManager(); - String[] packages = pm.getPackagesForUid(uid); - if(packages != null) { - StringBuilder buf = new StringBuilder(); - for(String packageName : packages) { - if(buf.length() != 0) buf.append("\n"); - buf.append(packageName); - } - summary.setText(buf.toString()); - } else { - summary.setText("(None)"); - } - } - } - - private class OLEDItem extends InfoItem { - public boolean available() { - if(uid < SystemInfo.AID_APP) return false; - return PhoneSelector.hasOled(); - } - - public void setupView() { - if(txt == null) return; - - txt.setText("No data"); - if(counterService != null) { - try { - long score = counterService.getUidExtra("OLEDSCORE", uid); - if(score >= 0) { - txt.setText("" + (100 - score)); - } - } catch(RemoteException e) { - Log.w(TAG, "Failed to request oled score information"); - } - } - - title.setText("OLED Score"); - summary.setText("100 is highly efficient\n0 is very inefficient\n" + - "Independent of brightness"); - } - } -} - +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.ui; + +import edu.umich.PowerTutor.R; +import edu.umich.PowerTutor.phone.PhoneSelector; +import edu.umich.PowerTutor.service.ICounterService; +import edu.umich.PowerTutor.service.PowerEstimator; +import edu.umich.PowerTutor.service.UMLoggerService; +import edu.umich.PowerTutor.util.Counter; +import edu.umich.PowerTutor.util.BatteryStats; +import edu.umich.PowerTutor.util.SystemInfo; + +import android.app.Activity; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.ListView; + +import java.util.ArrayList; + +public class MiscView extends Activity { + private static final String TAG = "MiscView"; + + private SharedPreferences prefs; + private int uid; + + private Runnable collector; + + private Intent serviceIntent; + private CounterServiceConnection conn; + private ICounterService counterService; + private Handler handler; + + private BatteryStats batteryStats; + + private String[] componentNames; + + public void refreshView() { + final ListView listView = new ListView(this); + + ArrayAdapter adapter = new ArrayAdapter(this, 0) { + public View getView(int position, View convertView, ViewGroup parent) { + View itemView = getLayoutInflater() + .inflate(R.layout.misc_item_layout, listView, false); + TextView title = (TextView)itemView.findViewById(R.id.title); + TextView summary = (TextView)itemView.findViewById(R.id.summary); + LinearLayout widgetGroup = + (LinearLayout)itemView.findViewById(R.id.widget_frame); + InfoItem item = (InfoItem)getItem(position); + item.initViews(title, summary, widgetGroup); + item.setupView(); + return itemView; + } + }; + + final ArrayList allItems = new ArrayList(); + allItems.add(new UidItem()); + allItems.add(new PackageItem()); + allItems.add(new OLEDItem()); + allItems.add(new InstantPowerItem()); + allItems.add(new AveragePowerItem()); + allItems.add(new CurrentItem()); + allItems.add(new ChargeItem()); + allItems.add(new VoltageItem()); + allItems.add(new TempItem()); + + for(InfoItem inf : allItems) { + if(inf.available()) { + adapter.add(inf); + } + } + + listView.setAdapter(adapter); + setContentView(listView); + + collector = new Runnable() { + public void run() { + for(InfoItem inf : allItems) { + if(inf.available()) { + inf.setupView(); + } + } + if(handler != null) { + handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL); + } + } + }; + if(handler != null) { + handler.post(collector); + } + } + + class CounterServiceConnection implements ServiceConnection { + public void onServiceConnected(ComponentName className, + IBinder boundService ) { + counterService = ICounterService.Stub.asInterface((IBinder)boundService); + try { + componentNames = counterService.getComponents(); + } catch(RemoteException e) { + componentNames = new String[0]; + } + refreshView(); + } + + public void onServiceDisconnected(ComponentName className) { + counterService = null; + getApplicationContext().unbindService(conn); + getApplicationContext().bindService(serviceIntent, conn, 0); + Log.w(TAG, "Unexpectedly lost connection to service"); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + prefs = PreferenceManager.getDefaultSharedPreferences(this); + uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL); + if(savedInstanceState != null) { + componentNames = savedInstanceState.getStringArray("componentNames"); + } + batteryStats = BatteryStats.getInstance(); + serviceIntent = new Intent(this, UMLoggerService.class); + conn = new CounterServiceConnection(); + } + + @Override + protected void onResume() { + super.onResume(); + handler = new Handler(); + getApplicationContext().bindService(serviceIntent, conn, 0); + refreshView(); + } + + @Override + protected void onPause() { + super.onPause(); + getApplicationContext().unbindService(conn); + if(collector != null) { + handler.removeCallbacks(collector); + collector = null; + handler = null; + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putStringArray("componentNames", componentNames); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + @Override + protected Dialog onCreateDialog(int id) { + return null; + } + + private abstract class InfoItem { + protected TextView title; + protected TextView summary; + protected TextView txt; + + public void initViews(TextView title, TextView summary, + LinearLayout widgetGroup) { + this.title = title; + this.summary = summary; + txt = new TextView(MiscView.this); + widgetGroup.addView(txt); + } + + public abstract boolean available(); + + public abstract void setupView(); + } + + private class CurrentItem extends InfoItem { + public boolean available() { + return uid == SystemInfo.AID_ALL && batteryStats.hasCurrent(); + } + + public void setupView() { + if(txt == null) return; + double current = batteryStats.getCurrent(); + if(current <= 0) { + txt.setText(String.format("%1$.1f mA", -current * 1000)); + } else { + double cp = batteryStats.getCapacity(); + if(0.01 <= cp && cp <= 0.99 && batteryStats.hasCharge()) { + long time = (long)(batteryStats.getCharge() / cp * (1.0 - cp) / + current); + txt.setText(String.format( + "%1$.1f mA\n(Charge time %2$d:%3$02d:%4$02d)", current * 1000, + time / 60 / 60, time / 60 % 60, time % 60)); + } + } + txt.setGravity(Gravity.CENTER); + + title.setText("Current"); + summary.setText("Battery current sensor reading"); + } + } + + private class ChargeItem extends InfoItem { + public boolean available() { + return uid == SystemInfo.AID_ALL && batteryStats.hasCharge(); + } + + public void setupView() { + if(txt == null) return; + double charge = batteryStats.getCharge() / 60 / 60 * 1e3; //As->mAh + double perc = batteryStats.getCapacity(); + if(perc < 0) { + txt.setText(String.format("%1$.1f mAh", charge)); + } else { + txt.setText(String.format("%1$.1f mAh\n(%2$.0f%%)", + charge, 100 * perc)); + } + txt.setGravity(Gravity.CENTER); + + title.setText("Charge"); + summary.setText("Battery charge sensor reading"); + } + } + + private class InstantPowerItem extends InfoItem { + private static final double POLY_WEIGHT = 0.10; + + public boolean available() { + return true; + } + + public void setupView() { + if(txt == null) return; + if(counterService != null) try { + // Compute what we're going to call the temporal power usage. + int count = 0; + int[] history = counterService.getComponentHistory( + 5 * 60, -1, uid); + double weightedAvgPower = 0; + for(int i = history.length - 1; i >= 0; i--) { + if(history[i] != 0) { + count++; + weightedAvgPower *= 1.0 - POLY_WEIGHT; + weightedAvgPower += POLY_WEIGHT * history[i] / 1000.0; + } + } + if(count > 0) { + double charge = batteryStats.getCharge(); + double volt = batteryStats.getVoltage(); + if(charge > 0 && volt > 0) { + weightedAvgPower /= 1.0 - Math.pow(1.0 - POLY_WEIGHT, count); + long time = (long)(charge * volt / weightedAvgPower); + txt.setText(String.format("%1$.0f mW\n" + + "time: %2$d:%3$02d:%4$02d", weightedAvgPower * 1000.0, + time / 60 / 60, time / 60 % 60, time % 60)); + } else { + txt.setText(String.format("%1$.0f mW", weightedAvgPower * 1000.0)); + } + } else { + txt.setText("No data"); + } + } catch(RemoteException e) { + txt.setText("Error"); + } else { + txt.setText("No data"); + } + + txt.setGravity(Gravity.CENTER); + title.setText("Current Power"); + summary.setText("Weighted average of power consumption over the last " + + "five minutes"); + } + } + + private class AveragePowerItem extends InfoItem { + public boolean available() { + return true; + } + + public void setupView() { + if(txt == null) return; + if(counterService != null) try { + // Compute what we're going to call the temporal power usage. + double power = 0; + long[] means = counterService.getMeans(uid, Counter.WINDOW_TOTAL); + if(means != null) for(long p : means) { + power += p / 1000.0; + } + + if(power > 0) { + double charge = batteryStats.getCharge(); + double volt = batteryStats.getVoltage(); + if(charge > 0 && volt > 0) { + long time = (long)(charge * volt / power); + txt.setText(String.format("%1$.0f mW\n" + + "time: %2$d:%3$02d:%4$02d", power * 1000.0, + time / 60 / 60, time / 60 % 60, time % 60)); + } else { + txt.setText(String.format("%1$.0f mW", power * 1000.0)); + } + } else { + txt.setText("No data"); + } + } catch(RemoteException e) { + txt.setText("Error"); + } else { + txt.setText("No data"); + } + + txt.setGravity(Gravity.CENTER); + title.setText("Average Power"); + summary.setText("Average power consumption since profiler started"); + } + } + + private class VoltageItem extends InfoItem { + public boolean available() { + return uid == SystemInfo.AID_ALL && batteryStats.hasVoltage(); + } + + public void setupView() { + if(txt == null) return; + double voltage = batteryStats.getVoltage(); + txt.setText(String.format("%1$.2f V", voltage)); + txt.setGravity(Gravity.CENTER); + + title.setText("Voltage"); + summary.setText("Battery voltage sensor reading"); + } + } + + private class TempItem extends InfoItem { + public boolean available() { + return uid == SystemInfo.AID_ALL && batteryStats.hasTemp(); + } + + public void setupView() { + if(txt == null) return; + double celcius = batteryStats.getTemp(); + double farenheit = 32 + celcius * 9.0 / 5.0; + txt.setText(String.format("%1$.1f \u00b0C\n(%2$.1f \u00b0F)", + celcius, farenheit)); + txt.setGravity(Gravity.CENTER); + + title.setText("Battery Temperature"); + summary.setText("Battery temperature sensor reading"); + } + } + + private class UidItem extends InfoItem { + public boolean available() { + return uid != SystemInfo.AID_ALL; + } + + public void setupView() { + if(txt == null) return; + txt.setText("" + uid); + txt.setGravity(Gravity.CENTER); + + title.setText("User ID"); + summary.setText("User ID for " + SystemInfo.getInstance().getUidName(uid, + getApplicationContext().getPackageManager())); + + } + } + + private class PackageItem extends InfoItem { + public boolean available() { + return uid >= SystemInfo.AID_APP; + } + + public void setupView() { + if(txt == null) return; + txt.setText(""); + + title.setText("Packages"); + + PackageManager pm = getApplicationContext().getPackageManager(); + String[] packages = pm.getPackagesForUid(uid); + if(packages != null) { + StringBuilder buf = new StringBuilder(); + for(String packageName : packages) { + if(buf.length() != 0) buf.append("\n"); + buf.append(packageName); + } + summary.setText(buf.toString()); + } else { + summary.setText("(None)"); + } + } + } + + private class OLEDItem extends InfoItem { + public boolean available() { + if(uid < SystemInfo.AID_APP) return false; + return PhoneSelector.hasOled(); + } + + public void setupView() { + if(txt == null) return; + + txt.setText("No data"); + if(counterService != null) { + try { + long score = counterService.getUidExtra("OLEDSCORE", uid); + if(score >= 0) { + txt.setText("" + (100 - score)); + } + } catch(RemoteException e) { + Log.w(TAG, "Failed to request oled score information"); + } + } + + title.setText("OLED Score"); + summary.setText("100 is highly efficient\n0 is very inefficient\n" + + "Independent of brightness"); + } + } +} + diff --git a/src/edu/umich/PowerTutor/ui/PowerPie.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerPie.java similarity index 96% rename from src/edu/umich/PowerTutor/ui/PowerPie.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerPie.java index 9600059..986954e 100644 --- a/src/edu/umich/PowerTutor/ui/PowerPie.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerPie.java @@ -1,295 +1,296 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.ui; - -import org.achartengine.GraphicalView; -import org.achartengine.chart.PieChart; -import org.achartengine.model.CategorySeries; -import org.achartengine.renderer.DefaultRenderer; -import org.achartengine.renderer.SimpleSeriesRenderer; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.ComponentName; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.LinearLayout; -import android.widget.TextView; -import edu.umich.PowerTutor.service.ICounterService; -import edu.umich.PowerTutor.service.PowerEstimator; -import edu.umich.PowerTutor.service.UMLoggerService; -import edu.umich.PowerTutor.util.Counter; -import edu.umich.PowerTutor.util.SystemInfo; - -public class PowerPie extends Activity { - private static final String TAG = "PowerPie"; - - private SharedPreferences prefs; - private int uid; - - private String[] componentNames; - private int noUidMask; - - private Runnable collector; - - private Intent serviceIntent; - private CounterServiceConnection conn; - private ICounterService counterService; - private Handler handler; - - private TextView displayText; - - public static final int[] COLORS = new int[] { - Color.BLUE, Color.GREEN, Color.MAGENTA, Color.YELLOW, - Color.RED, Color.LTGRAY, Color.DKGRAY, Color.CYAN - }; - - public void refreshView() { - if(counterService == null) { - TextView loadingText = new TextView(this); - loadingText.setText("Waiting for profiler service..."); - loadingText.setGravity(Gravity.CENTER); - setContentView(loadingText); - return; - } - - if(uid == SystemInfo.AID_ALL) { - /* If we are reporting global power usage then just set noUidMask to 0 so - * that all components get displayed. - */ - noUidMask = 0; - } - - displayText = new TextView(this); - displayText.setGravity(Gravity.CENTER); - updateDisplayText(); - - final CategorySeries series = new CategorySeries(""); - final DefaultRenderer renderer = new DefaultRenderer(); - renderer.setLabelsTextSize(15); - renderer.setLegendTextSize(15); - renderer.setMargins(new int[] { 5, 50, 5, 50 }); - - PieChart pieChart = new PieChart(series, renderer); - final GraphicalView chartView = new GraphicalView(this, pieChart); - - /* The collector is responsible for periodically updating the screen with - * new energy usage information for the current uid. - */ - collector = new Runnable() { - public void run() { - try { - long[] totals = counterService.getTotals(uid, - prefs.getInt("pieWindowType", 0)); - long sumTotal = 0; - for(int i = 0; i < totals.length; i++) { - totals[i] = totals[i] * PowerEstimator.ITERATION_INTERVAL / 1000; - sumTotal += totals[i]; - } - int index = 0; - if(sumTotal < 1e-7) { - series.set(0, "No data", 1); - } else for(int i = 0; i < totals.length; i++) { - if((noUidMask & 1 << i) != 0) { - continue; - } - String prefix; - double val = totals[i]; - if(val > 1e12) { - prefix = "G"; - val /= 1e12; - } else if(val > 1e9) { - prefix = "M"; - val /= 1e9; - } else if(val > 1e6) { - prefix = "k"; - val /= 1e6; - } else if(val > 1e3) { - prefix = ""; - val /= 1e3; - } else { - prefix = "m"; - } - - String label = String.format("%1$s %2$.1f %3$sJ", - componentNames[i], val, prefix); - if(series.getItemCount() == index) { - SimpleSeriesRenderer r = new SimpleSeriesRenderer(); - r.setColor(COLORS[i]); - renderer.addSeriesRenderer(r); - - series.add(label, totals[i]); - } else { - series.set(index, label, totals[i]); - } - index++; - } - chartView.invalidate(); - } catch(RemoteException e) { - Log.w(TAG, "Failed to contact power tutor profiling service"); - } - if(handler != null) { - handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL); - } - } - }; - if(handler != null) { - handler.post(collector); - } - - LinearLayout layout = new LinearLayout(this); - layout.setOrientation(LinearLayout.VERTICAL); - layout.addView(displayText); - layout.addView(chartView); - setContentView(layout); - } - - public void updateDisplayText() { - displayText.setText("Displaying energy usage over " + - Counter.WINDOW_DESCS[prefs.getInt("pieWindowType", 0)] + " for " + - (uid == SystemInfo.AID_ALL ? " the entire phone." : - SystemInfo.getInstance().getUidName(uid, getPackageManager()) + ".")); - } - - class CounterServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName className, - IBinder boundService ) { - counterService = ICounterService.Stub.asInterface((IBinder)boundService); - try { - componentNames = counterService.getComponents(); - noUidMask = counterService.getNoUidMask(); - refreshView(); - } catch(RemoteException e) { - counterService = null; - } - } - - public void onServiceDisconnected(ComponentName className) { - counterService = null; - getApplicationContext().unbindService(conn); - getApplicationContext().bindService(serviceIntent, conn, 0); - Log.w(TAG, "Unexpectedly lost connection to service"); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - prefs = PreferenceManager.getDefaultSharedPreferences(this); - uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL); - - if(savedInstanceState != null) { - componentNames = savedInstanceState.getStringArray("componentNames"); - noUidMask = savedInstanceState.getInt("noUidMask"); - } - - serviceIntent = new Intent(this, UMLoggerService.class); - conn = new CounterServiceConnection(); - } - - @Override - protected void onResume() { - super.onResume(); - handler = new Handler(); - getApplicationContext().bindService(serviceIntent, conn, 0); - - refreshView(); - } - - @Override - protected void onPause() { - super.onPause(); - getApplicationContext().unbindService(conn); - if(collector != null) { - handler.removeCallbacks(collector); - handler = null; - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putStringArray("componentNames", componentNames); - outState.putInt("noUidMask", noUidMask); - } - - private static final int MENU_WINDOW = 0; - private static final int DIALOG_WINDOW = 0; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_WINDOW, 0, "Time Span"); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - /* We need to make sure that the user can't cause any of the dialogs to be - * created before we have contacted the Power Tutor service to get the - * component names and such. - */ - for(int i = 0; i < menu.size(); i++) { - menu.getItem(i).setEnabled(counterService != null); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch(item.getItemId()) { - case MENU_WINDOW: - showDialog(DIALOG_WINDOW); - return true; - } - return false; - } - - @Override - protected Dialog onCreateDialog(int id) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - switch(id) { - case DIALOG_WINDOW: - builder.setTitle("Select window type"); - builder.setItems(Counter.WINDOW_NAMES, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int item) { - prefs.edit().putInt("pieWindowType", item).commit(); - updateDisplayText(); - } - }); - return builder.create(); - } - return null; - } -} - +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.ui; + +import org.achartengine.GraphicalView; +import org.achartengine.chart.PieChart; +import org.achartengine.model.CategorySeries; +import org.achartengine.renderer.DefaultRenderer; +import org.achartengine.renderer.SimpleSeriesRenderer; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.LinearLayout; +import android.widget.TextView; + +import edu.umich.PowerTutor.service.ICounterService; +import edu.umich.PowerTutor.service.PowerEstimator; +import edu.umich.PowerTutor.service.UMLoggerService; +import edu.umich.PowerTutor.util.Counter; +import edu.umich.PowerTutor.util.SystemInfo; + +public class PowerPie extends Activity { + private static final String TAG = "PowerPie"; + + private SharedPreferences prefs; + private int uid; + + private String[] componentNames; + private int noUidMask; + + private Runnable collector; + + private Intent serviceIntent; + private CounterServiceConnection conn; + private ICounterService counterService; + private Handler handler; + + private TextView displayText; + + public static final int[] COLORS = new int[] { + Color.BLUE, Color.GREEN, Color.MAGENTA, Color.YELLOW, + Color.RED, Color.LTGRAY, Color.DKGRAY, Color.CYAN + }; + + public void refreshView() { + if(counterService == null) { + TextView loadingText = new TextView(this); + loadingText.setText("Waiting for profiler service..."); + loadingText.setGravity(Gravity.CENTER); + setContentView(loadingText); + return; + } + + if(uid == SystemInfo.AID_ALL) { + /* If we are reporting global power usage then just set noUidMask to 0 so + * that all components get displayed. + */ + noUidMask = 0; + } + + displayText = new TextView(this); + displayText.setGravity(Gravity.CENTER); + updateDisplayText(); + + final CategorySeries series = new CategorySeries(""); + final DefaultRenderer renderer = new DefaultRenderer(); + renderer.setLabelsTextSize(15); + renderer.setLegendTextSize(15); + renderer.setMargins(new int[] { 5, 50, 5, 50 }); + + PieChart pieChart = new PieChart(series, renderer); + final GraphicalView chartView = new GraphicalView(this, pieChart); + + /* The collector is responsible for periodically updating the screen with + * new energy usage information for the current uid. + */ + collector = new Runnable() { + public void run() { + try { + long[] totals = counterService.getTotals(uid, + prefs.getInt("pieWindowType", 0)); + long sumTotal = 0; + for(int i = 0; i < totals.length; i++) { + totals[i] = totals[i] * PowerEstimator.ITERATION_INTERVAL / 1000; + sumTotal += totals[i]; + } + int index = 0; + if(sumTotal < 1e-7) { + series.set(0, "No data", 1); + } else for(int i = 0; i < totals.length; i++) { + if((noUidMask & 1 << i) != 0) { + continue; + } + String prefix; + double val = totals[i]; + if(val > 1e12) { + prefix = "G"; + val /= 1e12; + } else if(val > 1e9) { + prefix = "M"; + val /= 1e9; + } else if(val > 1e6) { + prefix = "k"; + val /= 1e6; + } else if(val > 1e3) { + prefix = ""; + val /= 1e3; + } else { + prefix = "m"; + } + + String label = String.format("%1$s %2$.1f %3$sJ", + componentNames[i], val, prefix); + if(series.getItemCount() == index) { + SimpleSeriesRenderer r = new SimpleSeriesRenderer(); + r.setColor(COLORS[i]); + renderer.addSeriesRenderer(r); + + series.add(label, totals[i]); + } else { + series.set(index, label, totals[i]); + } + index++; + } + chartView.invalidate(); + } catch(RemoteException e) { + Log.w(TAG, "Failed to contact power tutor profiling service"); + } + if(handler != null) { + handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL); + } + } + }; + if(handler != null) { + handler.post(collector); + } + + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(displayText); + layout.addView(chartView); + setContentView(layout); + } + + public void updateDisplayText() { + displayText.setText("Displaying energy usage over " + + Counter.WINDOW_DESCS[prefs.getInt("pieWindowType", 0)] + " for " + + (uid == SystemInfo.AID_ALL ? " the entire phone." : + SystemInfo.getInstance().getUidName(uid, getPackageManager()) + ".")); + } + + class CounterServiceConnection implements ServiceConnection { + public void onServiceConnected(ComponentName className, + IBinder boundService ) { + counterService = ICounterService.Stub.asInterface((IBinder)boundService); + try { + componentNames = counterService.getComponents(); + noUidMask = counterService.getNoUidMask(); + refreshView(); + } catch(RemoteException e) { + counterService = null; + } + } + + public void onServiceDisconnected(ComponentName className) { + counterService = null; + getApplicationContext().unbindService(conn); + getApplicationContext().bindService(serviceIntent, conn, 0); + Log.w(TAG, "Unexpectedly lost connection to service"); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + prefs = PreferenceManager.getDefaultSharedPreferences(this); + uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL); + + if(savedInstanceState != null) { + componentNames = savedInstanceState.getStringArray("componentNames"); + noUidMask = savedInstanceState.getInt("noUidMask"); + } + + serviceIntent = new Intent(this, UMLoggerService.class); + conn = new CounterServiceConnection(); + } + + @Override + protected void onResume() { + super.onResume(); + handler = new Handler(); + getApplicationContext().bindService(serviceIntent, conn, 0); + + refreshView(); + } + + @Override + protected void onPause() { + super.onPause(); + getApplicationContext().unbindService(conn); + if(collector != null) { + handler.removeCallbacks(collector); + handler = null; + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putStringArray("componentNames", componentNames); + outState.putInt("noUidMask", noUidMask); + } + + private static final int MENU_WINDOW = 0; + private static final int DIALOG_WINDOW = 0; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_WINDOW, 0, "Time Span"); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + /* We need to make sure that the user can't cause any of the dialogs to be + * created before we have contacted the Power Tutor service to get the + * component names and such. + */ + for(int i = 0; i < menu.size(); i++) { + menu.getItem(i).setEnabled(counterService != null); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case MENU_WINDOW: + showDialog(DIALOG_WINDOW); + return true; + } + return false; + } + + @Override + protected Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + switch(id) { + case DIALOG_WINDOW: + builder.setTitle("Select window type"); + builder.setItems(Counter.WINDOW_NAMES, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + prefs.edit().putInt("pieWindowType", item).commit(); + updateDisplayText(); + } + }); + return builder.create(); + } + return null; + } +} + diff --git a/src/edu/umich/PowerTutor/ui/PowerTabs.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerTabs.java similarity index 100% rename from src/edu/umich/PowerTutor/ui/PowerTabs.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerTabs.java diff --git a/src/edu/umich/PowerTutor/ui/PowerTop.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerTop.java similarity index 99% rename from src/edu/umich/PowerTutor/ui/PowerTop.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerTop.java index 21324f6..7a17dcc 100644 --- a/src/edu/umich/PowerTutor/ui/PowerTop.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerTop.java @@ -19,7 +19,6 @@ package edu.umich.PowerTutor.ui; -import edu.umich.PowerTutor.R; import edu.umich.PowerTutor.service.ICounterService; import edu.umich.PowerTutor.service.PowerEstimator; import edu.umich.PowerTutor.service.UMLoggerService; @@ -33,7 +32,6 @@ import android.app.Dialog; import android.content.DialogInterface; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; @@ -50,7 +48,6 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; diff --git a/src/edu/umich/PowerTutor/ui/PowerViewer.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerViewer.java similarity index 96% rename from src/edu/umich/PowerTutor/ui/PowerViewer.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerViewer.java index d0c0d96..dfec05f 100644 --- a/src/edu/umich/PowerTutor/ui/PowerViewer.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/PowerViewer.java @@ -1,350 +1,351 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.ui; - -import org.achartengine.GraphicalView; -import org.achartengine.chart.CubicLineChart; -import org.achartengine.model.XYMultipleSeriesDataset; -import org.achartengine.model.XYSeries; -import org.achartengine.renderer.XYMultipleSeriesRenderer; -import org.achartengine.renderer.XYSeriesRenderer; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.SystemClock; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; -import edu.umich.PowerTutor.service.ICounterService; -import edu.umich.PowerTutor.service.PowerEstimator; -import edu.umich.PowerTutor.service.UMLoggerService; -import edu.umich.PowerTutor.util.SystemInfo; - -public class PowerViewer extends Activity { - private static final String TAG = "PowerViewer"; - - private SharedPreferences prefs; - private int uid; - - private int components; - private String[] componentNames; - private int[] componentsMaxPower; - private int noUidMask; - private boolean collecting; - - private ValueCollector[] collectors; - - private Intent serviceIntent; - private CounterServiceConnection conn; - private ICounterService counterService; - - private Handler handler; - private LinearLayout chartLayout; - - public void refreshView() { - if(counterService == null) { - TextView loadingText = new TextView(this); - loadingText.setText("Waiting for profiler service..."); - loadingText.setGravity(Gravity.CENTER); - setContentView(loadingText); - return; - } - - chartLayout = new LinearLayout(this); - chartLayout.setOrientation(LinearLayout.VERTICAL); - - if(uid == SystemInfo.AID_ALL) { - /* If we are reporting global power usage then just set noUidMask to 0 so - * that all components get displayed. - */ - noUidMask = 0; - } - components = 0; - for(int i = 0; i < componentNames.length; i++) { - if((noUidMask & 1 << i) == 0) { - components++; - } - } - boolean showTotal = prefs.getBoolean("showTotalPower", false); - collectors = new ValueCollector[(showTotal ? 1 : 0) + components]; - - int pos = 0; - for(int i = showTotal ? -1 : 0; i < componentNames.length; i++) { - if(i != -1 && (noUidMask & 1 << i) != 0) { - continue; - } - String name = i == -1 ? "Total" : componentNames[i]; - double mxPower = (i == -1 ? 2100.0 : componentsMaxPower[i]) * 1.05; - - XYSeries series = new XYSeries(name); - XYMultipleSeriesDataset mseries = new XYMultipleSeriesDataset(); - mseries.addSeries(series); - - XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); - XYSeriesRenderer srenderer = new XYSeriesRenderer(); - renderer.setYAxisMin(0.0); - renderer.setYAxisMax(mxPower); - renderer.setYTitle(name + "(mW)"); - - int clr = PowerPie.COLORS[(PowerPie.COLORS.length + i) % - PowerPie.COLORS.length]; - srenderer.setColor(clr); - srenderer.setFillBelowLine(true); - srenderer.setFillBelowLineColor(((clr >> 1) & 0x7F7F7F) | - (clr & 0xFF000000)); - renderer.addSeriesRenderer(srenderer); - - View chartView = new GraphicalView(this, - new CubicLineChart(mseries, renderer, 0.5f)); - chartView.setMinimumHeight(100); - chartLayout.addView(chartView); - - collectors[pos] = new ValueCollector(series, renderer, chartView, i); - if(handler != null) { - handler.post(collectors[pos]); - } - pos++; - } - - /* We're giving 100 pixels per graph of vertical space for the chart view. - If we don't specify a minimum height the chart view ends up having a - height of 0 so this is important. */ - chartLayout.setMinimumHeight(100 * components); - - ScrollView scrollView = new ScrollView(this) { - public boolean onInterceptTouchEvent(android.view.MotionEvent ev) { - return true; - } - }; - - scrollView.addView(chartLayout); - scrollView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - setContentView(scrollView); - } - - private class CounterServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName className, - IBinder boundService) { - counterService = ICounterService.Stub.asInterface((IBinder)boundService); - try { - componentNames = counterService.getComponents(); - componentsMaxPower = counterService.getComponentsMaxPower(); - noUidMask = counterService.getNoUidMask(); - refreshView(); - } catch(RemoteException e) { - counterService = null; - } - } - - public void onServiceDisconnected(ComponentName className) { - counterService = null; - getApplicationContext().unbindService(conn); - getApplicationContext().bindService(serviceIntent, conn, 0); - Log.w(TAG, "Unexpectedly lost connection to service"); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - prefs = PreferenceManager.getDefaultSharedPreferences(this); - uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL); - - collecting = true; - if(savedInstanceState != null) { - collecting = savedInstanceState.getBoolean("collecting", true); - componentNames = savedInstanceState.getStringArray("componentNames"); - noUidMask = savedInstanceState.getInt("noUidMask"); - } - - serviceIntent = new Intent(this, UMLoggerService.class); - conn = new CounterServiceConnection(); - } - - @Override - protected void onResume() { - super.onResume(); - handler = new Handler(); - getApplicationContext().bindService(serviceIntent, conn, 0); - - refreshView(); - } - - @Override - protected void onPause() { - super.onPause(); - getApplicationContext().unbindService(conn); - if(collectors != null) for(int i = 0; i < components; i++) { - handler.removeCallbacks(collectors[i]); - } - counterService = null; - handler = null; - collecting = true; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean("collecting", collecting); - outState.putStringArray("componentNames", componentNames); - outState.putInt("noUidMask", noUidMask); - } - - /* Let all of the UI graphs lay themselves out again. */ - private void stateChanged() { - for(int i = 0; i < components; i++) { - collectors[i].layout(); - } - } - - private static final int MENU_OPTIONS = 0; - private static final int MENU_TOGGLE_COLLECTING = 1; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_OPTIONS, 0, "Options"); - menu.add(0, MENU_TOGGLE_COLLECTING, 0, ""); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.findItem(MENU_TOGGLE_COLLECTING).setTitle( - collecting ? "Pause" : "Resume"); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch(item.getItemId()) { - case MENU_OPTIONS: - startActivity(new Intent(this, ViewerPreferences.class)); - return true; - case MENU_TOGGLE_COLLECTING: - collecting = !collecting; - if(handler != null) { - if(collecting) for(int i = 0; i < components; i++) { - collectors[i].reset(); - handler.post(collectors[i]); - } else for(int i = 0; i < components; i++) { - handler.removeCallbacks(collectors[i]); - } - } - break; - } - return false; - } - - public class ValueCollector implements Runnable { - private XYSeries series; - private XYMultipleSeriesRenderer renderer; - private View chartView; - - private int componentId; - private long lastTime; - - int[] values; - - private boolean readHistory; - - public ValueCollector(XYSeries series, XYMultipleSeriesRenderer renderer, - View chartView, int componentId) { - this.series = series; - this.renderer = renderer; - this.chartView = chartView; - this.componentId = componentId; - lastTime = SystemClock.elapsedRealtime(); - layout(); - } - - public void layout() { - int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60")); - values = new int[numVals]; - - renderer.clearXTextLabels(); - renderer.setXAxisMin(0); - renderer.setXAxisMax(numVals - 1); - renderer.addXTextLabel(numVals - 1, "" + numVals); - renderer.setXLabels(0); - for(int j = 0; j < 10; j++) { - renderer.addXTextLabel(numVals * j / 10, "" + (1 + numVals * j / 10)); - } - - reset(); - } - - /** Restart points collecting from zero. */ - public void reset() { - series.clear(); - readHistory = true; - } - - public void run() { - int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60")); - if(counterService != null) try { - if(readHistory) { - values = counterService.getComponentHistory(numVals, - componentId, uid); - readHistory = false; - } else { - for(int i = numVals - 1; i > 0; i--) { - values[i] = values[i - 1]; - } - values[0] = counterService.getComponentHistory(1, componentId, - uid)[0]; - } - } catch(RemoteException e) { - Log.w(TAG, "Failed to get data from service"); - for(int i = 0; i < numVals; i++) { - values[i] = 0; - } - } - - series.clear(); - for(int i = 0; i < numVals; i++) { - series.add(i, values[i]); - } - - long curTime = SystemClock.elapsedRealtime(); - long tryTime = lastTime + PowerEstimator.ITERATION_INTERVAL * - (long)Math.max(1, 1 + (curTime - lastTime) / - PowerEstimator.ITERATION_INTERVAL); - if(handler != null) { - handler.postDelayed(this, tryTime - curTime); - } - - chartView.invalidate(); - } - }; -} +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.ui; + +import org.achartengine.GraphicalView; +import org.achartengine.chart.CubicLineChart; +import org.achartengine.model.XYMultipleSeriesDataset; +import org.achartengine.model.XYSeries; +import org.achartengine.renderer.XYMultipleSeriesRenderer; +import org.achartengine.renderer.XYSeriesRenderer; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import edu.umich.PowerTutor.service.ICounterService; +import edu.umich.PowerTutor.service.PowerEstimator; +import edu.umich.PowerTutor.service.UMLoggerService; +import edu.umich.PowerTutor.util.SystemInfo; + +public class PowerViewer extends Activity { + private static final String TAG = "PowerViewer"; + + private SharedPreferences prefs; + private int uid; + + private int components; + private String[] componentNames; + private int[] componentsMaxPower; + private int noUidMask; + private boolean collecting; + + private ValueCollector[] collectors; + + private Intent serviceIntent; + private CounterServiceConnection conn; + private ICounterService counterService; + + private Handler handler; + private LinearLayout chartLayout; + + public void refreshView() { + if(counterService == null) { + TextView loadingText = new TextView(this); + loadingText.setText("Waiting for profiler service..."); + loadingText.setGravity(Gravity.CENTER); + setContentView(loadingText); + return; + } + + chartLayout = new LinearLayout(this); + chartLayout.setOrientation(LinearLayout.VERTICAL); + + if(uid == SystemInfo.AID_ALL) { + /* If we are reporting global power usage then just set noUidMask to 0 so + * that all components get displayed. + */ + noUidMask = 0; + } + components = 0; + for(int i = 0; i < componentNames.length; i++) { + if((noUidMask & 1 << i) == 0) { + components++; + } + } + boolean showTotal = prefs.getBoolean("showTotalPower", false); + collectors = new ValueCollector[(showTotal ? 1 : 0) + components]; + + int pos = 0; + for(int i = showTotal ? -1 : 0; i < componentNames.length; i++) { + if(i != -1 && (noUidMask & 1 << i) != 0) { + continue; + } + String name = i == -1 ? "Total" : componentNames[i]; + double mxPower = (i == -1 ? 2100.0 : componentsMaxPower[i]) * 1.05; + + XYSeries series = new XYSeries(name); + XYMultipleSeriesDataset mseries = new XYMultipleSeriesDataset(); + mseries.addSeries(series); + + XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); + XYSeriesRenderer srenderer = new XYSeriesRenderer(); + renderer.setYAxisMin(0.0); + renderer.setYAxisMax(mxPower); + renderer.setYTitle(name + "(mW)"); + + int clr = PowerPie.COLORS[(PowerPie.COLORS.length + i) % + PowerPie.COLORS.length]; + srenderer.setColor(clr); + srenderer.setFillBelowLine(true); + srenderer.setFillBelowLineColor(((clr >> 1) & 0x7F7F7F) | + (clr & 0xFF000000)); + renderer.addSeriesRenderer(srenderer); + + View chartView = new GraphicalView(this, + new CubicLineChart(mseries, renderer, 0.5f)); + chartView.setMinimumHeight(100); + chartLayout.addView(chartView); + + collectors[pos] = new ValueCollector(series, renderer, chartView, i); + if(handler != null) { + handler.post(collectors[pos]); + } + pos++; + } + + /* We're giving 100 pixels per graph of vertical space for the chart view. + If we don't specify a minimum height the chart view ends up having a + height of 0 so this is important. */ + chartLayout.setMinimumHeight(100 * components); + + ScrollView scrollView = new ScrollView(this) { + public boolean onInterceptTouchEvent(android.view.MotionEvent ev) { + return true; + } + }; + + scrollView.addView(chartLayout); + scrollView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + setContentView(scrollView); + } + + private class CounterServiceConnection implements ServiceConnection { + public void onServiceConnected(ComponentName className, + IBinder boundService) { + counterService = ICounterService.Stub.asInterface((IBinder)boundService); + try { + componentNames = counterService.getComponents(); + componentsMaxPower = counterService.getComponentsMaxPower(); + noUidMask = counterService.getNoUidMask(); + refreshView(); + } catch(RemoteException e) { + counterService = null; + } + } + + public void onServiceDisconnected(ComponentName className) { + counterService = null; + getApplicationContext().unbindService(conn); + getApplicationContext().bindService(serviceIntent, conn, 0); + Log.w(TAG, "Unexpectedly lost connection to service"); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + prefs = PreferenceManager.getDefaultSharedPreferences(this); + uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL); + + collecting = true; + if(savedInstanceState != null) { + collecting = savedInstanceState.getBoolean("collecting", true); + componentNames = savedInstanceState.getStringArray("componentNames"); + noUidMask = savedInstanceState.getInt("noUidMask"); + } + + serviceIntent = new Intent(this, UMLoggerService.class); + conn = new CounterServiceConnection(); + } + + @Override + protected void onResume() { + super.onResume(); + handler = new Handler(); + getApplicationContext().bindService(serviceIntent, conn, 0); + + refreshView(); + } + + @Override + protected void onPause() { + super.onPause(); + getApplicationContext().unbindService(conn); + if(collectors != null) for(int i = 0; i < components; i++) { + handler.removeCallbacks(collectors[i]); + } + counterService = null; + handler = null; + collecting = true; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean("collecting", collecting); + outState.putStringArray("componentNames", componentNames); + outState.putInt("noUidMask", noUidMask); + } + + /* Let all of the UI graphs lay themselves out again. */ + private void stateChanged() { + for(int i = 0; i < components; i++) { + collectors[i].layout(); + } + } + + private static final int MENU_OPTIONS = 0; + private static final int MENU_TOGGLE_COLLECTING = 1; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_OPTIONS, 0, "Options"); + menu.add(0, MENU_TOGGLE_COLLECTING, 0, ""); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.findItem(MENU_TOGGLE_COLLECTING).setTitle( + collecting ? "Pause" : "Resume"); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case MENU_OPTIONS: + startActivity(new Intent(this, ViewerPreferences.class)); + return true; + case MENU_TOGGLE_COLLECTING: + collecting = !collecting; + if(handler != null) { + if(collecting) for(int i = 0; i < components; i++) { + collectors[i].reset(); + handler.post(collectors[i]); + } else for(int i = 0; i < components; i++) { + handler.removeCallbacks(collectors[i]); + } + } + break; + } + return false; + } + + public class ValueCollector implements Runnable { + private XYSeries series; + private XYMultipleSeriesRenderer renderer; + private View chartView; + + private int componentId; + private long lastTime; + + int[] values; + + private boolean readHistory; + + public ValueCollector(XYSeries series, XYMultipleSeriesRenderer renderer, + View chartView, int componentId) { + this.series = series; + this.renderer = renderer; + this.chartView = chartView; + this.componentId = componentId; + lastTime = SystemClock.elapsedRealtime(); + layout(); + } + + public void layout() { + int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60")); + values = new int[numVals]; + + renderer.clearXTextLabels(); + renderer.setXAxisMin(0); + renderer.setXAxisMax(numVals - 1); + renderer.addXTextLabel(numVals - 1, "" + numVals); + renderer.setXLabels(0); + for(int j = 0; j < 10; j++) { + renderer.addXTextLabel(numVals * j / 10, "" + (1 + numVals * j / 10)); + } + + reset(); + } + + /** Restart points collecting from zero. */ + public void reset() { + series.clear(); + readHistory = true; + } + + public void run() { + int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60")); + if(counterService != null) try { + if(readHistory) { + values = counterService.getComponentHistory(numVals, + componentId, uid); + readHistory = false; + } else { + for(int i = numVals - 1; i > 0; i--) { + values[i] = values[i - 1]; + } + values[0] = counterService.getComponentHistory(1, componentId, + uid)[0]; + } + } catch(RemoteException e) { + Log.w(TAG, "Failed to get data from service"); + for(int i = 0; i < numVals; i++) { + values[i] = 0; + } + } + + series.clear(); + for(int i = 0; i < numVals; i++) { + series.add(i, values[i]); + } + + long curTime = SystemClock.elapsedRealtime(); + long tryTime = lastTime + PowerEstimator.ITERATION_INTERVAL * + (long)Math.max(1, 1 + (curTime - lastTime) / + PowerEstimator.ITERATION_INTERVAL); + if(handler != null) { + handler.postDelayed(this, tryTime - curTime); + } + + chartView.invalidate(); + } + }; +} diff --git a/src/edu/umich/PowerTutor/ui/StartupReceiver.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/StartupReceiver.java similarity index 100% rename from src/edu/umich/PowerTutor/ui/StartupReceiver.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/StartupReceiver.java diff --git a/src/edu/umich/PowerTutor/ui/UMLogger.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/UMLogger.java similarity index 95% rename from src/edu/umich/PowerTutor/ui/UMLogger.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/UMLogger.java index ebaca34..8f492d9 100644 --- a/src/edu/umich/PowerTutor/ui/UMLogger.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/UMLogger.java @@ -24,32 +24,25 @@ import edu.umich.PowerTutor.service.ICounterService; import edu.umich.PowerTutor.service.UMLoggerService; +import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ComponentName; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.content.res.Configuration; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; -import android.os.RemoteException; import android.preference.PreferenceManager; -import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -59,11 +52,6 @@ import java.io.BufferedOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.UnknownHostException; /** The main view activity for PowerTutor*/ public class UMLogger extends Activity { @@ -71,6 +59,8 @@ public class UMLogger extends Activity { public static final String CURRENT_VERSION = "1.2"; // Don't change this... + private static final int LOCATION_PERMISSION_REQUEST_CODE = 42; + public static final String SERVER_IP = "spidermonkey.eecs.umich.edu"; public static final int SERVER_PORT = 5204; @@ -118,6 +108,20 @@ public void onCreate(Bundle savedInstanceState) { appViewerButton.setEnabled(false); sysViewerButton.setEnabled(false); } + + requestLocationPermission(); + } + + private void requestLocationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions( + new String[] { + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION + }, + LOCATION_PERMISSION_REQUEST_CODE + ); + } } @Override @@ -324,7 +328,7 @@ public void onClick(View v) { private class CounterServiceConnection implements ServiceConnection { public void onServiceConnected(ComponentName className, IBinder boundService) { - counterService = ICounterService.Stub.asInterface((IBinder)boundService); + counterService = ICounterService.Stub.asInterface((IBinder) boundService); serviceStartButton.setText("Stop Profiler"); serviceStartButton.setEnabled(true); appViewerButton.setEnabled(true); diff --git a/src/edu/umich/PowerTutor/ui/ViewerPreferences.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/ViewerPreferences.java similarity index 100% rename from src/edu/umich/PowerTutor/ui/ViewerPreferences.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/ui/ViewerPreferences.java diff --git a/src/edu/umich/PowerTutor/util/BatteryStats.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/BatteryStats.java similarity index 100% rename from src/edu/umich/PowerTutor/util/BatteryStats.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/BatteryStats.java diff --git a/src/edu/umich/PowerTutor/util/Counter.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/Counter.java similarity index 100% rename from src/edu/umich/PowerTutor/util/Counter.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/Counter.java diff --git a/src/edu/umich/PowerTutor/util/ForegroundDetector.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/ForegroundDetector.java similarity index 100% rename from src/edu/umich/PowerTutor/util/ForegroundDetector.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/ForegroundDetector.java diff --git a/src/edu/umich/PowerTutor/util/HexEncode.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/HexEncode.java similarity index 100% rename from src/edu/umich/PowerTutor/util/HexEncode.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/HexEncode.java diff --git a/src/edu/umich/PowerTutor/util/HistoryBuffer.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/HistoryBuffer.java similarity index 96% rename from src/edu/umich/PowerTutor/util/HistoryBuffer.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/HistoryBuffer.java index 65fe0a1..bd46e0e 100644 --- a/src/edu/umich/PowerTutor/util/HistoryBuffer.java +++ b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/HistoryBuffer.java @@ -1,136 +1,136 @@ -/* -Copyright (C) 2011 The University of Michigan - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Please send inquiries to powertutor@umich.edu -*/ - -package edu.umich.PowerTutor.util; - -import android.util.SparseArray; - -import java.util.LinkedList; -import java.util.ListIterator; - -public class HistoryBuffer { - private static class UidData { - public LinkedList queue; - public Counter sum; - public Counter count; - - public UidData() { - queue = new LinkedList(); - sum = new Counter(); - count = new Counter(); - } - } - - private static class HistoryDatum { - public HistoryDatum() { - } - - public void init(long iteration, int power) { - this.iteration = iteration; - this.power = power; - } - - public long iteration; - public int power; - } - - private int maxSize; - private SparseArray uidData; - - public HistoryBuffer(int maxSize) { - this.maxSize = maxSize; - uidData = new SparseArray(); - } - - /* The iteration should only increase across successive adds. */ - public synchronized void add(int uid, long iteration, int power) { - UidData data = uidData.get(uid); - if(data == null) { - data = new UidData(); - uidData.put(uid, data); - } - data.count.add(1); - if(power == 0) { - return; - } - data.sum.add(power); - if(maxSize == 0) { - return; - } - - LinkedList queue = data.queue; - HistoryDatum datum; - if(maxSize <= queue.size()) { - datum = queue.getLast(); - queue.removeLast(); - } else { - datum = new HistoryDatum(); - } - datum.init(iteration, power); - queue.addFirst(datum); - } - - /* Fills in the previous number timestamps starting from a timestamp and - * working backwards. Any timestamp with no information is just treated - * as using no power. - */ - public synchronized int[] get(int uid, long timestamp, int number) { - int ind = 0; - if(number < 0) number = 0; - if(number > maxSize) number = maxSize; - int[] ret = new int[number]; - UidData data = uidData.get(uid); - LinkedList queue = data == null ? null : data.queue; - if(queue == null || queue.isEmpty()) { - return ret; - } - if(timestamp == -1) { - timestamp = queue.getFirst().iteration; - } - for(ListIterator iter = queue.listIterator(); - iter.hasNext(); ) { - HistoryDatum datum = iter.next(); - while(datum.iteration < timestamp && ind < number) { - ind++; - timestamp--; - } - if(ind == number) { - break; - } - if(datum.iteration == timestamp) { - ret[ind++] = datum.power; - timestamp--; - } else { - /* datum happened after requested interval. */ - } - } - return ret; - } - - public synchronized long getTotal(int uid, int windowType) { - UidData data = uidData.get(uid); - return data == null ? 0 : data.sum.get(windowType); - } - - public synchronized long getCount(int uid, int windowType) { - UidData data = uidData.get(uid); - return data == null ? 0 : data.count.get(windowType); - } -} - +/* +Copyright (C) 2011 The University of Michigan + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Please send inquiries to powertutor@umich.edu +*/ + +package edu.umich.PowerTutor.util; + +import android.util.SparseArray; + +import java.util.LinkedList; +import java.util.ListIterator; + +public class HistoryBuffer { + private static class UidData { + public LinkedList queue; + public Counter sum; + public Counter count; + + public UidData() { + queue = new LinkedList(); + sum = new Counter(); + count = new Counter(); + } + } + + private static class HistoryDatum { + public HistoryDatum() { + } + + public void init(long iteration, int power) { + this.iteration = iteration; + this.power = power; + } + + public long iteration; + public int power; + } + + private int maxSize; + private SparseArray uidData; + + public HistoryBuffer(int maxSize) { + this.maxSize = maxSize; + uidData = new SparseArray(); + } + + /* The iteration should only increase across successive adds. */ + public synchronized void add(int uid, long iteration, int power) { + UidData data = uidData.get(uid); + if(data == null) { + data = new UidData(); + uidData.put(uid, data); + } + data.count.add(1); + if(power == 0) { + return; + } + data.sum.add(power); + if(maxSize == 0) { + return; + } + + LinkedList queue = data.queue; + HistoryDatum datum; + if(maxSize <= queue.size()) { + datum = queue.getLast(); + queue.removeLast(); + } else { + datum = new HistoryDatum(); + } + datum.init(iteration, power); + queue.addFirst(datum); + } + + /* Fills in the previous number timestamps starting from a timestamp and + * working backwards. Any timestamp with no information is just treated + * as using no power. + */ + public synchronized int[] get(int uid, long timestamp, int number) { + int ind = 0; + if(number < 0) number = 0; + if(number > maxSize) number = maxSize; + int[] ret = new int[number]; + UidData data = uidData.get(uid); + LinkedList queue = data == null ? null : data.queue; + if(queue == null || queue.isEmpty()) { + return ret; + } + if(timestamp == -1) { + timestamp = queue.getFirst().iteration; + } + for(ListIterator iter = queue.listIterator(); + iter.hasNext(); ) { + HistoryDatum datum = iter.next(); + while(datum.iteration < timestamp && ind < number) { + ind++; + timestamp--; + } + if(ind == number) { + break; + } + if(datum.iteration == timestamp) { + ret[ind++] = datum.power; + timestamp--; + } else { + /* datum happened after requested interval. */ + } + } + return ret; + } + + public synchronized long getTotal(int uid, int windowType) { + UidData data = uidData.get(uid); + return data == null ? 0 : data.sum.get(windowType); + } + + public synchronized long getCount(int uid, int windowType) { + UidData data = uidData.get(uid); + return data == null ? 0 : data.count.get(windowType); + } +} + diff --git a/src/edu/umich/PowerTutor/util/NativeLoader.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/NativeLoader.java similarity index 100% rename from src/edu/umich/PowerTutor/util/NativeLoader.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/NativeLoader.java diff --git a/src/edu/umich/PowerTutor/util/NotificationService.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/NotificationService.java similarity index 100% rename from src/edu/umich/PowerTutor/util/NotificationService.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/NotificationService.java diff --git a/src/edu/umich/PowerTutor/util/Recycler.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/Recycler.java similarity index 100% rename from src/edu/umich/PowerTutor/util/Recycler.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/Recycler.java diff --git a/src/edu/umich/PowerTutor/util/SystemInfo.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/util/SystemInfo.java similarity index 100% rename from src/edu/umich/PowerTutor/util/SystemInfo.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/util/SystemInfo.java diff --git a/src/edu/umich/PowerTutor/widget/Configure.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/Configure.java similarity index 100% rename from src/edu/umich/PowerTutor/widget/Configure.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/Configure.java diff --git a/src/edu/umich/PowerTutor/widget/DataSource.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/DataSource.java similarity index 100% rename from src/edu/umich/PowerTutor/widget/DataSource.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/DataSource.java diff --git a/src/edu/umich/PowerTutor/widget/DataSourceConfigure.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/DataSourceConfigure.java similarity index 100% rename from src/edu/umich/PowerTutor/widget/DataSourceConfigure.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/DataSourceConfigure.java diff --git a/src/edu/umich/PowerTutor/widget/PowerWidget.java b/PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/PowerWidget.java similarity index 100% rename from src/edu/umich/PowerTutor/widget/PowerWidget.java rename to PowerTutor2/src/main/java/edu/umich/PowerTutor/widget/PowerWidget.java diff --git a/jni/Android.mk b/PowerTutor2/src/main/jni/Android.mk similarity index 100% rename from jni/Android.mk rename to PowerTutor2/src/main/jni/Android.mk diff --git a/jni/bindings.cpp b/PowerTutor2/src/main/jni/bindings.cpp similarity index 100% rename from jni/bindings.cpp rename to PowerTutor2/src/main/jni/bindings.cpp diff --git a/res/drawable/background.png b/PowerTutor2/src/main/res/drawable/background.png similarity index 100% rename from res/drawable/background.png rename to PowerTutor2/src/main/res/drawable/background.png diff --git a/res/drawable/help.png b/PowerTutor2/src/main/res/drawable/help.png similarity index 100% rename from res/drawable/help.png rename to PowerTutor2/src/main/res/drawable/help.png diff --git a/res/drawable/icon.png b/PowerTutor2/src/main/res/drawable/icon.png similarity index 100% rename from res/drawable/icon.png rename to PowerTutor2/src/main/res/drawable/icon.png diff --git a/res/drawable/level.xml b/PowerTutor2/src/main/res/drawable/level.xml similarity index 98% rename from res/drawable/level.xml rename to PowerTutor2/src/main/res/drawable/level.xml index 7c87f58..9df13b6 100644 --- a/res/drawable/level.xml +++ b/PowerTutor2/src/main/res/drawable/level.xml @@ -1,12 +1,12 @@ - - android:id="@+id/l1" - - - - - - - - - - + + android:id="@+id/l1" + + + + + + + + + + diff --git a/res/drawable/level_1.png b/PowerTutor2/src/main/res/drawable/level_1.png similarity index 100% rename from res/drawable/level_1.png rename to PowerTutor2/src/main/res/drawable/level_1.png diff --git a/res/drawable/level_2.png b/PowerTutor2/src/main/res/drawable/level_2.png similarity index 100% rename from res/drawable/level_2.png rename to PowerTutor2/src/main/res/drawable/level_2.png diff --git a/res/drawable/level_3.png b/PowerTutor2/src/main/res/drawable/level_3.png similarity index 100% rename from res/drawable/level_3.png rename to PowerTutor2/src/main/res/drawable/level_3.png diff --git a/res/drawable/level_4.png b/PowerTutor2/src/main/res/drawable/level_4.png similarity index 100% rename from res/drawable/level_4.png rename to PowerTutor2/src/main/res/drawable/level_4.png diff --git a/res/drawable/level_5.png b/PowerTutor2/src/main/res/drawable/level_5.png similarity index 100% rename from res/drawable/level_5.png rename to PowerTutor2/src/main/res/drawable/level_5.png diff --git a/res/drawable/level_6.png b/PowerTutor2/src/main/res/drawable/level_6.png similarity index 100% rename from res/drawable/level_6.png rename to PowerTutor2/src/main/res/drawable/level_6.png diff --git a/res/drawable/level_7.png b/PowerTutor2/src/main/res/drawable/level_7.png similarity index 100% rename from res/drawable/level_7.png rename to PowerTutor2/src/main/res/drawable/level_7.png diff --git a/res/drawable/level_8.png b/PowerTutor2/src/main/res/drawable/level_8.png similarity index 100% rename from res/drawable/level_8.png rename to PowerTutor2/src/main/res/drawable/level_8.png diff --git a/res/drawable/level_9.png b/PowerTutor2/src/main/res/drawable/level_9.png similarity index 100% rename from res/drawable/level_9.png rename to PowerTutor2/src/main/res/drawable/level_9.png diff --git a/res/drawable/line.png b/PowerTutor2/src/main/res/drawable/line.png similarity index 100% rename from res/drawable/line.png rename to PowerTutor2/src/main/res/drawable/line.png diff --git a/res/drawable/power_off.png b/PowerTutor2/src/main/res/drawable/power_off.png similarity index 100% rename from res/drawable/power_off.png rename to PowerTutor2/src/main/res/drawable/power_off.png diff --git a/res/drawable/power_on.png b/PowerTutor2/src/main/res/drawable/power_on.png similarity index 100% rename from res/drawable/power_on.png rename to PowerTutor2/src/main/res/drawable/power_on.png diff --git a/res/drawable/time.xml b/PowerTutor2/src/main/res/drawable/time.xml similarity index 98% rename from res/drawable/time.xml rename to PowerTutor2/src/main/res/drawable/time.xml index 896640f..d8a2e5d 100644 --- a/res/drawable/time.xml +++ b/PowerTutor2/src/main/res/drawable/time.xml @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/res/drawable/time_0.png b/PowerTutor2/src/main/res/drawable/time_0.png similarity index 100% rename from res/drawable/time_0.png rename to PowerTutor2/src/main/res/drawable/time_0.png diff --git a/res/drawable/time_1.png b/PowerTutor2/src/main/res/drawable/time_1.png similarity index 100% rename from res/drawable/time_1.png rename to PowerTutor2/src/main/res/drawable/time_1.png diff --git a/res/drawable/time_10.png b/PowerTutor2/src/main/res/drawable/time_10.png similarity index 100% rename from res/drawable/time_10.png rename to PowerTutor2/src/main/res/drawable/time_10.png diff --git a/res/drawable/time_11.png b/PowerTutor2/src/main/res/drawable/time_11.png similarity index 100% rename from res/drawable/time_11.png rename to PowerTutor2/src/main/res/drawable/time_11.png diff --git a/res/drawable/time_12.png b/PowerTutor2/src/main/res/drawable/time_12.png similarity index 100% rename from res/drawable/time_12.png rename to PowerTutor2/src/main/res/drawable/time_12.png diff --git a/res/drawable/time_2.png b/PowerTutor2/src/main/res/drawable/time_2.png similarity index 100% rename from res/drawable/time_2.png rename to PowerTutor2/src/main/res/drawable/time_2.png diff --git a/res/drawable/time_3.png b/PowerTutor2/src/main/res/drawable/time_3.png similarity index 100% rename from res/drawable/time_3.png rename to PowerTutor2/src/main/res/drawable/time_3.png diff --git a/res/drawable/time_4.png b/PowerTutor2/src/main/res/drawable/time_4.png similarity index 100% rename from res/drawable/time_4.png rename to PowerTutor2/src/main/res/drawable/time_4.png diff --git a/res/drawable/time_5.png b/PowerTutor2/src/main/res/drawable/time_5.png similarity index 100% rename from res/drawable/time_5.png rename to PowerTutor2/src/main/res/drawable/time_5.png diff --git a/res/drawable/time_6.png b/PowerTutor2/src/main/res/drawable/time_6.png similarity index 100% rename from res/drawable/time_6.png rename to PowerTutor2/src/main/res/drawable/time_6.png diff --git a/res/drawable/time_7.png b/PowerTutor2/src/main/res/drawable/time_7.png similarity index 100% rename from res/drawable/time_7.png rename to PowerTutor2/src/main/res/drawable/time_7.png diff --git a/res/drawable/time_8.png b/PowerTutor2/src/main/res/drawable/time_8.png similarity index 100% rename from res/drawable/time_8.png rename to PowerTutor2/src/main/res/drawable/time_8.png diff --git a/res/drawable/time_9.png b/PowerTutor2/src/main/res/drawable/time_9.png similarity index 100% rename from res/drawable/time_9.png rename to PowerTutor2/src/main/res/drawable/time_9.png diff --git a/res/drawable/widget_bg.png b/PowerTutor2/src/main/res/drawable/widget_bg.png similarity index 100% rename from res/drawable/widget_bg.png rename to PowerTutor2/src/main/res/drawable/widget_bg.png diff --git a/res/layout/help.xml b/PowerTutor2/src/main/res/layout/help.xml similarity index 97% rename from res/layout/help.xml rename to PowerTutor2/src/main/res/layout/help.xml index 5746d8f..7b1e555 100644 --- a/res/layout/help.xml +++ b/PowerTutor2/src/main/res/layout/help.xml @@ -1,68 +1,68 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/res/layout/main.xml b/PowerTutor2/src/main/res/layout/main.xml similarity index 96% rename from res/layout/main.xml rename to PowerTutor2/src/main/res/layout/main.xml index 70dceae..6546181 100644 --- a/res/layout/main.xml +++ b/PowerTutor2/src/main/res/layout/main.xml @@ -1,78 +1,78 @@ - - - - - - - -