Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Add support for android:useEmbeddedDex (
Browse files Browse the repository at this point in the history
#6042)

Fixes #5925
Context https://developer.android.com/topic/security/dex

Android 10 introduced a new attribute for the application element in the
AndroidManifest.xml, `android:useEmbeddedDex`. This allows the .dex files
to be run directly from the apk, rather than unpacked. This new attribute
does require that the .dex files be STORED in the apk rather than
COMPRESSED. Otherwise you get the following error

    Failure [INSTALL_FAILED_INVALID_APK: Some dex are not uncompressed and aligned correctly

So lets update BuildApk to use the `GetCompressionMethod` method to figure
out if we need to compress the .dex files. We can then update the `ReadAndroidManifest`
task to check for the new attribute and update the `AndroidStoreUncompressedFileExtensions`
ItemGroup accordingly.

Also added unit tests.
  • Loading branch information
dellis1972 committed Jun 24, 2021
1 parent d5a1171 commit 6b87c5b
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
Expand Up @@ -172,10 +172,11 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
apk.FixupWindowsPathSeparators ((a, b) => Log.LogDebugMessage ($"Fixing up malformed entry `{a}` -> `{b}`"));

// Add classes.dx
CompressionMethod dexCompressionMethod = GetCompressionMethod (".dex");
foreach (var dex in DalvikClasses) {
string apkName = dex.GetMetadata ("ApkName");
string dexPath = string.IsNullOrWhiteSpace (apkName) ? Path.GetFileName (dex.ItemSpec) : apkName;
AddFileToArchiveIfNewer (apk, dex.ItemSpec, DalvikPath + dexPath);
AddFileToArchiveIfNewer (apk, dex.ItemSpec, DalvikPath + dexPath, compressionMethod: dexCompressionMethod);
}

if (EmbedAssemblies && !BundleAssemblies)
Expand Down
8 changes: 8 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/ReadAndroidManifest.cs
Expand Up @@ -32,6 +32,9 @@ public class ReadAndroidManifest : AndroidTask
[Output]
public ITaskItem [] UsesLibraries { get; set; }

[Output]
public bool UseEmbeddedDex { get; set; } = false;

public override bool RunTask ()
{
var androidNs = AndroidAppManifest.AndroidXNamespace;
Expand All @@ -44,6 +47,11 @@ public override bool RunTask ()
EmbeddedDSOsEnabled = !value;
}

text = app.Attribute (androidNs + "useEmbeddedDex")?.Value;
if (bool.TryParse (text, out value)) {
UseEmbeddedDex = value;
}

var libraries = new List<ITaskItem> ();
foreach (var uses_library in app.Elements ("uses-library")) {
var attribute = uses_library.Attribute (androidNs + "name");
Expand Down
Expand Up @@ -1505,9 +1505,11 @@ because xbuild doesn't support framework reference assemblies.
AndroidApiLevel="$(_AndroidApiLevel)">
<Output TaskParameter="EmbeddedDSOsEnabled" PropertyName="_EmbeddedDSOsEnabled" />
<Output TaskParameter="UsesLibraries" ItemName="AndroidExternalJavaLibrary" />
<Output TaskParameter="UseEmbeddedDex" PropertyName="_UseEmbeddedDex" />
</ReadAndroidManifest>
<PropertyGroup>
<AndroidStoreUncompressedFileExtensions Condition=" '$(_EmbeddedDSOsEnabled)' == 'True' ">.so;$(AndroidStoreUncompressedFileExtensions)</AndroidStoreUncompressedFileExtensions>
<AndroidStoreUncompressedFileExtensions Condition=" '$(_UseEmbeddedDex)' == 'True' ">.dex;$(AndroidStoreUncompressedFileExtensions)</AndroidStoreUncompressedFileExtensions>
</PropertyGroup>
</Target>

Expand Down
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Build.Tasks/Xamarin.Android.DX.targets
Expand Up @@ -93,7 +93,7 @@ Copyright (C) 2018 Xamarin. All rights reserved.
UseDx="$(UseDx)"
MultiDexEnabled="$(AndroidEnableMultiDex)"
MultiDexMainDexListFile="$(_AndroidMainDexListFile)"
JavaLibrariesToCompile="@(_JavaLibrariesToCompileForApp)"
JavaLibrariesToCompile="@(_JavaLibrariesToCompileForApp);@(_InstantRunJavaReference)"
AlternativeJarFiles="@(_AlternativeJarForAppDx)"
/>
<Touch Files="$(_AndroidStampDirectory)_CompileToDalvik.stamp" AlwaysCreate="true" />
Expand Down
4 changes: 2 additions & 2 deletions tests/MSBuildDeviceIntegration/Tests/DebuggingTest.cs
Expand Up @@ -41,7 +41,7 @@ void SetTargetFrameworkAndManifest(XamarinAndroidApplicationProject proj, Builde
}

[Test]
public void ApplicationRunsWithoutDebugger ([Values (false, true)] bool isRelease, [Values (false, true)] bool extractNativeLibs)
public void ApplicationRunsWithoutDebugger ([Values (false, true)] bool isRelease, [Values (false, true)] bool extractNativeLibs, [Values (false, true)] bool useEmbeddedDex)
{
AssertHasDevices ();

Expand All @@ -54,7 +54,7 @@ public void ApplicationRunsWithoutDebugger ([Values (false, true)] bool isReleas
proj.SetDefaultTargetDevice ();
using (var b = CreateApkBuilder (Path.Combine ("temp", TestName))) {
SetTargetFrameworkAndManifest (proj, b);
proj.AndroidManifest = proj.AndroidManifest.Replace ("<application ", $"<application android:extractNativeLibs=\"{extractNativeLibs.ToString ().ToLowerInvariant ()}\" ");
proj.AndroidManifest = proj.AndroidManifest.Replace ("<application ", $"<application android:extractNativeLibs=\"{extractNativeLibs.ToString ().ToLowerInvariant ()}\" android:useEmbeddedDex=\"{useEmbeddedDex.ToString ().ToLowerInvariant ()}\" ");
Assert.True (b.Install (proj), "Project should have installed.");
var manifest = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "android", "AndroidManifest.xml");
AssertExtractNativeLibs (manifest, extractNativeLibs);
Expand Down
36 changes: 36 additions & 0 deletions tests/MSBuildDeviceIntegration/Tests/InstantRunTest.cs
Expand Up @@ -322,5 +322,41 @@ public void InstantRunNativeLibrary ([Values ("dx", "d8")] string dexTool)
Assert.IsTrue (logLines.Any (l => l.Contains ("NotifySync CopyFile") && l.Contains ("libtest.so")), "libtest.so should have been uploaded");
}
}

[Test]
public void InstantRunFastDevDexes ([Values ("dx", "d8")] string dexTool, [Values (false, true)] bool useEmbeddedDex)
{
AssertDexToolSupported (dexTool);
AssertCommercialBuild ();
AssertHasDevices ();

var proj = new XamarinAndroidApplicationProject () {
AndroidFastDeploymentType = "Assemblies:Dexes",
UseLatestPlatformSdk = true,
DexTool = dexTool,
};
proj.SetDefaultTargetDevice ();
proj.AndroidManifest = proj.AndroidManifest.Replace ("<application ", $"<application android:useEmbeddedDex=\"{useEmbeddedDex.ToString ().ToLowerInvariant ()}\" ");
using (var b = CreateApkBuilder (Path.Combine ("temp", TestName))) {
Assert.IsTrue (b.Install (proj), "packaging should have succeeded. 0");
var logLines = b.LastBuildOutput;
Assert.IsTrue (logLines.Any (l => l.Contains ("Building target \"_BuildApkFastDev\" completely.") ||
l.Contains ("Target _BuildApkFastDev needs to be built")),
"Apk should have been built");
Assert.IsTrue (logLines.Any (l => l.Contains ("Building target \"_Upload\" completely")), "_Upload target should have run");
Assert.IsTrue (logLines.Any (l => l.Contains ("NotifySync CopyFile") && l.Contains ("classes.dex")), "classes.dex should have been uploaded");
ClearAdbLogcat ();
b.BuildLogFile = "run.log";
if (CommercialBuildAvailable)
Assert.True (b.RunTarget (proj, "_Run"), "Project should have run.");
else
AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity");

Assert.True (WaitForActivityToStart (proj.PackageName, "MainActivity",
Path.Combine (Root, b.ProjectDirectory, "logcat.log"), 30), "Activity should have started.");
b.BuildLogFile = "uninstall.log";
Assert.True (b.Uninstall (proj), "Project should have uninstalled.");
}
}
}
}

0 comments on commit 6b87c5b

Please sign in to comment.