Xamarin-android: Setting Target Version to "Use Compile" Does not set targetSdkVersion in Manifest

Created on 8 Jan 2018  Â·  15Comments  Â·  Source: xamarin/xamarin-android

Steps to Reproduce

  1. In VS or VS for Mac set Target SDK Version to "Use Compile" this is the default
  2. Look at current manifest (does not contain: targetSdkVersion)
  3. Compile application
  4. Look at output of AndroidManifest.xml that is generated in obj/android, still does not contain targetSdkVersion

Expected Behavior

The Value should be set correct for the targetSdkVersion in the AndroidManifest.xml. So if I compile against 25, it should show android:targetSdkVersion="25".

Failure to do this has some not so great side effects. Material design doesn't display correct, permissions don't work correctly, and a lot more.

Actual Behavior

Nothing is set in the AndroidManifest.xml

Most helpful comment

@jamesmontemagno: Paraphrasing the issue, I believe that you believe that //uses-sdk/@android:targetSdkVersion should always be set.

This is wrong, and will break the world.

When //uses-sdk/@android:targetSdkVersion is not set, then it defaults to the value of //uses-sdk/@android:minSdkVersion. This in turn controls how the operating system behaves.

Let's consider an example: suppose I have an app which:

  1. Is a "default" app, meaning //uses-sdk/@android:targetSdkVersion is not set, and
  2. It has the android.permission.CAMERA permission, and
  3. Uses the camera, and
  4. Has $(TargetFrameworkVersion)=v8.0 and/or $(AndroidUseLatestPlatformSdk)=True (not withstanding @JonDouglas 's objections), and
  5. DOES NOT use the Android 6.0+ system permissions API.

I can install that camera on my Android device -- any version! -- and it will work.

On all versions of Android, your app needs to declare both the normal and the dangerous permissions it needs in its app manifest.... However, the effect of that declaration is different depending on the system version and your app's target SDK level:

  • If the device is running Android 5.1 or lower, or your app's target SDK is 22 or lower: If you list a dangerous permission in your manifest, the user has to grant the permission when they install the app

Because the hypothetical app described above doesn't contain a //uses-sdk/@android:targetSdkVersion value, it works as expected.

Now, imagine a world in which we automatically set //uses-sdk/@android:targetSdkVersion based on $(TargetFrameworkVersion): the app wouldn't work at all, because it's missing the code to ask for user permission to access the camera.

"Weirder," you can imagine such an app being developed over many years, changing $(TargetFrameworkVersion) over time while ignoring //uses-sdk/@android:targetSdkVersion.

Simply changing $(TargetFrameworkVersion) shouldn't change whether or not your app works at all, IMHO.

I strongly believe that if we start adding //uses-sdk/@android:targetSdkVersion where we didn't have it before, lots of apps will suddenly "break" in a variety of "inexplicable" ways. This will be an extremely painful experience.

For example, consider monodroid-samples. There are 268 Properties/AndroidManifest.xml files:

$ find `find . -name Properties -type d` -name AndroidManifest.xml | wc -l
     268

Only 246 even have a //uses-sdk element:

$ find `find . -name Properties -type d` -name AndroidManifest.xml | xargs grep uses-sdk | wc -l
     246

Only 53 specify targetSdkVersion:

$ find `find . -name Properties -type d` -name AndroidManifest.xml | xargs grep uses-sdk.*targetSdk | wc -l
      53

That's possibly 215 projects which could break in obscure ways if we did this, in our own samples repo, including e.g. HoneycombGallery, which uses the Camera fragment -- and requests the CAMERA permission -- but doesn't explicitly provide the //uses-sdk/@android:targetSdkVersion attribute.


IMHO, //uses-sdk/@android:targetSdkVersion needs to be entirely under the control of the developer. Xamarin.Android ("we") should not provide defaults here.


This is why I want to proclaim this an IDE bug, because this isn't at all obvious. Visual Studio for Mac, in the Project Options > Build / Android Application > Target Android version drop-down, has the values (among others):

  • Automatic - use target framework version (API-26)
  • Override - Android 8.0 (API level 26)
  • Override - Android 7.1 (API level 25)
  • ...

I believe (IDE bug!) that these options are mis-named. "Automatic - use target framework version" does no such thing; it really means "no //uses-sdk/@android:targetSdkVersion entry."

There is a meaningful difference between "don't have a Target Android Version value" and "Use the value corresponding to the $(TargetFrameworkVersion)." Unfortunately, we don't actually support both, and the description that appears to sound like the latter is in fact the former.

All 15 comments

Removal/Modification of <AndroidUseLatestPlatformSdk> to explicitly set targetSdkVersion

Why - Problem

Xamarin.Android projects by default have a MSBuild property enabled that ends up confusing the developer with a various edge case of what targetSdkVersion means in an Android Application. It will set the following by default:

<AndroidUseLatestPlatformSdk>true</AndroidUseLatestPlatformSdk>

This tells the aapt task that we want the latest API version as an argument in the tooling:

https://github.com/xamarin/xamarin-android/blob/67ab27d9dce392259a6be62a2bfe36eaa81299c3/src/Xamarin.Android.Build.Tasks/Tasks/Aapt.cs#L326-L327

There is then various logic going on to ensure we try to set this to the same compileSdkVersion / <TargetFramework>:

https://github.com/xamarin/xamarin-android/blob/67ab27d9dce392259a6be62a2bfe36eaa81299c3/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs#L29-L127

The general guidance from google is the following:

minSdkVersion <= targetSdkVersion <= compileSdkVersion

However since we do not write a <uses-sdk android:targetSdkVersion="{API_LEVEL}"> in the AndroidManifest.xml unless explicitly overridden, the default behavior will instead be demoted to the minSdkVersion and thus Android OS features are not enabled for the API level we expect them to be. Rather we only get OS features up to the minSdkVersion.

An integer designating the API Level that the application targets. If not set, the default value equals that given to minSdkVersion.

How - Solution

Proposal 1 - Eliminate <AndroidUseLatestPlatformSdk> and explicitly set compileSdkVersion and targetSdkVersion.

This is the approach that Android Studio follows. It makes more sense to explicitly set these items and have the developer learn explicitly what each contribute to the Android application. There is very room for error this way as the user will be responsible for setting their compileSdkVersion, targetSdkVersion and minSdkVersion.

Pros

  • Makes future templating easier. Developers only will need to know what minSdkVersion they need to support. The compileSdkVersion and targetSdkVersion will automatically be set to the highest API level installed.
  • There is no sense of a "magic" value that sets the latest API version. Developers will know exactly what values they have set in their project as per the .csproj and AndroidManifest.xml.

Cons

  • Requires removal of code and alterations to existing build tasks, unit tests, etc.

Proposal 2 - Modify <AndroidUseLatestPlatformSdk> to set the <uses-sdk android:targetSdkVersion="26"> in the AndroidManifest.xml

This proposal keeps the current code and explicitly sets the android:targetSdkVersion in the AndroidManifest.xml to ensure there is no way Android OS feature flags can be set to the minSdkVersion.

Pros

  • Easy to implement as an extension to the MSBuild task
  • Leverages current implementation

Cons

  • Still keeps a "magic" MSBuild property that not many developers know what it does

Who - Developer Impact

Many developers who attempt to use new Android OS APIs end up shooting themselves in the foot with our tooling because we do not help them explicitly set the targetSdkVersion. Thus this strange behaviors when using new APIs but the Android OS feature is not enabled. This is mainly a problem with early evangelists who try our new APIs, and also Android beginners.

Related Bugs:

When - Target Milestone

15.7

Where - Affected Area

This is mainly caused by the Xamarin.Android MSBuild Tasks when the <AndroidUseLatestPlatformSdk> is set to true. Thus both Visual Studio 2017 and Visual Studio for Mac suffer because of this.

What - Mockups

Current Workflow

  1. Create a File -> New Xamarin.Android Project

  2. Build the Project and inspect the AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.TargetSdkVersionFeatureProposal">
  <!--suppress UsesMinSdkAttributes-->
  <uses-sdk android:minSdkVersion="10" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <application android:allowBackup="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:name="android.app.Application" android:debuggable="true">
    <activity android:icon="@mipmap/icon" android:label="TargetSdkVersionFeatureProposal" android:name="md57485980fa0287c9af2fc034840b9156f.MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="2147483647" android:authorities="com.companyname.TargetSdkVersionFeatureProposal.mono.MonoRuntimeProvider.__mono_init__" />
    <!--suppress ExportedReceiver-->
    <receiver android:name="mono.android.Seppuku">
      <intent-filter>
        <action android:name="mono.android.intent.action.SEPPUKU" />
        <category android:name="mono.android.intent.category.SEPPUKU.com.companyname.TargetSdkVersionFeatureProposal" />
      </intent-filter>
    </receiver>
  </application>
  <meta-data android:name="android.support.VERSION" android:value="25.3.1" />
</manifest>
  1. Notice that no android:targetSdkVersion is added in the AndroidManifest.xml

New Workflow

  1. Create a File -> New Xamarin.Android Project

  2. Build the Project and inspect the AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.TargetSdkVersionFeatureProposal">
  <!--suppress UsesMinSdkAttributes-->
  <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="26" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <application android:allowBackup="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:name="android.app.Application" android:debuggable="true">
    <activity android:icon="@mipmap/icon" android:label="TargetSdkVersionFeatureProposal" android:name="md57485980fa0287c9af2fc034840b9156f.MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="2147483647" android:authorities="com.companyname.TargetSdkVersionFeatureProposal.mono.MonoRuntimeProvider.__mono_init__" />
    <!--suppress ExportedReceiver-->
    <receiver android:name="mono.android.Seppuku">
      <intent-filter>
        <action android:name="mono.android.intent.action.SEPPUKU" />
        <category android:name="mono.android.intent.category.SEPPUKU.com.companyname.TargetSdkVersionFeatureProposal" />
      </intent-filter>
    </receiver>
  </application>
  <meta-data android:name="android.support.VERSION" android:value="25.3.1" />
</manifest>
  1. Notice that the android:targetSdkVersion is set to the latest installed explicitly without overriding.

Because solution 1 is going to be a big job, I have looked at solution 2. See https://github.com/xamarin/xamarin-android/pull/1168

Aside: I want to proclaim this as an IDE bug. :-)

Aside: @JonDouglas: While everything you wrote is true, I believe it is not entirely related to @jamesmontemagno's issue, and would be better tracked as either a separate issue or in the compileSdkVersion Support project. (Click Menu > (Show more) to read the project description.)

@jonpryor When I wrote it back in October, half of the proposal was around targetSdkVersion whereas the other half was around compileSdkVersion. I'll create a new issue if this is not the right place to post.

@jamesmontemagno: Paraphrasing the issue, I believe that you believe that //uses-sdk/@android:targetSdkVersion should always be set.

This is wrong, and will break the world.

When //uses-sdk/@android:targetSdkVersion is not set, then it defaults to the value of //uses-sdk/@android:minSdkVersion. This in turn controls how the operating system behaves.

Let's consider an example: suppose I have an app which:

  1. Is a "default" app, meaning //uses-sdk/@android:targetSdkVersion is not set, and
  2. It has the android.permission.CAMERA permission, and
  3. Uses the camera, and
  4. Has $(TargetFrameworkVersion)=v8.0 and/or $(AndroidUseLatestPlatformSdk)=True (not withstanding @JonDouglas 's objections), and
  5. DOES NOT use the Android 6.0+ system permissions API.

I can install that camera on my Android device -- any version! -- and it will work.

On all versions of Android, your app needs to declare both the normal and the dangerous permissions it needs in its app manifest.... However, the effect of that declaration is different depending on the system version and your app's target SDK level:

  • If the device is running Android 5.1 or lower, or your app's target SDK is 22 or lower: If you list a dangerous permission in your manifest, the user has to grant the permission when they install the app

Because the hypothetical app described above doesn't contain a //uses-sdk/@android:targetSdkVersion value, it works as expected.

Now, imagine a world in which we automatically set //uses-sdk/@android:targetSdkVersion based on $(TargetFrameworkVersion): the app wouldn't work at all, because it's missing the code to ask for user permission to access the camera.

"Weirder," you can imagine such an app being developed over many years, changing $(TargetFrameworkVersion) over time while ignoring //uses-sdk/@android:targetSdkVersion.

Simply changing $(TargetFrameworkVersion) shouldn't change whether or not your app works at all, IMHO.

I strongly believe that if we start adding //uses-sdk/@android:targetSdkVersion where we didn't have it before, lots of apps will suddenly "break" in a variety of "inexplicable" ways. This will be an extremely painful experience.

For example, consider monodroid-samples. There are 268 Properties/AndroidManifest.xml files:

$ find `find . -name Properties -type d` -name AndroidManifest.xml | wc -l
     268

Only 246 even have a //uses-sdk element:

$ find `find . -name Properties -type d` -name AndroidManifest.xml | xargs grep uses-sdk | wc -l
     246

Only 53 specify targetSdkVersion:

$ find `find . -name Properties -type d` -name AndroidManifest.xml | xargs grep uses-sdk.*targetSdk | wc -l
      53

That's possibly 215 projects which could break in obscure ways if we did this, in our own samples repo, including e.g. HoneycombGallery, which uses the Camera fragment -- and requests the CAMERA permission -- but doesn't explicitly provide the //uses-sdk/@android:targetSdkVersion attribute.


IMHO, //uses-sdk/@android:targetSdkVersion needs to be entirely under the control of the developer. Xamarin.Android ("we") should not provide defaults here.


This is why I want to proclaim this an IDE bug, because this isn't at all obvious. Visual Studio for Mac, in the Project Options > Build / Android Application > Target Android version drop-down, has the values (among others):

  • Automatic - use target framework version (API-26)
  • Override - Android 8.0 (API level 26)
  • Override - Android 7.1 (API level 25)
  • ...

I believe (IDE bug!) that these options are mis-named. "Automatic - use target framework version" does no such thing; it really means "no //uses-sdk/@android:targetSdkVersion entry."

There is a meaningful difference between "don't have a Target Android Version value" and "Use the value corresponding to the $(TargetFrameworkVersion)." Unfortunately, we don't actually support both, and the description that appears to sound like the latter is in fact the former.

It is the very first thing that I have to change in every project. If I don't then my app looks and feels different on L+ devices and additionally the runtime permissions no longer work as they fall back to using pre-M style permissions.

It would be very strange that you release an app without that property set at all. I haven't in 6 years of Android development. Google has specific guidance around it: https://medium.com/google-developers/picking-your-compilesdkversion-minsdkversion-targetsdkversion-a098a0341ebd

Although yes, the developer is in charge of this property.

I would ask what does Android Studio do as the default. Does Google make a choice for the developer? A new project will do this:

compileSdkVersion 26
defaultConfig {
    applicationId "com.example.xamarin.myapplication"
    minSdkVersion 15
    targetSdkVersion 26
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

Now that said I would say that most developer probably think we are setting it to the Compile Version as that is what we have said in the IDE. However, I understand the backwards compat issue and not breaking current projects. So to me then the IDE needs to change and add a new default for new projects which is "Use Compile" that actually sets the value correctly. Then the current "Use Compile" is really "Don't Set (Use Minimum)".

Thoughts?

Also I am just thinking that if you leave it off it uses minimum. Probably have to do some grepping there.

Now that said I would say that most developer probably think we are setting it to the Compile Version as that is what we have said in the IDE. However, I understand the backwards compat issue and not breaking current projects. So to me then the IDE needs to change and add a new default for new projects which is "Use Compile" that actually sets the value correctly. Then the current "Use Compile" is really "Don't Set (Use Minimum)".

Thoughts?

+1 exactly that.

The current option called "Use Compile using SDK version" (on Windows) or "Automatic - use target framework version" (on Mac?) doesn't do what its name implies. It doesn't set targetSdkVersion at all - which makes android unexpectedly set it to minSdkVersion (see https://developer.android.com/guide/topics/manifest/uses-sdk-element.html).

1) Rename "Use Compile using SDK version" to "Don't set Target SDK Version / Use Minimum SDK Version"
2) Add new option "Use Compile SDK Version" (that actually sets targetSdkVersion = compileSdkVersion)

I'm not sure what the best default value for new projects would be. When creating a new project in Android Studio, it only asks for the minSdkVersion and it automatically assigns the latest API version to compileSdkVersion and targetSdkVersion. Later on it's left fully up to the developer to adjust these numbers - there is no automatism like we have in Visual Studio. I guess that's a good thing actually because it guarantees that the behaviour of a tested app won't change even when it's run on a newer version of Android.

I think for backwards compatibility it would be the option ^ however I don't actually think it is a good idea to have a "don't set" option as no one should do that, but if we hide it at the bottom then I think that would be good and provide no breaking changes :)

@jonpryor:

Paraphrasing the issue, I believe that you believe that //uses-sdk/@android:targetSdkVersion should always be set.

This is wrong, and will break the world.

Maybe so, but rather shortly not setting it will also break the world, in the sense that if targetSdkVersion is not set and minSdkVersion is not at least 26, the APKs will not be accepted by Play Store as of August. IMO making "Automatic - use target framework version" do what it says has fewer downsides than changing the text to "Automatic - use configured minimum framework version".

So, instead of speaking since 3 years, is a fix on its way for the end of the week ? The original bug https://bugzilla.xamarin.com/show_bug.cgi?id=35208 was opened on 2015-10-23... And it's a bug as the IDE does not do what is written.

Time to stop chatting guys. Action !

@softlion The Android templates have been updated to set explicit values across the board to help this behavior. This was released in 15.7.

We have made improvements in Visual Studio 2017 Preview 15.8 in which we will automatically ensure your TargetFrameworkVersion and targetSdkVersion match for any reason you change your Target Framework Version value. You can also no longer select Use Latest Platform within the Target Framework Version dropdown.

Although it is still possible for the user to set Use Compile using SDK version in the targetSdkVersion and minSdkVersion manually, because of these two changes above, it will be much more difficult to run into the user experiences below. To fix this, we will also be removing these two values in the targetSdkVersion and minSdkVersion dropdowns so it's even less likely this scenario will happen.

The current user experience with Use Compile using SDK version is the following:

targetSdkVersion - is set to Use Compile using SDK version, the Android runtime flags won't be set to the proper Android level since no value is added to the AndroidManifest.xml and it will default to the minSdkVersion.

Source: https://developer.android.com/guide/topics/manifest/uses-sdk-element#target

minSdkVersion - is set to Use Compile using SDK version, we do not add a value to the AndroidManifest.xml and this will default minSdkVersion to 1 which means every Android version is supported, which is not practical.

Source: https://developer.android.com/guide/topics/manifest/uses-sdk-element#min

We'd really appreciate it if you gave the latest preview a try and let us know your feedback!

hi, even changing target version like below, i am seeing this warning message when i want to make a new release.

image

Warning:

Your app currently targets API level 19 and must target at least API level 26 to ensure it is built on the latest APIs optimised for security and performance.

@EmilAlipiev What is your Compile using Android version: (Target Framework) set to? I'm also assuming you're running into this in Google Play Console correct?

Nothing to do in this repo; the fix was within the IDEs, as per @JonDouglas' earlier comment.

<AndroidUseLatestPlatformSdk>true</AndroidUseLatestPlatformSdk> needs to be added in .csproj to fix an Azure Dev Ops build issue where Xamarin.iOS task triggers Xamarin.Andorid common targets during a NuGet restore. It's a workaround recommended as per reported here … https://developercommunity.visualstudio.com/content/problem/801739/xamarinios-task-triggers-xamarinandorid-common-tar.html

Was this page helpful?
0 / 5 - 0 ratings