29

I have been using a TextureView in my android app, and it was working fine. Just recently I tested my code on an Android device with Android API 25 (7.1.2). Same code now does not work and throws the error, java.lang.UnsupportedOperationException: TextureView doesn't support displaying a background drawable.

I know that void setBackgroundDrawable (Drawable background) had been deprecated for a long time, and now it must have been removed. But I am not even setting it by myself.

I am using latest buildTools and SDK. So, I wonder why hasn't the textureView internal implementation been updated.

Here is the relevant stack trace:

java.lang.UnsupportedOperationException: TextureView doesn't support displaying a background drawable
at android.view.TextureView.setBackgroundDrawable(TextureView.java:315)
at android.view.View.setBackground(View.java:18124)
at android.view.View.<init>(View.java:4573)
at android.view.View.<init>(View.java:4082)
at android.view.TextureView.<init>(TextureView.java:159)
at com.abdulwasaetariq.xyz.ui.customView.AutoFitTextureView.<init>(AutoFitTextureView.java:24)
at com.abdulwasaetariq.xyz.ui.customView.AutoFitTextureView.<init>(AutoFitTextureView.java:20)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[...]
at java.lang.Thread.run(Thread.java:745)

Here is how i use my (not-yet customized) custom TextureView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.abdulwasaetariq.xyz.ui.activity.MainActivity">

    <com.abdulwasaetariq.xyz.ui.customView.AutoFitTextureView
        android:id="@+id/texture"
        android:layout_width="1080px"
        android:layout_height="1080px"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true" />

</RelativeLayout>

Here is my relevant AutoFitTextureView.java: enter code here

public class AutoFitTextureView extends TextureView {

private int mRatioWidth = 0;
private int mRatioHeight = 0;

public AutoFitTextureView(Context context) {
    this(context, null);
}

public AutoFitTextureView(Context context, AttributeSet attrs) {
    this(context, attrs, 0); //(LINE#20)
}

public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle); //(LINE#24)
}

public void setAspectRatio(int width, int height) {
    if (width < 0 || height < 0) {
        throw new IllegalArgumentException("Size cannot be negative.");
    }
    mRatioWidth = width;
    mRatioHeight = height;
    requestLayout();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    if (0 == mRatioWidth || 0 == mRatioHeight) {
        setMeasuredDimension(width, height);
    } else {
        if (width < height * mRatioWidth / mRatioHeight) {
            setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
        } else {
            setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
        }
    }
}}

So, as you can see, the exceptions occur at the super() methods, which means that my custom TextureView is not responsible for this exception. It's an inside call.

Here is my gradle config:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.2'
    defaultConfig {
        applicationId "com.abdulwasaetariq.xyz"
        minSdkVersion 21
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
        })
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha8'
    compile 'com.github.hotchemi:permissionsdispatcher:2.3.2'
    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.2'
}

Any ideas why this may be happening? Any release-notes of Android API 25, where this change is talked about?

Abdul Wasae
  • 3,614
  • 4
  • 34
  • 56
  • 1
    Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/143023/discussion-on-question-by-abdul-wasae-android-nougat-textureview-doesnt-suppor). – Bhargav Rao Apr 30 '17 at 12:28

5 Answers5

8

The following are snippets from the source code for View for Android Nougat:

/**
 * Allow setForeground/setBackground to be called (and ignored) on a textureview,
 * without throwing
 */
static boolean sTextureViewIgnoresDrawableSetters = false;

In the single-argument constructor (called from all the others):

        // Prior to N, TextureView would silently ignore calls to setBackground/setForeground.
        // On N+, we throw, but that breaks compatibility with apps that use these methods.
        sTextureViewIgnoresDrawableSetters = targetSdkVersion <= M;

In the View constructor where your exception is thrown:

...
        switch (attr) {
            case com.android.internal.R.styleable.View_background:
                background = a.getDrawable(attr);
                break;
...
    if (background != null) {
        setBackground(background);  // <--- this is the problematic line, apparently "background" is not null here
    }

The actual definition of setBackground:

/**
 * Set the background to a given Drawable, or remove the background. If the
 * background has padding, this View's padding is set to the background's
 * padding. However, when a background is removed, this View's padding isn't
 * touched. If setting the padding is desired, please use
 * {@link #setPadding(int, int, int, int)}.
 *
 * @param background The Drawable to use as the background, or null to remove the
 *        background
 */
public void setBackground(Drawable background) {
    //noinspection deprecation
    setBackgroundDrawable(background);
}

Then the override of setBackgroundDrawable in TextureView:

@Override
public void setBackgroundDrawable(Drawable background) {
    if (background != null && !sTextureViewIgnoresDrawableSetters) {
        throw new UnsupportedOperationException(
                "TextureView doesn't support displaying a background drawable");
    }
}

So what you can piece together from all that is: 1) You have a target SDK N (Nougat) - obvious from your build file; 2) The constructor from View determines a non-null background (I cannot explain this part at the moment).

That's all it takes for this to be an actual problem. I do not see that you have managed to define a drawable in your xml, so overriding setBackground or setBackgroundDrawable seems to be the most sensible possibility to resolve the issue to me. There may be another workaround (or maybe "suggested usage" would be a better terminology) whereby you can manage to coerce the background variable in the constructor to remain null.

Dave
  • 4,282
  • 2
  • 19
  • 24
  • 5
    I've faced the same problem. It appeared that a solid color background was applied to my view. When I removed it everything started working again. But I do not see any background definition in you example. – Andrey Novikov May 02 '17 at 14:10
  • 2
    This definitely works around the problem. However, since I will very likely need to use the 'setBackgroundDrawable' method in the near future, i can only regard this as a make-do workaround, and not the proper solution to the original question. – Abdul Wasae Jun 17 '17 at 04:23
  • 3
    If you call the base `setBackgroundDrawable` on a `TextureView` on Android N+ with a non-null value, you will get this exception. I am unsure why you would want to do that or what you think it might accomplish. There is no "proper solution". It simply isn't supported. – Dave Jun 17 '17 at 23:11
  • Thanks, Andrey Novikov! It was the same with me. – eldes Nov 25 '17 at 18:35
6

If you look at the source for texture view for API 24 you will see the following:

/**
 * Subclasses of TextureView cannot do their own rendering
 * with the {@link Canvas} object.
 *
 * @param canvas The Canvas to which the View is rendered.
 */
@Override
public final void draw(Canvas canvas) {
    // NOTE: Maintain this carefully (see View#draw)
    mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /* Simplify drawing to guarantee the layer is the only thing drawn - so e.g. no background,
    scrolling, or fading edges. This guarantees all drawing is in the layer, so drawing
    properties (alpha, layer paint) affect all of the content of a TextureView. */

    if (canvas.isHardwareAccelerated()) {
        DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;

        HardwareLayer layer = getHardwareLayer();
        if (layer != null) {
            applyUpdate();
            applyTransformMatrix();

            mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
            displayListCanvas.drawHardwareLayer(layer);
        }
    }
}

The comment in the body of draw() gives the rationale for the change you have seen. This is the only documentation that I have found. Compare this to TextureView from API 23:

/**
 * Subclasses of TextureView cannot do their own rendering
 * with the {@link Canvas} object.
 *
 * @param canvas The Canvas to which the View is rendered.
 */
@Override
public final void draw(Canvas canvas) {
    // NOTE: Maintain this carefully (see View.java)
    mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    applyUpdate();
    applyTransformMatrix();
}

API 24 also introduced overrides for the "set background" methods that are not overridden in API 23. Setting a background is now clearly discouraged and is just not allowed. If you are seeing the unsupported operation exception and you are not explicitly setting a background then it is probably sneaking in through your styles. Try setting android:background="@null" in your XML to force the background to be null to get around the error. You can also add the following code to your custom view to retain the functionality on those versions that support setting a background:

@Override
public void setBackgroundDrawable(Drawable background) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && background != null) {
        setBackgroundDrawable(background);
    }
}

It is unclear how to replace the functionality that you have lost for API 24+ or if you even need it but just want to have the background tool in your arsenal.

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • 1
    Aah! '...it is probably sneaking in through your styles...' must be it. I'll check this out and come back. – Abdul Wasae Dec 13 '17 at 16:12
  • @AbdulWasae Was it your styles? – Cheticamp Dec 14 '17 at 12:09
  • unfortunately i have not been able to get my hands on an api 25 device. And the issue is not reproducing on api 24 and 26 :/ I'll try to test on api 25 and then be able to tell – Abdul Wasae Dec 14 '17 at 13:30
  • I am accepting the answer in faith, since i still haven't been able to arrange the same configuration as that in which i originally faced this issue. – Abdul Wasae Dec 22 '17 at 04:39
  • Thanks! Styles it is. Such a silly bug. `android:background="@null"` worked like a charm! – halxinate Mar 26 '21 at 07:09
2

Just to mention, not just TextureView: I found, that GridLayout also doesn't support displaying a background drawable since API 24.

I tried:

A) gridLayout.setBackgroundResource(R.drawable.board_960x960px_border_in_bg);

B) Resources res = getResources(); Drawable drawable = res.getDrawable(R.drawable.board_960x960px_border_in_bg); gridLayout.setBackground(drawable);

Neither of the above seems to be working above API 23.

However, TableLayout's background won't disappear even at API 24+, so I rewrote all my relevant code from GridLayout to TableLayout and now it's OK.

Hardzsi
  • 41
  • 1
  • 3
1

set background as null in the xml file itself for your TextureView.

e.g. android:background="@null"

Works for me.

Or if you have custom Texture class then override this method in TextureView

@Override public void setBackgroundDrawable(Drawable background) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && background != null) { setBackgroundDrawable(background); } }

Rohit Patil
  • 1,615
  • 12
  • 9
0

My issue resolved when I changed my targetSdkVersion to 23.

Sonia John Kavery
  • 2,099
  • 2
  • 20
  • 36