/* * Copyright (C) 2015 The Android Open Source Project * * 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.gms.googleservices; import com.google.common.base.Charsets; import com.google.common.io.Files; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.TreeMap; /** */ public class GoogleServicesTask extends DefaultTask { private static final String STATUS_DISABLED = "1"; private static final String STATUS_ENABLED = "2"; /** * The input is not technically optional but we want to control the error message. * Without @Optional, Gradle will complain itself the file is missing. */ @InputFile @Optional public File quickstartFile; @OutputDirectory public File intermediateDir; @Input public String packageName; @TaskAction public void action() throws IOException { if (!quickstartFile.isFile()) { getLogger().warn("File " + quickstartFile.getName() + " is missing from module root folder." + " The Google Quickstart Plugin cannot function without it."); // Skip the rest of the actions because it would not make sense if `quickstartFile` is missing. return; } // delete content of outputdir. deleteFolder(intermediateDir); if (!intermediateDir.mkdirs()) { throw new GradleException("Failed to create folder: " + intermediateDir); } JsonElement root = new JsonParser().parse(Files.newReader(quickstartFile, Charsets.UTF_8)); if (!root.isJsonObject()) { throw new GradleException("Malformed root json"); } JsonObject rootObject = root.getAsJsonObject(); Map<String, String> resValues = new TreeMap<String, String>(); handleProjectNumber(rootObject, resValues); JsonObject clientObject = getClientForPackageName(rootObject); if (clientObject != null) { handleAnalytics(clientObject, resValues); handleAdsService(clientObject, resValues); } else { getLogger().warn("No matching client found for package name '" + packageName + "'"); } // write the values file. File values = new File(intermediateDir, "values"); if (!values.exists() && !values.mkdirs()) { throw new GradleException("Failed to create folder: " + values); } Files.write(getValuesContent(resValues), new File(values, "values.xml"), Charsets.UTF_8); } /** * Handle project_info/project_number for @string/gcm_defaultSenderId, and fill the res map with the read value. * @param rootObject the root Json object. * @throws IOException */ private void handleProjectNumber(JsonObject rootObject, Map<String, String> resValues) throws IOException { JsonObject projectInfo = rootObject.getAsJsonObject("project_info"); if (projectInfo == null) { throw new GradleException("Missing project_info object"); } JsonPrimitive projectNumber = projectInfo.getAsJsonPrimitive("project_number"); if (projectNumber == null) { throw new GradleException("Missing project_info/project_number object"); } resValues.put("gcm_defaultSenderId", projectNumber.getAsString()); } /** * Handle a client object for analytics (@xml/global_tracker) * @param clientObject the client Json object. * @throws IOException */ private void handleAnalytics(JsonObject clientObject, Map<String, String> resValues) throws IOException { JsonObject analyticsService = getServiceByName(clientObject, "analytics_service"); if (analyticsService == null) return; JsonObject analyticsProp = analyticsService.getAsJsonObject("analytics_property"); if (analyticsProp == null) return; JsonPrimitive trackingId = analyticsProp.getAsJsonPrimitive("tracking_id"); if (trackingId == null) return; resValues.put("ga_trackingId", trackingId.getAsString()); File xml = new File(intermediateDir, "xml"); if (!xml.exists() && !xml.mkdirs()) { throw new GradleException("Failed to create folder: " + xml); } Files.write(getGlobalTrackerContent( trackingId.getAsString()), new File(xml, "global_tracker.xml"), Charsets.UTF_8); } /** * Handle a client object for analytics (@xml/global_tracker) * @param clientObject the client Json object. * @throws IOException */ private void handleAdsService(JsonObject clientObject, Map<String, String> resValues) throws IOException { JsonObject adsService = getServiceByName(clientObject, "ads_service"); if (adsService == null) return; findStringByName(adsService, "test_banner_ad_unit_id", resValues); findStringByName(adsService, "test_interstitial_ad_unit_id", resValues); } private static void findStringByName(JsonObject jsonObject, String stringName, Map<String, String> resValues) { JsonPrimitive id = jsonObject.getAsJsonPrimitive(stringName); if (id != null) { resValues.put(stringName, id.getAsString()); } } /** * find an item in the "client" array that match the package name of the app * @param jsonObject the root json object. * @return a JsonObject representing the client entry or null if no match is found. */ private JsonObject getClientForPackageName(JsonObject jsonObject) { JsonArray array = jsonObject.getAsJsonArray("client"); if (array != null) { final int count = array.size(); for (int i = 0 ; i < count ; i++) { JsonElement clientElement = array.get(i); if (clientElement == null || !clientElement.isJsonObject()) { continue; } JsonObject clientObject = clientElement.getAsJsonObject(); JsonObject clientInfo = clientObject.getAsJsonObject("client_info"); if (clientInfo == null) continue; JsonObject androidClientInfo = clientInfo.getAsJsonObject("android_client_info"); if (androidClientInfo == null) continue; JsonPrimitive clientPackageName = androidClientInfo.getAsJsonPrimitive("package_name"); if (clientPackageName == null) continue; if (packageName.equals(clientPackageName.getAsString())) { return clientObject; } } } return null; } /** * Finds a service by name in the client object. Returns null if the service is not found * or if the service is disabled. * * @param clientObject the json object that represents the client. * @param serviceName the service name * @return the service if found. */ private JsonObject getServiceByName(JsonObject clientObject, String serviceName) { JsonObject services = clientObject.getAsJsonObject("services"); if (services == null) return null; JsonObject service = services.getAsJsonObject(serviceName); if (service == null) return null; JsonPrimitive status = service.getAsJsonPrimitive("status"); if (status == null) return null; String statusStr = status.getAsString(); if (STATUS_DISABLED.equals(statusStr)) return null; if (!STATUS_ENABLED.equals(statusStr)) { getLogger().warn(String.format("Status with value '%1$s' for service '%2$s' is unknown", statusStr, serviceName)); return null; } return service; } private static String getGlobalTrackerContent(String ga_trackingId) { return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>\n" + " <string name=\"ga_trackingId\">" + ga_trackingId + "</string>\n" + "</resources>\n"; } private static String getValuesContent(Map<String, String> entries) { StringBuilder sb = new StringBuilder(256); sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>\n"); for (Map.Entry<String, String> entry : entries.entrySet()) { sb.append(" <string name=\"").append(entry.getKey()).append("\">") .append(entry.getValue()).append("</string>\n"); } sb.append("</resources>\n"); return sb.toString(); } private static void deleteFolder(final File folder) { if (!folder.exists()) { return; } File[] files = folder.listFiles(); if (files != null) { for (final File file : files) { if (file.isDirectory()) { deleteFolder(file); } else { if (!file.delete()) { throw new GradleException("Failed to delete: " + file); } } } } if (!folder.delete()) { throw new GradleException("Failed to delete: " + folder); } } }