diff --git a/buildSrc/src/main/groovy/com/google/firebase/gradle/GenerateMeasurementsTask.groovy b/buildSrc/src/main/groovy/com/google/firebase/gradle/GenerateMeasurementsTask.groovy new file mode 100644 index 00000000000..93fa0cf00d5 --- /dev/null +++ b/buildSrc/src/main/groovy/com/google/firebase/gradle/GenerateMeasurementsTask.groovy @@ -0,0 +1,226 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.google.firebase.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +/** + * Generates size measurements after building the test apps and outputs them as a text-format + * protocol buffer report. + * + *

This task requires two properties, an SDK map, as input, and the report, as output. The map is + * used to convert project names and build variants into the SDK identifiers used by the database. + * The report path is where the output should be stored. Additionally, a project property, {@code + * pull_request} is used in the report. Excluding this value will send a human-readable version + * to standard out. + */ +public class GenerateMeasurementsTask extends DefaultTask { + + /** + * The file storing the SDK map. + * + *

This may be any type recognized by Gradle as a file. The format of the file's contents is + * headerless CSV with a colon as a delimiter: projectName-buildVariant:sdkId. The first column + * contains both the project name and build variant separated by an hyphen. The SDK ID is the + * integer identifier used by the SQL database to represent this SDK and build variant pair. + * + *

A complete example follows: + *

{@code
+     * database-debug:1
+     * database-release:2
+     * firestore-release:7
+     * firestore-debug:4
+     *}
+ */ + @InputFile + File sdkMapFile + + /** + * The file for storing the report. + * + *

This may be any type recognized by Gradle as a file. The contents, if any, will be + * overwritten by the new report. + */ + @OutputFile + File reportFile + + @Override + Task configure(Closure closure) { + project.android.variantFilter { + if (it.buildType.name != "aggressive") { + it.ignore = true; + } + } + + outputs.upToDateWhen { false } + dependsOn "assemble" + return super.configure(closure) + } + + @TaskAction + def generate() { + // Check if we need to run human-readable or upload mode. + if (project.hasProperty("pull_request")) { + def pullRequestNumber = project.properties["pull_request"] + def sdkMap = createSdkMap() + def sizes = calculateSizesForUpload(sdkMap, project.android.applicationVariants) + def report = createReportForUpload(pullRequestNumber, sizes) + + reportFile.withWriter { + it.write(report) + } + } else { + def sizes = calculateHumanReadableSizes(project.android.applicationVariants) + printHumanReadableReport(sizes) + } + } + + private def calculateHumanReadableSizes(variants) { + def sizes = [:] + def processor = {flavor, build, size -> + sizes[new Tuple2(flavor, build)] = size + } + + calculateSizesFor(variants, processor) + return sizes + } + + private def calculateSizesForUpload(sdkMap, variants) { + def sizes = [:] + def processor = { flavor, build, size -> + def name = "${flavor}-${build}" + def sdk = sdkMap[name]; + + if (sdk == null) { + throw new IllegalStateException("$name not included in SDK map") + } + sizes[sdk] = size + } + + calculateSizesFor(variants, processor) + return sizes + } + + private def calculateSizesFor(variants, processor) { + // Each variant should have exactly one APK. If there are multiple APKs, then this file is + // out of sync with our Gradle configuration, and this task fails. If an APK is missing, it + // is silently ignored, and the APKs from the other variants will be used to build the + // report. + variants.each { variant -> + def flavorName = variant.flavorName + def buildType = variant.buildType.name + def apks = variant.outputs.findAll { it.outputFile.name.endsWith(".apk") } + if (apks.size() > 1) { + def msg = "${flavorName}-${buildType} produced more than one APK" + throw new IllegalStateException(msg) + } + + // This runs at most once, as each variant at this point has zero or one APK. + apks.each { + def size = it.outputFile.size() + processor.call(flavorName, buildType, size) + } + } + } + + private def printHumanReadableReport(sizes) { + project.logger.quiet("|------------------ APK Sizes ------------------|") + project.logger.quiet("|-- project --|-- build type --|-- size in bytes --|") + + sizes.each { key, value -> + def line = sprintf("|%-19s|%-19s|%-21s|", key.first, key.second, value) + project.logger.quiet(line) + } + } + + // TODO(allisonbm): Remove hard-coding protocol buffers. This code manually generates the + // text-format protocol buffer report. This eliminates requiring buildSrc to depend on the + // uploader (or simply, the protocol buffer library), but this isn't the most scalable option. + private def createReportForUpload(pullRequestNumber, sizes) { + def sdkId = 0 + def apkSize = 0 + + def pullRequestGroup = """ + groups { + table_name: "PullRequests" + column_names: "pull_request_id" + measurements { + values { + int_value: ${pullRequestNumber} + } + } + } + """ + + def sizeGroupHeader = """ + groups { + table_name: "ApkSizes" + column_names: "sdk_id" + column_names: "pull_request_id" + column_names: "apk_size" + """ + + def sizeGroupEntry = """ + measurements { + values { + int_value: ${->sdkId} + } + values { + int_value: ${pullRequestNumber} + } + values { + int_value: ${->apkSize} + } + } + """ + + def sizeGroupFooter = """ + } + """ + + + def builder = new StringBuilder() + builder.append(pullRequestGroup) + builder.append(sizeGroupHeader) + + sizes.each { key, value -> + // sdkId and apkSize are lazily interpolated into sizeGroupEntry. + sdkId = key + apkSize = value + builder.append(sizeGroupEntry) + } + + builder.append(sizeGroupFooter) + return builder.toString() + } + + private def createSdkMap() { + def map = [:] + + sdkMapFile.eachLine { + def delimiter = it.indexOf(":") + def key = it.substring(0, delimiter).trim() + def value = it.substring(delimiter + 1).trim() + map[key] = Integer.parseInt(value) + } + + return map + } +} diff --git a/buildSrc/src/main/groovy/com/google/firebase/gradle/UploadMeasurementsTask.groovy b/buildSrc/src/main/groovy/com/google/firebase/gradle/UploadMeasurementsTask.groovy new file mode 100644 index 00000000000..21af89d3999 --- /dev/null +++ b/buildSrc/src/main/groovy/com/google/firebase/gradle/UploadMeasurementsTask.groovy @@ -0,0 +1,93 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +package com.google.firebase.gradle + +import java.net.URL +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.TaskAction + +/** + * Takes the size information created by {@link GenerateMeasurementTask} and uploads it to the + * database using the uploader tool. + * + *

The uploader tool is fetched from the Internet using a URL. This URL, and the path to the + * report to upload, must be given as properties to this task. This task also requires a project + * property, {@code database_config} for connecting to the database. The format of this file is + * dictated by the uploader tool. + */ +public class UploadMeasurementsTask extends DefaultTask { + + /** + * The URL of the uploader tool. + * + *

This must be a valid URL as a {@link String}. + */ + @Input + String uploader + + /** + * The file to upload. + * + *

This file must exist prior to executing this task, but it may be created by other tasks + * provided they run first. + */ + @InputFile + File reportFile + + @TaskAction + def upload() { + if (!project.hasProperty("database_config")) { + throw new IllegalStateException("Cannot upload measurements without database config") + } + + def configuration = project.file(project.properties["database_config"]) + + withTempJar { jar -> + getUploaderUrl().withInputStream { + Files.copy(it, jar, StandardCopyOption.REPLACE_EXISTING) + } + + project.exec { + executable("java") + + args( + "-jar", + jar, + "--config_path=${configuration}", + "--proto_path=${reportFile}", + ) + }.rethrowFailure() + } + } + + def getUploaderUrl() { + return new URL(uploader) + } + + private def withTempJar(Closure action) { + def path = Files.createTempFile("uploader", ".jar") + try { + action.call(path) + } finally { + Files.delete(path); + } + } +} diff --git a/subprojects.cfg b/subprojects.cfg index fcfe44f2041..62fe85e130f 100644 --- a/subprojects.cfg +++ b/subprojects.cfg @@ -6,4 +6,5 @@ firebase-database-collection firebase-storage protolite-well-known-types +tools:apksize tools:errorprone diff --git a/tools/apksize/README.md b/tools/apksize/README.md new file mode 100644 index 00000000000..a310e5e2388 --- /dev/null +++ b/tools/apksize/README.md @@ -0,0 +1,37 @@ +# APK Size Tooling + +## Purpose + +This tooling measures the size of APKs using Firebase. The APKs are simple apps +that exercise only a small faction of the API surface. These numbers help to +show how an app's size might grow if Firebase is included. + +## How to Use + +There are two tasks defined in this subproject: generateMeasurements and +uploadMeasurements. The former gathers the measurements and writes them to a +file in the build directory. The latter is invoked by CI and uploads the report +to an SQL database. + +The generateMeasurements task may be manually run with `./gradlew -q +generateMeasurements`. This will output a human readable report to standard out. +Appending `-Ppull_request=999` will instead generate the report to upload, where +`999` is the pull request number to place in the report. + +The uploadMeasurements task is not intended to be invoked manually. However, it +may be invoked with the above pull request flag and `-Pdatabase_config=path` +where `path` is the path to the config file. The config file must have the +following structure where the values in all-caps are placeholders for the +relevant pieces of configuration: + +``` +host:HOST +database:DATABASE +user:USER +password:PASSWORD +``` + +## Current Support + +All projects in this repository are supported with an aggressive ProGuard +profile. Less aggressive ProGuard profiles will be added at a future date. diff --git a/tools/apksize/apksize.gradle b/tools/apksize/apksize.gradle new file mode 100644 index 00000000000..8ee091aa002 --- /dev/null +++ b/tools/apksize/apksize.gradle @@ -0,0 +1,67 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +import com.google.firebase.gradle.GenerateMeasurementsTask +import com.google.firebase.gradle.UploadMeasurementsTask + +// Linting needs to be disabled as none of these apps are real or intended to conform to +// linting standards. +tasks.whenTaskAdded { task -> + if (task.name.equals("lint")) { + //task.enabled = false + } +} + +apply plugin: "com.android.application" +apply from: "default.gradle" +android { + flavorDimensions "apkSize" +} + +apply from: "src/database/database.gradle" +apply from: "src/storage/storage.gradle" +apply from: "src/firestore/firestore.gradle" +apply from: "src/functions/functions.gradle" + +/** + * This task builds all supported variants (only aggressive as of writing) and writes the + * APK sizes to a text-format protocol buffer file. + * + * @param -Ppull_request the pull request number to be included in the report + */ +task generateMeasurements(type: GenerateMeasurementsTask) { + sdkMapFile = file("sdks.csv") + reportFile = file("$buildDir/size-report.textpb") +} + +/** + * This task uploads the report produced by the generate measurements task to a SQL database. + * + * @param -Pdatabase_config the file with the database configuration + * @param -Ppull_request the pull request number to be included in the report + */ +task uploadMeasurements(type: UploadMeasurementsTask) { + dependsOn generateMeasurements + + reportFile = file("$buildDir/size-report.textpb") + uploader = "https://storage.googleapis.com/firebase-engprod-metrics/upload_tool.jar" +} + +// ========================================================================== +// Copy from here down if you want to use the google-services plugin in your +// androidTest integration tests. +// ========================================================================== +ext.packageName = "com.google.apksize" +apply from: '../../gradle/googleServices.gradle' diff --git a/tools/apksize/debug.keystore b/tools/apksize/debug.keystore new file mode 100644 index 00000000000..c948a6e6414 Binary files /dev/null and b/tools/apksize/debug.keystore differ diff --git a/tools/apksize/default.gradle b/tools/apksize/default.gradle new file mode 100644 index 00000000000..2ba6977252b --- /dev/null +++ b/tools/apksize/default.gradle @@ -0,0 +1,59 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * Default build settings + */ +android { + adbOptions { + timeOutInMs 60 * 1000 + } + + lintOptions { + abortOnError false + checkReleaseBuilds false + } + compileSdkVersion 26 + + defaultConfig { + applicationId 'com.google.apksize' + minSdkVersion 26 + multiDexEnabled true + targetSdkVersion 26 + versionCode 1 + versionName '1.0' + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE-FIREBASE.txt' + exclude 'META-INF/NOTICE' + } + + buildTypes { + aggressive { + debuggable false + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules-aggressive.pro' + signingConfig signingConfigs.debug + matchingFallbacks = ['release'] + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/tools/apksize/proguard-rules-aggressive.pro b/tools/apksize/proguard-rules-aggressive.pro new file mode 100644 index 00000000000..8962eae7dbb --- /dev/null +++ b/tools/apksize/proguard-rules-aggressive.pro @@ -0,0 +1 @@ +-dontwarn ** diff --git a/tools/apksize/sdks.csv b/tools/apksize/sdks.csv new file mode 100644 index 00000000000..2380050269c --- /dev/null +++ b/tools/apksize/sdks.csv @@ -0,0 +1,4 @@ +database-aggressive:4 +firestore-aggressive:1 +functions-aggressive:2 +storage-aggressive:3 diff --git a/tools/apksize/src/database/database.gradle b/tools/apksize/src/database/database.gradle new file mode 100644 index 00000000000..f79341626d2 --- /dev/null +++ b/tools/apksize/src/database/database.gradle @@ -0,0 +1,34 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +android { + productFlavors { + database { + dimension "apkSize" + applicationId "com.google.apksize.database" + } + } + sourceSets { + database { + java.srcDirs = [ + "src/database/java", + ] + } + } +} +dependencies { + databaseImplementation project(":firebase-database") + databaseImplementation project(":firebase-common") +} diff --git a/tools/apksize/src/database/java/com.google.apksize/RealtimeDatabase.java b/tools/apksize/src/database/java/com.google.apksize/RealtimeDatabase.java new file mode 100644 index 00000000000..8a171b6f293 --- /dev/null +++ b/tools/apksize/src/database/java/com.google.apksize/RealtimeDatabase.java @@ -0,0 +1,50 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; +import android.util.Log; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; + +public class RealtimeDatabase implements SampleCode { + + @Override + public void runSample(Context context) { + + FirebaseApp.initializeApp(context, new FirebaseOptions.Builder().build()); + + DatabaseReference db = + FirebaseDatabase.getInstance().getReference("android/saving-data/fireblog/posts"); + + db.addListenerForSingleValueEvent( + new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + Log.d("test", "onDataChange"); + } + + @Override + public void onCancelled(DatabaseError databaseError) { + Log.d("test", "onDataChange"); + } + }); + } +} diff --git a/tools/apksize/src/database/java/com.google.apksize/SampleCodeLoader.java b/tools/apksize/src/database/java/com.google.apksize/SampleCodeLoader.java new file mode 100644 index 00000000000..863a17c7379 --- /dev/null +++ b/tools/apksize/src/database/java/com.google.apksize/SampleCodeLoader.java @@ -0,0 +1,24 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; + +public class SampleCodeLoader { + + public void runSamples(Context context) { + new RealtimeDatabase().runSample(context); + } +} diff --git a/tools/apksize/src/empty/empty.gradle b/tools/apksize/src/empty/empty.gradle new file mode 100644 index 00000000000..bcd80ddcfeb --- /dev/null +++ b/tools/apksize/src/empty/empty.gradle @@ -0,0 +1,23 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +android { + productFlavors { + empty { + dimension "apkSize" + applicationId "com.google.apksize.empty" + } + } +} diff --git a/tools/apksize/src/empty/java/com.google.apksize/SampleCodeLoader.java b/tools/apksize/src/empty/java/com.google.apksize/SampleCodeLoader.java new file mode 100644 index 00000000000..531038ac30b --- /dev/null +++ b/tools/apksize/src/empty/java/com.google.apksize/SampleCodeLoader.java @@ -0,0 +1,22 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; + +public class SampleCodeLoader { + + public void runSamples(Context context) {} +} diff --git a/tools/apksize/src/firestore/firestore.gradle b/tools/apksize/src/firestore/firestore.gradle new file mode 100644 index 00000000000..16ced1f72f4 --- /dev/null +++ b/tools/apksize/src/firestore/firestore.gradle @@ -0,0 +1,35 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +android { + productFlavors { + firestore { + dimension "apkSize" + applicationId "com.google.apksize.firestore" + } + } + sourceSets { + firestore { + java.srcDirs = [ + "src/firestore/java", + ] + } + } +} +dependencies { + firestoreImplementation project(":firebase-firestore") + firestoreImplementation "com.google.android.gms:play-services-auth:16.0.1" + firestoreImplementation "com.android.support:support-v4:27.1.0" +} diff --git a/tools/apksize/src/firestore/java/com.google.apksize/Firestore.java b/tools/apksize/src/firestore/java/com.google.apksize/Firestore.java new file mode 100644 index 00000000000..9b90237c1a2 --- /dev/null +++ b/tools/apksize/src/firestore/java/com.google.apksize/Firestore.java @@ -0,0 +1,99 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.Log; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.firestore.QuerySnapshot; +import java.util.HashMap; +import java.util.Map; + +public class Firestore implements SampleCode { + private static final String TAG = "sizetest"; + + @Override + public void runSample(Context context) { + FirebaseFirestore db = FirebaseFirestore.getInstance(); + + Map user = new HashMap<>(); + user.put("first", "Ada"); + user.put("last", "Lovelace"); + user.put("born", 1815); + + db.collection("users") + .add(user) + .addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(DocumentReference documentReference) { + Log.d(TAG, "DocumentSnapshot added with ID: " + documentReference.getId()); + } + }) + .addOnFailureListener( + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "Error adding document", e); + } + }); + // Create a new user with a first, middle, and last name + user = new HashMap<>(); + user.put("first", "Alan"); + user.put("middle", "Mathison"); + user.put("last", "Turing"); + user.put("born", 1912); + + // Add a new document with a generated ID + db.collection("users") + .add(user) + .addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(DocumentReference documentReference) { + Log.d(TAG, "DocumentSnapshot added with ID: " + documentReference.getId()); + } + }) + .addOnFailureListener( + new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "Error adding document", e); + } + }); + db.collection("users") + .get() + .addOnCompleteListener( + new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + for (QueryDocumentSnapshot document : task.getResult()) { + Log.d(TAG, document.getId() + " => " + document.getData()); + } + } else { + Log.w(TAG, "Error getting documents.", task.getException()); + } + } + }); + } +} diff --git a/tools/apksize/src/firestore/java/com.google.apksize/SampleCodeLoader.java b/tools/apksize/src/firestore/java/com.google.apksize/SampleCodeLoader.java new file mode 100644 index 00000000000..32506d8417d --- /dev/null +++ b/tools/apksize/src/firestore/java/com.google.apksize/SampleCodeLoader.java @@ -0,0 +1,24 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; + +public class SampleCodeLoader { + + public void runSamples(Context context) { + new Firestore().runSample(context); + } +} diff --git a/tools/apksize/src/functions/functions.gradle b/tools/apksize/src/functions/functions.gradle new file mode 100644 index 00000000000..f6ff6894251 --- /dev/null +++ b/tools/apksize/src/functions/functions.gradle @@ -0,0 +1,35 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +android { + productFlavors { + functions { + dimension "apkSize" + applicationId "com.google.apksize.functions" + } + } + sourceSets { + functions { + java.srcDirs = [ + "src/functions/java", + ] + } + } +} +dependencies { + functionsImplementation project(":firebase-functions") + functionsImplementation "com.google.android.gms:play-services-auth:16.0.1" + functionsImplementation "com.android.support:support-v4:27.1.0" +} diff --git a/tools/apksize/src/functions/java/com.google.apksize/Functions.java b/tools/apksize/src/functions/java/com.google.apksize/Functions.java new file mode 100644 index 00000000000..07c29fa814e --- /dev/null +++ b/tools/apksize/src/functions/java/com.google.apksize/Functions.java @@ -0,0 +1,51 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; +import android.support.annotation.NonNull; +import com.google.android.gms.tasks.Continuation; +import com.google.android.gms.tasks.Task; +import com.google.firebase.functions.FirebaseFunctions; +import com.google.firebase.functions.HttpsCallableResult; +import java.util.HashMap; +import java.util.Map; + +public class Functions implements SampleCode { + private static final String TAG = "sizetest"; + + @Override + public void runSample(Context context) { + FirebaseFunctions mFunctions = FirebaseFunctions.getInstance(); + Map data = new HashMap<>(); + data.put("firstNumber", 5); + data.put("secondNumber", 6); + // Call the function and extract the operation from the result + mFunctions + .getHttpsCallable("addNumbers") + .call(data) + .continueWith( + new Continuation() { + @Override + public Integer then(@NonNull Task task) throws Exception { + // This continuation runs on either success or failure, but if the task + // has failed then getResult() will throw an Exception which will be + // propagated down. + Map result = (Map) task.getResult().getData(); + return (Integer) result.get("operationResult"); + } + }); + } +} diff --git a/tools/apksize/src/functions/java/com.google.apksize/SampleCodeLoader.java b/tools/apksize/src/functions/java/com.google.apksize/SampleCodeLoader.java new file mode 100644 index 00000000000..d02b7f649f6 --- /dev/null +++ b/tools/apksize/src/functions/java/com.google.apksize/SampleCodeLoader.java @@ -0,0 +1,24 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; + +public class SampleCodeLoader { + + public void runSamples(Context context) { + new Functions().runSample(context); + } +} diff --git a/tools/apksize/src/main/AndroidManifest.xml b/tools/apksize/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..6c4dd85b647 --- /dev/null +++ b/tools/apksize/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/tools/apksize/src/main/java/com.google.apksize/MainActivity.java b/tools/apksize/src/main/java/com.google.apksize/MainActivity.java new file mode 100644 index 00000000000..89dad108bc6 --- /dev/null +++ b/tools/apksize/src/main/java/com.google.apksize/MainActivity.java @@ -0,0 +1,36 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +/** + * Every flavor contains SampleCodeLoader class that executes corresponding SampleCode instances + * + *

MainActivity will take SampleCodeLoader from every flavor and run the samples + * + *

We need to do this in order to prevent cutting out packages during pro-guarding + */ +import android.app.Activity; +import android.os.Bundle; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + new SampleCodeLoader().runSamples(this); + } +} diff --git a/tools/apksize/src/main/java/com.google.apksize/SampleCode.java b/tools/apksize/src/main/java/com.google.apksize/SampleCode.java new file mode 100644 index 00000000000..4c6bc01fff4 --- /dev/null +++ b/tools/apksize/src/main/java/com.google.apksize/SampleCode.java @@ -0,0 +1,22 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +/** SampleCode interface allows to load sample code for the selected build variant */ +import android.content.Context; + +public interface SampleCode { + void runSample(Context context); +} diff --git a/tools/apksize/src/main/res/layout/activity_main.xml b/tools/apksize/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..b3a97b3050b --- /dev/null +++ b/tools/apksize/src/main/res/layout/activity_main.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/tools/apksize/src/main/res/mipmap-hdpi/ic_launcher.png b/tools/apksize/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..cde69bcccec Binary files /dev/null and b/tools/apksize/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/tools/apksize/src/main/res/values/colors.xml b/tools/apksize/src/main/res/values/colors.xml new file mode 100644 index 00000000000..3ab3e9cbce0 --- /dev/null +++ b/tools/apksize/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/tools/apksize/src/main/res/values/dimens.xml b/tools/apksize/src/main/res/values/dimens.xml new file mode 100644 index 00000000000..812cb7be0a1 --- /dev/null +++ b/tools/apksize/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + 16dp + 16dp + 16dp + diff --git a/tools/apksize/src/main/res/values/strings.xml b/tools/apksize/src/main/res/values/strings.xml new file mode 100644 index 00000000000..2ff58d59c34 --- /dev/null +++ b/tools/apksize/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + APKSize + Test event + diff --git a/tools/apksize/src/main/res/values/styles.xml b/tools/apksize/src/main/res/values/styles.xml new file mode 100644 index 00000000000..20ef219a2cc --- /dev/null +++ b/tools/apksize/src/main/res/values/styles.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/tools/apksize/src/storage/java/com.google.apksize/SampleCodeLoader.java b/tools/apksize/src/storage/java/com.google.apksize/SampleCodeLoader.java new file mode 100644 index 00000000000..c2c985d1df0 --- /dev/null +++ b/tools/apksize/src/storage/java/com.google.apksize/SampleCodeLoader.java @@ -0,0 +1,24 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; + +public class SampleCodeLoader { + + public void runSamples(Context context) { + new Storage().runSample(context); + } +} diff --git a/tools/apksize/src/storage/java/com.google.apksize/Storage.java b/tools/apksize/src/storage/java/com.google.apksize/Storage.java new file mode 100644 index 00000000000..5ba43e5e2a0 --- /dev/null +++ b/tools/apksize/src/storage/java/com.google.apksize/Storage.java @@ -0,0 +1,32 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.apksize; + +import android.content.Context; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.StorageReference; +import java.io.File; + +public class Storage implements SampleCode { + + @Override + public void runSample(Context context) { + + String mBucketName = + "gs://" + context.getString(R.string.google_project_id) + ".storage.firebase.com"; + StorageReference mStorageRef = FirebaseStorage.getInstance().getReference(mBucketName); + mStorageRef.getFile(new File("")); + } +} diff --git a/tools/apksize/src/storage/res/values/strings.xml b/tools/apksize/src/storage/res/values/strings.xml new file mode 100644 index 00000000000..bee72ee1706 --- /dev/null +++ b/tools/apksize/src/storage/res/values/strings.xml @@ -0,0 +1,4 @@ + + APKSize + apk-size-37f26 + diff --git a/tools/apksize/src/storage/storage.gradle b/tools/apksize/src/storage/storage.gradle new file mode 100644 index 00000000000..5d07491c18f --- /dev/null +++ b/tools/apksize/src/storage/storage.gradle @@ -0,0 +1,35 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +android { + productFlavors { + storage { + dimension "apkSize" + applicationId "com.google.apksize.storage" + } + } + sourceSets { + storage { + java.srcDirs = [ + "src/storage/java", + ] + } + } +} + +dependencies { + storageImplementation project(":firebase-storage") + storageImplementation "com.google.android.gms:play-services-tasks:16.0.1" +}