diff options
Diffstat (limited to 'mobile/android/android-components/samples/glean/src')
16 files changed, 640 insertions, 0 deletions
diff --git a/mobile/android/android-components/samples/glean/src/androidTest/java/org/mozilla/samples/glean/MainActivityTest.kt b/mobile/android/android-components/samples/glean/src/androidTest/java/org/mozilla/samples/glean/MainActivityTest.kt new file mode 100644 index 0000000000..1a145e61c6 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/androidTest/java/org/mozilla/samples/glean/MainActivityTest.kt @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.samples.glean + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.rules.ActivityScenarioRule +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Rule +import org.junit.Test +import org.mozilla.samples.glean.GleanMetrics.Test as GleanTestMetrics + +class MainActivityTest { + @get:Rule + val activityRule: ActivityScenarioRule<MainActivity> = ActivityScenarioRule(MainActivity::class.java) + + @Test + fun checkGleanClickData() { + // We don't reset the storage in this test as the GleanTestRule does not + // work nicely in instrumented test. Just check the current value, increment + // by one and make it the expected value. + val expectedValue = if (GleanTestMetrics.counter.testGetValue() != null) { + GleanTestMetrics.counter.testGetValue()!! + 1 + } else { + 1 + } + + // Simulate a click on the button. + onView(withId(R.id.buttonGenerateData)).perform(click()) + + // Use the Glean testing API to check if the expected data was recorded. + assertNotNull(GleanTestMetrics.counter.testGetValue()) + assertEquals(expectedValue, GleanTestMetrics.counter.testGetValue()) + } +} diff --git a/mobile/android/android-components/samples/glean/src/androidTest/java/org/mozilla/samples/glean/pings/BaselinePingTest.kt b/mobile/android/android-components/samples/glean/src/androidTest/java/org/mozilla/samples/glean/pings/BaselinePingTest.kt new file mode 100644 index 0000000000..61057039d0 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/androidTest/java/org/mozilla/samples/glean/pings/BaselinePingTest.kt @@ -0,0 +1,138 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.samples.glean.pings + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import mozilla.components.service.glean.testing.GleanTestLocalServer +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.json.JSONObject +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.mozilla.samples.glean.MainActivity +import java.io.BufferedReader +import java.io.ByteArrayInputStream +import java.util.concurrent.TimeUnit +import java.util.zip.GZIPInputStream + +/** + * Decompress the GZIP returned by the glean-core layer. + * + * @param data the gzipped [ByteArray] to decompress + * @return a [String] containing the uncompressed data. + */ +fun decompressGZIP(data: ByteArray): String { + return GZIPInputStream(ByteArrayInputStream(data)).bufferedReader().use(BufferedReader::readText) +} + +/** + * Convenience method to get the body of a request as a String. + * The UTF8 representation of the request body will be returned. + * If the request body is gzipped, it will be decompressed first. + * + * @return a [String] containing the body of the request. + */ +fun RecordedRequest.getPlainBody(): String { + return if (this.getHeader("Content-Encoding") == "gzip") { + val bodyInBytes = this.body.readByteArray() + decompressGZIP(bodyInBytes) + } else { + this.body.readUtf8() + } +} + +class BaselinePingTest { + private val server = createMockWebServer() + + @get:Rule + val activityRule: ActivityScenarioRule<MainActivity> = ActivityScenarioRule(MainActivity::class.java) + + @get:Rule + val gleanRule = GleanTestLocalServer(context, server.port) + + private val context: Context + get() = ApplicationProvider.getApplicationContext() + + /** + * Create a mock webserver that accepts all requests and replies with "OK". + * @return a [MockWebServer] instance + */ + private fun createMockWebServer(): MockWebServer { + val server = MockWebServer() + server.dispatcher = + object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + return MockResponse().setBody("OK") + } + } + + return server + } + + private fun waitForPingContent( + pingName: String, + pingReason: String?, + maxAttempts: Int = 3, + ): JSONObject? { + var attempts = 0 + do { + attempts += 1 + val request = server.takeRequest(20L, TimeUnit.SECONDS) + val docType = request?.path?.split("/")?.get(3) + if (pingName == docType) { + val parsedPayload = JSONObject(request.getPlainBody()) + if (pingReason == null) { + return parsedPayload + } + + // If we requested a specific ping reason, look for it. + val reason = parsedPayload.getJSONObject("ping_info").getString("reason") + if (reason == pingReason) { + return parsedPayload + } + } + } while (attempts < maxAttempts) + + return null + } + + @Test + fun validateBaselinePing() { + // Wait for the app to be idle/ready. + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + device.waitForIdle() + + // Wait for 1 second: this should guarantee we have some valid duration in the + // ping. + Thread.sleep(1000) + + // Move it to background. + device.pressHome() + + // Validate the received data. + val baselinePing = waitForPingContent("baseline", "inactive")!! + val metrics = baselinePing.getJSONObject("metrics") + + // Make sure we have a 'duration' field with a reasonable value: it should be >= 1, since + // we slept for 1000ms. + val timespans = metrics.getJSONObject("timespan") + assertTrue(timespans.getJSONObject("glean.baseline.duration").getLong("value") >= 1L) + + // Make sure there's no errors. + val errors = metrics.optJSONObject("labeled_counter")?.keys() + errors?.forEach { + assertFalse(it.startsWith("glean.error.")) + } + } +} diff --git a/mobile/android/android-components/samples/glean/src/main/AndroidManifest.xml b/mobile/android/android-components/samples/glean/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..0fb5a873cb --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" + tools:ignore="ScopedStorage" /> + + <!-- Note: the usesCleartextTraffic is only required for making instrumentation + tests work on API 23+. Also note that this requires tools:ignore="UnusedAttribute" + for stopping the linter from complaining on API 21 <= x < 23. --> + <application + android:allowBackup="true" + android:fullBackupContent="@xml/backup_rules" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/Theme.AppCompat.Light.DarkActionBar" + android:usesCleartextTraffic="true" + tools:ignore="DataExtractionRules,UnusedAttribute" + android:name=".GleanApplication" + android:dataExtractionRules="@xml/data_extraction_rules"> + <activity android:name="org.mozilla.samples.glean.MainActivity" + android:windowSoftInputMode="adjustResize" + android:launchMode="singleTask" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/mobile/android/android-components/samples/glean/src/main/java/org/mozilla/samples/glean/GleanApplication.kt b/mobile/android/android-components/samples/glean/src/main/java/org/mozilla/samples/glean/GleanApplication.kt new file mode 100644 index 0000000000..bf0ff5f641 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/java/org/mozilla/samples/glean/GleanApplication.kt @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.samples.glean + +import android.app.Application +import android.content.Context +import android.net.Uri +import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient +import mozilla.components.service.glean.Glean +import mozilla.components.service.glean.config.Configuration +import mozilla.components.service.glean.net.ConceptFetchHttpUploader +import mozilla.components.service.nimbus.Nimbus +import mozilla.components.service.nimbus.NimbusApi +import mozilla.components.service.nimbus.NimbusAppInfo +import mozilla.components.service.nimbus.NimbusServerSettings +import mozilla.components.support.base.log.Log +import mozilla.components.support.base.log.sink.AndroidLogSink +import mozilla.components.support.rusthttp.RustHttpConfig +import mozilla.components.support.rustlog.RustLog +import org.mozilla.samples.glean.GleanMetrics.Basic +import org.mozilla.samples.glean.GleanMetrics.Custom +import org.mozilla.samples.glean.GleanMetrics.GleanBuildInfo +import org.mozilla.samples.glean.GleanMetrics.Pings +import org.mozilla.samples.glean.GleanMetrics.Test + +class GleanApplication : Application() { + + companion object { + lateinit var nimbus: NimbusApi + + const val SAMPLE_GLEAN_PREFERENCES = "sample_glean_preferences" + const val PREF_IS_FIRST_RUN = "isFirstRun" + } + + override fun onCreate() { + super.onCreate() + val settings = applicationContext.getSharedPreferences(SAMPLE_GLEAN_PREFERENCES, Context.MODE_PRIVATE) + val isFirstRun = settings.getBoolean(PREF_IS_FIRST_RUN, true) + + // We want the log messages of all builds to go to Android logcat + + Log.addSink(AndroidLogSink()) + + // Register the sample application's custom pings. + Glean.registerPings(Pings) + + // Initialize the Glean library. Ideally, this is the first thing that + // must be done right after enabling logging. + val client by lazy { HttpURLConnectionClient() } + val httpClient = ConceptFetchHttpUploader.fromClient(client) + val config = Configuration(httpClient = httpClient) + Glean.initialize( + applicationContext, + uploadEnabled = true, + configuration = config, + buildInfo = GleanBuildInfo.buildInfo, + ) + + /** Begin Nimbus component specific code. Note: this is not relevant to Glean */ + initNimbus(isFirstRun) + /** End Nimbus specific code. */ + + Test.timespan.start() + + Custom.counter.add() + + // Set a sample value for a metric. + Basic.os.set("Android") + + settings + .edit() + .putBoolean(PREF_IS_FIRST_RUN, false) + .apply() + } + + /** + * Initialize the Nimbus experiments library. This is only relevant to the Nimbus library, aside + * from recording the experiment in Glean. + */ + private fun initNimbus(isFirstRun: Boolean) { + RustLog.enable() + RustHttpConfig.setClient(lazy { HttpURLConnectionClient() }) + val url = Uri.parse(getString(R.string.nimbus_default_endpoint)) + val appInfo = NimbusAppInfo( + appName = "samples-glean", + channel = "samples", + ) + nimbus = Nimbus( + context = this, + appInfo = appInfo, + server = NimbusServerSettings(url), + ).also { nimbus -> + if (isFirstRun) { + // This file is bundled with the app, but derived from the server at build time. + // We'll use it now, on first run. + nimbus.setExperimentsLocally(R.raw.initial_experiments) + } + // Apply the experiments downloaded on last run, but on first run, it will + // use the contents of `R.raw.initial_experiments`. + nimbus.applyPendingExperiments() + + // In a real application, we might want to fetchExperiments() here. + // + // We won't do that in this app because: + // * the server's experiments will overwrite the current ones + // * it's not clear that the server will have a `test-color` experiment + // by the time you run this + // * an update experiments button is in `MainActivity` + // + // nimbus.fetchExperiments() + } + } +} diff --git a/mobile/android/android-components/samples/glean/src/main/java/org/mozilla/samples/glean/MainActivity.kt b/mobile/android/android-components/samples/glean/src/main/java/org/mozilla/samples/glean/MainActivity.kt new file mode 100644 index 0000000000..8020022d87 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/java/org/mozilla/samples/glean/MainActivity.kt @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.samples.glean + +import android.graphics.Color +import android.os.Bundle +import androidx.annotation.MainThread +import androidx.appcompat.app.AppCompatActivity +import org.mozilla.experiments.nimbus.EnrolledExperiment +import org.mozilla.experiments.nimbus.NimbusInterface +import org.mozilla.samples.glean.GleanMetrics.BrowserEngagement +import org.mozilla.samples.glean.GleanMetrics.Test +import org.mozilla.samples.glean.databinding.ActivityMainBinding +import org.mozilla.samples.glean.library.SamplesGleanLibrary + +/** + * Main Activity of the glean-sample-app + */ +open class MainActivity : AppCompatActivity(), NimbusInterface.Observer { + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + + setContentView(binding.root) + + // Generate an event when user clicks on the button. + binding.buttonGenerateData.setOnClickListener { + // These first two actions, adding to the string list and incrementing the counter are + // tied to a user lifetime metric which is persistent from launch to launch. + + // Adds the EditText's text content as a new string in the string list metric from the + // metrics.yaml file. + Test.stringList.add(binding.etStringListInput.text.toString()) + // Clear current text to help indicate something happened + binding.etStringListInput.setText("") + + // Increments the test_counter metric from the metrics.yaml file. + Test.counter.add() + + // This is referencing the event ping named 'click' from the metrics.yaml file. In + // order to illustrate adding extra information to the event, it is also adding to the + // 'extras' field a dictionary of values. Note that the dictionary keys must be + // declared in the metrics.yaml file under the 'extra_keys' section of an event metric. + BrowserEngagement.click.record( + BrowserEngagement.ClickExtra( + key1 = "extra_value_1", + key2 = "extra_value_2", + ), + ) + } + + Test.timespan.stop() + + // Update some metrics from a third-party library + SamplesGleanLibrary.recordMetric() + SamplesGleanLibrary.recordExperiment() + + // The following is not relevant to the Glean SDK, but to the Nimbus experiments library + setupNimbusExperiments() + } + + /** Begin Nimbus component specific functions */ + + /** + * This sets up the update receiver and sets the onClickListener for the "Update Experiments" + * button. This is not relevant to the Glean SDK, but to the Nimbus experiments library. + */ + private fun setupNimbusExperiments() { + // Register the main activity as a Nimbus observer + GleanApplication.nimbus.register(this) + + // Attach the click listener for the experiments button to the updateExperiments function + binding.buttonCheckExperiments.setOnClickListener { + // Once the experiments are fetched, then the activity's (a Nimbus observer) + // `onExperimentFetched()` method is called. + GleanApplication.nimbus.fetchExperiments() + } + + configureButton() + } + + /** + * Event to indicate that the experiments have been fetched from the endpoint + */ + override fun onExperimentsFetched() { + println("Experiments fetched") + GleanApplication.nimbus.applyPendingExperiments() + } + + /** + * Event to indicate that the experiment enrollments have been applied. Developers normally + * shouldn't care to observe this and rather rely on `onExperimentsFetched` and `withExperiment` + */ + override fun onUpdatesApplied(updated: List<EnrolledExperiment>) { + runOnUiThread { + configureButton() + } + } + + @MainThread + private fun configureButton() { + val nimbus = GleanApplication.nimbus + val branch = nimbus.getExperimentBranch("sample-experiment-feature") + + val color = when (branch) { + "blue" -> Color.BLUE + "red" -> Color.RED + "control" -> Color.DKGRAY + else -> Color.WHITE + } + val text = when (branch) { + null -> getString(R.string.experiment_not_active) + else -> getString(R.string.experiment_active_branch, branch) + } + + binding.textViewExperimentStatus.setBackgroundColor(color) + binding.textViewExperimentStatus.text = text + } + + /** End Nimbus component functions */ +} diff --git a/mobile/android/android-components/samples/glean/src/main/res/layout/activity_main.xml b/mobile/android/android-components/samples/glean/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..87e3025d0f --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/layout/activity_main.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/buttonList" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:gravity="center" + android:orientation="vertical" + android:padding="10dp" + tools:context="org.mozilla.samples.glean.MainActivity"> + + <!-- This is a dummy linear layout to capture focus and prevent the keyboard from popping + when the app first launches. This is a known issue of linear layouts and a common + workaround. --> + <LinearLayout android:focusable="true" + android:focusableInTouchMode="true" + android:layout_width="0px" + android:layout_height="0px" > + <requestFocus /> + </LinearLayout> + + <EditText + android:id="@+id/etStringListInput" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:hint="@string/string_list_input_hint" + android:inputType="textPersonName" + android:autofillHints="" + tools:ignore="UnusedAttribute" /> + + <Button + android:id="@+id/buttonGenerateData" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + android:text="@string/generate_data" + android:textAlignment="center" /> + + <TextView + android:id="@+id/textView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + android:text="@string/counter_metric_info" /> + + <Button + android:id="@+id/buttonCheckExperiments" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/check_experiments" + android:textAlignment="center" /> + + <TextView + android:id="@+id/textViewExperimentStatus" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/white" + android:text="@string/experiment_not_active" + android:textStyle="italic" /> + + <TextView + android:id="@+id/textView3" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/check_experiments_btn_description" /> + +</LinearLayout> diff --git a/mobile/android/android-components/samples/glean/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/android/android-components/samples/glean/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..a2f5908281 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/mobile/android/android-components/samples/glean/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/android/android-components/samples/glean/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..ff10afd6e1 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/mobile/android/android-components/samples/glean/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/android/android-components/samples/glean/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..dcd3cd8083 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/mobile/android/android-components/samples/glean/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/android-components/samples/glean/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..8ca12fe024 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/mobile/android/android-components/samples/glean/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/android-components/samples/glean/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000000..b824ebdd48 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/mobile/android/android-components/samples/glean/src/main/res/raw/initial_experiments.json b/mobile/android/android-components/samples/glean/src/main/res/raw/initial_experiments.json new file mode 100644 index 0000000000..e4eadb052b --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/raw/initial_experiments.json @@ -0,0 +1,60 @@ +{ + "data": [{ + "slug": "test-color", + "endDate": null, + "branches": [ + { + "slug": "control", + "ratio": 1, + "feature": { + "featureId": "sample-experiment-feature", + "enabled": true, + "value": null + } + }, + { + "slug": "red", + "ratio": 1, + "feature": { + "featureId": "sample-experiment-feature", + "enabled": true, + "value": null + } + }, + { + "slug": "blue", + "ratio": 1, + "feature": { + "featureId": "sample-experiment-feature", + "enabled": true, + "value": null + } + } + ], + "featureIds": [ + "sample-experiment-feature" + ], + "probeSets": [], + "startDate": null, + "targeting": "true", + "appName": "samples-glean", + "appId": "org.mozilla.samples.glean.debug", + "channel": "samples", + "bucketConfig": { + "count": 10000, + "start": 0, + "total": 10000, + "namespace": "test-color-1", + "randomizationUnit": "nimbus_id" + }, + "schemaVersion": "1.1.0", + "userFacingName": "Button color sample experiment", + "referenceBranch": "control", + "proposedDuration": null, + "isEnrollmentPaused": false, + "proposedEnrollment": 7, + "userFacingDescription": "A sample experiment to determine the color of a button", + "id": "test-color", + "last_modified": 1607613885917 + }] +} diff --git a/mobile/android/android-components/samples/glean/src/main/res/values/endpoints.xml b/mobile/android/android-components/samples/glean/src/main/res/values/endpoints.xml new file mode 100644 index 0000000000..1a6c4363ca --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/values/endpoints.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<resources> + <string name="nimbus_default_endpoint" translatable="false">https://settings.stage.mozaws.net</string> +</resources> diff --git a/mobile/android/android-components/samples/glean/src/main/res/values/strings.xml b/mobile/android/android-components/samples/glean/src/main/res/values/strings.xml new file mode 100644 index 0000000000..ed86142323 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<resources> + <string name="app_name">Glean - Metrics Demo</string> + <string name="generate_data">Record Text</string> + <string name="string_list_input_hint">Enter some text here</string> + <string name="counter_metric_info"> + Every time you click on the button above, the text is added to the test_string_list metric + and a counter metric type called testCounter is incremented. Both of these will be included + when the ping is sent. The counter and the string list are utilizing the `user` lifetime + and should persist from launch to launch of the app.\n\nAn event metric is also being used + to attach a dictionary of values to the extras of the event ping. See MainActivity for + where all of this is happening. + </string> + <string name="experiment_not_active">Experiment not active</string> + <string name="experiment_active_branch">Experiment active, branch: %1$s</string> + <string name="check_experiments">Check experiments</string> + <string name="check_experiments_btn_description">The CHECK EXPERIMENTS button checks the experiments status of the test-colorexperiment and sets the color accordingly.</string> +</resources> diff --git a/mobile/android/android-components/samples/glean/src/main/res/xml/backup_rules.xml b/mobile/android/android-components/samples/glean/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000000..820ae61afa --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/xml/backup_rules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<full-backup-content> + <include domain="sharedpref" path="."/> +</full-backup-content>
\ No newline at end of file diff --git a/mobile/android/android-components/samples/glean/src/main/res/xml/data_extraction_rules.xml b/mobile/android/android-components/samples/glean/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000000..55da967560 --- /dev/null +++ b/mobile/android/android-components/samples/glean/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<data-extraction-rules> + <cloud-backup> + <include domain="sharedpref" path="."/> + </cloud-backup> +</data-extraction-rules>
\ No newline at end of file |