Home | History | Annotate | Download | only in optimize
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "MultiApkGenerator.h"
     18 
     19 #include <algorithm>
     20 #include <regex>
     21 #include <string>
     22 
     23 #include "androidfw/StringPiece.h"
     24 
     25 #include "LoadedApk.h"
     26 #include "ResourceUtils.h"
     27 #include "ValueVisitor.h"
     28 #include "configuration/ConfigurationParser.h"
     29 #include "filter/AbiFilter.h"
     30 #include "filter/Filter.h"
     31 #include "format/Archive.h"
     32 #include "format/binary/XmlFlattener.h"
     33 #include "optimize/VersionCollapser.h"
     34 #include "process/IResourceTableConsumer.h"
     35 #include "split/TableSplitter.h"
     36 #include "util/Files.h"
     37 #include "xml/XmlDom.h"
     38 #include "xml/XmlUtil.h"
     39 
     40 namespace aapt {
     41 
     42 using ::aapt::configuration::AndroidSdk;
     43 using ::aapt::configuration::OutputArtifact;
     44 using ::aapt::xml::kSchemaAndroid;
     45 using ::aapt::xml::XmlResource;
     46 using ::android::StringPiece;
     47 
     48 /**
     49  * Context wrapper that allows the min Android SDK value to be overridden.
     50  */
     51 class ContextWrapper : public IAaptContext {
     52  public:
     53   explicit ContextWrapper(IAaptContext* context)
     54       : context_(context), min_sdk_(context_->GetMinSdkVersion()) {
     55   }
     56 
     57   PackageType GetPackageType() override {
     58     return context_->GetPackageType();
     59   }
     60 
     61   SymbolTable* GetExternalSymbols() override {
     62     return context_->GetExternalSymbols();
     63   }
     64 
     65   IDiagnostics* GetDiagnostics() override {
     66     if (source_diag_) {
     67       return source_diag_.get();
     68     }
     69     return context_->GetDiagnostics();
     70   }
     71 
     72   const std::string& GetCompilationPackage() override {
     73     return context_->GetCompilationPackage();
     74   }
     75 
     76   uint8_t GetPackageId() override {
     77     return context_->GetPackageId();
     78   }
     79 
     80   NameMangler* GetNameMangler() override {
     81     return context_->GetNameMangler();
     82   }
     83 
     84   bool IsVerbose() override {
     85     return context_->IsVerbose();
     86   }
     87 
     88   int GetMinSdkVersion() override {
     89     return min_sdk_;
     90   }
     91 
     92   void SetMinSdkVersion(int min_sdk) {
     93     min_sdk_ = min_sdk;
     94   }
     95 
     96   void SetSource(const std::string& source) {
     97     source_diag_ =
     98         util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics());
     99   }
    100 
    101  private:
    102   IAaptContext* context_;
    103   std::unique_ptr<SourcePathDiagnostics> source_diag_;
    104 
    105   int min_sdk_ = -1;
    106 };
    107 
    108 class SignatureFilter : public IPathFilter {
    109   bool Keep(const std::string& path) override {
    110     static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex");
    111     if (std::regex_search(path, signature_regex)) {
    112       return false;
    113     }
    114     return !(path == "META-INF/MANIFEST.MF");
    115   }
    116 };
    117 
    118 MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context)
    119     : apk_(apk), context_(context) {
    120 }
    121 
    122 bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
    123   std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
    124   std::unordered_set<std::string> filtered_artifacts;
    125   std::unordered_set<std::string> kept_artifacts;
    126 
    127   // For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
    128   for (const OutputArtifact& artifact : options.apk_artifacts) {
    129     FilterChain filters;
    130 
    131     ContextWrapper wrapped_context{context_};
    132     wrapped_context.SetSource(artifact.name);
    133 
    134     if (!options.kept_artifacts.empty()) {
    135       const auto& it = artifacts_to_keep.find(artifact.name);
    136       if (it == artifacts_to_keep.end()) {
    137         filtered_artifacts.insert(artifact.name);
    138         if (context_->IsVerbose()) {
    139           context_->GetDiagnostics()->Note(DiagMessage(artifact.name) << "skipping artifact");
    140         }
    141         continue;
    142       } else {
    143         artifacts_to_keep.erase(it);
    144         kept_artifacts.insert(artifact.name);
    145       }
    146     }
    147 
    148     std::unique_ptr<ResourceTable> table =
    149         FilterTable(context_, artifact, *apk_->GetResourceTable(), &filters);
    150     if (!table) {
    151       return false;
    152     }
    153 
    154     IDiagnostics* diag = wrapped_context.GetDiagnostics();
    155 
    156     std::unique_ptr<XmlResource> manifest;
    157     if (!UpdateManifest(artifact, &manifest, diag)) {
    158       diag->Error(DiagMessage() << "could not update AndroidManifest.xml for output artifact");
    159       return false;
    160     }
    161 
    162     std::string out = options.out_dir;
    163     if (!file::mkdirs(out)) {
    164       diag->Warn(DiagMessage() << "could not create out dir: " << out);
    165     }
    166     file::AppendPath(&out, artifact.name);
    167 
    168     if (context_->IsVerbose()) {
    169       diag->Note(DiagMessage() << "Generating split: " << out);
    170     }
    171 
    172     std::unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(diag, out);
    173 
    174     if (context_->IsVerbose()) {
    175       diag->Note(DiagMessage() << "Writing output: " << out);
    176     }
    177 
    178     filters.AddFilter(util::make_unique<SignatureFilter>());
    179     if (!apk_->WriteToArchive(&wrapped_context, table.get(), options.table_flattener_options,
    180                               &filters, writer.get(), manifest.get())) {
    181       return false;
    182     }
    183   }
    184 
    185   // Make sure all of the requested artifacts were valid. If there are any kept artifacts left,
    186   // either the config or the command line was wrong.
    187   if (!artifacts_to_keep.empty()) {
    188     context_->GetDiagnostics()->Error(
    189         DiagMessage() << "The configuration and command line to filter artifacts do not match");
    190 
    191     context_->GetDiagnostics()->Error(DiagMessage() << kept_artifacts.size() << " kept:");
    192     for (const auto& artifact : kept_artifacts) {
    193       context_->GetDiagnostics()->Error(DiagMessage() << "  " << artifact);
    194     }
    195 
    196     context_->GetDiagnostics()->Error(DiagMessage() << filtered_artifacts.size() << " filtered:");
    197     for (const auto& artifact : filtered_artifacts) {
    198       context_->GetDiagnostics()->Error(DiagMessage() << "  " << artifact);
    199     }
    200 
    201     context_->GetDiagnostics()->Error(DiagMessage() << artifacts_to_keep.size() << " missing:");
    202     for (const auto& artifact : artifacts_to_keep) {
    203       context_->GetDiagnostics()->Error(DiagMessage() << "  " << artifact);
    204     }
    205 
    206     return false;
    207   }
    208 
    209   return true;
    210 }
    211 
    212 std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(IAaptContext* context,
    213                                                               const OutputArtifact& artifact,
    214                                                               const ResourceTable& old_table,
    215                                                               FilterChain* filters) {
    216   TableSplitterOptions splits;
    217   AxisConfigFilter axis_filter;
    218   ContextWrapper wrapped_context{context};
    219   wrapped_context.SetSource(artifact.name);
    220 
    221   if (!artifact.abis.empty()) {
    222     filters->AddFilter(AbiFilter::FromAbiList(artifact.abis));
    223   }
    224 
    225   if (!artifact.screen_densities.empty()) {
    226     for (const auto& density_config : artifact.screen_densities) {
    227       splits.preferred_densities.push_back(density_config.density);
    228     }
    229   }
    230 
    231   if (!artifact.locales.empty()) {
    232     for (const auto& locale : artifact.locales) {
    233       axis_filter.AddConfig(locale);
    234     }
    235     splits.config_filter = &axis_filter;
    236   }
    237 
    238   if (artifact.android_sdk) {
    239     wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version);
    240   }
    241 
    242   std::unique_ptr<ResourceTable> table = old_table.Clone();
    243 
    244   VersionCollapser collapser;
    245   if (!collapser.Consume(&wrapped_context, table.get())) {
    246     context->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources");
    247     return {};
    248   }
    249 
    250   TableSplitter splitter{{}, splits};
    251   splitter.SplitTable(table.get());
    252   return table;
    253 }
    254 
    255 bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact,
    256                                        std::unique_ptr<XmlResource>* updated_manifest,
    257                                        IDiagnostics* diag) {
    258   const xml::XmlResource* apk_manifest = apk_->GetManifest();
    259   if (apk_manifest == nullptr) {
    260     return false;
    261   }
    262 
    263   *updated_manifest = apk_manifest->Clone();
    264   XmlResource* manifest = updated_manifest->get();
    265 
    266   // Make sure the first element is <manifest> with package attribute.
    267   xml::Element* manifest_el = manifest->root.get();
    268   if (manifest_el == nullptr) {
    269     return false;
    270   }
    271 
    272   if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
    273     diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>");
    274     return false;
    275   }
    276 
    277   // Update the versionCode attribute.
    278   xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
    279   if (versionCode == nullptr) {
    280     diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute");
    281     return false;
    282   }
    283 
    284   auto* compiled_version = ValueCast<BinaryPrimitive>(versionCode->compiled_value.get());
    285   if (compiled_version == nullptr) {
    286     diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid");
    287     return false;
    288   }
    289 
    290   int new_version = compiled_version->value.data + artifact.version;
    291   versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version));
    292 
    293   // Check to see if the minSdkVersion needs to be updated.
    294   if (artifact.android_sdk) {
    295     // TODO(safarmer): Handle the rest of the Android SDK.
    296     const AndroidSdk& android_sdk = artifact.android_sdk.value();
    297 
    298     if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
    299       if (xml::Attribute* min_sdk_attr =
    300               uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
    301         // Populate with a pre-compiles attribute to we don't need to relink etc.
    302         const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version);
    303         min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
    304       } else {
    305         // There was no minSdkVersion. This is strange since at this point we should have been
    306         // through the manifest fixer which sets the default minSdkVersion.
    307         diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>");
    308         return false;
    309       }
    310     } else {
    311       // No uses-sdk present. This is strange since at this point we should have been
    312       // through the manifest fixer which should have added it.
    313       diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>");
    314       return false;
    315     }
    316   }
    317 
    318   if (!artifact.screen_densities.empty()) {
    319     xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens");
    320     if (!screens_el) {
    321       // create a new element.
    322       std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>();
    323       new_screens_el->name = "compatible-screens";
    324       screens_el = new_screens_el.get();
    325       manifest_el->AppendChild(std::move(new_screens_el));
    326     } else {
    327       // clear out the old element.
    328       screens_el->GetChildElements().clear();
    329     }
    330 
    331     for (const auto& density : artifact.screen_densities) {
    332       AddScreens(density, screens_el);
    333     }
    334   }
    335 
    336   return true;
    337 }
    338 
    339 /**
    340  * Adds a screen element with both screenSize and screenDensity set. Since we only know the density
    341  * we add it for all screen sizes.
    342  *
    343  * This requires the resource IDs for the attributes from the framework library. Since these IDs are
    344  * a part of the public API (and in public.xml) we hard code the values.
    345  *
    346  * The excert from the framework is as follows:
    347  *    <public type="attr" name="screenSize" id="0x010102ca" />
    348  *    <public type="attr" name="screenDensity" id="0x010102cb" />
    349  */
    350 void MultiApkGenerator::AddScreens(const ConfigDescription& config, xml::Element* parent) {
    351   // Hard coded integer representation of the supported screen sizes:
    352   //  small   = 200
    353   //  normal  = 300
    354   //  large   = 400
    355   //  xlarge  = 500
    356   constexpr const uint32_t kScreenSizes[4] = {200, 300, 400, 500,};
    357   constexpr const uint32_t kScreenSizeResourceId = 0x010102ca;
    358   constexpr const uint32_t kScreenDensityResourceId = 0x010102cb;
    359 
    360   for (uint32_t screen_size : kScreenSizes) {
    361     std::unique_ptr<xml::Element> screen = util::make_unique<xml::Element>();
    362     screen->name = "screen";
    363 
    364     xml::Attribute* size = screen->FindOrCreateAttribute(kSchemaAndroid, "screenSize");
    365     size->compiled_attribute = xml::AaptAttribute(Attribute(), {kScreenSizeResourceId});
    366     size->compiled_value = ResourceUtils::MakeInt(screen_size);
    367 
    368     xml::Attribute* density = screen->FindOrCreateAttribute(kSchemaAndroid, "screenDensity");
    369     density->compiled_attribute = xml::AaptAttribute(Attribute(), {kScreenDensityResourceId});
    370     density->compiled_value = ResourceUtils::MakeInt(config.density);
    371 
    372 
    373     parent->AppendChild(std::move(screen));
    374   }
    375 }
    376 
    377 }  // namespace aapt
    378