├── .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 | [](https://travis-ci.org/mozilla-mobile/telemetry-android)
5 | [](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 | }
--------------------------------------------------------------------------------