├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── publish.gradle ├── settings.gradle └── telemetry ├── build.gradle ├── proguard-rules.pro └── src ├── main ├── AndroidManifest.xml └── java │ └── org │ └── mozilla │ └── telemetry │ ├── Telemetry.java │ ├── TelemetryHolder.java │ ├── config │ └── TelemetryConfiguration.java │ ├── event │ └── TelemetryEvent.java │ ├── measurement │ ├── ArchMeasurement.java │ ├── ClientIdMeasurement.java │ ├── CreatedDateMeasurement.java │ ├── CreatedTimestampMeasurement.java │ ├── DefaultSearchMeasurement.java │ ├── DeviceMeasurement.java │ ├── EventsMeasurement.java │ ├── FirstRunProfileDateMeasurement.java │ ├── LocaleMeasurement.java │ ├── OperatingSystemMeasurement.java │ ├── OperatingSystemVersionMeasurement.java │ ├── ProcessStartTimestampMeasurement.java │ ├── SearchesMeasurement.java │ ├── SequenceMeasurement.java │ ├── SessionCountMeasurement.java │ ├── SessionDurationMeasurement.java │ ├── SettingsMeasurement.java │ ├── StaticMeasurement.java │ ├── TelemetryMeasurement.java │ ├── TimezoneOffsetMeasurement.java │ └── VersionMeasurement.java │ ├── net │ ├── DebugLogClient.java │ ├── HttpURLConnectionTelemetryClient.java │ └── TelemetryClient.java │ ├── ping │ ├── TelemetryCorePingBuilder.java │ ├── TelemetryEventPingBuilder.java │ ├── TelemetryMobileEventPingBuilder.java │ ├── TelemetryPing.java │ └── TelemetryPingBuilder.java │ ├── schedule │ ├── TelemetryScheduler.java │ └── jobscheduler │ │ ├── JobSchedulerTelemetryScheduler.java │ │ └── TelemetryJobService.java │ ├── serialize │ ├── JSONPingSerializer.java │ └── TelemetryPingSerializer.java │ ├── storage │ ├── FileTelemetryStorage.java │ └── TelemetryStorage.java │ └── util │ ├── ContextUtils.java │ ├── FileUtils.java │ ├── IOUtils.java │ └── StringUtils.java └── test └── java └── org └── mozilla └── telemetry ├── TelemetryHolderTest.java ├── TelemetryTest.java ├── TestUtils.java ├── config └── TelemetryConfigurationTest.java ├── event └── TelemetryEventTest.java ├── measurement ├── ArchMeasurementTest.java ├── ClientIdMeasurementTest.java ├── CreatedDateMeasurementTest.java ├── CreatedTimestampMeasurementTest.java ├── DefaultSearchMeasurementTest.java ├── DeviceMeasurementTest.java ├── EventsMeasurementTest.java ├── FirstRunProfileDateMeasurementTest.java ├── LocaleMeasurementTest.java ├── OperatingSystemMeasurementTest.java ├── OperatingSystemVersionMeasurementTest.java ├── ProcessStartTimestampMeasurementTest.java ├── SearchesMeasurementTest.java ├── SequenceMeasurementTest.java ├── SessionCountMeasurementTest.java ├── SessionDurationMeasurementTest.java ├── SettingsMeasurementTest.java ├── StaticMeasurementTest.java ├── TimezoneOffsetMeasurementTest.java └── VersionMeasurementTest.java ├── net ├── DebugLogClientTest.java └── HttpUrlConnectionTelemetryClientTest.java ├── ping ├── TelemetryCorePingBuilderTest.java ├── TelemetryEventPingBuilderTest.java └── TelemetryMobileEventPingBuilderTest.java ├── schedule └── jobscheduler │ └── TelemetryJobServiceTest.java ├── serialize └── JSONPingSerializerTest.java ├── storage └── FileTelemetryStorageTest.java └── util ├── FileUtilsTest.java ├── IOUtilsTest.java └── StringUtilsTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | *.jks 41 | 42 | # Compiled python code 43 | *.pyc 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | android: 7 | components: 8 | - tools 9 | - platform-tools 10 | - build-tools-25.0.2 11 | - android-25 12 | - extra-google-google_play_services 13 | - extra-google-m2repository 14 | - extra-android-m2repository 15 | 16 | sudo: false 17 | 18 | before_install: 19 | - touch local.properties 20 | 21 | script: 22 | - ./gradlew --info clean assembleDebug lint test jacocoTestReport coveralls 23 | 24 | after_failure: 25 | - "cat $TRAVIS_BUILD_DIR/app/build/outputs/lint-results-debug.xml" 26 | 27 | before_cache: 28 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 29 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 30 | 31 | cache: 32 | directories: 33 | - $HOME/.gradle/caches/ 34 | - $HOME/.gradle/wrapper/ 35 | - $HOME/.android/build-cache 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ The telemetry library has been moved to the [android-components repository](https://github.com/mozilla-mobile/android-components). 2 | 3 | 4 | [![Build Status](https://travis-ci.org/mozilla-mobile/telemetry-android.svg?branch=master)](https://travis-ci.org/mozilla-mobile/telemetry-android) 5 | [![Coverage Status](https://coveralls.io/repos/github/mozilla-mobile/telemetry-android/badge.svg?branch=master)](https://coveralls.io/github/mozilla-mobile/telemetry-android?branch=master) 6 | 7 | telemetry-android 8 | ================= 9 | 10 | A generic library for sending telemetry pings from Android applications to Mozilla's telemetry service. 11 | 12 | ## Motivation 13 | 14 | The goal of this library is to provide a generic set of components to support a variety of telemetry use cases. It tries to not be opinionated about dependency injection frameworks or http clients. The only dependency is ``support-annotations`` to ensure code quality. 15 | 16 | ## Getting involved 17 | 18 | We encourage you to participate in this open source project. We love Pull Requests, Bug Reports, ideas, (security) code reviews or any kind of positive contribution. Please read the [Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/). 19 | 20 | * Issues: [https://github.com/mozilla-mobile/telemetry-android/issues](https://github.com/mozilla-mobile/telemetry-android/issues) 21 | 22 | * IRC: [#mobile (irc.mozilla.org)](https://wiki.mozilla.org/IRC) 23 | 24 | * Mailing list: [mobile-firefox-dev](https://mail.mozilla.org/listinfo/mobile-firefox-dev) 25 | 26 | ## License 27 | 28 | This Source Code Form is subject to the terms of the Mozilla Public 29 | License, v. 2.0. If a copy of the MPL was not distributed with this 30 | file, You can obtain one at http://mozilla.org/MPL/2.0/ 31 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | 10 | classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1' 11 | classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1' 12 | 13 | // Publish. 14 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' 15 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | jcenter() 25 | } 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | libRepositoryName=Mozilla-Mobile 20 | libGroupId=org.mozilla.telemetry 21 | libVersion=1.2.0 22 | libUrl=https://github.com/mozilla-mobile/telemetry-android 23 | libVcsUrl=https://github.com/mozilla-mobile/telemetry-android.git 24 | 25 | libLicense=MPL-2.0 26 | libLicenseUrl=https://www.mozilla.org/en-US/MPL/2.0/ 27 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-mobile/telemetry-android/85e481aa35367e4bedb0c2a25fbabe12a406703a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 08 11:51:44 CET 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /publish.gradle: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | def libRepoName = properties.libRepositoryName 6 | def libGroupId = properties.libGroupId 7 | def libUrl = properties.libUrl 8 | def libVcsUrl = properties.libVcsUrl 9 | def libLicense = properties.libLicense 10 | def libLicenseUrl = properties.libLicenseUrl 11 | 12 | ext.configurePublish = { artifactIdArg, descriptionArg -> 13 | apply plugin: 'com.github.dcendents.android-maven' 14 | 15 | group = properties.libGroupId 16 | 17 | install { 18 | repositories.mavenInstaller { 19 | pom { 20 | project { 21 | packaging 'aar' 22 | groupId libGroupId 23 | artifactId artifactIdArg 24 | 25 | name libRepoName 26 | description descriptionArg 27 | url libUrl 28 | 29 | licenses { 30 | license { 31 | name libLicense 32 | url libLicenseUrl 33 | } 34 | } 35 | 36 | developers { 37 | developer { 38 | id 'sebastian' 39 | name 'Sebastian Kaspari' 40 | email 's.kaspari@gmail.com' 41 | } 42 | } 43 | 44 | scm { 45 | connection libVcsUrl 46 | developerConnection libVcsUrl 47 | url libUrl 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | apply plugin: 'com.jfrog.bintray' 55 | 56 | version = properties.libVersion 57 | 58 | task sourcesJar(type: Jar) { 59 | from android.sourceSets.main.java.srcDirs 60 | classifier = 'sources' 61 | } 62 | 63 | task javadoc(type: Javadoc) { 64 | source = android.sourceSets.main.java.srcDirs 65 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 66 | } 67 | 68 | task javadocJar(type: Jar, dependsOn: javadoc) { 69 | classifier = 'javadoc' 70 | from javadoc.destinationDir 71 | } 72 | 73 | artifacts { 74 | //archives javadocJar 75 | archives sourcesJar 76 | } 77 | 78 | Properties localProperties = new Properties() 79 | localProperties.load(project.rootProject.file('local.properties').newDataInputStream()) 80 | 81 | bintray { 82 | user = localProperties.getProperty("bintray.user") 83 | key = localProperties.getProperty("bintray.apikey") 84 | 85 | configurations = ['archives'] 86 | pkg { 87 | repo = libRepoName 88 | name = artifactIdArg 89 | desc = descriptionArg 90 | websiteUrl = libUrl 91 | vcsUrl = libVcsUrl 92 | licenses = [libLicense] 93 | publish = true 94 | publicDownloadNumbers = true 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':telemetry' 2 | -------------------------------------------------------------------------------- /telemetry/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'jacoco' 4 | apply plugin: 'jacoco-android' 5 | apply plugin: 'com.github.kt3k.coveralls' 6 | 7 | android { 8 | compileSdkVersion 25 9 | buildToolsVersion "25.0.2" 10 | 11 | defaultConfig { 12 | minSdkVersion 21 13 | targetSdkVersion 25 14 | versionCode 1 15 | versionName "1.0" 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | } 22 | debug { 23 | testCoverageEnabled true 24 | } 25 | } 26 | testOptions { 27 | unitTests.all { 28 | jacoco { 29 | includeNoLocationClasses = true 30 | } 31 | } 32 | } 33 | lintOptions { 34 | ignoreWarnings false 35 | abortOnError true 36 | warningsAsErrors true 37 | } 38 | } 39 | 40 | jacocoAndroidUnitTestReport { 41 | csv.enabled false 42 | html.enabled true 43 | xml.enabled true 44 | } 45 | 46 | coveralls { 47 | jacocoReportPath = "${buildDir}/reports/jacoco/jacocoTestDebugUnitTestReport/jacocoTestDebugUnitTestReport.xml" 48 | } 49 | 50 | dependencies { 51 | compile 'com.android.support:support-annotations:25.3.1' 52 | 53 | testCompile 'junit:junit:4.12' 54 | testCompile 'org.robolectric:robolectric:3.3.1' 55 | testCompile 'org.mockito:mockito-core:2.7.19' 56 | testCompile 'com.squareup.okhttp3:mockwebserver:3.6.0' 57 | } 58 | 59 | apply from: '../publish.gradle' 60 | def packageName = 'telemetry' 61 | def description = ' A generic library for generating and sending telemetry pings from Android applications to Mozilla\'s telemetry service.' 62 | ext.configurePublish(packageName, description) 63 | -------------------------------------------------------------------------------- /telemetry/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/sebastian/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /telemetry/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/Telemetry.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry; 6 | 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.RestrictTo; 9 | import android.support.annotation.VisibleForTesting; 10 | 11 | import org.mozilla.telemetry.config.TelemetryConfiguration; 12 | import org.mozilla.telemetry.event.TelemetryEvent; 13 | import org.mozilla.telemetry.measurement.DefaultSearchMeasurement; 14 | import org.mozilla.telemetry.measurement.EventsMeasurement; 15 | import org.mozilla.telemetry.net.TelemetryClient; 16 | import org.mozilla.telemetry.ping.TelemetryCorePingBuilder; 17 | import org.mozilla.telemetry.ping.TelemetryEventPingBuilder; 18 | import org.mozilla.telemetry.ping.TelemetryMobileEventPingBuilder; 19 | import org.mozilla.telemetry.ping.TelemetryPing; 20 | import org.mozilla.telemetry.ping.TelemetryPingBuilder; 21 | import org.mozilla.telemetry.schedule.TelemetryScheduler; 22 | import org.mozilla.telemetry.storage.TelemetryStorage; 23 | 24 | import java.util.Collection; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | import java.util.concurrent.ExecutorService; 28 | import java.util.concurrent.Executors; 29 | 30 | public class Telemetry { 31 | private final TelemetryConfiguration configuration; 32 | private final TelemetryStorage storage; 33 | private final TelemetryClient client; 34 | private final TelemetryScheduler scheduler; 35 | 36 | private final Map pingBuilders; 37 | private final ExecutorService executor = Executors.newSingleThreadExecutor(); 38 | 39 | public Telemetry(TelemetryConfiguration configuration, TelemetryStorage storage, 40 | TelemetryClient client, TelemetryScheduler scheduler) { 41 | this.configuration = configuration; 42 | this.storage = storage; 43 | this.client = client; 44 | this.scheduler = scheduler; 45 | 46 | pingBuilders = new HashMap<>(); 47 | } 48 | 49 | public Telemetry addPingBuilder(TelemetryPingBuilder builder) { 50 | pingBuilders.put(builder.getType(), builder); 51 | return this; 52 | } 53 | 54 | public Telemetry queuePing(final String pingType) { 55 | if (!configuration.isCollectionEnabled()) { 56 | return this; 57 | } 58 | 59 | executor.submit(new Runnable() { 60 | @Override 61 | public void run() { 62 | final TelemetryPingBuilder pingBuilder = pingBuilders.get(pingType); 63 | 64 | if (!pingBuilder.canBuild()) { 65 | // We do not always want to build a ping. Sometimes we want to collect enough data so that 66 | // it is worth sending a ping. Here we exit early if the ping builder implementation 67 | // signals that it's not time to build a ping yet. 68 | return; 69 | } 70 | 71 | final TelemetryPing ping = pingBuilder.build(); 72 | storage.store(ping); 73 | } 74 | }); 75 | 76 | return this; 77 | } 78 | 79 | public Telemetry queueEvent(final TelemetryEvent event) { 80 | if (!configuration.isCollectionEnabled()) { 81 | return this; 82 | } 83 | 84 | executor.submit(new Runnable() { 85 | @Override 86 | public void run() { 87 | // We migrated from focus-event to mobile-event and unfortunately, this code was hard-coded to expect 88 | // a focus-event ping builder. We work around this by checking our new hardcoded code first for the new 89 | // ping type and then falling back on the legacy ping type. 90 | final TelemetryPingBuilder mobileEventBuilder = pingBuilders.get(TelemetryMobileEventPingBuilder.TYPE); 91 | final TelemetryPingBuilder focusEventBuilder = pingBuilders.get(TelemetryEventPingBuilder.TYPE); 92 | final EventsMeasurement measurement; 93 | final String addedPingType; 94 | if (mobileEventBuilder != null) { 95 | measurement = ((TelemetryMobileEventPingBuilder) mobileEventBuilder).getEventsMeasurement(); 96 | addedPingType = mobileEventBuilder.getType(); 97 | } else if (focusEventBuilder != null) { 98 | measurement = ((TelemetryEventPingBuilder) focusEventBuilder).getEventsMeasurement(); 99 | addedPingType = focusEventBuilder.getType(); 100 | } else { 101 | throw new IllegalStateException("Expect either TelemetryEventPingBuilder or " + 102 | "TelemetryMobileEventPingBuilder to be added to queue events"); 103 | } 104 | 105 | measurement.add(event); 106 | if (measurement.getEventCount() >= configuration.getMaximumNumberOfEventsPerPing()) { 107 | queuePing(addedPingType); 108 | } 109 | } 110 | }); 111 | 112 | return this; 113 | } 114 | 115 | public Collection getBuilders() { 116 | return pingBuilders.values(); 117 | } 118 | 119 | public Telemetry scheduleUpload() { 120 | if (!configuration.isUploadEnabled()) { 121 | return this; 122 | } 123 | 124 | executor.submit(new Runnable() { 125 | @Override 126 | public void run() { 127 | scheduler.scheduleUpload(configuration); 128 | } 129 | }); 130 | return this; 131 | } 132 | 133 | public void recordSessionStart() { 134 | if (!configuration.isCollectionEnabled()) { 135 | return; 136 | } 137 | 138 | if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) { 139 | throw new IllegalStateException("This configuration does not contain a core ping builder"); 140 | } 141 | 142 | final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE); 143 | 144 | builder.getSessionDurationMeasurement().recordSessionStart(); 145 | builder.getSessionCountMeasurement().countSession(); 146 | } 147 | 148 | public Telemetry recordSessionEnd() { 149 | if (!configuration.isCollectionEnabled()) { 150 | return this; 151 | } 152 | 153 | if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) { 154 | throw new IllegalStateException("This configuration does not contain a core ping builder"); 155 | } 156 | 157 | final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE); 158 | builder.getSessionDurationMeasurement().recordSessionEnd(); 159 | 160 | return this; 161 | } 162 | 163 | /** 164 | * Record a search for the given location and search engine identifier. 165 | * 166 | * Common location values used by Fennec and Focus: 167 | * 168 | * actionbar: the user types in the url bar and hits enter to use the default search engine 169 | * listitem: the user selects a search engine from the list of secondary search engines at 170 | * the bottom of the screen 171 | * suggestion: the user clicks on a search suggestion or, in the case that suggestions are 172 | * disabled, the row corresponding with the main engine 173 | * 174 | * @param location where search was started. 175 | * @param identifier of the used search engine. 176 | */ 177 | public Telemetry recordSearch(@NonNull String location, @NonNull String identifier) { 178 | if (!configuration.isCollectionEnabled()) { 179 | return this; 180 | } 181 | 182 | if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) { 183 | throw new IllegalStateException("This configuration does not contain a core ping builder"); 184 | } 185 | 186 | final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE); 187 | builder.getSearchesMeasurement() 188 | .recordSearch(location, identifier); 189 | 190 | return this; 191 | } 192 | 193 | public Telemetry setDefaultSearchProvider(DefaultSearchMeasurement.DefaultSearchEngineProvider provider) { 194 | if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) { 195 | throw new IllegalStateException("This configuration does not contain a core ping builder"); 196 | } 197 | 198 | final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE); 199 | builder.getDefaultSearchMeasurement() 200 | .setDefaultSearchEngineProvider(provider); 201 | 202 | return this; 203 | } 204 | 205 | public TelemetryClient getClient() { 206 | return client; 207 | } 208 | 209 | public TelemetryStorage getStorage() { 210 | return storage; 211 | } 212 | 213 | public TelemetryConfiguration getConfiguration() { 214 | return configuration; 215 | } 216 | 217 | /** 218 | * @hide 219 | */ 220 | @RestrictTo(RestrictTo.Scope.LIBRARY) 221 | @VisibleForTesting ExecutorService getExecutor() { 222 | return executor; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/TelemetryHolder.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry; 6 | 7 | /** 8 | * Holder of a static reference to the Telemetry instance. This is required for background services 9 | * that somehow need to get access to the configuration and storage. This is not particular nice. 10 | * Hopefully we can replace this with something better. 11 | */ 12 | public class TelemetryHolder { 13 | private static Telemetry telemetry; 14 | 15 | public static void set(Telemetry telemetry) { 16 | TelemetryHolder.telemetry = telemetry; 17 | } 18 | 19 | public static Telemetry get() { 20 | if (telemetry == null) { 21 | throw new IllegalStateException("You need to call set() on TelemetryHolder in your application class"); 22 | } 23 | 24 | return telemetry; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/event/TelemetryEvent.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.event; 6 | 7 | import android.os.SystemClock; 8 | import android.support.annotation.CheckResult; 9 | import android.support.annotation.NonNull; 10 | import android.support.annotation.Nullable; 11 | import android.support.annotation.RestrictTo; 12 | 13 | import org.json.JSONArray; 14 | import org.json.JSONObject; 15 | import org.mozilla.telemetry.TelemetryHolder; 16 | import org.mozilla.telemetry.ping.TelemetryEventPingBuilder; 17 | import org.mozilla.telemetry.util.StringUtils; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | /** 23 | * TelemetryEvent specifies a common events data format, which allows for broader, shared usage of 24 | * data processing tools. 25 | */ 26 | public class TelemetryEvent { 27 | private static final long startTime = SystemClock.elapsedRealtime(); 28 | 29 | private static final int MAX_LENGTH_CATEGORY = 30; 30 | private static final int MAX_LENGTH_METHOD = 20; 31 | private static final int MAX_LENGTH_OBJECT = 20; 32 | private static final int MAX_LENGTH_VALUE = 80; 33 | private static final int MAX_EXTRA_KEYS = 10; 34 | private static final int MAX_LENGTH_EXTRA_KEY = 15; 35 | private static final int MAX_LENGTH_EXTRA_VALUE = 80; 36 | 37 | /** 38 | * Create a new event with mandatory category, method and object. 39 | * 40 | * @param category identifier. The category is a group name for events and helps to avoid name conflicts. 41 | * @param method identifier. This describes the type of event that occured, e.g. click, keydown or focus. 42 | * @param object identifier. This is the object the event occured on, e.g. reload_button or urlbar. 43 | */ 44 | @CheckResult 45 | public static TelemetryEvent create(@NonNull String category, @NonNull String method, @Nullable String object) { 46 | return new TelemetryEvent(category, method, object, null); 47 | } 48 | 49 | /** 50 | * Create a new event with mandatory category, method, object and value. 51 | * 52 | * @param category identifier. The category is a group name for events and helps to avoid name conflicts. 53 | * @param method identifier. This describes the type of event that occured, e.g. click, keydown or focus. 54 | * @param object identifier. This is the object the event occured on, e.g. reload_button or urlbar. 55 | * @param value This is a user defined value, providing context for the event. 56 | */ 57 | @CheckResult 58 | public static TelemetryEvent create(@NonNull String category, @NonNull String method, @Nullable String object, String value) { 59 | return new TelemetryEvent(category, method, object, value); 60 | } 61 | 62 | /** 63 | * Positive number. This is the time in ms when the event was recorded, relative to the main 64 | * process start time. 65 | */ 66 | private final long timestamp; 67 | 68 | /** 69 | * Identifier. The category is a group name for events and helps to avoid name conflicts. 70 | */ 71 | private final String category; 72 | 73 | /** 74 | * Identifier. This describes the type of event that occurred, e.g. click, keydown or focus. 75 | */ 76 | private final String method; 77 | 78 | /** 79 | * Identifier. This is the object the event occurred on, e.g. reload_button or urlbar. 80 | */ 81 | private @Nullable final String object; 82 | 83 | /** 84 | * Optional, may be null. This is a user defined value, providing context for the event. 85 | */ 86 | private @Nullable String value; 87 | 88 | /** 89 | * Optional, may be null. This is an object of the form {"key": "value", ...}, used for events 90 | * where additional context is needed. 91 | */ 92 | private final Map extras; 93 | 94 | private TelemetryEvent(@NonNull String category, @NonNull String method, @Nullable String object, @Nullable String value) { 95 | timestamp = SystemClock.elapsedRealtime() - startTime; 96 | 97 | // We are naively assuming that all strings here are ASCII (1 character = 1 bytes) as the max lengths are defined in bytes 98 | // There's an opportunity here to make this more strict and throw - however we may want to make this configurable. 99 | this.category = StringUtils.safeSubstring(category, 0, MAX_LENGTH_CATEGORY); 100 | this.method = StringUtils.safeSubstring(method, 0, MAX_LENGTH_METHOD); 101 | this.object = object == null ? null : StringUtils.safeSubstring(object, 0, MAX_LENGTH_OBJECT); 102 | this.value = value == null ? null : StringUtils.safeSubstring(value, 0, MAX_LENGTH_VALUE); 103 | this.extras = new HashMap<>(); 104 | } 105 | 106 | public TelemetryEvent extra(String key, String value) { 107 | if (extras.size() > MAX_EXTRA_KEYS) { 108 | throw new IllegalArgumentException("Exceeding limit of " + MAX_EXTRA_KEYS + " extra keys"); 109 | } 110 | 111 | extras.put(StringUtils.safeSubstring(key, 0, MAX_LENGTH_EXTRA_KEY), 112 | StringUtils.safeSubstring(value, 0, MAX_LENGTH_EXTRA_VALUE)); 113 | 114 | return this; 115 | } 116 | 117 | /** 118 | * Queue this event to be sent with the next event ping. 119 | */ 120 | public void queue() { 121 | TelemetryHolder.get().queueEvent(this); 122 | } 123 | 124 | /** 125 | * Create a JSON representation of this event for storing and sending it. 126 | */ 127 | @RestrictTo(RestrictTo.Scope.LIBRARY) 128 | public String toJSON() { 129 | final JSONArray array = new JSONArray(); 130 | 131 | array.put(timestamp); 132 | array.put(category); 133 | array.put(method); 134 | array.put(object); 135 | 136 | if (value != null) { 137 | array.put(value); 138 | } 139 | 140 | if (extras != null && !extras.isEmpty()) { 141 | if (value == null) { 142 | array.put(null); 143 | } 144 | 145 | array.put(new JSONObject(extras)); 146 | } 147 | 148 | return array.toString(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/ArchMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.os.Build; 8 | import android.support.annotation.VisibleForTesting; 9 | 10 | import java.util.Locale; 11 | 12 | public class ArchMeasurement extends TelemetryMeasurement { 13 | private static final String FIELD_NAME = "arch"; 14 | 15 | public ArchMeasurement() { 16 | super(FIELD_NAME); 17 | } 18 | 19 | @Override 20 | public Object flush() { 21 | return getArchitecture(); 22 | } 23 | 24 | @VisibleForTesting String getArchitecture() { 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 26 | return Build.SUPPORTED_ABIS[0]; 27 | } else { 28 | //noinspection deprecation 29 | return Build.CPU_ABI; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/ClientIdMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | 11 | import java.util.UUID; 12 | 13 | /** 14 | * A unique, randomly generated UUID for this client. 15 | */ 16 | public class ClientIdMeasurement extends TelemetryMeasurement { 17 | private static final String FIELD_NAME = "clientId"; 18 | 19 | private static final String PREFERENCE_CLIENT_ID = "client_id"; 20 | 21 | private TelemetryConfiguration configuration; 22 | private String clientId; 23 | 24 | public ClientIdMeasurement(TelemetryConfiguration configuration) { 25 | super(FIELD_NAME); 26 | 27 | this.configuration = configuration; 28 | } 29 | 30 | @Override 31 | public Object flush() { 32 | if (clientId == null) { 33 | clientId = generateClientId(configuration); 34 | } 35 | 36 | return clientId; 37 | } 38 | 39 | private static synchronized String generateClientId(final TelemetryConfiguration configuration) { 40 | final SharedPreferences preferences = configuration.getSharedPreferences(); 41 | 42 | if (preferences.contains(PREFERENCE_CLIENT_ID)) { 43 | // We already generated a client id in the past. Let's use it. 44 | return preferences.getString(PREFERENCE_CLIENT_ID, /* unused default value */ null); 45 | } 46 | 47 | final String clientId = UUID.randomUUID().toString(); 48 | 49 | preferences.edit() 50 | .putString(PREFERENCE_CLIENT_ID, clientId) 51 | .apply(); 52 | 53 | return clientId; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/CreatedDateMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import java.text.DateFormat; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Calendar; 10 | import java.util.Locale; 11 | 12 | public class CreatedDateMeasurement extends TelemetryMeasurement { 13 | private static final String FIELD_NAME = "created"; 14 | 15 | public CreatedDateMeasurement() { 16 | super(FIELD_NAME); 17 | } 18 | 19 | @Override 20 | public Object flush() { 21 | final DateFormat pingCreationDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); 22 | final Calendar nowCalendar = Calendar.getInstance(); 23 | 24 | return pingCreationDateFormat.format(nowCalendar.getTime()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/CreatedTimestampMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | public class CreatedTimestampMeasurement extends TelemetryMeasurement { 8 | private static final String FIELD_NAME = "created"; 9 | 10 | public CreatedTimestampMeasurement() { 11 | super(FIELD_NAME); 12 | } 13 | 14 | @Override 15 | public Object flush() { 16 | return System.currentTimeMillis(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/DefaultSearchMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.json.JSONObject; 8 | 9 | public class DefaultSearchMeasurement extends TelemetryMeasurement { 10 | private static final String FIELD_NAME = "defaultSearch"; 11 | 12 | public interface DefaultSearchEngineProvider { 13 | String getDefaultSearchEngineIdentifier(); 14 | } 15 | 16 | private DefaultSearchEngineProvider provider; 17 | 18 | public DefaultSearchMeasurement() { 19 | super (FIELD_NAME); 20 | } 21 | 22 | public void setDefaultSearchEngineProvider(DefaultSearchEngineProvider provider) { 23 | this.provider = provider; 24 | } 25 | 26 | @Override 27 | public Object flush() { 28 | if (provider == null) { 29 | return JSONObject.NULL; 30 | } 31 | 32 | final String identifier = provider.getDefaultSearchEngineIdentifier(); 33 | return identifier != null ? identifier :JSONObject.NULL; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/DeviceMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.os.Build; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.VisibleForTesting; 10 | 11 | import org.mozilla.telemetry.util.StringUtils; 12 | 13 | public class DeviceMeasurement extends TelemetryMeasurement { 14 | private static final String FIELD_NAME = "device"; 15 | 16 | public DeviceMeasurement() { 17 | super(FIELD_NAME); 18 | } 19 | 20 | @Override 21 | public Object flush() { 22 | // We limit the device descriptor to 32 characters because it can get long. We give fewer 23 | // characters to the manufacturer because we're less likely to have manufacturers with 24 | // similar names than we are for a manufacturer to have two devices with the similar names 25 | // (e.g. Galaxy S6 vs. Galaxy Note 6). 26 | return StringUtils.safeSubstring(getManufacturer(), 0, 12) + '-' + StringUtils.safeSubstring(getModel(), 0, 19); 27 | } 28 | 29 | @VisibleForTesting String getManufacturer() { 30 | return Build.MANUFACTURER; 31 | } 32 | 33 | @VisibleForTesting String getModel() { 34 | return Build.MODEL; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/EventsMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | import android.support.annotation.VisibleForTesting; 9 | import android.util.Log; 10 | 11 | import org.json.JSONArray; 12 | import org.json.JSONException; 13 | import org.mozilla.telemetry.config.TelemetryConfiguration; 14 | import org.mozilla.telemetry.event.TelemetryEvent; 15 | import org.mozilla.telemetry.util.IOUtils; 16 | 17 | import java.io.BufferedReader; 18 | import java.io.BufferedWriter; 19 | import java.io.File; 20 | import java.io.FileInputStream; 21 | import java.io.FileNotFoundException; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStreamReader; 25 | import java.io.OutputStreamWriter; 26 | 27 | public class EventsMeasurement extends TelemetryMeasurement { 28 | private static final String LOG_TAG = EventsMeasurement.class.getSimpleName(); 29 | 30 | private static final int VERSION = 1; 31 | private static final String FIELD_NAME = "events"; 32 | 33 | private static final String PREFERENCE_EVENT_COUNT = "event_count"; 34 | 35 | private TelemetryConfiguration configuration; 36 | 37 | public EventsMeasurement(TelemetryConfiguration configuration) { 38 | super(FIELD_NAME); 39 | 40 | this.configuration = configuration; 41 | } 42 | 43 | public void add(final TelemetryEvent event) { 44 | saveEventToDisk(event); 45 | } 46 | 47 | @Override 48 | public Object flush() { 49 | return readAndClearEventsFromDisk(); 50 | } 51 | 52 | private synchronized JSONArray readAndClearEventsFromDisk() { 53 | final JSONArray events = new JSONArray(); 54 | final File file = getEventFile(); 55 | 56 | FileInputStream stream = null; 57 | 58 | try { 59 | stream = new FileInputStream(file); 60 | 61 | final BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); 62 | 63 | String line; 64 | 65 | while ((line = reader.readLine()) != null) { 66 | try { 67 | JSONArray event = new JSONArray(line); 68 | events.put(event); 69 | 70 | resetEventCount(); 71 | } catch (JSONException e) { 72 | // Let's log a warning and move on. This event is lost. 73 | Log.w(LOG_TAG, "Could not parse event from disk", e); 74 | } 75 | } 76 | } catch (FileNotFoundException e) { 77 | // This shouldn't happen because we do not create event pings if there are no events. 78 | // However in case the file disappears: Continue with no events. 79 | return new JSONArray(); 80 | } catch (IOException e) { 81 | // Handling this exception at this time is tricky: We might have been able to read some 82 | // events at the time this exception occurred. We can either try to add them to the 83 | // ping and remove the file or we retry later again. 84 | 85 | // We just log an error here. This means we are going to continue building the ping 86 | // with the events we were able to read from disk. The events file will be removed and 87 | // we might potentially lose events that we couldn't ready because of the exception. 88 | Log.w(LOG_TAG, "IOException while reading events from disk", e); 89 | } finally { 90 | IOUtils.safeClose(stream); 91 | 92 | if (!file.delete()) { 93 | Log.w(LOG_TAG, "Events file could not be deleted"); 94 | } 95 | } 96 | 97 | return events; 98 | } 99 | 100 | @VisibleForTesting File getEventFile() { 101 | return new File(configuration.getDataDirectory(), "events" + VERSION); 102 | } 103 | 104 | private synchronized void saveEventToDisk(TelemetryEvent event) { 105 | FileOutputStream stream = null; 106 | 107 | try { 108 | stream = new FileOutputStream(getEventFile(), true); 109 | 110 | final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream)); 111 | writer.write(event.toJSON()); 112 | writer.newLine(); 113 | writer.flush(); 114 | writer.close(); 115 | 116 | countEvent(); 117 | } catch (IOException e) { 118 | Log.w(LOG_TAG, "IOException while writing event to disk", e); 119 | throw new AssertionError("BOING"); 120 | } finally { 121 | IOUtils.safeClose(stream); 122 | } 123 | } 124 | 125 | private synchronized void countEvent() { 126 | final SharedPreferences preferences = configuration.getSharedPreferences(); 127 | 128 | long count = preferences.getLong(PREFERENCE_EVENT_COUNT, 0); 129 | 130 | preferences.edit() 131 | .putLong(PREFERENCE_EVENT_COUNT, ++count) 132 | .apply(); 133 | } 134 | 135 | private synchronized void resetEventCount() { 136 | final SharedPreferences preferences = configuration.getSharedPreferences(); 137 | 138 | preferences.edit() 139 | .putLong(PREFERENCE_EVENT_COUNT, 0) 140 | .apply(); 141 | } 142 | 143 | public long getEventCount() { 144 | final SharedPreferences preferences = configuration.getSharedPreferences(); 145 | 146 | return preferences.getLong(PREFERENCE_EVENT_COUNT, 0); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/FirstRunProfileDateMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | import android.support.annotation.VisibleForTesting; 9 | 10 | import org.mozilla.telemetry.config.TelemetryConfiguration; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * This measurement will save the timestamp of the first time it was instantiated and report this 16 | * as profile creation date. 17 | */ 18 | public class FirstRunProfileDateMeasurement extends TelemetryMeasurement { 19 | private static final String FIELD_NAME = "profileDate"; 20 | 21 | private static final String PREFERENCE_KEY = "profile_date"; 22 | 23 | private TelemetryConfiguration configuration; 24 | 25 | public FirstRunProfileDateMeasurement(TelemetryConfiguration configuration) { 26 | super(FIELD_NAME); 27 | 28 | this.configuration = configuration; 29 | 30 | ensureValueExists(); 31 | } 32 | 33 | @VisibleForTesting void ensureValueExists() { 34 | final SharedPreferences preferences = configuration.getSharedPreferences(); 35 | 36 | if (preferences.contains(PREFERENCE_KEY)) { 37 | return; 38 | } 39 | 40 | preferences.edit() 41 | .putLong(PREFERENCE_KEY, now()) 42 | .apply(); 43 | } 44 | 45 | @Override 46 | public Object flush() { 47 | return getProfileDateInDays(); 48 | } 49 | 50 | /** 51 | * Profile creation date in days since UNIX epoch. 52 | */ 53 | private long getProfileDateInDays() { 54 | long profileDateMilliseconds = configuration.getSharedPreferences().getLong(PREFERENCE_KEY, now()); 55 | 56 | return (long) Math.floor((double) profileDateMilliseconds / TimeUnit.DAYS.toMillis(1)); 57 | } 58 | 59 | @VisibleForTesting long now() { 60 | return System.currentTimeMillis(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/LocaleMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import java.util.Locale; 8 | 9 | public class LocaleMeasurement extends TelemetryMeasurement { 10 | private static final String FIELD_NAME = "locale"; 11 | 12 | public LocaleMeasurement() { 13 | super(FIELD_NAME); 14 | } 15 | 16 | @Override 17 | public Object flush() { 18 | return getLanguageTag(Locale.getDefault()); 19 | } 20 | 21 | /** 22 | * Gecko uses locale codes like "es-ES", whereas a Java {@link Locale} 23 | * stringifies as "es_ES". 24 | * 25 | * This method approximates the Java 7 method 26 | * Locale#toLanguageTag(). 27 | * 28 | * @return a locale string suitable for passing to Gecko. 29 | */ 30 | public String getLanguageTag(final Locale locale) { 31 | // If this were Java 7: 32 | // return locale.toLanguageTag(); 33 | 34 | final String language = getLanguage(locale); 35 | final String country = locale.getCountry(); // Can be an empty string. 36 | if (country.equals("")) { 37 | return language; 38 | } 39 | return language + "-" + country; 40 | } 41 | 42 | /** 43 | * Sometimes we want just the language for a locale, not the entire language 44 | * tag. But Java's .getLanguage method is wrong. 45 | * 46 | * @return a language string, such as "he" for the Hebrew locales. 47 | */ 48 | private String getLanguage(final Locale locale) { 49 | // Can, but should never be, an empty string. 50 | final String language = locale.getLanguage(); 51 | 52 | // Modernize certain language codes. 53 | if (language.equals("iw")) { 54 | return "he"; 55 | } 56 | 57 | if (language.equals("in")) { 58 | return "id"; 59 | } 60 | 61 | if (language.equals("ji")) { 62 | return "yi"; 63 | } 64 | 65 | return language; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/OperatingSystemMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | public class OperatingSystemMeasurement extends StaticMeasurement { 8 | private static final String FIELD_NAME = "os"; 9 | private static final String FIELD_VALUE = "Android"; 10 | 11 | public OperatingSystemMeasurement() { 12 | super(FIELD_NAME, FIELD_VALUE); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/OperatingSystemVersionMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.os.Build; 8 | 9 | public class OperatingSystemVersionMeasurement extends StaticMeasurement { 10 | private static final String FIELD_NAME = "osversion"; 11 | 12 | public OperatingSystemVersionMeasurement() { 13 | super(FIELD_NAME, Integer.toString(Build.VERSION.SDK_INT)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/ProcessStartTimestampMeasurement.java: -------------------------------------------------------------------------------- 1 | package org.mozilla.telemetry.measurement; 2 | 3 | import org.mozilla.telemetry.Telemetry; 4 | import org.mozilla.telemetry.config.TelemetryConfiguration; 5 | 6 | public class ProcessStartTimestampMeasurement extends TelemetryMeasurement { 7 | private static final String FIELD_NAME = "processStartTimestamp"; 8 | 9 | private final long processLoadTimestampMillis; 10 | 11 | public ProcessStartTimestampMeasurement(final TelemetryConfiguration configuration) { 12 | super(FIELD_NAME); 13 | 14 | // Ideally, we'd get the process start time, but considering Telemetry is 15 | // expected to be loaded early in the Activity lifecycle, this is good enough. 16 | processLoadTimestampMillis = configuration.getClassLoadTimestampMillis(); 17 | } 18 | 19 | @Override 20 | public Object flush() { 21 | return processLoadTimestampMillis; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/SearchesMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | import android.support.annotation.NonNull; 9 | 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | import org.mozilla.telemetry.config.TelemetryConfiguration; 13 | 14 | import java.util.Collections; 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | /** 19 | * A TelemetryMeasurement implementation to count the number of times a user has searched with a 20 | * specific engine from a specific location. 21 | */ 22 | public class SearchesMeasurement extends TelemetryMeasurement { 23 | private static final String FIELD_NAME = "searches"; 24 | 25 | private static final String PREFERENCE_SEARCH_KEYSET = "measurements-search-count-keyset"; 26 | private static final String PREFERENCE_SEARCH_PREFIX = "measurements-search-count-engine-"; 27 | 28 | public static final String LOCATION_ACTIONBAR = "actionbar"; 29 | public static final String LOCATION_SUGGESTION = "suggestion"; 30 | public static final String LOCATION_LISTITEM = "listitem"; 31 | 32 | private final TelemetryConfiguration configuration; 33 | 34 | public SearchesMeasurement(TelemetryConfiguration configuration) { 35 | super(FIELD_NAME); 36 | 37 | this.configuration = configuration; 38 | } 39 | 40 | @Override 41 | public Object flush() { 42 | return getSearchCountMapAndReset(); 43 | } 44 | 45 | /** 46 | * Get the stored search counts and reset all of them. 47 | */ 48 | private synchronized JSONObject getSearchCountMapAndReset() { 49 | try { 50 | final SharedPreferences preferences = configuration.getSharedPreferences(); 51 | final SharedPreferences.Editor editor = preferences.edit(); 52 | 53 | final JSONObject object = new JSONObject(); 54 | 55 | final Set keys = preferences.getStringSet(PREFERENCE_SEARCH_KEYSET, Collections.emptySet()); 56 | for (String locationAndIdentifier : keys) { 57 | final String key = getEngineSearchCountKey(locationAndIdentifier); 58 | 59 | object.put(locationAndIdentifier, preferences.getInt(key, 0)); 60 | editor.remove(key); 61 | } 62 | 63 | editor.remove(PREFERENCE_SEARCH_KEYSET) 64 | .apply(); 65 | 66 | return object; 67 | } catch (JSONException e) { 68 | throw new AssertionError("Should not happen: Can't construct search count JSON", e); 69 | } 70 | } 71 | 72 | /** 73 | * Record a search for the given location and search engine identifier. 74 | * 75 | * @param location where search was started. 76 | * @param identifier of the used search engine. 77 | */ 78 | public synchronized void recordSearch(@NonNull String location, @NonNull String identifier) { 79 | final String locationAndIdentifier = location + "." + identifier; 80 | 81 | storeCount(locationAndIdentifier); 82 | storeKey(locationAndIdentifier); 83 | } 84 | 85 | /** 86 | * Store count for this engine in preferences. 87 | */ 88 | private void storeCount(String locationAndIdentifier) { 89 | final SharedPreferences preferences = configuration.getSharedPreferences(); 90 | 91 | final String key = getEngineSearchCountKey(locationAndIdentifier); 92 | 93 | final int count = preferences.getInt(key, 0); 94 | 95 | preferences.edit() 96 | .putInt(key, count + 1) 97 | .apply(); 98 | } 99 | 100 | /** 101 | * Store name of this engine in preferences so that we can retrieve the count later. 102 | */ 103 | private void storeKey(String locationAndIdentifier) { 104 | final SharedPreferences preferences = configuration.getSharedPreferences(); 105 | 106 | final Set keys = preferences.getStringSet(PREFERENCE_SEARCH_KEYSET, Collections.emptySet()); 107 | if (keys.contains(locationAndIdentifier)) { 108 | // We already know about this key. 109 | return; 110 | } 111 | 112 | // Create a copy that we can modify 113 | final Set updatedKeys = new HashSet<>(keys); 114 | updatedKeys.add(locationAndIdentifier); 115 | 116 | preferences.edit() 117 | .putStringSet(PREFERENCE_SEARCH_KEYSET, updatedKeys) 118 | .apply(); 119 | } 120 | 121 | private static String getEngineSearchCountKey(String locationAndIdentifier) { 122 | return PREFERENCE_SEARCH_PREFIX + locationAndIdentifier; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/SequenceMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.mozilla.telemetry.ping.TelemetryPingBuilder; 11 | 12 | public class SequenceMeasurement extends TelemetryMeasurement { 13 | private static final String FIELD_NAME = "seq"; 14 | 15 | private static final String PREFERENCE_PREFIX = "sequence_"; 16 | 17 | private final TelemetryConfiguration configuration; 18 | private final String preferenceKeySequence; 19 | 20 | public SequenceMeasurement(TelemetryConfiguration configuration, TelemetryPingBuilder ping) { 21 | super(FIELD_NAME); 22 | 23 | this.configuration = configuration; 24 | this.preferenceKeySequence = PREFERENCE_PREFIX + ping.getType(); 25 | } 26 | 27 | @Override 28 | public Object flush() { 29 | return getAndIncrementSequence(); 30 | } 31 | 32 | private synchronized long getAndIncrementSequence() { 33 | final SharedPreferences preferences = configuration.getSharedPreferences(); 34 | 35 | long sequence = preferences.getLong(preferenceKeySequence, 0); 36 | 37 | preferences.edit() 38 | .putLong(preferenceKeySequence, ++sequence) 39 | .apply(); 40 | 41 | return sequence; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/SessionCountMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | 11 | public class SessionCountMeasurement extends TelemetryMeasurement { 12 | private static final String FIELD_NAME = "sessions"; 13 | 14 | private static final String PREFERENCE_COUNT = "session_count"; 15 | 16 | private final TelemetryConfiguration configuration; 17 | 18 | public SessionCountMeasurement(TelemetryConfiguration configuration) { 19 | super(FIELD_NAME); 20 | 21 | this.configuration = configuration; 22 | } 23 | 24 | public synchronized void countSession() { 25 | final SharedPreferences preferences = configuration.getSharedPreferences(); 26 | 27 | long count = preferences.getLong(PREFERENCE_COUNT, 0); 28 | 29 | preferences.edit() 30 | .putLong(PREFERENCE_COUNT, ++count) 31 | .apply(); 32 | } 33 | 34 | @Override 35 | public Object flush() { 36 | return getAndResetCount(); 37 | } 38 | 39 | private synchronized long getAndResetCount() { 40 | final SharedPreferences preferences = configuration.getSharedPreferences(); 41 | 42 | long count = preferences.getLong(PREFERENCE_COUNT, 0); 43 | 44 | preferences.edit() 45 | .putLong(PREFERENCE_COUNT, 0) 46 | .apply(); 47 | 48 | return count; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/SessionDurationMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | import android.support.annotation.VisibleForTesting; 9 | 10 | import org.mozilla.telemetry.config.TelemetryConfiguration; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class SessionDurationMeasurement extends TelemetryMeasurement { 15 | private static final String FIELD_NAME = "durations"; 16 | 17 | private static final String PREFERENCE_DURATION = "session_duration"; 18 | 19 | private final TelemetryConfiguration configuration; 20 | 21 | private boolean sessionStarted = false; 22 | private long timeAtSessionStartNano = -1; 23 | 24 | public SessionDurationMeasurement(TelemetryConfiguration configuration) { 25 | super(FIELD_NAME); 26 | 27 | this.configuration = configuration; 28 | } 29 | 30 | public synchronized void recordSessionStart() { 31 | if (sessionStarted) { 32 | throw new IllegalStateException("Trying to start session but it is already started"); 33 | } 34 | 35 | sessionStarted = true; 36 | timeAtSessionStartNano = getSystemTimeNano(); 37 | } 38 | 39 | public synchronized void recordSessionEnd() { 40 | if (!sessionStarted) { 41 | throw new IllegalStateException("Expected session to be started before session end is called"); 42 | } 43 | 44 | sessionStarted = false; 45 | 46 | final long sessionElapsedSeconds = TimeUnit.NANOSECONDS.toSeconds(getSystemTimeNano() - timeAtSessionStartNano); 47 | 48 | final SharedPreferences preferences = configuration.getSharedPreferences(); 49 | 50 | final long totalElapsedSeconds = preferences.getLong(PREFERENCE_DURATION, 0); 51 | 52 | preferences.edit() 53 | .putLong(PREFERENCE_DURATION, totalElapsedSeconds + sessionElapsedSeconds) 54 | .apply(); 55 | } 56 | 57 | @Override 58 | public Object flush() { 59 | return getAndResetDuration(); 60 | } 61 | 62 | private synchronized long getAndResetDuration() { 63 | final SharedPreferences preferences = configuration.getSharedPreferences(); 64 | 65 | long duration = preferences.getLong(PREFERENCE_DURATION, 0); 66 | 67 | preferences.edit() 68 | .putLong(PREFERENCE_DURATION, 0) 69 | .apply(); 70 | 71 | return duration; 72 | } 73 | 74 | /** 75 | * Returns (roughly) the system uptime in nanoseconds. 76 | */ 77 | @VisibleForTesting long getSystemTimeNano() { 78 | return System.nanoTime(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/SettingsMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.preference.PreferenceManager; 8 | 9 | import org.json.JSONException; 10 | import org.json.JSONObject; 11 | import org.mozilla.telemetry.config.TelemetryConfiguration; 12 | 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class SettingsMeasurement extends TelemetryMeasurement { 17 | /** 18 | * A generic interface for implementations that can provide settings values. 19 | */ 20 | public interface SettingsProvider { 21 | /** 22 | * Notify this provider that we are going to read values from it. Some providers might need 23 | * to perform some actions to be able to provide a fresh set of values. 24 | */ 25 | void update(TelemetryConfiguration configuration); 26 | 27 | /** 28 | * Returns true if a settings value is available for the given key. 29 | */ 30 | boolean containsKey(String key); 31 | 32 | /** 33 | * Get the setting value for the given key. 34 | */ 35 | Object getValue(String key); 36 | 37 | /** 38 | * Notify the provider that we finished reading from it and that it can release resources now. 39 | */ 40 | void release(); 41 | } 42 | 43 | /** 44 | * Setting provider implementation that reads values from SharedPreferences. 45 | */ 46 | public static class SharedPreferenceSettingsProvider implements SettingsProvider { 47 | private Map preferences; 48 | 49 | @Override 50 | public void update(TelemetryConfiguration configuration) { 51 | preferences = PreferenceManager.getDefaultSharedPreferences( 52 | configuration.getContext()).getAll(); 53 | } 54 | 55 | @Override 56 | public boolean containsKey(String key) { 57 | return preferences != null && preferences.containsKey(key); 58 | } 59 | 60 | @Override 61 | public Object getValue(String key) { 62 | return preferences.get(key); 63 | } 64 | 65 | @Override 66 | public void release() { 67 | preferences = null; 68 | } 69 | } 70 | 71 | private static final String FIELD_NAME = "settings"; 72 | 73 | private final TelemetryConfiguration configuration; 74 | 75 | public SettingsMeasurement(TelemetryConfiguration configuration) { 76 | super(FIELD_NAME); 77 | 78 | this.configuration = configuration; 79 | } 80 | 81 | @Override 82 | public Object flush() { 83 | final SettingsProvider settingsProvider = configuration.getSettingsProvider(); 84 | settingsProvider.update(configuration); 85 | 86 | final JSONObject object = new JSONObject(); 87 | 88 | final Set preferenceKeys = configuration.getPreferencesImportantForTelemetry(); 89 | if (preferenceKeys.isEmpty()) { 90 | return object; 91 | } 92 | 93 | for (String key : preferenceKeys) { 94 | try { 95 | if (settingsProvider.containsKey(key)) { 96 | object.put(key, String.valueOf(settingsProvider.getValue(key))); 97 | } else { 98 | object.put(key, JSONObject.NULL); 99 | } 100 | } catch (JSONException e) { 101 | throw new AssertionError("Preference value can't be serialized to JSON", e); 102 | } 103 | } 104 | 105 | settingsProvider.release(); 106 | 107 | return object; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/StaticMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | public class StaticMeasurement extends TelemetryMeasurement { 8 | private final Object value; 9 | 10 | public StaticMeasurement(String fieldName, Object value) { 11 | super(fieldName); 12 | 13 | this.value = value; 14 | } 15 | 16 | @Override 17 | public Object flush() { 18 | return value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/TelemetryMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | public abstract class TelemetryMeasurement { 8 | private final String fieldName; 9 | 10 | public TelemetryMeasurement(String fieldName) { 11 | this.fieldName = fieldName; 12 | } 13 | 14 | public String getFieldName() { 15 | return fieldName; 16 | } 17 | 18 | /** 19 | * Flush this measurement in order for serializing a ping. Calling this method should create 20 | * an Object representing the current state of this measurement. Optionally this measurement 21 | * might be reset. 22 | * 23 | * For example a TelemetryMeasurement implementation for the OS version of the device might 24 | * just return a String like "7.0.1". 25 | * However a TelemetryMeasurement implementation for counting the usage of search engines might 26 | * return a HashMap mapping search engine names to search counts. Additionally those counts will 27 | * be reset after flushing. 28 | */ 29 | public abstract Object flush(); 30 | } 31 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/TimezoneOffsetMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.VisibleForTesting; 9 | 10 | import java.util.Calendar; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class TimezoneOffsetMeasurement extends TelemetryMeasurement { 14 | private static final String FIELD_NAME = "tz"; 15 | 16 | public TimezoneOffsetMeasurement() { 17 | super(FIELD_NAME); 18 | } 19 | 20 | @Override 21 | public Object flush() { 22 | return getTimezoneOffsetInMinutesForGivenDate(now()); 23 | } 24 | 25 | /** 26 | * Returns the time zone offset for the given date in minutes. The date makes a difference due to daylight 27 | * savings time in some regions. We return minutes because we can accurately represent time zones that are 28 | * offset by non-integer hour values, e.g. parts of New Zealand at UTC+12:45. 29 | * 30 | * @param calendar A calendar with the appropriate time zone & date already set. 31 | */ 32 | private static int getTimezoneOffsetInMinutesForGivenDate(@NonNull final Calendar calendar) { 33 | // via Date.getTimezoneOffset deprecated docs (note: it had incorrect order of operations). 34 | // Also, we cast to int because we should never overflow here - the max should be GMT+14 = 840. 35 | return (int) TimeUnit.MILLISECONDS.toMinutes(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)); 36 | } 37 | 38 | @VisibleForTesting Calendar now() { 39 | return Calendar.getInstance(); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/measurement/VersionMeasurement.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | public class VersionMeasurement extends StaticMeasurement { 8 | private static final String FIELD_NAME = "v"; 9 | 10 | public VersionMeasurement(int version) { 11 | super(FIELD_NAME, version); 12 | 13 | if (version <= 0) { 14 | throw new IllegalArgumentException("Version should be a positive integer > 0"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/net/DebugLogClient.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.net; 6 | 7 | import android.util.Log; 8 | 9 | import org.json.JSONException; 10 | import org.json.JSONObject; 11 | import org.mozilla.telemetry.config.TelemetryConfiguration; 12 | 13 | /** 14 | * This client just prints pings to logcat instead of uploading them. Therefore this client is only 15 | * useful for debugging purposes. 16 | */ 17 | public class DebugLogClient implements TelemetryClient { 18 | private final String tag; 19 | 20 | public DebugLogClient(String tag) { 21 | this.tag = tag; 22 | } 23 | 24 | @Override 25 | public boolean uploadPing(TelemetryConfiguration configuration, String path, String serializedPing) { 26 | Log.d(tag, "---PING--- " + path); 27 | 28 | try { 29 | JSONObject object = new JSONObject(serializedPing); 30 | Log.d(tag, object.toString(2)); 31 | } catch (JSONException e) { 32 | Log.d(tag, "Corrupt JSON", e); 33 | } 34 | 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/net/HttpURLConnectionTelemetryClient.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.net; 6 | 7 | import android.support.annotation.VisibleForTesting; 8 | import android.util.Log; 9 | 10 | import org.mozilla.telemetry.config.TelemetryConfiguration; 11 | import org.mozilla.telemetry.util.IOUtils; 12 | 13 | import java.io.BufferedWriter; 14 | import java.io.IOException; 15 | import java.io.OutputStream; 16 | import java.io.OutputStreamWriter; 17 | import java.net.HttpURLConnection; 18 | import java.net.MalformedURLException; 19 | import java.net.URL; 20 | import java.text.SimpleDateFormat; 21 | import java.util.Calendar; 22 | import java.util.Locale; 23 | import java.util.TimeZone; 24 | 25 | public class HttpURLConnectionTelemetryClient implements TelemetryClient { 26 | private static final String LOG_TAG = "HttpURLTelemetryClient"; 27 | 28 | @Override 29 | public boolean uploadPing(TelemetryConfiguration configuration, String path, String serializedPing) { 30 | HttpURLConnection connection = null; 31 | 32 | try { 33 | connection = openConnectionConnection(configuration.getServerEndpoint(), path); 34 | connection.setConnectTimeout(configuration.getConnectTimeout()); 35 | connection.setReadTimeout(configuration.getReadTimeout()); 36 | 37 | connection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); 38 | connection.setRequestProperty("User-Agent", configuration.getUserAgent()); 39 | connection.setRequestProperty("Date", createDateHeaderValue()); 40 | 41 | connection.setRequestMethod("POST"); 42 | connection.setDoOutput(true); 43 | 44 | int responseCode = upload(connection, serializedPing); 45 | 46 | Log.d(LOG_TAG, "Ping upload: " + responseCode); 47 | 48 | if (responseCode >= 200 && responseCode <= 299) { 49 | // Known success errors (2xx): 50 | // 200 - OK. Request accepted into the pipeline. 51 | 52 | // We treat all success codes as successful upload even though we only expect 200. 53 | return true; 54 | } else if (responseCode >= 400 && responseCode <= 499) { 55 | // Known client (4xx) errors: 56 | // 404 - not found - POST/PUT to an unknown namespace 57 | // 405 - wrong request type (anything other than POST/PUT) 58 | // 411 - missing content-length header 59 | // 413 - request body too large (Note that if we have badly-behaved clients that 60 | // retry on 4XX, we should send back 202 on body/path too long). 61 | // 414 - request path too long (See above) 62 | 63 | // Something our client did is not correct. It's unlikely that the client is going 64 | // to recover from this by re-trying again, so we just log and error and report a 65 | // successful upload to the service. 66 | Log.e(LOG_TAG, "Server returned client error code: " + responseCode); 67 | return true; 68 | } else { 69 | // Known other errors: 70 | // 500 - internal error 71 | 72 | // For all other errors we log a warning an try again at a later time. 73 | Log.w(LOG_TAG, "Server returned response code: " + responseCode); 74 | return false; 75 | } 76 | } catch (MalformedURLException e) { 77 | // There's nothing we can do to recover from this here. So let's just log an error and 78 | // notify the service that this job has been completed - even though we didn't upload 79 | // anything to the server. 80 | Log.e(LOG_TAG, "Could not upload telemetry due to malformed URL", e); 81 | return true; 82 | } catch (IOException e) { 83 | Log.w(LOG_TAG, "IOException while uploading ping", e); 84 | return false; 85 | } finally { 86 | if (connection != null) { 87 | connection.disconnect(); 88 | } 89 | } 90 | } 91 | 92 | @VisibleForTesting int upload(HttpURLConnection connection, String serializedPing) throws IOException { 93 | OutputStream stream = null; 94 | 95 | try { 96 | final BufferedWriter writer = new BufferedWriter( 97 | new OutputStreamWriter(stream = connection.getOutputStream())); 98 | 99 | writer.write(serializedPing); 100 | writer.flush(); 101 | writer.close(); 102 | 103 | return connection.getResponseCode(); 104 | } catch (ArrayIndexOutOfBoundsException e) { 105 | // This exception is sometimes thrown from the inside of HttpUrlConnection/OkHttp: 106 | // https://github.com/mozilla-mobile/telemetry-android/issues/54 107 | // We wrap it and handle it like other IO exceptions. 108 | throw new IOException(e); 109 | } finally { 110 | IOUtils.safeClose(stream); 111 | } 112 | } 113 | 114 | @VisibleForTesting HttpURLConnection openConnectionConnection(String endpoint, String path) throws IOException { 115 | final URL url = new URL(endpoint + path); 116 | return (HttpURLConnection) url.openConnection(); 117 | } 118 | 119 | @VisibleForTesting String createDateHeaderValue() { 120 | final Calendar calendar = Calendar.getInstance(); 121 | final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); 122 | dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 123 | return dateFormat.format(calendar.getTime()); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/net/TelemetryClient.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.net; 6 | 7 | import org.mozilla.telemetry.config.TelemetryConfiguration; 8 | 9 | public interface TelemetryClient { 10 | boolean uploadPing(TelemetryConfiguration configuration, String path, String serializedPing); 11 | } 12 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/ping/TelemetryCorePingBuilder.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import org.mozilla.telemetry.config.TelemetryConfiguration; 8 | import org.mozilla.telemetry.measurement.ArchMeasurement; 9 | import org.mozilla.telemetry.measurement.CreatedDateMeasurement; 10 | import org.mozilla.telemetry.measurement.DefaultSearchMeasurement; 11 | import org.mozilla.telemetry.measurement.DeviceMeasurement; 12 | import org.mozilla.telemetry.measurement.FirstRunProfileDateMeasurement; 13 | import org.mozilla.telemetry.measurement.LocaleMeasurement; 14 | import org.mozilla.telemetry.measurement.OperatingSystemMeasurement; 15 | import org.mozilla.telemetry.measurement.OperatingSystemVersionMeasurement; 16 | import org.mozilla.telemetry.measurement.SearchesMeasurement; 17 | import org.mozilla.telemetry.measurement.SequenceMeasurement; 18 | import org.mozilla.telemetry.measurement.SessionCountMeasurement; 19 | import org.mozilla.telemetry.measurement.SessionDurationMeasurement; 20 | import org.mozilla.telemetry.measurement.TimezoneOffsetMeasurement; 21 | 22 | /** 23 | * This mobile-specific ping is intended to provide the most critical data in a concise format, 24 | * allowing for frequent uploads. 25 | * 26 | * Since this ping is used to measure retention, it should be sent each time the app is opened. 27 | * 28 | * https://gecko.readthedocs.io/en/latest/toolkit/components/telemetry/telemetry/data/core-ping.html 29 | */ 30 | public class TelemetryCorePingBuilder extends TelemetryPingBuilder { 31 | public static final String TYPE = "core"; 32 | private static final int VERSION = 7; 33 | 34 | private SessionCountMeasurement sessionCountMeasurement; 35 | private SessionDurationMeasurement sessionDurationMeasurement; 36 | private DefaultSearchMeasurement defaultSearchMeasurement; 37 | private SearchesMeasurement searchesMeasurement; 38 | 39 | public TelemetryCorePingBuilder(TelemetryConfiguration configuration) { 40 | super(configuration, TYPE, VERSION); 41 | 42 | addMeasurement(new SequenceMeasurement(configuration, this)); 43 | addMeasurement(new LocaleMeasurement()); 44 | addMeasurement(new OperatingSystemMeasurement()); 45 | addMeasurement(new OperatingSystemVersionMeasurement()); 46 | addMeasurement(new DeviceMeasurement()); 47 | addMeasurement(new ArchMeasurement()); 48 | addMeasurement(new FirstRunProfileDateMeasurement(configuration)); 49 | addMeasurement(defaultSearchMeasurement = new DefaultSearchMeasurement()); 50 | addMeasurement(new CreatedDateMeasurement()); 51 | addMeasurement(new TimezoneOffsetMeasurement()); 52 | addMeasurement(sessionCountMeasurement = new SessionCountMeasurement(configuration)); 53 | addMeasurement(sessionDurationMeasurement = new SessionDurationMeasurement(configuration)); 54 | addMeasurement(searchesMeasurement = new SearchesMeasurement(configuration)); 55 | } 56 | 57 | public SessionCountMeasurement getSessionCountMeasurement() { 58 | return sessionCountMeasurement; 59 | } 60 | 61 | public SessionDurationMeasurement getSessionDurationMeasurement() { 62 | return sessionDurationMeasurement; 63 | } 64 | 65 | public SearchesMeasurement getSearchesMeasurement() { 66 | return searchesMeasurement; 67 | } 68 | 69 | public DefaultSearchMeasurement getDefaultSearchMeasurement() { 70 | return defaultSearchMeasurement; 71 | } 72 | 73 | @Override 74 | protected String getUploadPath(final String documentId) { 75 | return super.getUploadPath(documentId) + "?v=4"; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/ping/TelemetryEventPingBuilder.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import org.mozilla.telemetry.config.TelemetryConfiguration; 8 | import org.mozilla.telemetry.measurement.CreatedTimestampMeasurement; 9 | import org.mozilla.telemetry.measurement.EventsMeasurement; 10 | import org.mozilla.telemetry.measurement.LocaleMeasurement; 11 | import org.mozilla.telemetry.measurement.OperatingSystemMeasurement; 12 | import org.mozilla.telemetry.measurement.OperatingSystemVersionMeasurement; 13 | import org.mozilla.telemetry.measurement.SequenceMeasurement; 14 | import org.mozilla.telemetry.measurement.SettingsMeasurement; 15 | import org.mozilla.telemetry.measurement.TimezoneOffsetMeasurement; 16 | 17 | /** 18 | * A telemetry ping builder for pings of type "focus-event". 19 | * 20 | * @deprecated prefer {@link TelemetryMobileEventPingBuilder}. "focus-event" was the original type but 21 | * we're migrating to the generic "mobile-event" type. In order to prevent breaking public APIs, we couldn't 22 | * change this class directly and copied it into the new ping type. 23 | */ 24 | @Deprecated 25 | public class TelemetryEventPingBuilder extends TelemetryPingBuilder { 26 | public static final String TYPE = "focus-event"; 27 | private static final int VERSION = 1; 28 | 29 | private EventsMeasurement eventsMeasurement; 30 | 31 | public TelemetryEventPingBuilder(TelemetryConfiguration configuration) { 32 | super(configuration, TYPE, VERSION); 33 | 34 | addMeasurement(new SequenceMeasurement(configuration, this)); 35 | addMeasurement(new LocaleMeasurement()); 36 | addMeasurement(new OperatingSystemMeasurement()); 37 | addMeasurement(new OperatingSystemVersionMeasurement()); 38 | addMeasurement(new CreatedTimestampMeasurement()); 39 | addMeasurement(new TimezoneOffsetMeasurement()); 40 | addMeasurement(new SettingsMeasurement(configuration)); 41 | addMeasurement(eventsMeasurement = new EventsMeasurement(configuration)); 42 | } 43 | 44 | public EventsMeasurement getEventsMeasurement() { 45 | return eventsMeasurement; 46 | } 47 | 48 | @Override 49 | public boolean canBuild() { 50 | return eventsMeasurement.getEventCount() >= getConfiguration().getMinimumEventsForUpload(); 51 | } 52 | 53 | @Override 54 | protected String getUploadPath(final String documentId) { 55 | return super.getUploadPath(documentId) + "?v=4"; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/ping/TelemetryMobileEventPingBuilder.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import org.mozilla.telemetry.config.TelemetryConfiguration; 8 | import org.mozilla.telemetry.measurement.CreatedTimestampMeasurement; 9 | import org.mozilla.telemetry.measurement.EventsMeasurement; 10 | import org.mozilla.telemetry.measurement.LocaleMeasurement; 11 | import org.mozilla.telemetry.measurement.OperatingSystemMeasurement; 12 | import org.mozilla.telemetry.measurement.OperatingSystemVersionMeasurement; 13 | import org.mozilla.telemetry.measurement.ProcessStartTimestampMeasurement; 14 | import org.mozilla.telemetry.measurement.SequenceMeasurement; 15 | import org.mozilla.telemetry.measurement.SettingsMeasurement; 16 | import org.mozilla.telemetry.measurement.TimezoneOffsetMeasurement; 17 | 18 | /** 19 | * A telemetry ping builder for events of type "mobile-event". 20 | * 21 | * See the schema for more details: 22 | * https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/master/schemas/telemetry/mobile-event/mobile-event.1.schema.json 23 | */ 24 | public class TelemetryMobileEventPingBuilder extends TelemetryPingBuilder { 25 | public static final String TYPE = "mobile-event"; 26 | private static final int VERSION = 1; 27 | 28 | private EventsMeasurement eventsMeasurement; 29 | 30 | public TelemetryMobileEventPingBuilder(TelemetryConfiguration configuration) { 31 | super(configuration, TYPE, VERSION); 32 | 33 | addMeasurement(new ProcessStartTimestampMeasurement(configuration)); 34 | addMeasurement(new SequenceMeasurement(configuration, this)); 35 | addMeasurement(new LocaleMeasurement()); 36 | addMeasurement(new OperatingSystemMeasurement()); 37 | addMeasurement(new OperatingSystemVersionMeasurement()); 38 | addMeasurement(new CreatedTimestampMeasurement()); 39 | addMeasurement(new TimezoneOffsetMeasurement()); 40 | addMeasurement(new SettingsMeasurement(configuration)); 41 | addMeasurement(eventsMeasurement = new EventsMeasurement(configuration)); 42 | } 43 | 44 | public EventsMeasurement getEventsMeasurement() { 45 | return eventsMeasurement; 46 | } 47 | 48 | @Override 49 | public boolean canBuild() { 50 | return eventsMeasurement.getEventCount() >= getConfiguration().getMinimumEventsForUpload(); 51 | } 52 | 53 | @Override 54 | protected String getUploadPath(final String documentId) { 55 | return super.getUploadPath(documentId) + "?v=4"; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/ping/TelemetryPing.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import java.util.Map; 8 | 9 | public class TelemetryPing { 10 | private final String type; 11 | private final String documentId; 12 | private final String uploadPath; 13 | private final Map measurementResults; 14 | 15 | /* package */ TelemetryPing(String type, String documentId, String uploadPath, Map measurementResults) { 16 | this.type = type; 17 | this.documentId = documentId; 18 | this.uploadPath = uploadPath; 19 | this.measurementResults = measurementResults; 20 | } 21 | 22 | public String getType() { 23 | return type; 24 | } 25 | 26 | public String getDocumentId() { 27 | return documentId; 28 | } 29 | 30 | public String getUploadPath() { 31 | return uploadPath; 32 | } 33 | 34 | public Map getMeasurementResults() { 35 | return measurementResults; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/ping/TelemetryPingBuilder.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.RestrictTo; 9 | import android.support.annotation.VisibleForTesting; 10 | 11 | import org.mozilla.telemetry.config.TelemetryConfiguration; 12 | import org.mozilla.telemetry.measurement.ClientIdMeasurement; 13 | import org.mozilla.telemetry.measurement.TelemetryMeasurement; 14 | import org.mozilla.telemetry.measurement.VersionMeasurement; 15 | 16 | import java.util.LinkedHashMap; 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.UUID; 21 | 22 | public abstract class TelemetryPingBuilder { 23 | private final String type; 24 | private final List measurements; 25 | 26 | private TelemetryConfiguration configuration; 27 | 28 | public TelemetryPingBuilder(@NonNull TelemetryConfiguration configuration, @NonNull String type, int version) { 29 | this.configuration = configuration; 30 | this.type = type; 31 | this.measurements = new LinkedList<>(); 32 | 33 | // All pings contain a version and a client id 34 | addMeasurement(new VersionMeasurement(version)); 35 | addMeasurement(new ClientIdMeasurement(configuration)); 36 | } 37 | 38 | public TelemetryConfiguration getConfiguration() { 39 | return configuration; 40 | } 41 | 42 | @NonNull 43 | public String getType() { 44 | return type; 45 | } 46 | 47 | protected void addMeasurement(TelemetryMeasurement measurement) { 48 | measurements.add(measurement); 49 | } 50 | 51 | public boolean canBuild() { 52 | return true; 53 | } 54 | 55 | public TelemetryPing build() { 56 | final String documentId = generateDocumentId(); 57 | 58 | return new TelemetryPing( 59 | getType(), 60 | documentId, 61 | getUploadPath(documentId), 62 | flushMeasurements()); 63 | } 64 | 65 | protected String getUploadPath(final String documentId) { 66 | final TelemetryConfiguration configuration = getConfiguration(); 67 | 68 | return String.format("/submit/telemetry/%s/%s/%s/%s/%s/%s", 69 | documentId, 70 | getType(), 71 | configuration.getAppName(), 72 | configuration.getAppVersion(), 73 | configuration.getUpdateChannel(), 74 | configuration.getBuildId()); 75 | } 76 | 77 | private Map flushMeasurements() { 78 | final Map measurementResults = new LinkedHashMap<>(); 79 | 80 | for (TelemetryMeasurement measurement : measurements) { 81 | measurementResults.put(measurement.getFieldName(), measurement.flush()); 82 | } 83 | 84 | return measurementResults; 85 | } 86 | 87 | @VisibleForTesting 88 | @RestrictTo(RestrictTo.Scope.LIBRARY) 89 | public String generateDocumentId() { 90 | return UUID.randomUUID().toString(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/schedule/TelemetryScheduler.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.schedule; 6 | 7 | import org.mozilla.telemetry.config.TelemetryConfiguration; 8 | 9 | public interface TelemetryScheduler { 10 | void scheduleUpload(TelemetryConfiguration configuration); 11 | } 12 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/schedule/jobscheduler/JobSchedulerTelemetryScheduler.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.schedule.jobscheduler; 6 | 7 | import android.app.job.JobInfo; 8 | import android.app.job.JobScheduler; 9 | import android.content.ComponentName; 10 | import android.content.Context; 11 | import android.os.PersistableBundle; 12 | 13 | import org.mozilla.telemetry.config.TelemetryConfiguration; 14 | import org.mozilla.telemetry.schedule.TelemetryScheduler; 15 | 16 | /** 17 | * TelemetryScheduler implementation that uses Android's JobScheduler API to schedule ping uploads. 18 | */ 19 | public class JobSchedulerTelemetryScheduler implements TelemetryScheduler { 20 | public static final int JOB_ID = 42; // TODO: Make this configurable (Issue #24) 21 | 22 | @Override 23 | public void scheduleUpload(TelemetryConfiguration configuration) { 24 | final ComponentName jobService = new ComponentName(configuration.getContext(), TelemetryJobService.class); 25 | 26 | final JobInfo jobInfo = new JobInfo.Builder(JOB_ID, jobService) 27 | .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 28 | .setPersisted(true) 29 | .setBackoffCriteria(configuration.getInitialBackoffForUpload(), JobInfo.BACKOFF_POLICY_EXPONENTIAL) 30 | .build(); 31 | 32 | final JobScheduler scheduler = (JobScheduler) configuration.getContext() 33 | .getSystemService(Context.JOB_SCHEDULER_SERVICE); 34 | 35 | scheduler.schedule(jobInfo); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/schedule/jobscheduler/TelemetryJobService.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.schedule.jobscheduler; 6 | 7 | import android.app.job.JobParameters; 8 | import android.app.job.JobService; 9 | import android.content.SharedPreferences; 10 | import android.os.AsyncTask; 11 | import android.support.annotation.VisibleForTesting; 12 | import android.util.Log; 13 | 14 | import org.mozilla.telemetry.Telemetry; 15 | import org.mozilla.telemetry.TelemetryHolder; 16 | import org.mozilla.telemetry.config.TelemetryConfiguration; 17 | import org.mozilla.telemetry.net.TelemetryClient; 18 | import org.mozilla.telemetry.ping.TelemetryPingBuilder; 19 | import org.mozilla.telemetry.storage.TelemetryStorage; 20 | 21 | import java.util.Calendar; 22 | 23 | public class TelemetryJobService extends JobService { 24 | private static final String LOG_TAG = "TelemetryJobService"; 25 | 26 | private static final String PREFERENCE_UPLOAD_COUNT_PREFIX = "upload_count_"; 27 | private static final String PREFERENCE_LAST_UPLOAD_PREFIX = "last_uploade_"; 28 | 29 | private UploadPingsTask uploadTask; 30 | 31 | @Override 32 | public boolean onStartJob(JobParameters params) { 33 | uploadTask = new UploadPingsTask(); 34 | uploadTask.execute(params); 35 | return true; 36 | } 37 | 38 | @Override 39 | public boolean onStopJob(JobParameters params) { 40 | if (uploadTask != null) { 41 | uploadTask.cancel(true); 42 | } 43 | return true; 44 | } 45 | 46 | private class UploadPingsTask extends AsyncTask { 47 | @Override 48 | protected Void doInBackground(JobParameters... params) { 49 | final JobParameters parameters = params[0]; 50 | uploadPingsInBackground(this, parameters); 51 | return null; 52 | } 53 | } 54 | 55 | @VisibleForTesting public void uploadPingsInBackground(AsyncTask task, JobParameters parameters) { 56 | final Telemetry telemetry = TelemetryHolder.get(); 57 | final TelemetryConfiguration configuration = telemetry.getConfiguration(); 58 | final TelemetryStorage storage = telemetry.getStorage(); 59 | 60 | for (TelemetryPingBuilder builder : telemetry.getBuilders()) { 61 | final String pingType = builder.getType(); 62 | Log.d(LOG_TAG, "Performing upload of ping type: " + pingType); 63 | 64 | if (task.isCancelled()) { 65 | Log.d(LOG_TAG, "Job stopped. Exiting."); 66 | return; // Job will be rescheduled from onStopJob(). 67 | } 68 | 69 | if (storage.countStoredPings(pingType) == 0) { 70 | Log.d(LOG_TAG, "No pings of type " + pingType + " to upload"); 71 | continue; 72 | } 73 | 74 | if (hasReachedUploadLimit(configuration, pingType)) { 75 | Log.d(LOG_TAG, "Daily upload limit for type " + pingType + " reached"); 76 | continue; 77 | } 78 | 79 | if (!performPingUpload(telemetry, pingType)) { 80 | Log.i(LOG_TAG, "Upload aborted. Rescheduling job if limit not reached."); 81 | jobFinished(parameters, !hasReachedUploadLimit(configuration, pingType)); 82 | return; 83 | } 84 | } 85 | 86 | Log.d(LOG_TAG, "All uploads performed"); 87 | jobFinished(parameters, false); 88 | } 89 | 90 | /** 91 | * Increment the upload counter for this ping type. 92 | */ 93 | private boolean incrementUploadCount(TelemetryConfiguration configuration, String pingType) { 94 | final SharedPreferences preferences = configuration.getSharedPreferences(); 95 | 96 | final long lastUpload = preferences.getLong(PREFERENCE_LAST_UPLOAD_PREFIX + pingType, 0); 97 | final long now = now(); 98 | 99 | final long count = isSameDay(lastUpload, now) 100 | ? preferences.getLong(PREFERENCE_UPLOAD_COUNT_PREFIX + pingType, 0) + 1 101 | : 1; 102 | 103 | preferences.edit() 104 | .putLong(PREFERENCE_LAST_UPLOAD_PREFIX + pingType, now) 105 | .putLong(PREFERENCE_UPLOAD_COUNT_PREFIX + pingType, count) 106 | .apply(); 107 | 108 | return true; 109 | } 110 | 111 | /** 112 | * Return true if the upload limit for this ping type has been reached. 113 | */ 114 | private boolean hasReachedUploadLimit(TelemetryConfiguration configuration, String pingType) { 115 | final SharedPreferences preferences = configuration.getSharedPreferences(); 116 | 117 | final long lastUpload = preferences.getLong(PREFERENCE_LAST_UPLOAD_PREFIX + pingType, 0); 118 | final long count = preferences.getLong(PREFERENCE_UPLOAD_COUNT_PREFIX + pingType, 0); 119 | 120 | return isSameDay(lastUpload, now()) 121 | && count >= configuration.getMaximumNumberOfPingUploadsPerDay(); 122 | } 123 | 124 | @VisibleForTesting boolean isSameDay(long timestamp1, long timestamp2) { 125 | final Calendar calendar1 = Calendar.getInstance(); 126 | calendar1.setTimeInMillis(timestamp1); 127 | 128 | final Calendar calendar2 = Calendar.getInstance(); 129 | calendar2.setTimeInMillis(timestamp2); 130 | 131 | return (calendar1.get(Calendar.ERA) == calendar2.get(Calendar.ERA) && 132 | calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR) && 133 | calendar1.get(Calendar.DAY_OF_YEAR) == calendar2.get(Calendar.DAY_OF_YEAR)); 134 | } 135 | 136 | @VisibleForTesting long now() { 137 | return System.currentTimeMillis(); 138 | } 139 | 140 | private boolean performPingUpload(Telemetry telemetry, final String pingType) { 141 | final TelemetryConfiguration configuration = telemetry.getConfiguration(); 142 | final TelemetryStorage storage = telemetry.getStorage(); 143 | final TelemetryClient client = telemetry.getClient(); 144 | 145 | return storage.process(pingType, new TelemetryStorage.TelemetryStorageCallback() { 146 | @Override 147 | public boolean onTelemetryPingLoaded(String path, String serializedPing) { 148 | return !hasReachedUploadLimit(configuration, pingType) 149 | && client.uploadPing(configuration, path, serializedPing) 150 | && incrementUploadCount(configuration, pingType); 151 | } 152 | }); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/serialize/JSONPingSerializer.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.serialize; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | import org.mozilla.telemetry.ping.TelemetryPing; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * TelemetryPingSerializer that uses the org.json library provided by the Android system. 15 | */ 16 | public class JSONPingSerializer implements TelemetryPingSerializer { 17 | @Override 18 | public String serialize(TelemetryPing ping) { 19 | try { 20 | final JSONObject object = new JSONObject(); 21 | 22 | for (Map.Entry result : ping.getMeasurementResults().entrySet()) { 23 | object.put(result.getKey(), result.getValue()); 24 | } 25 | 26 | return object.toString(); 27 | } catch (JSONException e) { 28 | throw new AssertionError("Can't serialize ping", e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/serialize/TelemetryPingSerializer.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.serialize; 6 | 7 | import org.mozilla.telemetry.ping.TelemetryPing; 8 | 9 | public interface TelemetryPingSerializer { 10 | String serialize(TelemetryPing ping); 11 | } 12 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/storage/FileTelemetryStorage.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.storage; 6 | 7 | import android.support.annotation.RestrictTo; 8 | import android.support.annotation.VisibleForTesting; 9 | import android.util.Log; 10 | 11 | import org.mozilla.telemetry.config.TelemetryConfiguration; 12 | import org.mozilla.telemetry.ping.TelemetryPing; 13 | import org.mozilla.telemetry.serialize.TelemetryPingSerializer; 14 | import org.mozilla.telemetry.util.FileUtils; 15 | import org.mozilla.telemetry.util.IOUtils; 16 | 17 | import java.io.BufferedReader; 18 | import java.io.BufferedWriter; 19 | import java.io.File; 20 | import java.io.FileNotFoundException; 21 | import java.io.FileOutputStream; 22 | import java.io.FileReader; 23 | import java.io.FilenameFilter; 24 | import java.io.IOException; 25 | import java.io.OutputStreamWriter; 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.Collections; 29 | import java.util.List; 30 | import java.util.regex.Pattern; 31 | 32 | /** 33 | * TelemetryStorage implementation that stores pings as files on disk. 34 | */ 35 | public class FileTelemetryStorage implements TelemetryStorage { 36 | private static final String LOG_TAG = "FileTelemetryStorage"; 37 | 38 | private static final String FILE_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; 39 | private static final String STORAGE_DIRECTORY = "storage"; 40 | 41 | private final TelemetryConfiguration configuration; 42 | private final TelemetryPingSerializer serializer; 43 | 44 | private final File storageDirectory; 45 | 46 | public FileTelemetryStorage(TelemetryConfiguration configuration, TelemetryPingSerializer serializer) { 47 | this.configuration = configuration; 48 | this.serializer = serializer; 49 | 50 | this.storageDirectory = new File(configuration.getDataDirectory(), STORAGE_DIRECTORY); 51 | 52 | FileUtils.assertDirectory(storageDirectory); 53 | } 54 | 55 | @Override 56 | public synchronized void store(TelemetryPing ping) { 57 | storePing(ping); 58 | maybePrunePings(ping.getType()); 59 | } 60 | 61 | @Override 62 | public boolean process(String pingType, TelemetryStorageCallback callback) { 63 | for (File file : listPingFiles(pingType)) { 64 | FileReader reader = null; 65 | 66 | try { 67 | final BufferedReader bufferedReader = new BufferedReader(reader = new FileReader(file)); 68 | final String path = bufferedReader.readLine(); 69 | final String serializedPing = bufferedReader.readLine(); 70 | 71 | final boolean processed = serializedPing == null || callback.onTelemetryPingLoaded(path, serializedPing); 72 | 73 | if (processed) { 74 | if (!file.delete()) { 75 | Log.w(LOG_TAG, "Could not delete local ping file after processing"); 76 | } 77 | } else { 78 | // The callback couldn't process this file. Let's stop and rety later. 79 | return false; 80 | } 81 | } catch (FileNotFoundException e) { 82 | // This shouldn't happen after we queried the directory. But whatever. Let's continue. 83 | } catch(IOException e) { 84 | // Something is not right. Let's stop. 85 | return false; 86 | } finally { 87 | IOUtils.safeClose(reader); 88 | } 89 | } 90 | 91 | return true; 92 | } 93 | 94 | private void storePing(TelemetryPing ping) { 95 | final File pingStorageDirectory = new File(storageDirectory, ping.getType()); 96 | FileUtils.assertDirectory(pingStorageDirectory); 97 | 98 | final String serializedPing = serializer.serialize(ping); 99 | 100 | final File pingFile = new File(pingStorageDirectory, ping.getDocumentId()); 101 | 102 | FileOutputStream stream = null; 103 | 104 | try { 105 | stream = new FileOutputStream(pingFile, true); 106 | 107 | final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream)); 108 | writer.write(ping.getUploadPath()); 109 | writer.newLine(); 110 | writer.write(serializedPing); 111 | writer.newLine(); 112 | writer.flush(); 113 | writer.close(); 114 | } catch (IOException e) { 115 | Log.w(LOG_TAG, "IOException while writing event to disk", e); 116 | } finally { 117 | IOUtils.safeClose(stream); 118 | } 119 | } 120 | 121 | private void maybePrunePings(final String pingType) { 122 | final File[] files = listPingFiles(pingType); 123 | 124 | final int pingsToRemove = files.length - configuration.getMaximumNumberOfPingsPerType(); 125 | 126 | if (pingsToRemove <= 0) { 127 | return; 128 | } 129 | 130 | final List sortedFiles = new ArrayList<>(Arrays.asList(files)); 131 | Collections.sort(sortedFiles, new FileUtils.FileLastModifiedComparator()); 132 | 133 | for (File file : sortedFiles) { 134 | System.out.println(file.lastModified() + " " + file.getAbsolutePath()); 135 | } 136 | 137 | for (int i = 0; i < pingsToRemove; i++) { 138 | final File file = sortedFiles.get(i); 139 | 140 | if (!file.delete()) { 141 | Log.w(LOG_TAG, "Can't prune ping file: " + file.getAbsolutePath()); 142 | } 143 | } 144 | } 145 | 146 | @VisibleForTesting File[] listPingFiles(String pingType) { 147 | final File pingStorageDirectory = new File(storageDirectory, pingType); 148 | 149 | final Pattern uuidPattern = Pattern.compile(FILE_PATTERN); 150 | 151 | final FilenameFilter uuidFilenameFilter = new FileUtils.FilenameRegexFilter(uuidPattern); 152 | final File[] files = pingStorageDirectory.listFiles(uuidFilenameFilter); 153 | if (files == null) { 154 | return new File[0]; 155 | } 156 | return files; 157 | } 158 | 159 | @Override 160 | @RestrictTo(RestrictTo.Scope.LIBRARY) 161 | public int countStoredPings(String pingType) { 162 | return listPingFiles(pingType).length; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/storage/TelemetryStorage.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.storage; 6 | 7 | import org.mozilla.telemetry.ping.TelemetryPing; 8 | 9 | public interface TelemetryStorage { 10 | interface TelemetryStorageCallback { 11 | boolean onTelemetryPingLoaded(String path, String serializedPing); 12 | } 13 | 14 | void store(TelemetryPing ping); 15 | 16 | boolean process(String pingType, TelemetryStorageCallback callback); 17 | 18 | int countStoredPings(String pingType); 19 | } 20 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/util/ContextUtils.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.util; 6 | 7 | import android.content.Context; 8 | import android.content.pm.ApplicationInfo; 9 | import android.content.pm.PackageInfo; 10 | import android.content.pm.PackageManager; 11 | import android.support.annotation.RestrictTo; 12 | 13 | @RestrictTo(RestrictTo.Scope.LIBRARY) 14 | public class ContextUtils { 15 | public static String getVersionName(Context context) { 16 | return getPackageInfo(context).versionName; 17 | } 18 | 19 | public static int getVersionCode(Context context) { 20 | return getPackageInfo(context).versionCode; 21 | } 22 | 23 | public static String getAppName(Context context) { 24 | final PackageManager packageManager = context.getPackageManager(); 25 | return getApplicationInfo(context).loadLabel(packageManager).toString(); 26 | } 27 | 28 | private static PackageInfo getPackageInfo(Context context) { 29 | try { 30 | return context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 31 | } catch (PackageManager.NameNotFoundException e) { 32 | throw new AssertionError("Could not get own package info"); 33 | } 34 | } 35 | 36 | private static ApplicationInfo getApplicationInfo(Context context) { 37 | try { 38 | return context.getPackageManager().getApplicationInfo(context.getPackageName(), 0); 39 | } catch (PackageManager.NameNotFoundException e) { 40 | throw new AssertionError("Could not get own package info"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.util; 6 | 7 | import android.support.annotation.RestrictTo; 8 | 9 | import java.io.File; 10 | import java.io.FilenameFilter; 11 | import java.util.Comparator; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | @RestrictTo(RestrictTo.Scope.LIBRARY) 16 | public class FileUtils { 17 | public static void assertDirectory(File directory) { 18 | if (!directory.exists() && !directory.mkdirs()) { 19 | throw new IllegalStateException( 20 | "Directory doesn't exist and can't be created: " + directory.getAbsolutePath()); 21 | } 22 | 23 | if (!directory.isDirectory() || !directory.canWrite()) { 24 | throw new IllegalStateException( 25 | "Directory is not writable directory: " + directory.getAbsolutePath()); 26 | } 27 | } 28 | 29 | public static class FilenameRegexFilter implements FilenameFilter { 30 | private final Pattern mPattern; 31 | 32 | // Each time `Pattern.matcher` is called, a new matcher is created. We can avoid the excessive object creation 33 | // by caching the returned matcher and calling `Matcher.reset` on it. Since Matcher's are not thread safe, 34 | // this assumes `FilenameFilter.accept` is not run in parallel (which, according to the source, it is not). 35 | private Matcher mCachedMatcher; 36 | 37 | public FilenameRegexFilter(final Pattern pattern) { 38 | mPattern = pattern; 39 | } 40 | 41 | @Override 42 | public boolean accept(final File dir, final String filename) { 43 | if (mCachedMatcher == null) { 44 | mCachedMatcher = mPattern.matcher(filename); 45 | } else { 46 | mCachedMatcher.reset(filename); 47 | } 48 | return mCachedMatcher.matches(); 49 | } 50 | } 51 | 52 | public static class FileLastModifiedComparator implements Comparator { 53 | @Override 54 | public int compare(final File lhs, final File rhs) { 55 | // Long.compare is API 19+. 56 | final long lhsModified = lhs.lastModified(); 57 | final long rhsModified = rhs.lastModified(); 58 | if (lhsModified < rhsModified) { 59 | return -1; 60 | } else if (lhsModified == rhsModified) { 61 | return 0; 62 | } else { 63 | return 1; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/util/IOUtils.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.util; 6 | 7 | import android.support.annotation.RestrictTo; 8 | 9 | import java.io.Closeable; 10 | import java.io.IOException; 11 | 12 | @RestrictTo(RestrictTo.Scope.LIBRARY) 13 | public class IOUtils { 14 | public static void safeClose(Closeable stream) { 15 | try { 16 | if (stream != null) { 17 | stream.close(); 18 | } 19 | } catch (IOException ignored) { } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /telemetry/src/main/java/org/mozilla/telemetry/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.util; 6 | 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.RestrictTo; 9 | 10 | @RestrictTo(RestrictTo.Scope.LIBRARY) 11 | public class StringUtils { 12 | public static String safeSubstring(@NonNull final String str, final int start, final int end) { 13 | return str.substring( 14 | Math.max(0, start), 15 | Math.min(end, str.length())); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/TelemetryHolderTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.*; 10 | import static org.mockito.Mockito.mock; 11 | 12 | public class TelemetryHolderTest { 13 | @Test(expected = IllegalStateException.class) 14 | public void callingGetterWithoutSettingsThrowsException() { 15 | TelemetryHolder.set(null); 16 | TelemetryHolder.get(); 17 | } 18 | 19 | @Test 20 | public void testGetterReturnsSameInstance() { 21 | Telemetry telemetry = mock(Telemetry.class); 22 | 23 | TelemetryHolder.set(telemetry); 24 | 25 | assertEquals(telemetry, TelemetryHolder.get()); 26 | } 27 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/TestUtils.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry; 6 | 7 | import java.util.concurrent.ExecutionException; 8 | 9 | public class TestUtils { 10 | /** 11 | * Wait for the internal executor service to execute all scheduled runnables. 12 | */ 13 | public static void waitForExecutor(Telemetry telemetry) throws ExecutionException, InterruptedException { 14 | telemetry.getExecutor().submit(new Runnable() { 15 | @Override 16 | public void run() { 17 | 18 | } 19 | }).get(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/config/TelemetryConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.config; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | import org.robolectric.RuntimeEnvironment; 11 | 12 | import java.io.File; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | @RunWith(RobolectricTestRunner.class) 17 | public class TelemetryConfigurationTest { 18 | @Test 19 | public void testBuilderSanity() { 20 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 21 | .setAppName("AwesomeApp") 22 | .setAppVersion("42.0") 23 | .setBuildId("20170330") 24 | .setCollectionEnabled(false) 25 | .setConnectTimeout(5000) 26 | .setDataDirectory(new File(RuntimeEnvironment.application.getCacheDir(), "telemetry-test")) 27 | .setInitialBackoffForUpload(120000) 28 | .setMinimumEventsForUpload(12) 29 | .setPreferencesImportantForTelemetry("cat", "dog", "duck") 30 | .setReadTimeout(7000) 31 | .setServerEndpoint("http://127.0.0.1") 32 | .setUserAgent("AwesomeTelemetry/29") 33 | .setUpdateChannel("release") 34 | .setUploadEnabled(false); 35 | 36 | assertEquals(RuntimeEnvironment.application, configuration.getContext()); 37 | assertEquals("AwesomeApp", configuration.getAppName()); 38 | assertEquals("42.0", configuration.getAppVersion()); 39 | assertEquals("20170330", configuration.getBuildId()); 40 | assertFalse(configuration.isCollectionEnabled()); 41 | assertEquals(5000, configuration.getConnectTimeout()); 42 | assertEquals(new File(RuntimeEnvironment.application.getCacheDir(), "telemetry-test").getAbsolutePath(), 43 | configuration.getDataDirectory().getAbsolutePath()); 44 | assertEquals(120000, configuration.getInitialBackoffForUpload()); 45 | assertEquals(12, configuration.getMinimumEventsForUpload()); 46 | assertEquals(3, configuration.getPreferencesImportantForTelemetry().size()); 47 | assertTrue(configuration.getPreferencesImportantForTelemetry().contains("cat")); 48 | assertTrue(configuration.getPreferencesImportantForTelemetry().contains("dog")); 49 | assertTrue(configuration.getPreferencesImportantForTelemetry().contains("duck")); 50 | assertEquals(7000, configuration.getReadTimeout()); 51 | assertEquals("http://127.0.0.1", configuration.getServerEndpoint()); 52 | assertEquals("AwesomeTelemetry/29", configuration.getUserAgent()); 53 | assertEquals("release", configuration.getUpdateChannel()); 54 | assertFalse(configuration.isUploadEnabled()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/event/TelemetryEventTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.event; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mozilla.telemetry.Telemetry; 12 | import org.mozilla.telemetry.TelemetryHolder; 13 | import org.mozilla.telemetry.TestUtils; 14 | import org.mozilla.telemetry.config.TelemetryConfiguration; 15 | import org.mozilla.telemetry.measurement.EventsMeasurement; 16 | import org.mozilla.telemetry.net.TelemetryClient; 17 | import org.mozilla.telemetry.ping.TelemetryEventPingBuilder; 18 | import org.mozilla.telemetry.ping.TelemetryMobileEventPingBuilder; 19 | import org.mozilla.telemetry.schedule.TelemetryScheduler; 20 | import org.mozilla.telemetry.storage.TelemetryStorage; 21 | import org.robolectric.RobolectricTestRunner; 22 | import org.robolectric.RuntimeEnvironment; 23 | 24 | import static org.junit.Assert.*; 25 | import static org.mockito.Mockito.doReturn; 26 | import static org.mockito.Mockito.mock; 27 | import static org.mockito.Mockito.verify; 28 | 29 | @RunWith(RobolectricTestRunner.class) 30 | public class TelemetryEventTest { 31 | @Test 32 | public void testEventCreation() { 33 | TelemetryEvent event = TelemetryEvent.create("action", "type_url", "search_bar"); 34 | 35 | final String rawEvent = event.toJSON(); 36 | 37 | assertNotNull(rawEvent); 38 | assertFalse(TextUtils.isEmpty(rawEvent)); 39 | assertFalse(rawEvent.contains("\n")); 40 | assertFalse(rawEvent.contains("\r")); 41 | assertTrue(rawEvent.startsWith("[")); 42 | assertTrue(rawEvent.endsWith("]")); 43 | } 44 | 45 | @Test 46 | public void testQueuing() throws Exception { 47 | final EventsMeasurement measurement = mock(EventsMeasurement.class); 48 | 49 | final TelemetryMobileEventPingBuilder builder = mock(TelemetryMobileEventPingBuilder.class); 50 | doReturn(TelemetryMobileEventPingBuilder.TYPE).when(builder).getType(); 51 | doReturn(measurement).when(builder).getEventsMeasurement(); 52 | 53 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 54 | final TelemetryStorage storage = mock(TelemetryStorage.class); 55 | final TelemetryClient client = mock(TelemetryClient.class); 56 | final TelemetryScheduler scheduler = mock(TelemetryScheduler.class); 57 | 58 | final Telemetry telemetry = new Telemetry(configuration, storage, client, scheduler) 59 | .addPingBuilder(builder); 60 | 61 | TelemetryHolder.set(telemetry); 62 | 63 | final TelemetryEvent event = TelemetryEvent.create("action", "type_url", "search_bar"); 64 | event.queue(); 65 | 66 | TestUtils.waitForExecutor(telemetry); 67 | 68 | verify(measurement).add(event); 69 | 70 | TelemetryHolder.set(null); 71 | } 72 | 73 | @Test 74 | @Deprecated // If you change this test, change the one above it too. 75 | public void testQueuingLegacyPingType() throws Exception { 76 | final EventsMeasurement measurement = mock(EventsMeasurement.class); 77 | 78 | final TelemetryEventPingBuilder builder = mock(TelemetryEventPingBuilder.class); 79 | doReturn(TelemetryEventPingBuilder.TYPE).when(builder).getType(); 80 | doReturn(measurement).when(builder).getEventsMeasurement(); 81 | 82 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 83 | final TelemetryStorage storage = mock(TelemetryStorage.class); 84 | final TelemetryClient client = mock(TelemetryClient.class); 85 | final TelemetryScheduler scheduler = mock(TelemetryScheduler.class); 86 | 87 | final Telemetry telemetry = new Telemetry(configuration, storage, client, scheduler) 88 | .addPingBuilder(builder); 89 | 90 | TelemetryHolder.set(telemetry); 91 | 92 | final TelemetryEvent event = TelemetryEvent.create("action", "type_url", "search_bar"); 93 | event.queue(); 94 | 95 | TestUtils.waitForExecutor(telemetry); 96 | 97 | verify(measurement).add(event); 98 | 99 | TelemetryHolder.set(null); 100 | } 101 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/ArchMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertNotNull; 13 | import static org.junit.Assert.assertTrue; 14 | import static org.mockito.Mockito.doReturn; 15 | import static org.mockito.Mockito.spy; 16 | 17 | @RunWith(RobolectricTestRunner.class) 18 | public class ArchMeasurementTest { 19 | @Test 20 | public void testValue() { 21 | final ArchMeasurement measurement = spy(new ArchMeasurement()); 22 | doReturn("armeabi-v7a").when(measurement).getArchitecture(); 23 | 24 | final Object value = measurement.flush(); 25 | assertNotNull(value); 26 | assertTrue(value instanceof String); 27 | 28 | final String architecture = (String) value; 29 | assertEquals("armeabi-v7a", architecture); 30 | } 31 | 32 | @Test 33 | public void testDefaultValue() { 34 | final ArchMeasurement measurement = spy(new ArchMeasurement()); 35 | assertEquals("unknown", measurement.flush()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/ClientIdMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mozilla.telemetry.config.TelemetryConfiguration; 12 | import org.robolectric.RobolectricTestRunner; 13 | import org.robolectric.RuntimeEnvironment; 14 | 15 | import java.util.UUID; 16 | 17 | import static org.junit.Assert.*; 18 | 19 | @RunWith(RobolectricTestRunner.class) 20 | public class ClientIdMeasurementTest { 21 | private static final String UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; 22 | 23 | @Test 24 | public void testClientIdIsAUUID() { 25 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 26 | 27 | final ClientIdMeasurement measurement = new ClientIdMeasurement(configuration); 28 | 29 | final Object value = measurement.flush(); 30 | assertNotNull(value); 31 | assertTrue(value instanceof String); 32 | 33 | final String clientId = (String) value; 34 | assertFalse(TextUtils.isEmpty(clientId)); 35 | assertTrue(clientId.matches(UUID_PATTERN)); 36 | 37 | final UUID uuid = UUID.fromString(clientId); // Should throw if invalid 38 | assertEquals(4, uuid.version()); 39 | } 40 | 41 | @Test 42 | public void testReturnsAlwaysTheSameClientId() { 43 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 44 | 45 | final ClientIdMeasurement measurement = new ClientIdMeasurement(configuration); 46 | final String clientId1 = (String) measurement.flush(); 47 | final String clientId2 = (String) measurement.flush(); 48 | 49 | assertEquals(clientId1, clientId2); 50 | } 51 | 52 | @Test 53 | public void testMultipleMeasurementsReturnSameClientId() { 54 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 55 | 56 | final String clientId1 = (String) new ClientIdMeasurement(configuration).flush(); 57 | final String clientId2 = (String) new ClientIdMeasurement(configuration).flush(); 58 | 59 | assertEquals(clientId1, clientId2); 60 | } 61 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/CreatedDateMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricTestRunner; 12 | 13 | import java.text.SimpleDateFormat; 14 | import java.util.Date; 15 | import java.util.Locale; 16 | 17 | import static org.junit.Assert.*; 18 | 19 | @RunWith(RobolectricTestRunner.class) 20 | public class CreatedDateMeasurementTest { 21 | @Test 22 | public void testFormat() throws Exception { 23 | final CreatedDateMeasurement measurement = new CreatedDateMeasurement(); 24 | 25 | final Object value = measurement.flush(); 26 | assertNotNull(value); 27 | assertTrue(value instanceof String); 28 | 29 | final String created = (String) value; 30 | assertFalse(TextUtils.isEmpty(created)); 31 | 32 | final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US); 33 | final Date date = format.parse(created); 34 | assertNotNull(date); 35 | } 36 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/CreatedTimestampMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | 11 | import static org.junit.Assert.assertNotNull; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | @RunWith(RobolectricTestRunner.class) 15 | public class CreatedTimestampMeasurementTest { 16 | @Test 17 | public void testFormat() throws Exception { 18 | final CreatedTimestampMeasurement measurement = new CreatedTimestampMeasurement(); 19 | 20 | final Object value = measurement.flush(); 21 | assertNotNull(value); 22 | assertTrue(value instanceof Long); 23 | 24 | final long created = (Long) value; 25 | assertTrue(created > 0); 26 | 27 | // This is the timestamp of the point in time this test was written. Assuming that time is 28 | // not going backwards our timestamp created by this class should be bigger. If it is not 29 | // then either your clock is wrong or you are experiencing a rare event. 30 | assertTrue(created > 1490727495664L); 31 | } 32 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/DefaultSearchMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.json.JSONObject; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.robolectric.RobolectricTestRunner; 13 | 14 | import org.mozilla.telemetry.measurement.DefaultSearchMeasurement.DefaultSearchEngineProvider; 15 | 16 | import static org.junit.Assert.*; 17 | import static org.mockito.Mockito.doReturn; 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.verify; 20 | 21 | @RunWith(RobolectricTestRunner.class) 22 | public class DefaultSearchMeasurementTest { 23 | @Test 24 | public void testDefault() { 25 | final DefaultSearchMeasurement measurement = new DefaultSearchMeasurement(); 26 | 27 | final Object value = measurement.flush(); 28 | assertNotNull(value); 29 | assertEquals(JSONObject.NULL, value); 30 | } 31 | 32 | @Test 33 | public void testProviderReturningNull() { 34 | final DefaultSearchEngineProvider provider = mock(DefaultSearchEngineProvider.class); 35 | doReturn(null).when(provider).getDefaultSearchEngineIdentifier(); 36 | 37 | final DefaultSearchMeasurement measurement = new DefaultSearchMeasurement(); 38 | measurement.setDefaultSearchEngineProvider(provider); 39 | 40 | final Object value = measurement.flush(); 41 | assertNotNull(value); 42 | assertEquals(JSONObject.NULL, value); 43 | 44 | verify(provider).getDefaultSearchEngineIdentifier(); 45 | } 46 | 47 | @Test 48 | public void testProviderReturningValue() { 49 | final DefaultSearchEngineProvider provider = mock(DefaultSearchEngineProvider.class); 50 | doReturn("TestValue0").when(provider).getDefaultSearchEngineIdentifier(); 51 | 52 | final DefaultSearchMeasurement measurement = new DefaultSearchMeasurement(); 53 | measurement.setDefaultSearchEngineProvider(provider); 54 | 55 | final Object value = measurement.flush(); 56 | assertNotNull(value); 57 | assertTrue(value instanceof String); 58 | 59 | final String defaultSearchEngine = (String) value; 60 | assertFalse(TextUtils.isEmpty(defaultSearchEngine)); 61 | 62 | verify(provider).getDefaultSearchEngineIdentifier(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/DeviceMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricTestRunner; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.doReturn; 15 | import static org.mockito.Mockito.spy; 16 | 17 | @RunWith(RobolectricTestRunner.class) 18 | public class DeviceMeasurementTest { 19 | @Test 20 | public void testFormat() { 21 | final DeviceMeasurement measurement = spy(new DeviceMeasurement()); 22 | doReturn("Samsung").when(measurement).getManufacturer(); 23 | doReturn("Galaxy S5").when(measurement).getModel(); 24 | 25 | final Object value = measurement.flush(); 26 | assertNotNull(value); 27 | assertTrue(value instanceof String); 28 | 29 | final String device = (String) value; 30 | assertFalse(TextUtils.isEmpty(device)); 31 | 32 | assertEquals("Samsung-Galaxy S5", device); 33 | } 34 | 35 | @Test 36 | public void testWithLongManufacturer() { 37 | final DeviceMeasurement measurement = spy(new DeviceMeasurement()); 38 | doReturn("SuperMegaDevice Corp.").when(measurement).getManufacturer(); 39 | doReturn("Flip One").when(measurement).getModel(); 40 | 41 | final Object value = measurement.flush(); 42 | assertNotNull(value); 43 | assertTrue(value instanceof String); 44 | 45 | final String device = (String) value; 46 | assertFalse(TextUtils.isEmpty(device)); 47 | 48 | assertEquals("SuperMegaDev-Flip One", device); 49 | } 50 | 51 | @Test 52 | public void testWithLongModel() { 53 | final DeviceMeasurement measurement = spy(new DeviceMeasurement()); 54 | doReturn("Banjo").when(measurement).getManufacturer(); 55 | doReturn("Little Cricket Daywalker").when(measurement).getModel(); 56 | 57 | final Object value = measurement.flush(); 58 | assertNotNull(value); 59 | assertTrue(value instanceof String); 60 | 61 | final String device = (String) value; 62 | assertFalse(TextUtils.isEmpty(device)); 63 | 64 | assertEquals("Banjo-Little Cricket Dayw", device); 65 | } 66 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/EventsMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONObject; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mozilla.telemetry.config.TelemetryConfiguration; 12 | import org.mozilla.telemetry.event.TelemetryEvent; 13 | import org.robolectric.RobolectricTestRunner; 14 | import org.robolectric.RuntimeEnvironment; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.UUID; 19 | 20 | import static org.junit.Assert.*; 21 | 22 | @RunWith(RobolectricTestRunner.class) 23 | public class EventsMeasurementTest { 24 | @Test 25 | public void testStoringEventsAndFlushing() throws Exception { 26 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 27 | final EventsMeasurement measurement = new EventsMeasurement(configuration); 28 | 29 | final String category = "category828"; 30 | final String method = "method910"; 31 | final String object = "object010"; 32 | 33 | final TelemetryEvent event = TelemetryEvent.create(category, method, object); 34 | measurement.add(event); 35 | 36 | final Object value = measurement.flush(); 37 | 38 | assertNotNull(value); 39 | assertTrue(value instanceof JSONArray); 40 | 41 | final JSONArray array = (JSONArray) value; 42 | 43 | assertEquals(1, array.length()); 44 | 45 | final JSONArray flushedEvent = array.getJSONArray(0); 46 | 47 | assertEquals(category, flushedEvent.getString(1)); 48 | assertEquals(method, flushedEvent.getString(2)); 49 | assertEquals(object, flushedEvent.getString(3)); 50 | } 51 | 52 | @Test 53 | public void testFlushingEmptyMeasurement() { 54 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 55 | final EventsMeasurement measurement = new EventsMeasurement(configuration); 56 | 57 | final Object value = measurement.flush(); 58 | 59 | assertNotNull(value); 60 | assertTrue(value instanceof JSONArray); 61 | 62 | JSONArray array = (JSONArray) value; 63 | 64 | assertEquals(0, array.length()); 65 | } 66 | 67 | @Test 68 | public void testLocalFileIsDeletedAfterFlushing() throws Exception { 69 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 70 | final EventsMeasurement measurement = new EventsMeasurement(configuration); 71 | 72 | assertFalse(measurement.getEventFile().exists()); 73 | 74 | final TelemetryEvent event = TelemetryEvent.create("category", "method", "object"); 75 | measurement.add(event); 76 | 77 | assertTrue(measurement.getEventFile().exists()); 78 | 79 | measurement.flush(); 80 | 81 | assertFalse(measurement.getEventFile().exists()); 82 | } 83 | 84 | @Test 85 | public void testToJSONCreatesArray() throws Exception { 86 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 87 | final EventsMeasurement measurement = new EventsMeasurement(configuration); 88 | 89 | measurement.add(TelemetryEvent.create("action", "type_url", "search_bar")); 90 | measurement.add(TelemetryEvent.create("action", "click", "erase_button")); 91 | 92 | final Object value = measurement.flush(); 93 | assertNotNull(value); 94 | assertTrue(value instanceof JSONArray); 95 | 96 | final JSONArray events = (JSONArray) value; 97 | assertEquals(2, events.length()); 98 | 99 | final JSONArray event1 = events.getJSONArray(0); 100 | assertEquals(4, event1.length()); 101 | assertTrue(event1.getLong(0) >= 0); 102 | assertEquals("action", event1.getString(1)); 103 | assertEquals("type_url", event1.getString(2)); 104 | assertEquals("search_bar", event1.getString(3)); 105 | 106 | final JSONArray event2 = events.getJSONArray(1); 107 | assertEquals(4, event2.length()); 108 | assertTrue(event2.getLong(0) >= 0); 109 | assertEquals("action", event2.getString(1)); 110 | assertEquals("click", event2.getString(2)); 111 | assertEquals("erase_button", event2.getString(3)); 112 | } 113 | 114 | @Test 115 | public void testToJSONWithComplexEvent() throws Exception { 116 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 117 | final EventsMeasurement measurement = new EventsMeasurement(configuration); 118 | 119 | measurement 120 | .add(TelemetryEvent.create("action", "change", "setting", "pref_search_engine") 121 | .extra("from", "Google") 122 | .extra("to", "Yahoo")); 123 | 124 | final Object value = measurement.flush(); 125 | assertNotNull(value); 126 | assertTrue(value instanceof JSONArray); 127 | 128 | final JSONArray events = (JSONArray) value; 129 | assertEquals(1, events.length()); 130 | 131 | final JSONArray event = events.getJSONArray(0); 132 | assertEquals(6, event.length()); 133 | assertTrue(event.getLong(0) >= 0); 134 | assertEquals("action", event.getString(1)); 135 | assertEquals("change", event.getString(2)); 136 | assertEquals("setting", event.getString(3)); 137 | assertEquals("pref_search_engine", event.getString(4)); 138 | 139 | final JSONObject eventExtras = event.getJSONObject(5); 140 | assertEquals(2, eventExtras.length()); 141 | assertTrue(eventExtras.has("from")); 142 | assertTrue(eventExtras.has("to")); 143 | assertEquals("Google", eventExtras.getString("from")); 144 | assertEquals("Yahoo", eventExtras.getString("to")); 145 | } 146 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/FirstRunProfileDateMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.robolectric.RobolectricTestRunner; 11 | import org.robolectric.RuntimeEnvironment; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.doReturn; 15 | import static org.mockito.Mockito.spy; 16 | 17 | @RunWith(RobolectricTestRunner.class) 18 | public class FirstRunProfileDateMeasurementTest { 19 | @Test 20 | public void testDefaultValue() { 21 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 22 | final FirstRunProfileDateMeasurement measurement = new FirstRunProfileDateMeasurement(configuration); 23 | 24 | final Object value = measurement.flush(); 25 | assertNotNull(value); 26 | assertTrue(value instanceof Long); 27 | 28 | final long profileCreationInDays = (long) value; // in days since UNIX epoch. 29 | assertTrue(profileCreationInDays > 17261); // This is the timestamp of the day before the test was written 30 | assertTrue(profileCreationInDays < System.currentTimeMillis()); // Days < Milliseconds 31 | } 32 | 33 | @Test 34 | public void testCalculation() { 35 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 36 | final FirstRunProfileDateMeasurement measurement = spy(new FirstRunProfileDateMeasurement(configuration)); 37 | 38 | // April 6, 2017 39 | doReturn(1491487779305L).when(measurement).now(); 40 | 41 | // Re-run because the first time it was called from the constructor and we couldn't mock now() before 42 | // the object is actually constructed. 43 | configuration.getSharedPreferences().edit().clear().apply(); 44 | measurement.ensureValueExists(); 45 | 46 | // January 1, 1970 -> April 6, 2017 -> 17262 days 47 | assertEquals(17262L, measurement.flush()); 48 | } 49 | 50 | @Test 51 | public void testFallback() { 52 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 53 | final FirstRunProfileDateMeasurement measurement = spy(new FirstRunProfileDateMeasurement(configuration)); 54 | 55 | // Remove stored first run date 56 | configuration.getSharedPreferences() 57 | .edit() 58 | .clear() 59 | .apply(); 60 | 61 | final Object value = measurement.flush(); 62 | assertNotNull(value); 63 | assertTrue(value instanceof Long); 64 | 65 | // We should still return a date that makes sense (like as if the profile was created right now) 66 | final long profileCreationInDays = (long) value; 67 | assertTrue(profileCreationInDays > 17261); 68 | assertTrue(profileCreationInDays < System.currentTimeMillis()); 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/LocaleMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricTestRunner; 12 | 13 | import java.util.Locale; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertFalse; 17 | import static org.junit.Assert.assertNotNull; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | @RunWith(RobolectricTestRunner.class) 21 | public class LocaleMeasurementTest { 22 | @Test 23 | public void testDefaultValue() { 24 | final LocaleMeasurement measurement = new LocaleMeasurement(); 25 | 26 | final Object value = measurement.flush(); 27 | assertNotNull(value); 28 | assertTrue(value instanceof String); 29 | 30 | final String locale = (String) value; 31 | assertFalse(TextUtils.isEmpty(locale)); 32 | assertEquals("en-US", locale); 33 | } 34 | 35 | @Test 36 | public void testWithChangedLocale() { 37 | final Locale defaultLocale = Locale.getDefault(); 38 | 39 | try { 40 | Locale.setDefault(new Locale("fy", "NL")); 41 | 42 | final LocaleMeasurement measurement = new LocaleMeasurement(); 43 | 44 | final Object value = measurement.flush(); 45 | assertNotNull(value); 46 | assertTrue(value instanceof String); 47 | 48 | final String locale = (String) value; 49 | assertFalse(TextUtils.isEmpty(locale)); 50 | assertEquals("fy-NL", locale); 51 | } finally { 52 | Locale.setDefault(defaultLocale); 53 | } 54 | } 55 | 56 | /** 57 | * iw_IL -> he-IL 58 | */ 59 | @Test 60 | public void testReturnsFixedLanguageTag() { 61 | final Locale defaultLocale = Locale.getDefault(); 62 | 63 | try { 64 | Locale.setDefault(new Locale("iw", "IL")); 65 | 66 | final LocaleMeasurement measurement = new LocaleMeasurement(); 67 | 68 | final Object value = measurement.flush(); 69 | assertNotNull(value); 70 | assertTrue(value instanceof String); 71 | 72 | final String locale = (String) value; 73 | assertFalse(TextUtils.isEmpty(locale)); 74 | assertEquals("he-IL", locale); 75 | } finally { 76 | Locale.setDefault(defaultLocale); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/OperatingSystemMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricTestRunner; 12 | 13 | import static org.junit.Assert.*; 14 | 15 | @RunWith(RobolectricTestRunner.class) 16 | public class OperatingSystemMeasurementTest { 17 | @Test 18 | public void testValue() { 19 | final OperatingSystemMeasurement measurement = new OperatingSystemMeasurement(); 20 | 21 | final Object value = measurement.flush(); 22 | assertNotNull(value); 23 | assertTrue(value instanceof String); 24 | 25 | final String os = (String) value; 26 | assertFalse(TextUtils.isEmpty(os)); 27 | assertEquals("Android", os); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/OperatingSystemVersionMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.text.TextUtils; 8 | 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.robolectric.RobolectricTestRunner; 12 | import org.robolectric.annotation.Config; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | @RunWith(RobolectricTestRunner.class) 17 | public class OperatingSystemVersionMeasurementTest { 18 | @Test 19 | public void testValue() { 20 | final OperatingSystemVersionMeasurement measurement = new OperatingSystemVersionMeasurement(); 21 | 22 | final Object value = measurement.flush(); 23 | assertNotNull(value); 24 | assertTrue(value instanceof String); 25 | 26 | final String version = (String) value; 27 | assertFalse(TextUtils.isEmpty(version)); 28 | assertEquals("16", version); // 16 is the default API level of this Robolectric version 29 | } 30 | 31 | @Test 32 | @Config(sdk = 23) 33 | public void testAPI23() { 34 | final OperatingSystemVersionMeasurement measurement = new OperatingSystemVersionMeasurement(); 35 | 36 | final Object value = measurement.flush(); 37 | assertNotNull(value); 38 | assertTrue(value instanceof String); 39 | 40 | final String version = (String) value; 41 | assertFalse(TextUtils.isEmpty(version)); 42 | assertEquals("23", version); 43 | } 44 | 45 | @Test 46 | @Config(sdk = 21) 47 | public void testAPI21() { 48 | final OperatingSystemVersionMeasurement measurement = new OperatingSystemVersionMeasurement(); 49 | 50 | final Object value = measurement.flush(); 51 | assertNotNull(value); 52 | assertTrue(value instanceof String); 53 | 54 | final String version = (String) value; 55 | assertFalse(TextUtils.isEmpty(version)); 56 | assertEquals("21", version); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/ProcessStartTimestampMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.mozilla.telemetry.config.TelemetryConfiguration; 9 | 10 | import static org.junit.Assert.*; 11 | import static org.mockito.Mockito.*; 12 | 13 | public class ProcessStartTimestampMeasurementTest { 14 | @Test 15 | public void testTimestampComesFromTelemetryConfiguration() throws Exception { 16 | final long expectedTimestamp = 1357389201; 17 | final TelemetryConfiguration mockTelemetryConfiguration = mock(TelemetryConfiguration.class); 18 | when(mockTelemetryConfiguration.getClassLoadTimestampMillis()).thenReturn(expectedTimestamp); 19 | assertEquals(expectedTimestamp, new ProcessStartTimestampMeasurement(mockTelemetryConfiguration).flush()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/SearchesMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.json.JSONObject; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mozilla.telemetry.config.TelemetryConfiguration; 11 | import org.robolectric.RobolectricTestRunner; 12 | import org.robolectric.RuntimeEnvironment; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | @RunWith(RobolectricTestRunner.class) 17 | public class SearchesMeasurementTest { 18 | @Test 19 | public void testDefault() { 20 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 21 | final SearchesMeasurement measurement = new SearchesMeasurement(configuration); 22 | 23 | final Object value = measurement.flush(); 24 | assertNotNull(value); 25 | assertTrue(value instanceof JSONObject); 26 | 27 | final JSONObject searches = (JSONObject) value; 28 | assertTrue(searches.length() == 0); 29 | } 30 | 31 | @Test 32 | public void testRecording() throws Exception { 33 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 34 | final SearchesMeasurement measurement = new SearchesMeasurement(configuration); 35 | 36 | { // Record a bunch of things 37 | measurement.recordSearch(SearchesMeasurement.LOCATION_ACTIONBAR, "google"); 38 | measurement.recordSearch(SearchesMeasurement.LOCATION_ACTIONBAR, "yahoo"); 39 | measurement.recordSearch(SearchesMeasurement.LOCATION_ACTIONBAR, "wikipedia-es"); 40 | measurement.recordSearch(SearchesMeasurement.LOCATION_ACTIONBAR, "wikipedia-es"); 41 | 42 | measurement.recordSearch(SearchesMeasurement.LOCATION_SUGGESTION, "google"); 43 | measurement.recordSearch(SearchesMeasurement.LOCATION_SUGGESTION, "yahoo"); 44 | 45 | measurement.recordSearch(SearchesMeasurement.LOCATION_ACTIONBAR, "wikipedia-es"); 46 | measurement.recordSearch(SearchesMeasurement.LOCATION_ACTIONBAR, "yahoo-mx"); 47 | measurement.recordSearch(SearchesMeasurement.LOCATION_ACTIONBAR, "yahoo"); 48 | 49 | measurement.recordSearch(SearchesMeasurement.LOCATION_SUGGESTION, "google"); 50 | measurement.recordSearch(SearchesMeasurement.LOCATION_SUGGESTION, "google"); 51 | 52 | final JSONObject searches = (JSONObject) measurement.flush(); 53 | assertEquals(6, searches.length()); 54 | 55 | assertTrue(searches.has("actionbar.google")); 56 | assertTrue(searches.has("actionbar.wikipedia-es")); 57 | assertTrue(searches.has("actionbar.yahoo-mx")); 58 | assertTrue(searches.has("actionbar.yahoo")); 59 | 60 | assertTrue(searches.has("suggestion.yahoo")); 61 | assertTrue(searches.has("suggestion.google")); 62 | 63 | assertEquals(1, searches.getInt("actionbar.google")); 64 | assertEquals(2, searches.getInt("actionbar.yahoo")); 65 | assertEquals(3, searches.getInt("actionbar.wikipedia-es")); 66 | assertEquals(1, searches.getInt("actionbar.yahoo-mx")); 67 | 68 | assertEquals(1, searches.getInt("suggestion.yahoo")); 69 | assertEquals(3, searches.getInt("suggestion.google")); 70 | } 71 | { // After flushing all values should be reset 72 | final JSONObject searches = (JSONObject) measurement.flush(); 73 | assertEquals(0, searches.length()); 74 | } 75 | { // New measurements should not pick up old values 76 | measurement.recordSearch(SearchesMeasurement.LOCATION_SUGGESTION, "google"); 77 | measurement.recordSearch(SearchesMeasurement.LOCATION_SUGGESTION, "yahoo"); 78 | 79 | final JSONObject searches = (JSONObject) measurement.flush(); 80 | assertEquals(2, searches.length()); 81 | 82 | assertTrue(searches.has("suggestion.yahoo")); 83 | assertTrue(searches.has("suggestion.google")); 84 | 85 | assertEquals(1, searches.getInt("suggestion.yahoo")); 86 | assertEquals(1, searches.getInt("suggestion.google")); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/SequenceMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.mozilla.telemetry.ping.TelemetryPingBuilder; 11 | import org.robolectric.RobolectricTestRunner; 12 | import org.robolectric.RuntimeEnvironment; 13 | 14 | import static org.junit.Assert.*; 15 | import static org.mockito.Mockito.doReturn; 16 | import static org.mockito.Mockito.mock; 17 | 18 | @RunWith(RobolectricTestRunner.class) 19 | public class SequenceMeasurementTest { 20 | @Test 21 | public void testSequenceStartsAtOne() { 22 | final TelemetryPingBuilder builder = mock(TelemetryPingBuilder.class); 23 | doReturn("test").when(builder).getType(); 24 | 25 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 26 | 27 | final SequenceMeasurement measurement = new SequenceMeasurement(configuration, builder); 28 | 29 | final Object value = measurement.flush(); 30 | assertNotNull(value); 31 | assertTrue(value instanceof Long); 32 | 33 | long sequence = (Long) value; 34 | assertEquals(1, sequence); 35 | } 36 | 37 | @Test 38 | public void testSequenceNumberIsIncremented() { 39 | final TelemetryPingBuilder builder = mock(TelemetryPingBuilder.class); 40 | doReturn("test").when(builder).getType(); 41 | 42 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 43 | 44 | final SequenceMeasurement measurement = new SequenceMeasurement(configuration, builder); 45 | 46 | // (1) 47 | 48 | final Object first = measurement.flush(); 49 | assertNotNull(first); 50 | assertTrue(first instanceof Long); 51 | 52 | long sequence1 = (Long) first; 53 | assertEquals(1, sequence1); 54 | 55 | // (2) 56 | 57 | final Object second = measurement.flush(); 58 | assertNotNull(second); 59 | assertTrue(second instanceof Long); 60 | 61 | long sequence2 = (Long) second; 62 | assertEquals(2, sequence2); 63 | 64 | // (3) 65 | 66 | final Object third = measurement.flush(); 67 | assertNotNull(third); 68 | assertTrue(third instanceof Long); 69 | 70 | long sequence3 = (Long) third; 71 | assertEquals(3, sequence3); 72 | } 73 | 74 | @Test 75 | public void testSamePingTypeSharesSequence() { 76 | final TelemetryPingBuilder builder = mock(TelemetryPingBuilder.class); 77 | doReturn("test").when(builder).getType(); 78 | 79 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 80 | 81 | final SequenceMeasurement measurement1 = new SequenceMeasurement(configuration, builder); 82 | final SequenceMeasurement measurement2 = new SequenceMeasurement(configuration, builder); 83 | 84 | // Measurement #1 starts at 1 85 | 86 | final Object first = measurement1.flush(); 87 | assertNotNull(first); 88 | assertTrue(first instanceof Long); 89 | 90 | long sequence1 = (Long) first; 91 | assertEquals(1, sequence1); 92 | 93 | // Flush measurement #2 three times 94 | 95 | measurement2.flush(); 96 | measurement2.flush(); 97 | measurement2.flush(); 98 | 99 | // Measurement #1 is now at 5 100 | 101 | final Object second = measurement1.flush(); 102 | assertNotNull(second); 103 | assertTrue(second instanceof Long); 104 | 105 | long sequence2 = (Long) second; 106 | assertEquals(5, sequence2); 107 | 108 | // And Measurement #2 continues with 6 109 | 110 | final Object third = measurement2.flush(); 111 | assertNotNull(third); 112 | assertTrue(third instanceof Long); 113 | 114 | long sequence3 = (Long) third; 115 | assertEquals(6, sequence3); 116 | } 117 | 118 | @Test 119 | public void testDifferentPingsHaveTheirOwnSequence() { 120 | final TelemetryPingBuilder testBuilder1 = mock(TelemetryPingBuilder.class); 121 | doReturn("test1").when(testBuilder1).getType(); 122 | 123 | final TelemetryPingBuilder testBuilder2 = mock(TelemetryPingBuilder.class); 124 | doReturn("test2").when(testBuilder2).getType(); 125 | 126 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 127 | 128 | final SequenceMeasurement measurement1 = new SequenceMeasurement(configuration, testBuilder1); 129 | final SequenceMeasurement measurement2 = new SequenceMeasurement(configuration, testBuilder2); 130 | 131 | // Measurement #1 starts at 1 132 | 133 | final Object first = measurement1.flush(); 134 | assertNotNull(first); 135 | assertTrue(first instanceof Long); 136 | 137 | long sequence1 = (Long) first; 138 | assertEquals(1, sequence1); 139 | 140 | // Flush measurement #2 three times 141 | 142 | measurement2.flush(); 143 | measurement2.flush(); 144 | measurement2.flush(); 145 | 146 | // Measurement #1 continues on its own is now at 2 147 | 148 | final Object second = measurement1.flush(); 149 | assertNotNull(second); 150 | assertTrue(second instanceof Long); 151 | 152 | long sequence2 = (Long) second; 153 | assertEquals(2, sequence2); 154 | 155 | // Measurement #2 is independ and is at 4 now 156 | 157 | final Object third = measurement2.flush(); 158 | assertNotNull(third); 159 | assertTrue(third instanceof Long); 160 | 161 | long sequence3 = (Long) third; 162 | assertEquals(4, sequence3); 163 | } 164 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/SessionCountMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.robolectric.RobolectricTestRunner; 11 | import org.robolectric.RuntimeEnvironment; 12 | 13 | import static org.junit.Assert.*; 14 | 15 | @RunWith(RobolectricTestRunner.class) 16 | public class SessionCountMeasurementTest { 17 | @Test 18 | public void testByDefaultSessionCountStaysAtZero() { 19 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 20 | final SessionCountMeasurement measurement = new SessionCountMeasurement(configuration); 21 | 22 | { 23 | final Object value = measurement.flush(); 24 | assertNotNull(value); 25 | assertTrue(value instanceof Long); 26 | 27 | final long count = (Long) value; 28 | assertEquals(0, count); 29 | } 30 | { 31 | final Object value = measurement.flush(); 32 | assertNotNull(value); 33 | assertTrue(value instanceof Long); 34 | 35 | final long count = (Long) value; 36 | assertEquals(0, count); 37 | } 38 | } 39 | 40 | @Test 41 | public void testCountingSession() { 42 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 43 | final SessionCountMeasurement measurement = new SessionCountMeasurement(configuration); 44 | 45 | assertEquals(0, (long) measurement.flush()); 46 | 47 | measurement.countSession(); 48 | 49 | assertEquals(1, (long) measurement.flush()); 50 | 51 | // Counter is reset after every flush 52 | assertEquals(0, (long) measurement.flush()); 53 | 54 | measurement.countSession(); 55 | measurement.countSession(); 56 | measurement.countSession(); 57 | 58 | assertEquals(3, (long) measurement.flush()); 59 | assertEquals(0, (long) measurement.flush()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/SessionDurationMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.robolectric.RobolectricTestRunner; 11 | import org.robolectric.RuntimeEnvironment; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertNotNull; 15 | import static org.junit.Assert.assertTrue; 16 | import static org.mockito.Mockito.doReturn; 17 | import static org.mockito.Mockito.spy; 18 | 19 | @RunWith(RobolectricTestRunner.class) 20 | public class SessionDurationMeasurementTest { 21 | @Test 22 | public void testDefaultIsZero() { 23 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 24 | final SessionDurationMeasurement measurement = new SessionDurationMeasurement(configuration); 25 | 26 | final Object value = measurement.flush(); 27 | assertNotNull(value); 28 | assertTrue(value instanceof Long); 29 | 30 | final long duration = (Long) value; 31 | assertEquals(0, duration); 32 | 33 | assertEquals(0, (long) measurement.flush()); 34 | } 35 | 36 | @Test 37 | public void testRecordingSessionStartAndEnd() { 38 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 39 | 40 | final SessionDurationMeasurement measurement = spy(new SessionDurationMeasurement(configuration)); 41 | doReturn(1337000000L).doReturn(5337000000L).when(measurement).getSystemTimeNano(); 42 | 43 | assertEquals(0, (long) measurement.flush()); 44 | 45 | measurement.recordSessionStart(); 46 | 47 | // No duration is reported as long as the session is still active 48 | assertEquals(0, (long) measurement.flush()); 49 | 50 | measurement.recordSessionEnd(); 51 | 52 | // 4 seconds session 53 | assertEquals(4, (long) measurement.flush()); 54 | 55 | // No new session was started so we should report 0 again 56 | assertEquals(0, (long) measurement.flush()); 57 | } 58 | 59 | @Test 60 | public void testMultipleSessionsAreSummedUp() { 61 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 62 | 63 | final SessionDurationMeasurement measurement = spy(new SessionDurationMeasurement(configuration)); 64 | doReturn(1337000000L) // Session start 65 | .doReturn(5337000000L) // Session end, duration: 4 seconds 66 | .doReturn(8337000000L) // Session start, 3 seconds after last session 67 | .doReturn(10337000000L) // Session end, duration: 2 seconds 68 | .when(measurement).getSystemTimeNano(); 69 | 70 | assertEquals(0, (long) measurement.flush()); 71 | 72 | measurement.recordSessionStart(); 73 | measurement.recordSessionEnd(); // 4 seconds 74 | 75 | measurement.recordSessionStart(); 76 | measurement.recordSessionEnd(); // 2 seconds 77 | 78 | // Total 6 seconds session 79 | assertEquals(6, (long) measurement.flush()); 80 | 81 | // No new session was started so we should report 0 again 82 | assertEquals(0, (long) measurement.flush()); 83 | } 84 | 85 | @Test(expected = IllegalStateException.class) 86 | public void testStartingAlreadyStartedSessionThrowsException() { 87 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 88 | final SessionDurationMeasurement measurement = new SessionDurationMeasurement(configuration); 89 | 90 | measurement.recordSessionStart(); 91 | measurement.recordSessionStart(); 92 | } 93 | 94 | @Test(expected = IllegalStateException.class) 95 | public void testStoppingAlreadyStoppedSessionThrowsException() { 96 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 97 | final SessionDurationMeasurement measurement = new SessionDurationMeasurement(configuration); 98 | 99 | measurement.recordSessionStart(); 100 | 101 | measurement.recordSessionEnd(); 102 | measurement.recordSessionEnd(); 103 | } 104 | 105 | @Test(expected = IllegalStateException.class) 106 | public void testStoppingNeverStartedSessionThrowsException() { 107 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 108 | final SessionDurationMeasurement measurement = new SessionDurationMeasurement(configuration); 109 | 110 | measurement.recordSessionEnd(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/SettingsMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import android.content.SharedPreferences; 8 | import android.preference.PreferenceManager; 9 | import android.util.ArraySet; 10 | 11 | import org.json.JSONObject; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.mozilla.telemetry.config.TelemetryConfiguration; 15 | import org.robolectric.RobolectricTestRunner; 16 | import org.robolectric.RuntimeEnvironment; 17 | 18 | import java.util.Arrays; 19 | import java.util.HashSet; 20 | import java.util.UUID; 21 | 22 | import static org.junit.Assert.*; 23 | import static org.mockito.ArgumentMatchers.anyString; 24 | import static org.mockito.Mockito.doReturn; 25 | import static org.mockito.Mockito.mock; 26 | import static org.mockito.Mockito.only; 27 | import static org.mockito.Mockito.times; 28 | import static org.mockito.Mockito.verify; 29 | 30 | @RunWith(RobolectricTestRunner.class) 31 | public class SettingsMeasurementTest { 32 | @Test 33 | public void testDefaultBehavior() { 34 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 35 | final SettingsMeasurement measurement = new SettingsMeasurement(configuration); 36 | 37 | final Object value = measurement.flush(); 38 | 39 | assertNotNull(value); 40 | assertTrue(value instanceof JSONObject); 41 | 42 | final JSONObject object = (JSONObject) value; 43 | 44 | assertEquals(0, object.length()); 45 | } 46 | 47 | @Test 48 | public void testWithConfiguredPreferences() throws Exception { 49 | final String keyPreferenceExisting = "pref_existing_" + UUID.randomUUID(); 50 | final String keyPreferenceNeverWritten = "pref_never_written_" + UUID.randomUUID(); 51 | 52 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 53 | configuration.setPreferencesImportantForTelemetry( 54 | keyPreferenceExisting, 55 | keyPreferenceNeverWritten 56 | ); 57 | 58 | final SettingsMeasurement measurement = new SettingsMeasurement(configuration); 59 | 60 | final String preferenceValue = "value_" + UUID.randomUUID(); 61 | 62 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application); 63 | preferences.edit() 64 | .putString(keyPreferenceExisting, preferenceValue) 65 | .apply(); 66 | 67 | final Object value = measurement.flush(); 68 | 69 | assertNotNull(value); 70 | assertTrue(value instanceof JSONObject); 71 | 72 | final JSONObject object = (JSONObject) value; 73 | 74 | assertEquals(2, object.length()); 75 | 76 | assertEquals(preferenceValue, object.get(keyPreferenceExisting)); 77 | assertEquals(JSONObject.NULL, object.get(keyPreferenceNeverWritten)); 78 | } 79 | 80 | @Test 81 | public void testDifferentTypes() throws Exception { 82 | final String keyBooleanPreference = "booleanPref"; 83 | final String keyLongPreference = "longPref"; 84 | final String keyStringPreference = "stringPref"; 85 | final String keyIntPreference = "intPref"; 86 | final String keyFloatPreference = "floatPref"; 87 | final String keyStringSetPreference = "stringSetPref"; 88 | 89 | PreferenceManager.getDefaultSharedPreferences(RuntimeEnvironment.application) 90 | .edit() 91 | .putBoolean(keyBooleanPreference, true) 92 | .putLong(keyLongPreference, 1337) 93 | .putString(keyStringPreference, "Hello Telemetry") 94 | .putInt(keyIntPreference, 42) 95 | .putFloat(keyFloatPreference, 23.45f) 96 | .putStringSet(keyStringSetPreference, new HashSet<>(Arrays.asList("chicken", "waffles"))) 97 | .apply(); 98 | 99 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 100 | .setPreferencesImportantForTelemetry( 101 | keyBooleanPreference, 102 | keyLongPreference, 103 | keyStringPreference, 104 | keyIntPreference, 105 | keyFloatPreference, 106 | keyStringSetPreference 107 | ); 108 | 109 | final SettingsMeasurement measurement = new SettingsMeasurement(configuration); 110 | 111 | final Object value = measurement.flush(); 112 | 113 | assertNotNull(value); 114 | assertTrue(value instanceof JSONObject); 115 | 116 | final JSONObject object = (JSONObject) value; 117 | 118 | assertEquals(6, object.length()); 119 | 120 | assertTrue(object.get(keyBooleanPreference) instanceof String); 121 | assertEquals("true", object.get(keyBooleanPreference)); 122 | 123 | assertTrue(object.get(keyLongPreference) instanceof String); 124 | assertEquals("1337", object.get(keyLongPreference)); 125 | 126 | assertTrue(object.get(keyStringPreference) instanceof String); 127 | assertEquals("Hello Telemetry", object.get(keyStringPreference)); 128 | 129 | assertTrue(object.get(keyIntPreference) instanceof String); 130 | assertEquals("42", object.get(keyIntPreference)); 131 | 132 | assertTrue(object.get(keyFloatPreference) instanceof String); 133 | assertEquals("23.45", object.get(keyFloatPreference)); 134 | 135 | assertTrue(object.get(keyStringSetPreference) instanceof String); 136 | assertEquals("[chicken, waffles]", object.get(keyStringSetPreference)); 137 | } 138 | 139 | @Test 140 | public void testSettingsProviderIsUsed() { 141 | final SettingsMeasurement.SettingsProvider settingsProvider = mock(SettingsMeasurement.SettingsProvider.class); 142 | doReturn(true).when(settingsProvider).containsKey(anyString()); 143 | doReturn("value").when(settingsProvider).getValue(anyString()); 144 | 145 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 146 | .setSettingsProvider(settingsProvider) 147 | .setPreferencesImportantForTelemetry("a", "b", "c"); 148 | 149 | final SettingsMeasurement measurement = new SettingsMeasurement(configuration); 150 | 151 | measurement.flush(); 152 | 153 | verify(settingsProvider).update(configuration); 154 | 155 | verify(settingsProvider).containsKey("a"); 156 | verify(settingsProvider).getValue("a"); 157 | 158 | verify(settingsProvider).containsKey("b"); 159 | verify(settingsProvider).getValue("b"); 160 | 161 | verify(settingsProvider).containsKey("c"); 162 | verify(settingsProvider).getValue("c"); 163 | 164 | verify(settingsProvider).release(); 165 | } 166 | 167 | @Test 168 | public void testCustomSettingsProvider() throws Exception { 169 | // A custom settings provider that will return for an integer key x the value x * 2. For all 170 | // not even integer keys it will pretent that they are not in the settings. 171 | final SettingsMeasurement.SettingsProvider settingsProvider = new SettingsMeasurement.SettingsProvider() { 172 | @Override 173 | public void update(TelemetryConfiguration configuration) {} 174 | 175 | @Override 176 | public boolean containsKey(String key) { 177 | return Integer.parseInt(key) % 2 == 0; 178 | } 179 | 180 | @Override 181 | public Object getValue(String key) { 182 | return Integer.parseInt(key) * 2; 183 | } 184 | 185 | @Override 186 | public void release() {} 187 | }; 188 | 189 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 190 | .setSettingsProvider(settingsProvider) 191 | .setPreferencesImportantForTelemetry("1", "2", "3", "4"); 192 | 193 | final SettingsMeasurement measurement = new SettingsMeasurement(configuration); 194 | 195 | measurement.flush(); 196 | 197 | final Object value = measurement.flush(); 198 | 199 | assertNotNull(value); 200 | assertTrue(value instanceof JSONObject); 201 | 202 | final JSONObject object = (JSONObject) value; 203 | 204 | assertEquals(4, object.length()); 205 | 206 | assertTrue(object.has("1")); 207 | assertTrue(object.isNull("1")); 208 | 209 | assertTrue(object.has("2")); 210 | assertTrue(object.get("2") instanceof String); 211 | assertEquals("4", object.get("2")); 212 | 213 | assertTrue(object.has("3")); 214 | assertTrue(object.isNull("3")); 215 | 216 | assertTrue(object.has("4")); 217 | assertTrue(object.get("4") instanceof String); 218 | assertEquals("8", object.get("4")); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/StaticMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | @RunWith(RobolectricTestRunner.class) 14 | public class StaticMeasurementTest { 15 | @Test 16 | public void testWithMultipleValues() { 17 | assertEquals(null, new StaticMeasurement("key", null).flush()); 18 | 19 | assertEquals(true, new StaticMeasurement("key", true).flush()); 20 | assertEquals(false, new StaticMeasurement("key", false).flush()); 21 | 22 | assertEquals(0, new StaticMeasurement("key", 0).flush()); 23 | assertEquals(1, new StaticMeasurement("key", 1).flush()); 24 | assertEquals(-1, new StaticMeasurement("key", -1).flush()); 25 | assertEquals(2093492, new StaticMeasurement("key", 2093492).flush()); 26 | assertEquals(-99822, new StaticMeasurement("key", -99822).flush()); 27 | assertEquals(Integer.MAX_VALUE, new StaticMeasurement("key", Integer.MAX_VALUE).flush()); 28 | assertEquals(Integer.MIN_VALUE, new StaticMeasurement("key", Integer.MIN_VALUE).flush()); 29 | 30 | assertEquals(0L, new StaticMeasurement("key", 0L).flush()); 31 | assertEquals(1L, new StaticMeasurement("key", 1L).flush()); 32 | assertEquals(-1L, new StaticMeasurement("key", -1L).flush()); 33 | assertEquals(2093492L, new StaticMeasurement("key", 2093492L).flush()); 34 | assertEquals(-99822L, new StaticMeasurement("key", -99822L).flush()); 35 | assertEquals(-9872987239847L, new StaticMeasurement("key", -9872987239847L).flush()); 36 | assertEquals(1230948239847239487L, new StaticMeasurement("key", 1230948239847239487L).flush()); 37 | assertEquals(Long.MAX_VALUE, new StaticMeasurement("key", Long.MAX_VALUE).flush()); 38 | assertEquals(Long.MIN_VALUE, new StaticMeasurement("key", Long.MIN_VALUE).flush()); 39 | 40 | assertEquals("", new StaticMeasurement("key", "").flush()); 41 | assertEquals("Hello", new StaticMeasurement("key", "Hello").flush()); 42 | assertEquals("löäüß!?", new StaticMeasurement("key", "löäüß!?").flush()); 43 | 44 | assertArrayEquals(new String[] { "duck", "dog", "cat"}, 45 | (String[]) new StaticMeasurement("key", new String[] { "duck", "dog", "cat"}).flush()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/TimezoneOffsetMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | 9 | import java.util.Calendar; 10 | import java.util.TimeZone; 11 | 12 | import static org.junit.Assert.*; 13 | import static org.mockito.Mockito.doReturn; 14 | import static org.mockito.Mockito.spy; 15 | 16 | public class TimezoneOffsetMeasurementTest { 17 | @Test 18 | public void testValueForUTC() { 19 | final TimezoneOffsetMeasurement measurement = spy(new TimezoneOffsetMeasurement()); 20 | doReturn(createCalendarInTimeZone("UTC")).when(measurement).now(); 21 | 22 | final Object value = measurement.flush(); 23 | assertNotNull(value); 24 | assertTrue(value instanceof Integer); 25 | 26 | int offset = (Integer) value; 27 | assertEquals(0, offset); // There should be no offset if we are in UTC already 28 | } 29 | 30 | @Test 31 | public void testDifferentTimezones() { 32 | final TimezoneOffsetMeasurement measurement = spy(new TimezoneOffsetMeasurement()); 33 | doReturn(createCalendarInTimeZone("GMT-8:00")) 34 | .doReturn(createCalendarInTimeZone("MST")) 35 | .doReturn(createCalendarInTimeZone("Australia/Darwin")) 36 | .doReturn(createCalendarInTimeZone("CEST")) 37 | .doReturn(createCalendarInTimeZone("GMT+2:00")) 38 | .when(measurement).now(); 39 | 40 | assertEquals(-480, (int) measurement.flush()); 41 | assertEquals(-420, (int) measurement.flush()); 42 | assertEquals(570, (int) measurement.flush()); 43 | assertEquals(0, (int) measurement.flush()); 44 | assertEquals(120, (int) measurement.flush()); 45 | } 46 | 47 | private Calendar createCalendarInTimeZone(String timzone) { 48 | return Calendar.getInstance(TimeZone.getTimeZone(timzone)); 49 | } 50 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/measurement/VersionMeasurementTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.measurement; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | @RunWith(RobolectricTestRunner.class) 14 | public class VersionMeasurementTest { 15 | @Test 16 | public void testValue() { 17 | final VersionMeasurement measurement = new VersionMeasurement(1); 18 | 19 | final Object value = measurement.flush(); 20 | assertNotNull(value); 21 | assertTrue(value instanceof Integer); 22 | 23 | final int version = (int) value; 24 | assertEquals(1, version); 25 | } 26 | 27 | @Test 28 | public void testDifferentValues() { 29 | assertEquals(2, (int) new VersionMeasurement(2).flush()); 30 | assertEquals(5000, (int) new VersionMeasurement(5000).flush()); 31 | assertEquals(Integer.MAX_VALUE, (int) new VersionMeasurement(Integer.MAX_VALUE).flush()); 32 | } 33 | 34 | @Test(expected = IllegalArgumentException.class) 35 | public void testZeroShouldThrowException() { 36 | new VersionMeasurement(0); 37 | } 38 | 39 | @Test(expected = IllegalArgumentException.class) 40 | public void testNegativeVersionShouldThrowException() { 41 | new VersionMeasurement(-1); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/net/DebugLogClientTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.net; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.robolectric.RobolectricTestRunner; 11 | import org.robolectric.RuntimeEnvironment; 12 | import org.robolectric.shadows.ShadowLog; 13 | 14 | import java.io.PrintStream; 15 | 16 | import static org.mockito.ArgumentMatchers.anyString; 17 | import static org.mockito.Mockito.mock; 18 | import static org.mockito.Mockito.never; 19 | import static org.mockito.Mockito.times; 20 | import static org.mockito.Mockito.verify; 21 | 22 | @RunWith(RobolectricTestRunner.class) 23 | public class DebugLogClientTest { 24 | @Test 25 | public void testDebugLogClientWritesSomethingToLog() { 26 | final PrintStream stream = mock(PrintStream.class); 27 | ShadowLog.stream = stream; 28 | 29 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 30 | 31 | final DebugLogClient client = new DebugLogClient("TEST"); 32 | 33 | // Nothing has written to the log so far 34 | verify(stream, never()).println(anyString()); 35 | 36 | client.uploadPing(configuration, "path", "{'hello': 'world'}"); 37 | 38 | // Our client has logged two lines, a separator and the JSON for the ping 39 | verify(stream, times(2)).println(anyString()); 40 | } 41 | 42 | @Test 43 | public void testCorruptJSONJustTriggersLogWrite() { 44 | final PrintStream stream = mock(PrintStream.class); 45 | ShadowLog.stream = stream; 46 | 47 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 48 | 49 | final DebugLogClient client = new DebugLogClient("TEST"); 50 | 51 | // Nothing has written to the log so far 52 | verify(stream, never()).println(anyString()); 53 | 54 | client.uploadPing(configuration, "path", "{]]]{{"); 55 | 56 | // Our client has logged two lines, a separator and the JSON for the ping 57 | verify(stream, times(2)).println(anyString()); 58 | } 59 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/net/HttpUrlConnectionTelemetryClientTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.net; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.mozilla.telemetry.net.HttpURLConnectionTelemetryClient; 11 | import org.robolectric.RobolectricTestRunner; 12 | import org.robolectric.RuntimeEnvironment; 13 | import org.robolectric.annotation.Config; 14 | 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.net.HttpURLConnection; 19 | import java.net.MalformedURLException; 20 | 21 | import okhttp3.internal.http.HttpMethod; 22 | import okhttp3.mockwebserver.MockResponse; 23 | import okhttp3.mockwebserver.MockWebServer; 24 | import okhttp3.mockwebserver.RecordedRequest; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertFalse; 28 | import static org.junit.Assert.assertTrue; 29 | import static org.mockito.ArgumentMatchers.any; 30 | import static org.mockito.ArgumentMatchers.anyString; 31 | import static org.mockito.Mockito.doNothing; 32 | import static org.mockito.Mockito.doReturn; 33 | import static org.mockito.Mockito.doThrow; 34 | import static org.mockito.Mockito.mock; 35 | import static org.mockito.Mockito.spy; 36 | import static org.mockito.Mockito.verify; 37 | 38 | @RunWith(RobolectricTestRunner.class) 39 | public class HttpUrlConnectionTelemetryClientTest { 40 | private static final String TEST_PATH = "/some/random/path/not/important"; 41 | private static final String TEST_PING = "{ 'ping': 'test' }"; 42 | 43 | @Test 44 | public void testTimeoutsAreSet() throws Exception { 45 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 46 | .setReadTimeout(7050) 47 | .setConnectTimeout(3050); 48 | 49 | final HttpURLConnection connection = mock(HttpURLConnection.class); 50 | 51 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 52 | doReturn(connection).when(client).openConnectionConnection(anyString(), anyString()); 53 | doReturn(200).when(client).upload(connection, TEST_PING); 54 | 55 | client.uploadPing(configuration, TEST_PATH, TEST_PING); 56 | 57 | verify(connection).setReadTimeout(7050); 58 | verify(connection).setConnectTimeout(3050); 59 | } 60 | 61 | @Test 62 | public void testUserAgentIsSet() throws Exception { 63 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 64 | .setUserAgent("Telemetry/Test 25.0.2"); 65 | final HttpURLConnection connection = mock(HttpURLConnection.class); 66 | 67 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 68 | doReturn(connection).when(client).openConnectionConnection(anyString(), anyString()); 69 | doReturn(200).when(client).upload(connection, TEST_PING); 70 | 71 | client.uploadPing(configuration, TEST_PATH, TEST_PING); 72 | 73 | verify(connection).setRequestProperty("User-Agent", "Telemetry/Test 25.0.2"); 74 | } 75 | 76 | @Test 77 | public void testReturnsTrueFor200ResponseCode() throws Exception { 78 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 79 | 80 | final HttpURLConnection connection = mock(HttpURLConnection.class); 81 | doReturn(200).when(connection).getResponseCode(); 82 | doReturn(mock(OutputStream.class)).when(connection).getOutputStream(); 83 | 84 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 85 | doReturn(connection).when(client).openConnectionConnection(anyString(), anyString()); 86 | 87 | assertTrue(client.uploadPing(configuration, TEST_PATH, TEST_PING)); 88 | } 89 | 90 | @Test 91 | public void testReturnsFalseFor5XXResponseCodes() throws IOException { 92 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 93 | 94 | for (int responseCode = 500; responseCode <= 527; responseCode++) { 95 | final HttpURLConnection connection = mock(HttpURLConnection.class); 96 | doReturn(responseCode).when(connection).getResponseCode(); 97 | doReturn(mock(OutputStream.class)).when(connection).getOutputStream(); 98 | 99 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 100 | doReturn(connection).when(client).openConnectionConnection(anyString(), anyString()); 101 | 102 | assertFalse(client.uploadPing(configuration, TEST_PATH, TEST_PING)); 103 | } 104 | } 105 | 106 | @Test 107 | public void testReturnsTrueFor2XXResponseCodes() throws IOException { 108 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 109 | 110 | for (int responseCode = 200; responseCode <= 226; responseCode++) { 111 | final HttpURLConnection connection = mock(HttpURLConnection.class); 112 | doReturn(responseCode).when(connection).getResponseCode(); 113 | doReturn(mock(OutputStream.class)).when(connection).getOutputStream(); 114 | 115 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 116 | doReturn(connection).when(client).openConnectionConnection(anyString(), anyString()); 117 | 118 | assertTrue(client.uploadPing(configuration, TEST_PATH, TEST_PING)); 119 | } 120 | } 121 | 122 | @Test 123 | public void testReturnsTrueFor4XXResponseCodes() throws IOException { 124 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 125 | 126 | for (int responseCode = 400; responseCode <= 451; responseCode++) { 127 | final HttpURLConnection connection = mock(HttpURLConnection.class); 128 | doReturn(responseCode).when(connection).getResponseCode(); 129 | doReturn(mock(OutputStream.class)).when(connection).getOutputStream(); 130 | 131 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 132 | doReturn(connection).when(client).openConnectionConnection(anyString(), anyString()); 133 | 134 | assertTrue(client.uploadPing(configuration, TEST_PATH, TEST_PING)); 135 | } 136 | } 137 | 138 | @Test 139 | public void testUpload() throws Exception { 140 | final MockWebServer server = new MockWebServer(); 141 | server.enqueue(new MockResponse().setBody("OK")); 142 | 143 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 144 | .setUserAgent("Telemetry/42.23") 145 | .setServerEndpoint("http://" + server.getHostName() + ":" + server.getPort()); 146 | 147 | final HttpURLConnectionTelemetryClient client = new HttpURLConnectionTelemetryClient(); 148 | assertTrue(client.uploadPing(configuration, TEST_PATH, TEST_PING)); 149 | 150 | final RecordedRequest request = server.takeRequest(); 151 | assertEquals(TEST_PATH, request.getPath()); 152 | assertEquals("POST", request.getMethod()); 153 | assertEquals(TEST_PING, request.getBody().readUtf8()); 154 | assertEquals("Telemetry/42.23", request.getHeader("User-Agent")); 155 | assertEquals("application/json; charset=utf-8", request.getHeader("Content-Type")); 156 | 157 | server.shutdown(); 158 | } 159 | 160 | @Test 161 | public void testMalformedUrl() throws Exception { 162 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 163 | 164 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 165 | doThrow(new MalformedURLException()).when(client).openConnectionConnection(anyString(), anyString()); 166 | 167 | // If the URL is malformed then there's nothing we can do to recover. Therefore this is treated 168 | // like a successful upload. 169 | assertTrue(client.uploadPing(configuration, "path", "ping")); 170 | } 171 | 172 | @Test 173 | public void testIOExceptionWhileUpload() throws Exception { 174 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 175 | 176 | final OutputStream stream = mock(OutputStream.class); 177 | doThrow(new IOException()).when(stream).write(any(byte[].class)); 178 | 179 | final HttpURLConnection connection = mock(HttpURLConnection.class); 180 | doReturn(stream).when(connection).getOutputStream(); 181 | 182 | final HttpURLConnectionTelemetryClient client = spy(new HttpURLConnectionTelemetryClient()); 183 | doReturn(connection).when(client).openConnectionConnection(anyString(), anyString()); 184 | 185 | // And IOException during upload is a failed upload that we should retry. The client should 186 | // return false in this case. 187 | assertFalse(client.uploadPing(configuration, "path", "ping")); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/ping/TelemetryCorePingBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import android.os.Build; 8 | import android.text.TextUtils; 9 | 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mozilla.telemetry.config.TelemetryConfiguration; 13 | import org.robolectric.RobolectricTestRunner; 14 | import org.robolectric.RuntimeEnvironment; 15 | import org.robolectric.annotation.Config; 16 | 17 | import java.util.Map; 18 | import java.util.UUID; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertFalse; 22 | import static org.junit.Assert.assertNotNull; 23 | import static org.junit.Assert.assertTrue; 24 | 25 | @RunWith(RobolectricTestRunner.class) 26 | public class TelemetryCorePingBuilderTest { 27 | @Test 28 | public void testBuildingEmptyPing() { 29 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 30 | final TelemetryCorePingBuilder builder = new TelemetryCorePingBuilder(configuration); 31 | 32 | final TelemetryPing ping = builder.build(); 33 | assertNotNull(ping); 34 | assertUUID(ping.getDocumentId()); 35 | assertFalse(TextUtils.isEmpty(ping.getUploadPath())); 36 | assertEquals("core", ping.getType()); 37 | 38 | final Map results = ping.getMeasurementResults(); 39 | assertNotNull(results); 40 | 41 | assertTrue(results.containsKey("seq")); 42 | assertEquals(1L, results.get("seq")); 43 | 44 | assertTrue(results.containsKey("locale")); 45 | assertEquals("en-US", results.get("locale")); 46 | 47 | assertTrue(results.containsKey("os")); 48 | assertEquals("Android", results.get("os")); 49 | 50 | assertTrue(results.containsKey("osversion")); 51 | assertEquals("16", results.get("osversion")); // API 16 is the default used by this Robolectric version 52 | 53 | assertTrue(results.containsKey("device")); 54 | assertFalse(TextUtils.isEmpty((String) results.get("device"))); 55 | 56 | assertTrue(results.containsKey("created")); 57 | 58 | assertTrue(results.containsKey("tz")); 59 | 60 | assertTrue(results.containsKey("sessions")); 61 | assertEquals(0L, results.get("sessions")); 62 | 63 | assertTrue(results.containsKey("durations")); 64 | assertEquals(0L, results.get("durations")); 65 | } 66 | 67 | private void assertUUID(String uuid) { 68 | //noinspection ResultOfMethodCallIgnored 69 | UUID.fromString(uuid); // Should throw IllegalArgumentException if parameter does not conform 70 | } 71 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/ping/TelemetryEventPingBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.robolectric.RobolectricTestRunner; 11 | import org.robolectric.RuntimeEnvironment; 12 | 13 | import java.util.Map; 14 | import java.util.UUID; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertNotNull; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | @RunWith(RobolectricTestRunner.class) 21 | public class TelemetryEventPingBuilderTest { 22 | @Test 23 | public void testBuildingEmptyPing() { 24 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 25 | final TelemetryEventPingBuilder builder = new TelemetryEventPingBuilder(configuration); 26 | 27 | final TelemetryPing ping = builder.build(); 28 | 29 | assertUUID(ping.getDocumentId()); 30 | assertEquals("focus-event", ping.getType()); 31 | 32 | final Map results = ping.getMeasurementResults(); 33 | assertNotNull(results); 34 | 35 | assertTrue(results.containsKey("seq")); 36 | assertEquals(1L, results.get("seq")); 37 | 38 | assertTrue(results.containsKey("locale")); 39 | assertEquals("en-US", results.get("locale")); 40 | 41 | assertTrue(results.containsKey("os")); 42 | assertEquals("Android", results.get("os")); 43 | 44 | assertTrue(results.containsKey("osversion")); 45 | assertEquals("16", results.get("osversion")); 46 | } 47 | 48 | @Test 49 | public void testSequenceNumber() { 50 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 51 | final TelemetryEventPingBuilder builder = new TelemetryEventPingBuilder(configuration); 52 | 53 | { 54 | final TelemetryPing ping1 = builder.build(); 55 | 56 | final Map results = ping1.getMeasurementResults(); 57 | assertNotNull(results); 58 | 59 | assertTrue(results.containsKey("seq")); 60 | assertEquals(1L, results.get("seq")); 61 | } 62 | 63 | { 64 | final TelemetryPing ping2 = builder.build(); 65 | 66 | final Map results2 = ping2.getMeasurementResults(); 67 | assertNotNull(results2); 68 | 69 | assertTrue(results2.containsKey("seq")); 70 | assertEquals(2L, results2.get("seq")); 71 | } 72 | } 73 | 74 | private void assertUUID(String uuid) { 75 | //noinspection ResultOfMethodCallIgnored 76 | UUID.fromString(uuid); // Should throw IllegalArgumentException if parameter does not conform 77 | } 78 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/ping/TelemetryMobileEventPingBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.ping; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mozilla.telemetry.config.TelemetryConfiguration; 10 | import org.robolectric.RobolectricTestRunner; 11 | import org.robolectric.RuntimeEnvironment; 12 | 13 | import java.util.Map; 14 | import java.util.UUID; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertNotNull; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | @RunWith(RobolectricTestRunner.class) 21 | public class TelemetryMobileEventPingBuilderTest { 22 | @Test 23 | public void testBuildingEmptyPing() { 24 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 25 | final TelemetryMobileEventPingBuilder builder = new TelemetryMobileEventPingBuilder(configuration); 26 | 27 | final TelemetryPing ping = builder.build(); 28 | 29 | assertUUID(ping.getDocumentId()); 30 | assertEquals("mobile-event", ping.getType()); 31 | 32 | final Map results = ping.getMeasurementResults(); 33 | assertNotNull(results); 34 | 35 | assertTrue(results.containsKey("seq")); 36 | assertEquals(1L, results.get("seq")); 37 | 38 | assertTrue(results.containsKey("locale")); 39 | assertEquals("en-US", results.get("locale")); 40 | 41 | assertTrue(results.containsKey("os")); 42 | assertEquals("Android", results.get("os")); 43 | 44 | assertTrue(results.containsKey("osversion")); 45 | assertEquals("16", results.get("osversion")); 46 | } 47 | 48 | @Test 49 | public void testSequenceNumber() { 50 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application); 51 | final TelemetryMobileEventPingBuilder builder = new TelemetryMobileEventPingBuilder(configuration); 52 | 53 | { 54 | final TelemetryPing ping1 = builder.build(); 55 | 56 | final Map results = ping1.getMeasurementResults(); 57 | assertNotNull(results); 58 | 59 | assertTrue(results.containsKey("seq")); 60 | assertEquals(1L, results.get("seq")); 61 | } 62 | 63 | { 64 | final TelemetryPing ping2 = builder.build(); 65 | 66 | final Map results2 = ping2.getMeasurementResults(); 67 | assertNotNull(results2); 68 | 69 | assertTrue(results2.containsKey("seq")); 70 | assertEquals(2L, results2.get("seq")); 71 | } 72 | } 73 | 74 | private void assertUUID(String uuid) { 75 | //noinspection ResultOfMethodCallIgnored 76 | UUID.fromString(uuid); // Should throw IllegalArgumentException if parameter does not conform 77 | } 78 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/schedule/jobscheduler/TelemetryJobServiceTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.schedule.jobscheduler; 6 | 7 | import android.app.job.JobParameters; 8 | import android.os.AsyncTask; 9 | 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.mozilla.telemetry.Telemetry; 14 | import org.mozilla.telemetry.TelemetryHolder; 15 | import org.mozilla.telemetry.TestUtils; 16 | import org.mozilla.telemetry.config.TelemetryConfiguration; 17 | import org.mozilla.telemetry.net.TelemetryClient; 18 | import org.mozilla.telemetry.ping.TelemetryCorePingBuilder; 19 | import org.mozilla.telemetry.schedule.TelemetryScheduler; 20 | import org.mozilla.telemetry.serialize.JSONPingSerializer; 21 | import org.mozilla.telemetry.serialize.TelemetryPingSerializer; 22 | import org.mozilla.telemetry.storage.FileTelemetryStorage; 23 | import org.mozilla.telemetry.storage.TelemetryStorage; 24 | import org.robolectric.Robolectric; 25 | import org.robolectric.RobolectricTestRunner; 26 | import org.robolectric.RuntimeEnvironment; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.mockito.ArgumentMatchers.any; 30 | import static org.mockito.ArgumentMatchers.anyBoolean; 31 | import static org.mockito.ArgumentMatchers.anyString; 32 | import static org.mockito.ArgumentMatchers.eq; 33 | import static org.mockito.Mockito.doNothing; 34 | import static org.mockito.Mockito.doReturn; 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.spy; 37 | import static org.mockito.Mockito.verify; 38 | 39 | @RunWith(RobolectricTestRunner.class) 40 | public class TelemetryJobServiceTest { 41 | @Test 42 | public void testDailyLimitIsEnforced() throws Exception { 43 | final TelemetryConfiguration configuration = new TelemetryConfiguration(RuntimeEnvironment.application) 44 | .setMaximumNumberOfPingUploadsPerDay(2); 45 | 46 | final TelemetryPingSerializer serializer = new JSONPingSerializer(); 47 | 48 | final TelemetryStorage storage = new FileTelemetryStorage(configuration, serializer); 49 | 50 | final TelemetryClient client = mock(TelemetryClient.class); 51 | doReturn(true).when(client).uploadPing(eq(configuration), anyString(), anyString()); 52 | 53 | final TelemetryScheduler scheduler = mock(TelemetryScheduler.class); 54 | 55 | final Telemetry telemetry = new Telemetry(configuration, storage, client, scheduler) 56 | .addPingBuilder(new TelemetryCorePingBuilder(configuration)); 57 | TelemetryHolder.set(telemetry); 58 | 59 | telemetry.queuePing(TelemetryCorePingBuilder.TYPE) 60 | .queuePing(TelemetryCorePingBuilder.TYPE) 61 | .queuePing(TelemetryCorePingBuilder.TYPE) 62 | .queuePing(TelemetryCorePingBuilder.TYPE) 63 | .queuePing(TelemetryCorePingBuilder.TYPE); 64 | 65 | TestUtils.waitForExecutor(telemetry); 66 | 67 | assertEquals(5, storage.countStoredPings(TelemetryCorePingBuilder.TYPE)); 68 | 69 | final TelemetryJobService service = spy(Robolectric.buildService(TelemetryJobService.class) 70 | .create() 71 | .get()); 72 | doReturn(1337L).when(service).now(); 73 | doNothing().when(service).jobFinished(any(JobParameters.class), anyBoolean()); 74 | 75 | final JobParameters parameters = mock(JobParameters.class); 76 | final AsyncTask task = mock(AsyncTask.class); 77 | doReturn(false).when(task).isCancelled(); 78 | 79 | service.uploadPingsInBackground(task, parameters); 80 | 81 | // Job finished and should not be re-scheduled even though we didn't upload everything 82 | verify(service).jobFinished(parameters, false); 83 | 84 | // 3 pings are still in the storage 85 | assertEquals(3, storage.countStoredPings(TelemetryCorePingBuilder.TYPE)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/util/FileUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.util; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | import org.robolectric.RuntimeEnvironment; 11 | 12 | import java.io.File; 13 | import java.util.UUID; 14 | 15 | import static org.mockito.Mockito.doReturn; 16 | import static org.mockito.Mockito.mock; 17 | 18 | @RunWith(RobolectricTestRunner.class) 19 | public class FileUtilsTest { 20 | @Test 21 | public void testAssertDirectory() { 22 | final File folder = new File(RuntimeEnvironment.application.getCacheDir(), UUID.randomUUID().toString()); 23 | FileUtils.assertDirectory(folder); 24 | } 25 | 26 | @SuppressWarnings("ResultOfMethodCallIgnored") 27 | @Test(expected = IllegalStateException.class) 28 | public void testThrowsIfDirectoryCannotBeCreated() { 29 | final File file = mock(File.class); 30 | doReturn(false).when(file).exists(); 31 | doReturn(false).when(file).mkdirs(); 32 | 33 | FileUtils.assertDirectory(file); 34 | } 35 | 36 | @SuppressWarnings("ResultOfMethodCallIgnored") 37 | @Test(expected = IllegalStateException.class) 38 | public void testThrowsIfFileIsNotDirectory() { 39 | final File file = mock(File.class); 40 | doReturn(true).when(file).exists(); 41 | doReturn(true).when(file).mkdirs(); 42 | doReturn(false).when(file).isDirectory(); 43 | 44 | FileUtils.assertDirectory(file); 45 | } 46 | 47 | @SuppressWarnings("ResultOfMethodCallIgnored") 48 | @Test(expected = IllegalStateException.class) 49 | public void testThrowsIfDirectoryIsNotWritable() { 50 | final File file = mock(File.class); 51 | doReturn(true).when(file).exists(); 52 | doReturn(true).when(file).mkdirs(); 53 | doReturn(true).when(file).isDirectory(); 54 | doReturn(false).when(file).canWrite(); 55 | 56 | FileUtils.assertDirectory(file); 57 | } 58 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/util/IOUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.util; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.robolectric.RobolectricTestRunner; 10 | 11 | import java.io.Closeable; 12 | import java.io.IOException; 13 | 14 | import static org.mockito.Mockito.doThrow; 15 | import static org.mockito.Mockito.mock; 16 | import static org.mockito.Mockito.verify; 17 | 18 | @RunWith(RobolectricTestRunner.class) 19 | public class IOUtilsTest { 20 | @Test 21 | public void testCloseIsCalled() throws Exception { 22 | final Closeable stream = mock(Closeable.class); 23 | 24 | IOUtils.safeClose(stream); 25 | 26 | verify(stream).close(); 27 | } 28 | 29 | @Test 30 | public void testExceptionIsSwallowed() throws Exception { 31 | final Closeable stream = mock(Closeable.class); 32 | doThrow(IOException.class).when(stream).close(); 33 | 34 | IOUtils.safeClose(stream); 35 | 36 | verify(stream).close(); 37 | } 38 | 39 | @Test 40 | public void testNullIsIgnored() { 41 | IOUtils.safeClose(null); 42 | } 43 | } -------------------------------------------------------------------------------- /telemetry/src/test/java/org/mozilla/telemetry/util/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | package org.mozilla.telemetry.util; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | public class StringUtilsTest { 12 | @Test 13 | public void testSafeSubstring() { 14 | assertEquals("hello", StringUtils.safeSubstring("hello", 0, 20)); 15 | assertEquals("hell", StringUtils.safeSubstring("hello", 0, 4)); 16 | assertEquals("hello", StringUtils.safeSubstring("hello", -5, 15)); 17 | } 18 | } --------------------------------------------------------------------------------