PSA: Android 6.0 Theme.NoDisplay Regression
If you have been using Theme.NoDisplay in one or more activities in
your app, and you have not tested them on Android 6.0 yet, I recommend
that you do so soon. An undocumented regression in Android 6.0 will
cause some of those activities to crash upon launch,
if your targetSdkVersion is 23 or higher.
If the activity does all of its work in onCreate() (e.g., starts up
a service), then calls finish(), you seem to be OK.
But sometimes
the work that needs to be done is a bit more involved than that.
In particular, calling startActivityForResult(), with an eye towards
calling finish() in onActivityResult(), will cause your app to crash
with an IllegalStateException saying that your activity
“did not call finish() prior to onResume() completing”. This, apparently,
is a new requirement of Theme.NoDisplay activities.
You might wonder why you need a Theme.NoDisplay activity to be calling
startActivityForResult() in the first place. The scenario I encountered
was trying to use the media projection APIs, specifically to set up
recording a screencast. To use the media projection APIs, you need to call
startActivityForResult() on a system-supplied Intent, then use the
result data to set up a MediaProjection. In the case of this sample
app, the user interface was primarily going to be a foreground
Notification of a service (plus some command-line scripts), so I had
no need for an activity, except as a means of enabling the media projection
APIs.
So, I have something like this:
package com.commonsware.android.andcorder;
import android.app.Activity;
import android.content.Intent;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
public class MainActivity extends Activity {
private static final int REQUEST_SCREENSHOT=59706;
private MediaProjectionManager mgr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mgr=(MediaProjectionManager)getSystemService(MEDIA_PROJECTION_SERVICE);
startActivityForResult(mgr.createScreenCaptureIntent(),
REQUEST_SCREENSHOT);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode==REQUEST_SCREENSHOT) {
if (resultCode==RESULT_OK) {
Intent i=
new Intent(this, RecorderService.class)
.putExtra(RecorderService.EXTRA_RESULT_CODE, resultCode)
.putExtra(RecorderService.EXTRA_RESULT_INTENT, data);
startService(i);
}
}
finish();
}
}
This works just fine with Theme.NoDisplay on Android 5.1, passing control
to my RecorderService to actually manage starting and stopping the screen
recording. On Android 6.0, the app crashes with the aforementioned exception.
Another scenario is using requestPermissions() from the
Android 6.0 runtime permission system.
Under the covers, requestPermissions() uses startActivityForResult().
If you have an app where the UI is mostly not an activity (e.g., app
widget, Notification, wear app), but you need runtime permissions,
you might try creating a Theme.NoDisplay activity just to call
requestPermissions() and handle onRequestPermissionsResult(). I would
expect this to generate the same exception on Android 6.0, though I have
not tried this scenario to confirm the theory.
The issue
that was filed on the M Developer Preview about this ran
into the problem when trying to show a Dialog from onCreate().
The issue went nowhere.
One fix described in that issue — calling setVisible(true)
in onStart() — works, but you wind up with a visible activity
UI (black background and old-timey gray title bar).
What worked better for me was
to
use Theme.Translucent.NoTitleBar as the theme on Android 6.0+.
If you want to stick with Theme.NoDisplay on older devices, create
a custom theme that inherits from Theme.NoDisplay in res/values/
but from Theme.Translucent.NoTitleBar in res/values-v23/, then use
your custom theme.
Or, go with targetSdkVersion 22 temporarily. Eventually, something will
drive you to using targetSdkVersion of 23 or higher, though.
UPDATE: 2015-11-08 Dianne Hackborn
responded to this post,
explaining that Theme.NoDisplay is somewhat
documented
to require a finish() before onResume() and outlines a bit of the
rationale for the change to detect this case and fail fast. She and I
disagree on what constitutes a regression (IMHO, a substantial change
in behavior is a regression, even if documentation hints at the validity
of the change), but it is great that we have
a more-or-less official source for what is going on.
Many thanks to the Android Weekly newsletter for happening to mention Ms. Hackborn’s post in this week’s issue.

