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

com.android.build.gradle.internal.VariantManager Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version
/*
 * Copyright (C) 2014 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.android.build.gradle.internal;

import static com.android.build.OutputFile.NO_FILTER;
import static com.android.builder.core.BuilderConstants.DEBUG;
import static com.android.builder.core.BuilderConstants.LINT;
import static com.android.builder.core.VariantType.ANDROID_TEST;
import static com.android.builder.core.VariantType.UNIT_TEST;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.BaseExtension;
import com.android.build.gradle.api.AndroidSourceSet;
import com.android.build.gradle.api.BaseVariant;
import com.android.build.gradle.internal.ProductFlavorData.ConfigurationProviderImpl;
import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
import com.android.build.gradle.internal.api.ReadOnlyObjectProvider;
import com.android.build.gradle.internal.api.TestVariantImpl;
import com.android.build.gradle.internal.api.TestedVariant;
import com.android.build.gradle.internal.api.VariantFilter;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.dependency.VariantDependencies;
import com.android.build.gradle.internal.dsl.BuildType;
import com.android.build.gradle.internal.dsl.GroupableProductFlavor;
import com.android.build.gradle.internal.dsl.ProductFlavor;
import com.android.build.gradle.internal.dsl.SigningConfig;
import com.android.build.gradle.internal.dsl.Splits;
import com.android.build.gradle.internal.variant.ApplicationVariantFactory;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.build.gradle.internal.variant.BaseVariantOutputData;
import com.android.build.gradle.internal.variant.TestVariantData;
import com.android.build.gradle.internal.variant.TestedVariantData;
import com.android.build.gradle.internal.variant.VariantFactory;
import com.android.builder.core.AndroidBuilder;
import com.android.builder.core.VariantType;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.internal.reflect.Instantiator;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import groovy.lang.Closure;

/**
 * Class to create, manage variants.
 */
public class VariantManager implements VariantModel {

    protected static final String COM_ANDROID_SUPPORT_MULTIDEX =
            "com.android.support:multidex:1.0.0";

    @NonNull
    private final Project project;
    @NonNull
    private final AndroidBuilder androidBuilder;
    @NonNull
    private final BaseExtension extension;
    @NonNull
    private final VariantFactory variantFactory;
    @NonNull
    private final TaskManager taskManager;
    @NonNull
    private final Instantiator instantiator;
    @NonNull
    private ProductFlavorData defaultConfigData;
    @NonNull
    private final Map buildTypes = Maps.newHashMap();
    @NonNull
    private final Map> productFlavors = Maps.newHashMap();
    @NonNull
    private final Map signingConfigs = Maps.newHashMap();

    @NonNull
    private final ReadOnlyObjectProvider readOnlyObjectProvider = new ReadOnlyObjectProvider();
    @NonNull
    private final VariantFilter variantFilter = new VariantFilter(readOnlyObjectProvider);

    @NonNull
    private final List> variantDataList = Lists.newArrayList();

    public VariantManager(
            @NonNull Project project,
            @NonNull AndroidBuilder androidBuilder,
            @NonNull BaseExtension extension,
            @NonNull VariantFactory variantFactory,
            @NonNull TaskManager taskManager,
            @NonNull Instantiator instantiator) {
        this.extension = extension;
        this.androidBuilder = androidBuilder;
        this.project = project;
        this.variantFactory = variantFactory;
        this.taskManager = taskManager;
        this.instantiator = instantiator;

        DefaultAndroidSourceSet mainSourceSet =
                (DefaultAndroidSourceSet) extension.getSourceSets().getByName(extension.getDefaultConfig().getName());
        DefaultAndroidSourceSet androidTestSourceSet =
                (DefaultAndroidSourceSet) extension.getSourceSets().getByName(ANDROID_TEST.getPrefix());
        DefaultAndroidSourceSet unitTestSourceSet =
                (DefaultAndroidSourceSet) extension.getSourceSets().getByName(UNIT_TEST.getPrefix());

        defaultConfigData = new ProductFlavorData(
                extension.getDefaultConfig(), mainSourceSet,
                androidTestSourceSet, unitTestSourceSet, project);
    }

    @NonNull
    @Override
    public ProductFlavorData getDefaultConfig() {
        return defaultConfigData;
    }

    @Override
    @NonNull
    public Map getBuildTypes() {
        return buildTypes;
    }

    @Override
    @NonNull
    public Map> getProductFlavors() {
        return productFlavors;
    }

    @Override
    @NonNull
    public Map getSigningConfigs() {
        return signingConfigs;
    }

    public void addSigningConfig(@NonNull SigningConfig signingConfig) {
        signingConfigs.put(signingConfig.getName(), signingConfig);
    }

    /**
     * Adds new BuildType, creating a BuildTypeData, and the associated source set,
     * and adding it to the map.
     * @param buildType the build type.
     */
    public void addBuildType(@NonNull BuildType buildType) {
        buildType.init(signingConfigs.get(DEBUG));

        String name = buildType.getName();
        checkName(name, "BuildType");

        if (productFlavors.containsKey(name)) {
            throw new RuntimeException("BuildType names cannot collide with ProductFlavor names");
        }

        DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(name);
        DefaultAndroidSourceSet unitTestSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
                UNIT_TEST.getPrefix() + StringHelper.capitalize(buildType.getName()));

        BuildTypeData buildTypeData = new BuildTypeData(buildType, project, mainSourceSet, unitTestSourceSet);
        project.getTasks().getByName("assemble").dependsOn(buildTypeData.getAssembleTask());

        buildTypes.put(name, buildTypeData);
    }

    /**
     * Adds a new ProductFlavor, creating a ProductFlavorData and associated source sets,
     * and adding it to the map.
     *
     * @param productFlavor the product flavor
     */
    public void addProductFlavor(@NonNull GroupableProductFlavor productFlavor) {
        String name = productFlavor.getName();
        checkName(name, "ProductFlavor");

        if (buildTypes.containsKey(name)) {
            throw new RuntimeException("ProductFlavor names cannot collide with BuildType names");
        }

        DefaultAndroidSourceSet mainSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
                productFlavor.getName());
        DefaultAndroidSourceSet androidTestSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
                ANDROID_TEST.getPrefix() + StringHelper.capitalize(productFlavor.getName()));
        DefaultAndroidSourceSet unitTestSourceSet = (DefaultAndroidSourceSet) extension.getSourceSetsContainer().maybeCreate(
                UNIT_TEST.getPrefix() + StringHelper.capitalize(productFlavor.getName()));

        ProductFlavorData productFlavorData =
                new ProductFlavorData(
                        productFlavor, mainSourceSet, androidTestSourceSet,
                        unitTestSourceSet, project);

        productFlavors.put(productFlavor.getName(), productFlavorData);
    }

    /**
     * Return a list of all created VariantData.
     */
    @NonNull
    public List> getVariantDataList() {
        return variantDataList;
    }

    /**
     * Task creation entry point.
     */
    public void createAndroidTasks(@Nullable com.android.builder.model.SigningConfig signingOverride) {
        variantFactory.validateModel(this);

        taskManager.createAssembleAndroidTestTask();

        if (variantDataList.isEmpty()) {
            populateVariantDataList(signingOverride);
        }

        for (BaseVariantData variantData : variantDataList) {
            createTasksForVariantData(project.getTasks(), variantData);
        }

        // create the lint tasks.
        taskManager.createLintTasks(variantDataList);

        // create the test tasks.
        taskManager.createConnectedCheckTasks(variantDataList, !productFlavors.isEmpty(), false /*isLibrary*/);
        taskManager.createUnitTestTasks(variantDataList);

        // Create the variant API objects after the tasks have been created!
        createApiObjects();

        taskManager.createReportTasks(variantDataList);
    }

    /**
     * Create tasks for the specified variantData.
     */
    public void createTasksForVariantData(TaskContainer tasks,
            BaseVariantData variantData) {
        VariantType variantType = variantData.getType();

        if (variantType.isForTesting()) {
            GradleVariantConfiguration testVariantConfig = variantData.getVariantConfiguration();
            BaseVariantData testedVariantData = (BaseVariantData) ((TestVariantData) variantData)
                    .getTestedVariantData();

            /// add the container of dependencies
            // the order of the libraries is important. In descending order:
            // flavors, defaultConfig. No build type for tests
            List testVariantProviders = Lists.newArrayListWithExpectedSize(
                    2 + testVariantConfig.getProductFlavors().size());

            for (com.android.build.gradle.api.GroupableProductFlavor productFlavor :
                    testVariantConfig.getProductFlavors()) {
                ProductFlavorData data =
                        productFlavors.get(productFlavor.getName());
                testVariantProviders.add(data.getTestConfigurationProvider(variantType));
            }

            // now add the default config
            testVariantProviders.add(defaultConfigData.getTestConfigurationProvider(variantType));

            assert(testVariantConfig.getTestedConfig() != null);
            if (testVariantConfig.getTestedConfig().getType() == VariantType.LIBRARY) {
                testVariantProviders.add(testedVariantData.getVariantDependency());
            }

            // If the variant being tested is a library variant, VariantDependencies must be
            // computed after the tasks for the tested variant is created.  Therefore, the
            // VariantDependencies is computed here instead of when the VariantData was created.
            VariantDependencies variantDep = VariantDependencies.compute(
                    project, testVariantConfig.getFullName(),
                    false /*publishVariant*/,
                    variantFactory.isLibrary(),
                    testVariantProviders.toArray(
                            new ConfigurationProvider[testVariantProviders.size()]));
            variantData.setVariantDependency(variantDep);

            taskManager.resolveDependencies(variantDep,
                    testVariantConfig.getTestedConfig().getType() == VariantType.LIBRARY
                        ? null
                        : testedVariantData.getVariantDependency());
            testVariantConfig.setDependencies(variantDep);
            switch (variantType) {
                case ANDROID_TEST:
                    taskManager.createAndroidTestVariantTasks((TestVariantData) variantData);
                    break;
                case UNIT_TEST:
                    taskManager.createUnitTestVariantTasks((TestVariantData) variantData);
                    break;
                default:
                    throw new IllegalArgumentException("Unknown test type " + variantType);
            }
        } else {
            if (productFlavors.isEmpty()) {
                taskManager.createTasksForVariantData(
                        variantData,
                        buildTypes.get(
                                variantData.getVariantConfiguration().getBuildType().getName())
                                .getAssembleTask());
            } else {
                taskManager.createTasksForVariantData(variantData, null);

                // setup the task dependencies
                // build type
                buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName())
                        .getAssembleTask().dependsOn(variantData.assembleVariantTask);

                // each flavor
                GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
                for (GroupableProductFlavor flavor : variantConfig.getProductFlavors()) {
                    productFlavors.get(flavor.getName()).getAssembleTask()
                            .dependsOn(variantData.assembleVariantTask);
                }

                Task assembleTask = null;
                // assembleTask for this flavor(dimension), created on demand if needed.
                if (variantConfig.getProductFlavors().size() > 1) {
                    String name = StringHelper.capitalize(variantConfig.getFlavorName());
                    assembleTask = tasks.findByName("assemble" + name);
                    if (assembleTask == null) {
                        assembleTask = tasks.create("assemble" + name);
                        assembleTask.setDescription(
                                "Assembles all builds for flavor combination: " + name);
                        assembleTask.setGroup("Build");

                        tasks.getByName("assemble").dependsOn(assembleTask);
                    }
                }
                // flavor combo
                if (assembleTask != null) {
                    assembleTask.dependsOn(variantData.assembleVariantTask);
                }
            }
        }
    }

    /**
     * Create all variants.
     *
     * @param signingOverride a signing override. Generally driven through the IDE.
     */
    public void populateVariantDataList(@Nullable com.android.builder.model.SigningConfig signingOverride) {
        // Add a compile lint task
        taskManager.createLintCompileTask();

        if (productFlavors.isEmpty()) {
            createVariantDataForProductFlavors(signingOverride,
                    Collections.emptyList());
        } else {
            List flavorDimensionList = extension.getFlavorDimensionList();

            // Create iterable to get GroupableProductFlavor from ProductFlavorData.
            Iterable flavorDsl =
                    Iterables.transform(
                            productFlavors.values(),
                            new Function, GroupableProductFlavor>() {
                                @Override
                                public GroupableProductFlavor apply(
                                        ProductFlavorData data) {
                                    return data.getProductFlavor();
                                }
                            });

            // Get a list of all combinations of product flavors.
            List flavorComboList = ProductFlavorCombo.createCombinations(
                    flavorDimensionList,
                    flavorDsl);

            for (ProductFlavorCombo flavorCombo : flavorComboList) {
                createVariantDataForProductFlavors(signingOverride, flavorCombo.getFlavorList());
            }
        }
    }

    /**
     * Create a VariantData for a specific combination of BuildType and GroupableProductFlavor list.
     */
    public BaseVariantData createVariantData(
            @NonNull com.android.builder.model.BuildType buildType,
            @NonNull List productFlavorList,
            @Nullable com.android.builder.model.SigningConfig signingOverride) {
        Splits splits = extension.getSplits();
        Set densities = splits.getDensityFilters();
        Set abis = splits.getAbiFilters();

        // check against potentially empty lists. We always need to generate at least one output
        densities = densities.isEmpty() ? Collections.singleton(NO_FILTER) : densities;
        abis = abis.isEmpty() ? Collections.singleton(NO_FILTER) : abis;

        ProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
        DefaultAndroidSourceSet defaultConfigSourceSet = defaultConfigData.getSourceSet();

        BuildTypeData buildTypeData = buildTypes.get(buildType.getName());

        Set compatibleScreens = extension.getSplits().getDensity()
                .getCompatibleScreens();

        GradleVariantConfiguration variantConfig = new GradleVariantConfiguration(
                defaultConfig,
                defaultConfigSourceSet,
                buildTypeData.getBuildType(),
                buildTypeData.getSourceSet(),
                variantFactory.getVariantConfigurationType(),
                signingOverride);

        // sourceSetContainer in case we are creating variant specific sourceSets.
        NamedDomainObjectContainer sourceSetsContainer = extension
                .getSourceSetsContainer();

        // We must first add the flavors to the variant config, in order to get the proper
        // variant-specific and multi-flavor name as we add/create the variant providers later.
        for (com.android.build.gradle.api.GroupableProductFlavor productFlavor : productFlavorList) {
            ProductFlavorData data = productFlavors.get(
                    productFlavor.getName());

            String dimensionName = productFlavor.getFlavorDimension();
            if (dimensionName == null) {
                dimensionName = "";
            }

            variantConfig.addProductFlavor(
                    data.getProductFlavor(),
                    data.getSourceSet(),
                    dimensionName);
        }

        // Add the container of dependencies.
        // The order of the libraries is important, in descending order:
        // variant-specific, build type, multi-flavor, flavor1, flavor2, ..., defaultConfig.
        // variant-specific if the full combo of flavors+build type. Does not exist if no flavors.
        // multi-flavor is the combination of all flavor dimensions. Does not exist if <2 dimension.

        final List variantProviders =
                Lists.newArrayListWithExpectedSize(productFlavorList.size() + 4);

        // 1. add the variant-specific if applicable.
        if (!productFlavorList.isEmpty()) {
            DefaultAndroidSourceSet variantSourceSet =
                    (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
                            variantConfig.getFullName());
            variantConfig.setVariantSourceProvider(variantSourceSet);
            variantProviders.add(new ConfigurationProviderImpl(project, variantSourceSet));
        }

        // 2. the build type.
        variantProviders.add(buildTypeData);

        // 3. the multi-flavor combination
        if (productFlavorList.size() > 1) {
            DefaultAndroidSourceSet multiFlavorSourceSet =
                    (DefaultAndroidSourceSet) sourceSetsContainer.maybeCreate(
                            variantConfig.getFlavorName());
            variantConfig.setMultiFlavorSourceProvider(multiFlavorSourceSet);
            variantProviders.add(new ConfigurationProviderImpl(project, multiFlavorSourceSet));
        }

        // 4. the flavors.
        for (com.android.build.gradle.api.GroupableProductFlavor productFlavor : productFlavorList) {
            variantProviders.add(productFlavors.get(productFlavor.getName()).getMainProvider());
        }

        // 5. The defaultConfig
        variantProviders.add(defaultConfigData.getMainProvider());

        // Done. Create the variant and get its internal storage object.
        BaseVariantData variantData = variantFactory.createVariantData(variantConfig,
                densities, abis, compatibleScreens, taskManager);

        VariantDependencies variantDep = VariantDependencies.compute(
                project, variantConfig.getFullName(),
                isVariantPublished(),
                variantFactory.isLibrary(),
                variantProviders.toArray(new ConfigurationProvider[variantProviders.size()]));
        variantData.setVariantDependency(variantDep);

        if (variantConfig.isMultiDexEnabled() && variantConfig.isLegacyMultiDexMode()) {
            project.getDependencies().add(
                    variantDep.getCompileConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
            project.getDependencies().add(
                    variantDep.getPackageConfiguration().getName(), COM_ANDROID_SUPPORT_MULTIDEX);
        }

        taskManager.resolveDependencies(variantDep, null);
        variantConfig.setDependencies(variantDep);

        return variantData;
    }

    /**
     * Create a TestVariantData for the specified testedVariantData.
     */
    public TestVariantData createTestVariantData(
            BaseVariantData testedVariantData,
            com.android.builder.model.SigningConfig signingOverride,
            VariantType type) {
        ProductFlavor defaultConfig = defaultConfigData.getProductFlavor();
        BuildType buildType = testedVariantData.getVariantConfiguration().getBuildType();
        BuildTypeData buildTypeData = buildTypes.get(buildType.getName());

        GradleVariantConfiguration testedConfig = testedVariantData.getVariantConfiguration();
        List productFlavorList = testedConfig.getProductFlavors();

        // handle test variant
        GradleVariantConfiguration testVariantConfig = new GradleVariantConfiguration(
                testedVariantData.getVariantConfiguration(),
                defaultConfig,
                defaultConfigData.getTestSourceSet(type),
                buildType,
                buildTypeData.getTestSourceSet(type),
                type,
                signingOverride);

        for (com.android.build.gradle.api.GroupableProductFlavor productFlavor : productFlavorList) {
            ProductFlavorData data = productFlavors
                    .get(productFlavor.getName());

            String dimensionName = productFlavor.getFlavorDimension();
            if (dimensionName == null) {
                dimensionName = "";
            }
            testVariantConfig.addProductFlavor(
                    data.getProductFlavor(),
                    data.getTestSourceSet(type),
                    dimensionName);
        }

        // create the internal storage for this variant.
        TestVariantData testVariantData = new TestVariantData(
                extension, taskManager, testVariantConfig, (TestedVariantData) testedVariantData);
        // link the testVariant to the tested variant in the other direction
        ((TestedVariantData) testedVariantData).setTestVariantData(testVariantData, type);

        return testVariantData;
    }

    /**
     * Creates VariantData for a specified list of product flavor.
     *
     * This will create VariantData for all build types of the given flavors.
     *
     * @param signingOverride a signing override. Generally driven through the IDE.
     * @param productFlavorList the flavor(s) to build.
     */
    private void createVariantDataForProductFlavors(
            @Nullable com.android.builder.model.SigningConfig signingOverride,
            @NonNull List productFlavorList) {
        BuildTypeData testBuildTypeData = buildTypes.get(extension.getTestBuildType());
        if (testBuildTypeData == null) {
            throw new RuntimeException(String.format(
                    "Test Build Type '%1$s' does not exist.", extension.getTestBuildType()));
        }

        BaseVariantData variantForAndroidTest = null;

        ProductFlavor defaultConfig = defaultConfigData.getProductFlavor();

        Closure variantFilterClosure = extension.getVariantFilter();

        for (BuildTypeData buildTypeData : buildTypes.values()) {
            boolean ignore = false;
            if (variantFilterClosure != null) {
                variantFilter.reset(defaultConfig, buildTypeData.getBuildType(), productFlavorList);
                variantFilterClosure.call(variantFilter);
                ignore = variantFilter.isIgnore();
            }

            if (!ignore) {
                BaseVariantData variantData = createVariantData(
                        buildTypeData.getBuildType(),
                        productFlavorList,
                        signingOverride);
                variantDataList.add(variantData);

                TestVariantData unitTestVariantData = createTestVariantData(
                        variantData,
                        signingOverride,
                        UNIT_TEST);
                variantDataList.add(unitTestVariantData);

                if (buildTypeData == testBuildTypeData) {
                    GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration();
                    if (variantConfig.isMinifyEnabled() && variantConfig.getUseJack()) {
                        throw new RuntimeException("Cannot test obfuscated variants when compiling with jack.");
                    }
                    variantForAndroidTest = variantData;
                }
            }
        }

        if (variantForAndroidTest != null) {
            TestVariantData androidTestVariantData = createTestVariantData(
                    variantForAndroidTest,
                    signingOverride,
                    ANDROID_TEST);
            variantDataList.add(androidTestVariantData);
        }
    }

    private void createApiObjects() {
        for (BaseVariantData variantData : variantDataList) {
            if (variantData.getType().isForTesting()) {
                // Testing variants are handled together with their "owners".
                continue;
            }

            BaseVariant variantApi =
                    variantFactory.createVariantApi(variantData, readOnlyObjectProvider);
            extension.addVariant(variantApi);

            // TODO: Handle UNIT_TEST variants as well.
            TestVariantData androidTestVariantData =
                    ((TestedVariantData) variantData).getTestVariantData(ANDROID_TEST);

            if (androidTestVariantData != null) {
                TestVariantImpl androidTestVariant = instantiator.newInstance(
                        TestVariantImpl.class,
                        androidTestVariantData,
                        variantApi,
                        androidBuilder,
                        readOnlyObjectProvider);

                // add the test output.
                ApplicationVariantFactory.createApkOutputApiObjects(
                        instantiator,
                        androidTestVariantData,
                        androidTestVariant);

                extension.addTestVariant(androidTestVariant);
                ((TestedVariant) variantApi).setTestVariant(androidTestVariant);
            }
        }
    }

    private boolean isVariantPublished() {
        return extension.getPublishNonDefault();
    }

    private static void checkName(@NonNull String name, @NonNull String displayName) {
        checkPrefix(name, displayName, ANDROID_TEST.getPrefix());
        checkPrefix(name, displayName, UNIT_TEST.getPrefix());

        if (LINT.equals(name)) {
            throw new RuntimeException(String.format(
                    "%1$s names cannot be %2$s", displayName, LINT));
        }
    }

    private static void checkPrefix(String name, String displayName, String prefix) {
        if (name.startsWith(prefix)) {
            throw new RuntimeException(String.format(
                    "%1$s names cannot start with '%2$s'", displayName, prefix));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy