All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gms.googleservices.GoogleServicesTask Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
/*
 * 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.base.Strings;
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.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencySet;
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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 */
public class GoogleServicesTask extends DefaultTask {

    private static final String STATUS_DISABLED = "1";
    private static final String STATUS_ENABLED = "2";

    private static final String OAUTH_CLIENT_TYPE_WEB = "3";

    private static final Pattern GOOGLE_APP_ID_REGEX =
        Pattern.compile("(\\d+):(\\d+):(\\p{Alnum}+):(\\p{XDigit}+)");
    private static final String GOOGLE_APP_ID_VERSION = "1";

    /**
     * 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;

    @Input
    public String moduleGroup;

    @Input
    public String moduleGroupFirebase;

    @Input
    public String moduleVersion;

    @Input
    public String searchedLocation;

    @TaskAction
    public void action() throws IOException {
        checkVersionConflict();
        if (!quickstartFile.isFile()) {
            throw new GradleException(String.format("File %s is missing. " +
                 "The Google Services Plugin cannot function without it. %n Searched Location: %s",
                 quickstartFile.getName(), searchedLocation));
        }

        getProject().getLogger().warn("Parsing json file: " + quickstartFile.getPath());

        // 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 resValues = new TreeMap();
        Map> resAttributes = new TreeMap>();

        handleProjectNumberAndProjectId(rootObject, resValues);
        handleFirebaseUrl(rootObject, resValues);

        JsonObject clientObject = getClientForPackageName(rootObject);

        if (clientObject != null) {
            handleAnalytics(clientObject, resValues);
            handleMapsService(clientObject, resValues);
            handleGoogleApiKey(clientObject, resValues);
            handleGoogleAppId(clientObject, resValues);
            handleWebClientId(clientObject, resValues);
        } else {
            throw new GradleException("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, resAttributes), new File(values, "values.xml"), Charsets.UTF_8);
    }

    /**
     * Check if there is any conflict between Play-Services Version
     */
    private void checkVersionConflict() {
        Project project = getProject();
        ConfigurationContainer configurations = project.getConfigurations();
        if (configurations == null) {
            return;
        }
        boolean hasConflict = false;
        for (Configuration configuration : configurations) {
            if (configuration == null) {
                continue;
            }
            DependencySet dependencies = configuration.getDependencies();
            if (dependencies == null) {
                continue;
            }
            for (Dependency dependency : dependencies) {
                if (dependency == null || dependency.getGroup() == null || dependency.getVersion() == null) {
                    continue;
                }
                String dependencyGroup = dependency.getGroup();
                String dependencyVersion = dependency.getVersion();
                if ((dependencyGroup.equals(moduleGroup) || dependencyGroup.equals(moduleGroupFirebase))
                        && !dependencyVersion.equals(moduleVersion)) {
                    hasConflict = true;
                    project.getLogger().warn("Found " + dependencyGroup + ":" +
                        dependency.getName() + ":" + dependencyVersion + ", but version " +
                        moduleVersion + " is needed for the google-services plugin.");
                }
            }
        }
        if (hasConflict) {
            throw new GradleException("Please fix the version conflict either by updating the version " +
                "of the google-services plugin (information about the latest version is available at " +
                "https://bintray.com/android/android-tools/com.google.gms.google-services/) or updating " +
                "the version of " + moduleGroup + " to " + moduleVersion + ".");
        }
    }

    private void handleFirebaseUrl(JsonObject rootObject, Map resValues)
            throws IOException {
        JsonObject projectInfo = rootObject.getAsJsonObject("project_info");
        if (projectInfo == null) {
            throw new GradleException("Missing project_info object");
        }

        JsonPrimitive firebaseUrl = projectInfo.getAsJsonPrimitive("firebase_url");
        if (firebaseUrl != null) {
            resValues.put("firebase_database_url", firebaseUrl.getAsString());
        }
    }

    /**
     * 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 handleProjectNumberAndProjectId(JsonObject rootObject, Map 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());

        JsonPrimitive projectId = projectInfo.getAsJsonPrimitive("project_id");

        if (projectId == null) {
            throw new GradleException("Missing project_info/project_id object");
        }
        resValues.put("project_id", projectId.getAsString());


        JsonPrimitive bucketName = projectInfo.getAsJsonPrimitive("storage_bucket");
        if (bucketName != null) {
            resValues.put("google_storage_bucket", bucketName.getAsString());
        }

    }

    private void handleWebClientId(JsonObject clientObject, Map resValues) {
        JsonArray array = clientObject.getAsJsonArray("oauth_client");
        if (array != null) {
            final int count = array.size();
            for (int i = 0 ; i < count ; i++) {
                JsonElement oauthClientElement = array.get(i);
                if (oauthClientElement == null || !oauthClientElement.isJsonObject()) {
                    continue;
                }
                JsonObject oauthClientObject = oauthClientElement.getAsJsonObject();
                JsonPrimitive clientType = oauthClientObject.getAsJsonPrimitive("client_type");
                if (clientType == null) {
                    continue;
                }
                String clientTypeStr = clientType.getAsString();
                if (!OAUTH_CLIENT_TYPE_WEB.equals(clientTypeStr)) {
                    continue;
                }
                JsonPrimitive clientId = oauthClientObject.getAsJsonPrimitive("client_id");
                if (clientId == null) {
                    continue;
                }
                resValues.put("default_web_client_id", clientId.getAsString());
                return;
            }
        }
    }

    /**
     * Handle a client object for analytics (@xml/global_tracker)
     * @param clientObject the client Json object.
     * @throws IOException
     */
    private void handleAnalytics(JsonObject clientObject, Map 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 maps (@string/google_maps_key).
     * @param clientObject the client Json object.
     * @throws IOException
     */
    private void handleMapsService(JsonObject clientObject, Map resValues)
            throws IOException {
        JsonObject mapsService = getServiceByName(clientObject, "maps_service");
        if (mapsService == null) return;

        String apiKey = getAndroidApiKey(clientObject);
        if (apiKey != null) {
            resValues.put("google_maps_key", apiKey);
            return;
        }
        throw new GradleException("Missing api_key/current_key object");
    }

    private void handleGoogleApiKey(JsonObject clientObject, Map resValues) {
        String apiKey = getAndroidApiKey(clientObject);
        if (apiKey != null) {
            resValues.put("google_api_key", apiKey);
            // TODO: remove this once SDK starts to use google_api_key.
            resValues.put("google_crash_reporting_api_key", apiKey);
            return;
        }

        // if google_crash_reporting_api_key is missing.
        // throw new GradleException("Missing api_key/current_key object");
        throw new GradleException("Missing api_key/current_key object");
    }

    private String getAndroidApiKey(JsonObject clientObject) {
        JsonArray array = clientObject.getAsJsonArray("api_key");
        if (array != null) {
            final int count = array.size();
            for (int i = 0 ; i < count ; i++) {
                JsonElement apiKeyElement = array.get(i);
                if (apiKeyElement == null || !apiKeyElement.isJsonObject()) {
                    continue;
                }
                JsonObject apiKeyObject = apiKeyElement.getAsJsonObject();
                JsonPrimitive currentKey = apiKeyObject.getAsJsonPrimitive("current_key");
                if (currentKey == null) {
                    continue;
                }
                return currentKey.getAsString();
            }
        }
        return null;
    }

    private static void findStringByName(JsonObject jsonObject, String stringName,
            Map 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;
    }

    /**
     * Handle a client object for Google App Id.
     */
    private void handleGoogleAppId(JsonObject clientObject, Map resValues)
            throws IOException {
        JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
        if (clientInfo == null) {
            // Should not happen
            throw new GradleException("Client does not have client info");
        }

        JsonPrimitive googleAppId = clientInfo.getAsJsonPrimitive("mobilesdk_app_id");

        String googleAppIdStr = googleAppId == null ? null : googleAppId.getAsString();
        if (Strings.isNullOrEmpty(googleAppIdStr)) {
            throw new GradleException("Missing Google App Id. " +
                "Please follow instructions on https://firebase.google.com/ to get a valid " +
                "config file that contains a Google App Id");
        }

        Matcher matcher = GOOGLE_APP_ID_REGEX.matcher(googleAppIdStr);
        if (!matcher.matches()) {
            throw new GradleException("Unexpected format of Google App ID. " +
                "Please follow instructions on https://firebase.google.com/ to get a config file " +
                "that contains a valid Google App Id or update the plugin version if you believe " +
                "your Google App Id [" + googleAppIdStr + "] is correct.");
        }

        String version = matcher.group(1);
        if (!GOOGLE_APP_ID_VERSION.equals(version)) {
            throw new GradleException("Google App Id Version is incompatible with this plugin. " +
                "Please update the plugin version.");
        }

        String platform = matcher.group(3);
        if (!platform.equals("android")) {
            throw new GradleException("Expect Google App Id for Android App, but get " + platform);
        }

        resValues.put("google_app_id", googleAppIdStr);
    }

    /**
     * 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 "\n" +
                "\n" +
                "    " + ga_trackingId + "\n" +
                "\n";
    }

    private static String getValuesContent(Map values,
        Map> attributes) {
        StringBuilder sb = new StringBuilder(256);

        sb.append("\n" +
                "\n");

        for (Map.Entry entry : values.entrySet()) {
            String name = entry.getKey();
            sb.append("     attr : attributes.get(name).entrySet()) {
                    sb.append(" ").append(attr.getKey()).append("=\"")
                        .append(attr.getValue()).append("\"");
                }
            }
            sb.append(">").append(entry.getValue()).append("\n");
        }

        sb.append("\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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy